[spirv-reader][ir] Support `OpSpecConstantComposite`.
The `OpSpecConstantComposite` is the only way to build a composite
object from spec constants. In WGSL there is no equivalent, so if the
`OpSpecConstantComposite` has an attached `SpecId`, we error. In the
case there is no `SpecId` associated, we can turn the
`OpSpecConstantComposite` into a `Construct`. This construct needs to be
emitted in the block of usage, as it can not appear in the root block.
Bug: 398008657
Change-Id: I0ab4e55957aab63597d4e4eba87382738162422f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245994
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 54be1de..b7c6cb2 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -309,6 +309,35 @@
CreateOverride(inst, Value(inst.result_id()), std::nullopt);
break;
}
+ case spv::Op::OpSpecConstantComposite: {
+ auto spec_id = GetSpecId(inst);
+ if (spec_id.has_value()) {
+ TINT_ICE()
+ << "OpSpecConstantCompositeOp not supported when set with a SpecId";
+ }
+
+ auto* cnst = SpvConstant(inst.result_id());
+ if (cnst != nullptr) {
+ // The spec constant is made of literals, so it's return as a constant from
+ // SPIR-V Tools Opt. We can just ignore it and let the normal constant
+ // handling take over.
+ break;
+ }
+
+ Vector<uint32_t, 4> args;
+ args.Reserve(inst.NumInOperands());
+
+ for (uint32_t i = 0; i < inst.NumInOperands(); ++i) {
+ uint32_t id = inst.GetSingleWordInOperand(i);
+ args.Push(id);
+ }
+
+ spec_composites_.insert({inst.result_id(), SpecComposite{
+ .type = Type(inst.type_id()),
+ .args = args,
+ }});
+ break;
+ }
default:
break;
}
@@ -882,6 +911,11 @@
return false;
}
+ // Get the spirv constant for the given `id`. `nullptr` if no constant exists.
+ const spvtools::opt::analysis::Constant* SpvConstant(uint32_t id) {
+ return spirv_context_->get_constant_mgr()->FindDeclaredConstant(id);
+ }
+
/// Attempts to retrieve the current Tint IR value for `id`. This ignores scoping for the
/// variable, if it exists it's returned (or if it's constant it's created). The value will not
/// propagate up through control instructions.
@@ -894,12 +928,27 @@
return *v;
}
- if (auto* c = spirv_context_->get_constant_mgr()->FindDeclaredConstant(id)) {
+ if (auto* c = SpvConstant(id)) {
auto* val = b_.Constant(Constant(c));
values_.Add(id, val);
return val;
}
+ // If this was a spec composite, then it currently isn't in scope, so we construct
+ // a new copy and assign the constant ID to the new construct in this scope.
+ auto iter = spec_composites_.find(id);
+ if (iter != spec_composites_.end()) {
+ Vector<core::ir::Value*, 4> args;
+ for (auto arg : iter->second.args) {
+ args.Push(Value(arg));
+ }
+
+ auto* construct = b_.Construct(iter->second.type, args);
+ current_block_->Append(construct);
+ values_.Replace(id, construct->Result());
+ return construct->Result();
+ }
+
TINT_UNREACHABLE() << "missing value for result ID " << id;
}
@@ -3328,6 +3377,17 @@
// Map of certain instructions back to their originating spirv block
std::unordered_map<core::ir::Instruction*, uint32_t> inst_to_spirv_block_;
+
+ // Structure hold spec composite information
+ struct SpecComposite {
+ // The composite type
+ const core::type::Type* type;
+ // The composite arguments
+ Vector<uint32_t, 4> args;
+ };
+
+ // The set of SPIR-V IDs which map to `OpSpecConstantComposite` information
+ std::unordered_map<uint32_t, SpecComposite> spec_composites_;
};
} // namespace
diff --git a/src/tint/lang/spirv/reader/parser/var_test.cc b/src/tint/lang/spirv/reader/parser/var_test.cc
index c7f333c..d1e845c 100644
--- a/src/tint/lang/spirv/reader/parser/var_test.cc
+++ b/src/tint/lang/spirv/reader/parser/var_test.cc
@@ -1069,5 +1069,264 @@
)");
}
+// In the case of all literals, SPIR-V opt treats the `OpSpecConstantComposite` as an
+// `OpConstantComposite` so it appears in the constant manager already. This then needs no handling
+// on our side.
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_vec2_literals) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %1 "myconst"
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %v2i = OpTypeVector %i32 2
+ %one = OpConstant %i32 1
+ %two = OpConstant %i32 2
+ %1 = OpSpecConstantComposite %v2i %one %two
+ %voidfn = OpTypeFunction %void
+ %main = OpFunction %void None %voidfn
+ %main_entry = OpLabel
+ %2 = OpIAdd %v2i %1 %1
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B1: {
+ %2:vec2<i32> = spirv.add<i32> vec2<i32>(1i, 2i), vec2<i32>(1i, 2i)
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_vec2_SpecConstants) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %1 "myconst"
+ OpDecorate %one SpecId 1
+ OpDecorate %two SpecId 2
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %v2i = OpTypeVector %i32 2
+ %one = OpSpecConstant %i32 1
+ %two = OpSpecConstant %i32 2
+ %1 = OpSpecConstantComposite %v2i %one %two
+ %voidfn = OpTypeFunction %void
+ %main = OpFunction %void None %voidfn
+ %main_entry = OpLabel
+ %2 = OpIAdd %v2i %1 %1
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+$B1: { # root
+ %1:i32 = override 1i @id(1)
+ %2:i32 = override 2i @id(2)
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ %4:vec2<i32> = construct %1, %2
+ %5:vec2<i32> = spirv.add<i32> %4, %4
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_vec4_Mixed) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %1 "myconst"
+ OpDecorate %one SpecId 1
+ OpDecorate %three SpecId 3
+ %void = OpTypeVoid
+ %i32 = OpTypeInt 32 1
+ %v4i = OpTypeVector %i32 4
+ %one = OpSpecConstant %i32 1
+ %two = OpConstant %i32 2
+ %three = OpSpecConstant %i32 3
+ %four = OpConstant %i32 4
+ %1 = OpSpecConstantComposite %v4i %one %two %three %four
+ %voidfn = OpTypeFunction %void
+ %main = OpFunction %void None %voidfn
+ %main_entry = OpLabel
+ %2 = OpIAdd %v4i %1 %1
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+$B1: { # root
+ %1:i32 = override 1i @id(1)
+ %2:i32 = override 3i @id(3)
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ %4:vec4<i32> = construct %1, 2i, %2, 4i
+ %5:vec4<i32> = spirv.add<i32> %4, %4
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_mat3x4_Mixed) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %1 "myconst"
+ OpDecorate %one SpecId 1
+ OpDecorate %three SpecId 3
+ %void = OpTypeVoid
+ %f32 = OpTypeFloat 32
+ %v3f = OpTypeVector %f32 3
+ %mat4x3 = OpTypeMatrix %v3f 4
+ %one = OpSpecConstant %f32 1
+ %two = OpConstant %f32 2
+ %three = OpSpecConstant %f32 3
+ %four = OpConstant %f32 4
+ %1 = OpSpecConstantComposite %v3f %one %two %three
+ %2 = OpSpecConstantComposite %mat4x3 %1 %1 %1 %1
+ %voidfn = OpTypeFunction %void
+ %main = OpFunction %void None %voidfn
+ %main_entry = OpLabel
+ %3 = OpMatrixTimesScalar %mat4x3 %2 %four
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+$B1: { # root
+ %1:f32 = override 1.0f @id(1)
+ %2:f32 = override 3.0f @id(3)
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ %4:vec3<f32> = construct %1, 2.0f, %2
+ %5:mat4x3<f32> = construct %4, %4, %4, %4
+ %6:mat4x3<f32> = mul %5, 4.0f
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_array_Mixed) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %1 "myconst"
+ OpDecorate %one SpecId 1
+ OpDecorate %three SpecId 3
+ %void = OpTypeVoid
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_3 = OpConstant %uint 3
+ %f32 = OpTypeFloat 32
+ %ary = OpTypeArray %f32 %uint_3
+ %ptr = OpTypePointer Function %f32
+ %fn_ptr = OpTypePointer Function %ary
+ %one = OpSpecConstant %f32 1
+ %two = OpConstant %f32 2
+ %three = OpSpecConstant %f32 3
+ %1 = OpSpecConstantComposite %ary %one %two %three
+ %voidfn = OpTypeFunction %void
+ %main = OpFunction %void None %voidfn
+ %main_entry = OpLabel
+ %indexable = OpVariable %fn_ptr Function
+ OpStore %indexable %1
+ %20 = OpAccessChain %ptr %indexable %uint_2
+ %21 = OpLoad %f32 %20
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+$B1: { # root
+ %1:f32 = override 1.0f @id(1)
+ %2:f32 = override 3.0f @id(3)
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ %4:ptr<function, array<f32, 3>, read_write> = var undef
+ %5:array<f32, 3> = construct %1, 2.0f, %2
+ store %4, %5
+ %6:ptr<function, f32, read_write> = access %4, 2u
+ %7:f32 = load %6
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, Var_OpSpecConstantComposite_struct_Mixed) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpExtension "SPV_KHR_storage_buffer_storage_class"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 1 1 1
+ OpDecorate %str Block
+ OpMemberDecorate %str 0 Offset 0
+ %void = OpTypeVoid
+ %uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
+ %f32 = OpTypeFloat 32
+ %str = OpTypeStruct %uint %f32
+ %ptr_str = OpTypePointer Function %str
+ %ptr_f32 = OpTypePointer Function %f32
+ %int_1 = OpConstant %int 1
+ %one = OpSpecConstant %uint 1
+ %two = OpConstant %f32 2
+ %5 = OpTypeFunction %void
+ %2 = OpSpecConstantComposite %str %one %two
+ %1 = OpFunction %void None %5
+ %11 = OpLabel
+ %b = OpVariable %ptr_str Function
+ OpStore %b %2
+ %24 = OpAccessChain %ptr_f32 %b %int_1
+ %17 = OpLoad %f32 %24
+ %25 = OpFAdd %f32 %17 %17
+ OpReturn
+ OpFunctionEnd
+)",
+ R"(
+tint_symbol_2 = struct @align(4) {
+ tint_symbol:u32 @offset(0)
+ tint_symbol_1:f32 @offset(4)
+}
+
+$B1: { # root
+ %1:u32 = override 1u
+}
+
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ %3:ptr<function, tint_symbol_2, read_write> = var undef
+ %4:tint_symbol_2 = construct %1, 2.0f
+ store %3, %4
+ %5:ptr<function, f32, read_write> = access %3, 1i
+ %6:f32 = load %5
+ %7:f32 = add %6, %6
+ ret
+ }
+}
+)");
+}
+
} // namespace
} // namespace tint::spirv::reader