Import Tint changes from Dawn Changes: - 07c73adc95a1177315563225ad57517af4376633 [tint] Remove unused includes from MSL validate.h by James Price <jrprice@google.com> - 700892daf642d290bee5885644389af86d1d3ff0 Add WGSL writer helper to create a WGSL program. by dan sinclair <dsinclair@chromium.org> - b94b9ca68ecc762078f1540e98e917f395ba1747 [tint][fuzz][ir] Include IR disassembly when SPIR-V val f... by Ben Clayton <bclayton@google.com> - fc36dcfaa23772a4771dfdeb4df8d7b5d378a6a8 [tint][ir] Fix UAF in validator by Ben Clayton <bclayton@google.com> - 0a60d528a4a693ba225af966043f01ae1fdfb997 [tint][ir] Stylize more validator diagnostics by Ben Clayton <bclayton@google.com> - a655053bb2a77aeb4a7ebecd5f697c2102c12d6a [tint] Use EXPECT_DEATH_IF_SUPPORTED() by Ben Clayton <bclayton@google.com> - 590110ebb1dae02b4fc98ed5ca08f9c3aeabb3f5 [tint][ir] Fix Std140 transform for arrays of matrices by Ben Clayton <bclayton@google.com> - f35bd1bae613c97b4d93c19dd23a7009cd10e5fb [spirv-reader] Add transform to handle shader IO by James Price <jrprice@google.com> - a5ded402136cc3e734bcf5692c3534f7aa2e5afd [ir] Add ReferencedModuleVars helper by James Price <jrprice@google.com> - 0bb6d4d0b5c854e83cf3846092eb3079afd72194 [tint][ast] Include WGSL dump in fuzzer ICE message by Ben Clayton <bclayton@google.com> - c25a748790c61d35b185fb2996875efaeb530662 [ir] Add Instruction::DetachResult() helper by James Price <jrprice@google.com> GitOrigin-RevId: 07c73adc95a1177315563225ad57517af4376633 Change-Id: Ifa9a927222958aed0240d2b9a99024a3c8572750 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/187800 Kokoro: James Price <jrprice@google.com> Commit-Queue: James Price <jrprice@google.com> Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc index 49fcf79..9f83e9a 100644 --- a/src/tint/cmd/common/helper.cc +++ b/src/tint/cmd/common/helper.cc
@@ -124,23 +124,19 @@ exit(1); } - // Convert the IR module to a WGSL string. + // Convert the IR module to a Program. tint::wgsl::writer::ProgramOptions writer_options; writer_options.allow_non_uniform_derivatives = opts.spirv_reader_options.allow_non_uniform_derivatives; writer_options.allowed_features = opts.spirv_reader_options.allowed_features; - auto wgsl_result = tint::wgsl::writer::WgslFromIR(result.Get(), writer_options); - if (wgsl_result != Success) { - std::cerr << "Failed to convert IR to WGSL:\n\n" - << wgsl_result.Failure().reason << "\n"; + auto prog_result = tint::wgsl::writer::ProgramFromIR(result.Get(), writer_options); + if (prog_result != Success) { + std::cerr << "Failed to convert IR to Program:\n\n" + << prog_result.Failure().reason << "\n"; exit(1); } - // Parse the WGSL string to produce a WGSL AST. - tint::wgsl::reader::Options reader_options; - reader_options.allowed_features = tint::wgsl::AllowedFeatures::Everything(); - auto file = std::make_unique<tint::Source::File>(opts.filename, wgsl_result->wgsl); - return tint::wgsl::reader::Parse(file.get(), reader_options); + return prog_result.Move(); #else std::cerr << "Tint not built with the WGSL writer enabled" << std::endl; exit(1);
diff --git a/src/tint/cmd/remote_compile/BUILD.bazel b/src/tint/cmd/remote_compile/BUILD.bazel index 823c8da..501d62a 100644 --- a/src/tint/cmd/remote_compile/BUILD.bazel +++ b/src/tint/cmd/remote_compile/BUILD.bazel
@@ -42,11 +42,8 @@ "main.cc", ], deps = [ - "//src/tint/lang/wgsl/ast", "//src/tint/utils/macros", "//src/tint/utils/socket", - "//src/tint/utils/text", - "//src/tint/utils/traits", ] + select({ ":tint_build_msl_writer": [
diff --git a/src/tint/cmd/remote_compile/BUILD.cmake b/src/tint/cmd/remote_compile/BUILD.cmake index a54b9fb..6841744 100644 --- a/src/tint/cmd/remote_compile/BUILD.cmake +++ b/src/tint/cmd/remote_compile/BUILD.cmake
@@ -43,11 +43,8 @@ ) tint_target_add_dependencies(tint_cmd_remote_compile_cmd cmd - tint_lang_wgsl_ast tint_utils_macros tint_utils_socket - tint_utils_text - tint_utils_traits ) tint_target_add_external_dependencies(tint_cmd_remote_compile_cmd cmd
diff --git a/src/tint/cmd/remote_compile/BUILD.gn b/src/tint/cmd/remote_compile/BUILD.gn index d2b7baf..6653c2a 100644 --- a/src/tint/cmd/remote_compile/BUILD.gn +++ b/src/tint/cmd/remote_compile/BUILD.gn
@@ -43,11 +43,8 @@ sources = [ "main.cc" ] deps = [ "${tint_src_dir}:thread", - "${tint_src_dir}/lang/wgsl/ast", "${tint_src_dir}/utils/macros", "${tint_src_dir}/utils/socket", - "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", ] if (tint_build_msl_writer) {
diff --git a/src/tint/cmd/test/BUILD.bazel b/src/tint/cmd/test/BUILD.bazel index c99263c..b6d308b 100644 --- a/src/tint/cmd/test/BUILD.bazel +++ b/src/tint/cmd/test/BUILD.bazel
@@ -48,6 +48,7 @@ "//src/tint/cmd/common:test", "//src/tint/lang/core/constant:test", "//src/tint/lang/core/intrinsic:test", + "//src/tint/lang/core/ir/transform/common:test", "//src/tint/lang/core/ir/transform:test", "//src/tint/lang/core/ir:test", "//src/tint/lang/core/type:test",
diff --git a/src/tint/cmd/test/BUILD.cmake b/src/tint/cmd/test/BUILD.cmake index 0cb839e..94c5d90 100644 --- a/src/tint/cmd/test/BUILD.cmake +++ b/src/tint/cmd/test/BUILD.cmake
@@ -49,6 +49,7 @@ tint_cmd_common_test tint_lang_core_constant_test tint_lang_core_intrinsic_test + tint_lang_core_ir_transform_common_test tint_lang_core_ir_transform_test tint_lang_core_ir_test tint_lang_core_type_test
diff --git a/src/tint/cmd/test/BUILD.gn b/src/tint/cmd/test/BUILD.gn index 54897e3..b8647d4 100644 --- a/src/tint/cmd/test/BUILD.gn +++ b/src/tint/cmd/test/BUILD.gn
@@ -57,6 +57,7 @@ "${tint_src_dir}/lang/core/intrinsic:unittests", "${tint_src_dir}/lang/core/ir:unittests", "${tint_src_dir}/lang/core/ir/transform:unittests", + "${tint_src_dir}/lang/core/ir/transform/common:unittests", "${tint_src_dir}/lang/core/type:unittests", "${tint_src_dir}/lang/hlsl/writer/common:unittests", "${tint_src_dir}/lang/msl/ir:unittests",
diff --git a/src/tint/lang/core/ir/access_test.cc b/src/tint/lang/core/ir/access_test.cc index aee7288..072a953 100644 --- a/src/tint/lang/core/ir/access_test.cc +++ b/src/tint/lang/core/ir/access_test.cc
@@ -62,7 +62,7 @@ } TEST_F(IR_AccessTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/bitcast_test.cc b/src/tint/lang/core/ir/bitcast_test.cc index c585375..debb722 100644 --- a/src/tint/lang/core/ir/bitcast_test.cc +++ b/src/tint/lang/core/ir/bitcast_test.cc
@@ -72,7 +72,7 @@ } TEST_F(IR_BitcastTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/block_param_test.cc b/src/tint/lang/core/ir/block_param_test.cc index 96a8e02..d5f14e2 100644 --- a/src/tint/lang/core/ir/block_param_test.cc +++ b/src/tint/lang/core/ir/block_param_test.cc
@@ -37,7 +37,7 @@ using IR_BlockParamTest = IRTestHelper; TEST_F(IR_BlockParamTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/break_if_test.cc b/src/tint/lang/core/ir/break_if_test.cc index 0e14900..d472c47 100644 --- a/src/tint/lang/core/ir/break_if_test.cc +++ b/src/tint/lang/core/ir/break_if_test.cc
@@ -60,7 +60,7 @@ } TEST_F(IR_BreakIfTest, Fail_NullLoop) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h index 99e94d1..84fc0ed 100644 --- a/src/tint/lang/core/ir/builder.h +++ b/src/tint/lang/core/ir/builder.h
@@ -960,6 +960,19 @@ /// @returns the instruction ir::Discard* Discard(); + /// Creates a user function call instruction with an existing instruction result + /// @param result the instruction result to use + /// @param func the function to call + /// @param args the call arguments + /// @returns the instruction + template <typename... ARGS> + ir::UserCall* CallWithResult(ir::InstructionResult* result, + ir::Function* func, + ARGS&&... args) { + return Append(ir.allocators.instructions.Create<ir::UserCall>( + result, func, Values(std::forward<ARGS>(args)...))); + } + /// Creates a user function call instruction /// @param func the function to call /// @param args the call arguments @@ -976,8 +989,7 @@ /// @returns the instruction template <typename... ARGS> ir::UserCall* Call(const core::type::Type* type, ir::Function* func, ARGS&&... args) { - return Append(ir.allocators.instructions.Create<ir::UserCall>( - InstructionResult(type), func, Values(std::forward<ARGS>(args)...))); + return CallWithResult(InstructionResult(type), func, Values(std::forward<ARGS>(args)...)); } /// Creates a user function call instruction @@ -988,8 +1000,20 @@ template <typename TYPE, typename... ARGS> ir::UserCall* Call(ir::Function* func, ARGS&&... args) { auto* type = ir.Types().Get<TYPE>(); - return Append(ir.allocators.instructions.Create<ir::UserCall>( - InstructionResult(type), func, Values(std::forward<ARGS>(args)...))); + return CallWithResult(InstructionResult(type), func, Values(std::forward<ARGS>(args)...)); + } + + /// Creates a core builtin call instruction with an existing instruction result + /// @param result the instruction result to use + /// @param func the builtin function to call + /// @param args the call arguments + /// @returns the instruction + template <typename... ARGS> + ir::CoreBuiltinCall* CallWithResult(core::ir::InstructionResult* result, + core::BuiltinFn func, + ARGS&&... args) { + return Append(ir.allocators.instructions.Create<ir::CoreBuiltinCall>( + result, func, Values(std::forward<ARGS>(args)...))); } /// Creates a core builtin call instruction @@ -999,8 +1023,7 @@ /// @returns the instruction template <typename... ARGS> ir::CoreBuiltinCall* Call(const core::type::Type* type, core::BuiltinFn func, ARGS&&... args) { - return Append(ir.allocators.instructions.Create<ir::CoreBuiltinCall>( - InstructionResult(type), func, Values(std::forward<ARGS>(args)...))); + return CallWithResult(InstructionResult(type), func, Values(std::forward<ARGS>(args)...)); } /// Creates a core builtin call instruction @@ -1011,11 +1034,22 @@ template <typename TYPE, typename... ARGS> ir::CoreBuiltinCall* Call(core::BuiltinFn func, ARGS&&... args) { auto* type = ir.Types().Get<TYPE>(); - return Append(ir.allocators.instructions.Create<ir::CoreBuiltinCall>( - InstructionResult(type), func, Values(std::forward<ARGS>(args)...))); + return CallWithResult(InstructionResult(type), func, Values(std::forward<ARGS>(args)...)); } - /// Creates a core builtin call instruction + /// Creates a builtin call instruction with an existing instruction result + /// @param result the instruction result to use + /// @param func the builtin function to call + /// @param args the call arguments + /// @returns the instruction + template <typename KLASS, typename FUNC, typename... ARGS> + tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::BuiltinCall>, KLASS*> + CallWithResult(ir::InstructionResult* result, FUNC func, ARGS&&... args) { + return Append(ir.allocators.instructions.Create<KLASS>( + result, func, Values(std::forward<ARGS>(args)...))); + } + + /// Creates a builtin call instruction /// @param type the return type of the call /// @param func the builtin function to call /// @param args the call arguments @@ -1023,8 +1057,8 @@ template <typename KLASS, typename FUNC, typename... ARGS> tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::BuiltinCall>, KLASS*> Call(const core::type::Type* type, FUNC func, ARGS&&... args) { - return Append(ir.allocators.instructions.Create<KLASS>( - InstructionResult(type), func, Values(std::forward<ARGS>(args)...))); + return CallWithResult<KLASS>(InstructionResult(type), func, + Values(std::forward<ARGS>(args)...)); } /// Creates a value conversion instruction to the template type T @@ -1046,6 +1080,16 @@ InstructionResult(to), Value(std::forward<VAL>(val)))); } + /// Creates a value constructor instruction with an existing instruction result + /// @param result the instruction result to use + /// @param args the arguments to the constructor + /// @returns the instruction + template <typename... ARGS> + ir::Construct* ConstructWithResult(ir::InstructionResult* result, ARGS&&... args) { + return Append(ir.allocators.instructions.Create<ir::Construct>( + result, Values(std::forward<ARGS>(args)...))); + } + /// Creates a value constructor instruction to the template type T /// @param args the arguments to the constructor /// @returns the instruction @@ -1061,8 +1105,17 @@ /// @returns the instruction template <typename... ARGS> ir::Construct* Construct(const core::type::Type* type, ARGS&&... args) { - return Append(ir.allocators.instructions.Create<ir::Construct>( - InstructionResult(type), Values(std::forward<ARGS>(args)...))); + return ConstructWithResult(InstructionResult(type), Values(std::forward<ARGS>(args)...)); + } + + /// Creates a load instruction with an existing result + /// @param result the instruction result to use + /// @param from the expression being loaded from + /// @returns the instruction + template <typename VAL> + ir::Load* LoadWithResult(ir::InstructionResult* result, VAL&& from) { + auto* value = Value(std::forward<VAL>(from)); + return Append(ir.allocators.instructions.Create<ir::Load>(result, value)); } /// Creates a load instruction @@ -1071,8 +1124,7 @@ template <typename VAL> ir::Load* Load(VAL&& from) { auto* value = Value(std::forward<VAL>(from)); - return Append(ir.allocators.instructions.Create<ir::Load>( - InstructionResult(value->Type()->UnwrapPtrOrRef()), value)); + return LoadWithResult(InstructionResult(value->Type()->UnwrapPtrOrRef()), value); } /// Creates a store instruction @@ -1102,6 +1154,22 @@ value_val)); } + /// Creates a load vector element instruction with an existing instruction result + /// @param result the instruction result to use + /// @param from the vector pointer expression being loaded from + /// @param index the new vector element index + /// @returns the instruction + template <typename FROM, typename INDEX> + ir::LoadVectorElement* LoadVectorElementWithResult(ir::InstructionResult* result, + FROM&& from, + INDEX&& index) { + CheckForNonDeterministicEvaluation<FROM, INDEX>(); + auto* from_val = Value(std::forward<FROM>(from)); + auto* index_val = Value(std::forward<INDEX>(index)); + return Append( + ir.allocators.instructions.Create<ir::LoadVectorElement>(result, from_val, index_val)); + } + /// Creates a load vector element instruction /// @param from the vector pointer expression being loaded from /// @param index the new vector element index @@ -1112,8 +1180,7 @@ auto* from_val = Value(std::forward<FROM>(from)); auto* index_val = Value(std::forward<INDEX>(index)); auto* res = InstructionResult(VectorPtrElementType(from_val->Type())); - return Append( - ir.allocators.instructions.Create<ir::LoadVectorElement>(res, from_val, index_val)); + return LoadVectorElementWithResult(res, from_val, index_val); } /// Creates a new `var` declaration @@ -1351,6 +1418,19 @@ return FunctionParam(type); } + /// Creates a new `Access` with an existing instruction result + /// @param result the instruction result to use + /// @param object the object being accessed + /// @param indices the access indices + /// @returns the instruction + template <typename OBJ, typename... ARGS> + ir::Access* AccessWithResult(ir::InstructionResult* result, OBJ&& object, ARGS&&... indices) { + CheckForNonDeterministicEvaluation<OBJ, ARGS...>(); + auto* obj_val = Value(std::forward<OBJ>(object)); + return Append(ir.allocators.instructions.Create<ir::Access>( + result, obj_val, Values(std::forward<ARGS>(indices)...))); + } + /// Creates a new `Access` /// @param type the return type /// @param object the object being accessed @@ -1358,10 +1438,8 @@ /// @returns the instruction template <typename OBJ, typename... ARGS> ir::Access* Access(const core::type::Type* type, OBJ&& object, ARGS&&... indices) { - CheckForNonDeterministicEvaluation<OBJ, ARGS...>(); - auto* obj_val = Value(std::forward<OBJ>(object)); - return Append(ir.allocators.instructions.Create<ir::Access>( - InstructionResult(type), obj_val, Values(std::forward<ARGS>(indices)...))); + return AccessWithResult(InstructionResult(type), std::forward<OBJ>(object), + Values(std::forward<ARGS>(indices)...)); } /// Creates a new `Access`
diff --git a/src/tint/lang/core/ir/constant_test.cc b/src/tint/lang/core/ir/constant_test.cc index c8b6ef1..e960936 100644 --- a/src/tint/lang/core/ir/constant_test.cc +++ b/src/tint/lang/core/ir/constant_test.cc
@@ -112,11 +112,11 @@ } TEST_F(IR_ConstantTest, Fail_NullValue) { - EXPECT_DEATH({ Constant c(nullptr); }, ""); + EXPECT_DEATH_IF_SUPPORTED({ Constant c(nullptr); }, ""); } TEST_F(IR_ConstantTest, Fail_Builder_NullValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/construct_test.cc b/src/tint/lang/core/ir/construct_test.cc index 6e4b005..d8cef8e 100644 --- a/src/tint/lang/core/ir/construct_test.cc +++ b/src/tint/lang/core/ir/construct_test.cc
@@ -56,7 +56,7 @@ } TEST_F(IR_ConstructTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/continue_test.cc b/src/tint/lang/core/ir/continue_test.cc index 79875cf..d4d9c6b 100644 --- a/src/tint/lang/core/ir/continue_test.cc +++ b/src/tint/lang/core/ir/continue_test.cc
@@ -58,7 +58,7 @@ } TEST_F(IR_ContinueTest, Fail_NullLoop) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/convert_test.cc b/src/tint/lang/core/ir/convert_test.cc index 73c3076..321809c 100644 --- a/src/tint/lang/core/ir/convert_test.cc +++ b/src/tint/lang/core/ir/convert_test.cc
@@ -35,7 +35,7 @@ using IR_ConvertTest = IRTestHelper; TEST_F(IR_ConvertTest, Fail_NullToType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/core_binary_test.cc b/src/tint/lang/core/ir/core_binary_test.cc index 6fde891..6ce32d7 100644 --- a/src/tint/lang/core/ir/core_binary_test.cc +++ b/src/tint/lang/core/ir/core_binary_test.cc
@@ -40,7 +40,7 @@ using IR_BinaryTest = IRTestHelper; TEST_F(IR_BinaryTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/core_builtin_call_test.cc b/src/tint/lang/core/ir/core_builtin_call_test.cc index 7897862..bfc897e 100644 --- a/src/tint/lang/core/ir/core_builtin_call_test.cc +++ b/src/tint/lang/core/ir/core_builtin_call_test.cc
@@ -55,7 +55,7 @@ } TEST_F(IR_CoreBuiltinCallTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -65,7 +65,7 @@ } TEST_F(IR_CoreBuiltinCallTest, Fail_NoneFunction) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/core_unary_test.cc b/src/tint/lang/core/ir/core_unary_test.cc index 88de1c1..26054a0 100644 --- a/src/tint/lang/core/ir/core_unary_test.cc +++ b/src/tint/lang/core/ir/core_unary_test.cc
@@ -80,7 +80,7 @@ } TEST_F(IR_UnaryTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/disassembly.cc b/src/tint/lang/core/ir/disassembly.cc index a07033f..df0cc4e 100644 --- a/src/tint/lang/core/ir/disassembly.cc +++ b/src/tint/lang/core/ir/disassembly.cc
@@ -30,6 +30,7 @@ #include <string_view> #include "src//tint/lang/core/ir/unary.h" +#include "src/tint/lang/core/binary_op.h" #include "src/tint/lang/core/constant/composite.h" #include "src/tint/lang/core/constant/scalar.h" #include "src/tint/lang/core/constant/splat.h" @@ -796,64 +797,7 @@ void Disassembly::EmitBinary(const Binary* b) { SourceMarker sm(this); EmitValueWithType(b); - out_ << " = "; - switch (b->Op()) { - case BinaryOp::kAdd: - out_ << StyleInstruction("add"); - break; - case BinaryOp::kSubtract: - out_ << StyleInstruction("sub"); - break; - case BinaryOp::kMultiply: - out_ << StyleInstruction("mul"); - break; - case BinaryOp::kDivide: - out_ << StyleInstruction("div"); - break; - case BinaryOp::kModulo: - out_ << StyleInstruction("mod"); - break; - case BinaryOp::kAnd: - out_ << StyleInstruction("and"); - break; - case BinaryOp::kOr: - out_ << StyleInstruction("or"); - break; - case BinaryOp::kXor: - out_ << StyleInstruction("xor"); - break; - case BinaryOp::kEqual: - out_ << StyleInstruction("eq"); - break; - case BinaryOp::kNotEqual: - out_ << StyleInstruction("neq"); - break; - case BinaryOp::kLessThan: - out_ << StyleInstruction("lt"); - break; - case BinaryOp::kGreaterThan: - out_ << StyleInstruction("gt"); - break; - case BinaryOp::kLessThanEqual: - out_ << StyleInstruction("lte"); - break; - case BinaryOp::kGreaterThanEqual: - out_ << StyleInstruction("gte"); - break; - case BinaryOp::kShiftLeft: - out_ << StyleInstruction("shl"); - break; - case BinaryOp::kShiftRight: - out_ << StyleInstruction("shr"); - break; - case BinaryOp::kLogicalAnd: - out_ << StyleInstruction("logical-and"); - break; - case BinaryOp::kLogicalOr: - out_ << StyleInstruction("logical-or"); - break; - } - out_ << " "; + out_ << " = " << NameOf(b->Op()) << " "; EmitOperandList(b); sm.Store(b); @@ -862,25 +806,7 @@ void Disassembly::EmitUnary(const Unary* u) { SourceMarker sm(this); EmitValueWithType(u); - out_ << " = "; - switch (u->Op()) { - case UnaryOp::kComplement: - out_ << StyleInstruction("complement"); - break; - case UnaryOp::kNegation: - out_ << StyleInstruction("negation"); - break; - case UnaryOp::kAddressOf: - out_ << StyleInstruction("ref-to-ptr"); - break; - case UnaryOp::kIndirection: - out_ << StyleInstruction("ptr-to-ref"); - break; - case UnaryOp::kNot: - out_ << StyleInstruction("not"); - break; - } - out_ << " "; + out_ << " = " << NameOf(u->Op()) << " "; EmitOperandList(u); sm.Store(u); @@ -984,4 +910,62 @@ return StyledText{} << StyleInstruction(name); } +StyledText Disassembly::NameOf(BinaryOp op) { + switch (op) { + case BinaryOp::kAdd: + return StyledText{} << StyleInstruction("add"); + case BinaryOp::kSubtract: + return StyledText{} << StyleInstruction("sub"); + case BinaryOp::kMultiply: + return StyledText{} << StyleInstruction("mul"); + case BinaryOp::kDivide: + return StyledText{} << StyleInstruction("div"); + case BinaryOp::kModulo: + return StyledText{} << StyleInstruction("mod"); + case BinaryOp::kAnd: + return StyledText{} << StyleInstruction("and"); + case BinaryOp::kOr: + return StyledText{} << StyleInstruction("or"); + case BinaryOp::kXor: + return StyledText{} << StyleInstruction("xor"); + case BinaryOp::kEqual: + return StyledText{} << StyleInstruction("eq"); + case BinaryOp::kNotEqual: + return StyledText{} << StyleInstruction("neq"); + case BinaryOp::kLessThan: + return StyledText{} << StyleInstruction("lt"); + case BinaryOp::kGreaterThan: + return StyledText{} << StyleInstruction("gt"); + case BinaryOp::kLessThanEqual: + return StyledText{} << StyleInstruction("lte"); + case BinaryOp::kGreaterThanEqual: + return StyledText{} << StyleInstruction("gte"); + case BinaryOp::kShiftLeft: + return StyledText{} << StyleInstruction("shl"); + case BinaryOp::kShiftRight: + return StyledText{} << StyleInstruction("shr"); + case BinaryOp::kLogicalAnd: + return StyledText{} << StyleInstruction("logical-and"); + case BinaryOp::kLogicalOr: + return StyledText{} << StyleInstruction("logical-or"); + } + TINT_UNREACHABLE() << op; +} + +StyledText Disassembly::NameOf(UnaryOp op) { + switch (op) { + case UnaryOp::kComplement: + return StyledText{} << StyleInstruction("complement"); + case UnaryOp::kNegation: + return StyledText{} << StyleInstruction("negation"); + case UnaryOp::kAddressOf: + return StyledText{} << StyleInstruction("ref-to-ptr"); + case UnaryOp::kIndirection: + return StyledText{} << StyleInstruction("ptr-to-ref"); + case UnaryOp::kNot: + return StyledText{} << StyleInstruction("not"); + } + TINT_UNREACHABLE() << op; +} + } // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/disassembly.h b/src/tint/lang/core/ir/disassembly.h index 677b386..70c48b4 100644 --- a/src/tint/lang/core/ir/disassembly.h +++ b/src/tint/lang/core/ir/disassembly.h
@@ -32,6 +32,7 @@ #include <string> #include <string_view> +#include "src/tint/lang/core/binary_op.h" #include "src/tint/lang/core/ir/binary.h" #include "src/tint/lang/core/ir/block.h" #include "src/tint/lang/core/ir/block_param.h" @@ -108,6 +109,12 @@ /// @returns the disassembled name for the Switch @p inst StyledText NameOf(const Switch* inst); + /// @returns the disassembled name for the BinaryOp @p op + StyledText NameOf(BinaryOp op); + + /// @returns the disassembled name for the UnaryOp @p op + StyledText NameOf(UnaryOp op); + /// @param inst the instruction to retrieve /// @returns the source for the instruction Source InstructionSource(const Instruction* inst) const {
diff --git a/src/tint/lang/core/ir/function_param_test.cc b/src/tint/lang/core/ir/function_param_test.cc index 49bc373..80b150f 100644 --- a/src/tint/lang/core/ir/function_param_test.cc +++ b/src/tint/lang/core/ir/function_param_test.cc
@@ -37,7 +37,7 @@ using IR_FunctionParamTest = IRTestHelper; TEST_F(IR_FunctionParamTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -47,7 +47,7 @@ } TEST_F(IR_FunctionParamTest, Fail_SetDuplicateBuiltin) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/function_test.cc b/src/tint/lang/core/ir/function_test.cc index 19443f6..9f0161a 100644 --- a/src/tint/lang/core/ir/function_test.cc +++ b/src/tint/lang/core/ir/function_test.cc
@@ -37,7 +37,7 @@ using IR_FunctionTest = IRTestHelper; TEST_F(IR_FunctionTest, Fail_NullReturnType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -47,7 +47,7 @@ } TEST_F(IR_FunctionTest, Fail_DoubleReturnBuiltin) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -59,7 +59,7 @@ } TEST_F(IR_FunctionTest, Fail_NullParam) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -70,7 +70,7 @@ } TEST_F(IR_FunctionTest, Fail_NullBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/if_test.cc b/src/tint/lang/core/ir/if_test.cc index cb02e52..236a47d 100644 --- a/src/tint/lang/core/ir/if_test.cc +++ b/src/tint/lang/core/ir/if_test.cc
@@ -55,7 +55,7 @@ } TEST_F(IR_IfTest, Fail_NullTrueBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -65,7 +65,7 @@ } TEST_F(IR_IfTest, Fail_NullFalseBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/instruction.cc b/src/tint/lang/core/ir/instruction.cc index 8d5218c..3f99d68 100644 --- a/src/tint/lang/core/ir/instruction.cc +++ b/src/tint/lang/core/ir/instruction.cc
@@ -73,4 +73,11 @@ Block()->Remove(this); } +InstructionResult* Instruction::DetachResult() { + TINT_ASSERT(Results().Length() == 1u); + auto* result = Results()[0]; + SetResults({}); + return result; +} + } // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/instruction.h b/src/tint/lang/core/ir/instruction.h index f8c72d5..cff4ab0 100644 --- a/src/tint/lang/core/ir/instruction.h +++ b/src/tint/lang/core/ir/instruction.h
@@ -115,6 +115,10 @@ /// Removes this instruction from the owning block void Remove(); + /// Detach an instruction result from this instruction. + /// @returns the instruction result that was detached + InstructionResult* DetachResult(); + /// @param idx the index of the operand /// @returns the operand with index @p idx, or `nullptr` if there are no operands or the index /// is out of bounds.
diff --git a/src/tint/lang/core/ir/instruction_result_test.cc b/src/tint/lang/core/ir/instruction_result_test.cc index 665f17c..01aff8d 100644 --- a/src/tint/lang/core/ir/instruction_result_test.cc +++ b/src/tint/lang/core/ir/instruction_result_test.cc
@@ -37,7 +37,7 @@ using IR_InstructionResultTest = IRTestHelper; TEST_F(IR_InstructionResultTest, Destroy_HasInstruction) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/instruction_test.cc b/src/tint/lang/core/ir/instruction_test.cc index fa95ba5..3813711 100644 --- a/src/tint/lang/core/ir/instruction_test.cc +++ b/src/tint/lang/core/ir/instruction_test.cc
@@ -30,6 +30,8 @@ #include "src/tint/lang/core/ir/ir_helper_test.h" #include "src/tint/lang/core/ir/module.h" +using namespace tint::core::number_suffixes; // NOLINT + namespace tint::core::ir { namespace { @@ -46,7 +48,7 @@ } TEST_F(IR_InstructionTest, Fail_InsertBeforeNullptr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -58,7 +60,7 @@ } TEST_F(IR_InstructionTest, Fail_InsertBeforeNotInserted) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -81,7 +83,7 @@ } TEST_F(IR_InstructionTest, Fail_InsertAfterNullptr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -93,7 +95,7 @@ } TEST_F(IR_InstructionTest, Fail_InsertAfterNotInserted) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -117,7 +119,7 @@ } TEST_F(IR_InstructionTest, Fail_ReplaceWithNullptr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -131,7 +133,7 @@ } TEST_F(IR_InstructionTest, Fail_ReplaceWithNotInserted) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -155,7 +157,7 @@ } TEST_F(IR_InstructionTest, Fail_RemoveNotInserted) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -166,5 +168,16 @@ ""); } +TEST_F(IR_InstructionTest, DetachResult) { + auto* inst = b.Let("foo", 42_u); + auto* result = inst->Result(0); + EXPECT_EQ(result->Instruction(), inst); + + auto* detached = inst->DetachResult(); + EXPECT_EQ(detached, result); + EXPECT_EQ(detached->Instruction(), nullptr); + EXPECT_EQ(inst->Results().Length(), 0u); +} + } // namespace } // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/let_test.cc b/src/tint/lang/core/ir/let_test.cc index 218bd1e..3d998d6 100644 --- a/src/tint/lang/core/ir/let_test.cc +++ b/src/tint/lang/core/ir/let_test.cc
@@ -41,7 +41,7 @@ using IR_LetTest = IRTestHelper; TEST_F(IR_LetTest, Fail_NullValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/loop_test.cc b/src/tint/lang/core/ir/loop_test.cc index 0f522a0..6a7e214 100644 --- a/src/tint/lang/core/ir/loop_test.cc +++ b/src/tint/lang/core/ir/loop_test.cc
@@ -47,7 +47,7 @@ } TEST_F(IR_LoopTest, Fail_NullInitializerBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -57,7 +57,7 @@ } TEST_F(IR_LoopTest, Fail_NullBodyBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -67,7 +67,7 @@ } TEST_F(IR_LoopTest, Fail_NullContinuingBlock) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/multi_in_block_test.cc b/src/tint/lang/core/ir/multi_in_block_test.cc index 5d4d459..583f544 100644 --- a/src/tint/lang/core/ir/multi_in_block_test.cc +++ b/src/tint/lang/core/ir/multi_in_block_test.cc
@@ -36,7 +36,7 @@ using IR_MultiInBlockTest = IRTestHelper; TEST_F(IR_MultiInBlockTest, Fail_NullInboundBranch) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/next_iteration_test.cc b/src/tint/lang/core/ir/next_iteration_test.cc index e1554cb..89b4d40 100644 --- a/src/tint/lang/core/ir/next_iteration_test.cc +++ b/src/tint/lang/core/ir/next_iteration_test.cc
@@ -35,7 +35,7 @@ using IR_NextIterationTest = IRTestHelper; TEST_F(IR_NextIterationTest, Fail_NullLoop) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/swizzle_test.cc b/src/tint/lang/core/ir/swizzle_test.cc index ba911f7..6d26ab0 100644 --- a/src/tint/lang/core/ir/swizzle_test.cc +++ b/src/tint/lang/core/ir/swizzle_test.cc
@@ -54,7 +54,7 @@ } TEST_F(IR_SwizzleTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -65,7 +65,7 @@ } TEST_F(IR_SwizzleTest, Fail_EmptyIndices) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -76,7 +76,7 @@ } TEST_F(IR_SwizzleTest, Fail_TooManyIndices) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod}; @@ -87,7 +87,7 @@ } TEST_F(IR_SwizzleTest, Fail_IndexOutOfRange) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel index 467e451..3ea71c4 100644 --- a/src/tint/lang/core/ir/transform/BUILD.bazel +++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -85,6 +85,7 @@ "//src/tint/lang/core/constant", "//src/tint/lang/core/intrinsic", "//src/tint/lang/core/ir", + "//src/tint/lang/core/ir/transform/common", "//src/tint/lang/core/type", "//src/tint/utils/containers", "//src/tint/utils/diagnostic",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake index 1ae8efb..f3a8b32 100644 --- a/src/tint/lang/core/ir/transform/BUILD.cmake +++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -34,6 +34,8 @@ # Do not modify this file directly ################################################################################ +include(lang/core/ir/transform/common/BUILD.cmake) + ################################################################################ # Target: tint_lang_core_ir_transform # Kind: lib @@ -84,6 +86,7 @@ tint_lang_core_constant tint_lang_core_intrinsic tint_lang_core_ir + tint_lang_core_ir_transform_common tint_lang_core_type tint_utils_containers tint_utils_diagnostic
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn index 95de9fc..db194b7 100644 --- a/src/tint/lang/core/ir/transform/BUILD.gn +++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -88,6 +88,7 @@ "${tint_src_dir}/lang/core/constant", "${tint_src_dir}/lang/core/intrinsic", "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/ir/transform/common", "${tint_src_dir}/lang/core/type", "${tint_src_dir}/utils/containers", "${tint_src_dir}/utils/diagnostic",
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc index 7d4a962..a5aec3c 100644 --- a/src/tint/lang/core/ir/transform/binary_polyfill.cc +++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -91,29 +91,18 @@ // Polyfill the binary instructions that we found. for (auto* binary : worklist) { - ir::Value* replacement = nullptr; switch (binary->Op()) { case BinaryOp::kDivide: case BinaryOp::kModulo: - replacement = IntDivMod(binary); + IntDivMod(binary); break; case BinaryOp::kShiftLeft: case BinaryOp::kShiftRight: - replacement = MaskShiftAmount(binary); + MaskShiftAmount(binary); break; default: break; } - TINT_ASSERT(replacement); - - if (replacement != binary->Result(0)) { - // Replace the old binary instruction result with the new value. - if (auto name = ir.NameOf(binary->Result(0))) { - ir.SetName(replacement, name); - } - binary->Result(0)->ReplaceAllUsesWith(replacement); - binary->Destroy(); - } } } @@ -145,8 +134,7 @@ /// Replace an integer divide or modulo with a call to helper function that prevents /// divide-by-zero and signed integer overflow. /// @param binary the binary instruction - /// @returns the replacement value - ir::Value* IntDivMod(ir::CoreBinary* binary) { + void IntDivMod(ir::CoreBinary* binary) { auto* result_ty = binary->Result(0)->Type(); bool is_div = binary->Op() == BinaryOp::kDivide; bool is_signed = result_ty->is_signed_integer_scalar_or_vector(); @@ -217,26 +205,23 @@ }; // Call the helper function, splatting the arguments to match the target vector width. - Value* result = nullptr; b.InsertBefore(binary, [&] { auto* lhs = maybe_splat(binary->LHS()); auto* rhs = maybe_splat(binary->RHS()); - result = b.Call(result_ty, helper, lhs, rhs)->Result(0); + b.CallWithResult(binary->DetachResult(), helper, lhs, rhs); }); - return result; + binary->Destroy(); } /// Mask the RHS of a shift instruction to ensure it is modulo the bitwidth of the LHS. /// @param binary the binary instruction - /// @returns the replacement value - ir::Value* MaskShiftAmount(ir::CoreBinary* binary) { + void MaskShiftAmount(ir::CoreBinary* binary) { auto* lhs = binary->LHS(); auto* rhs = binary->RHS(); auto* mask = b.Constant(u32(lhs->Type()->DeepestElement()->Size() * 8 - 1)); auto* masked = b.And(rhs->Type(), rhs, MatchWidth(mask, rhs->Type())); masked->InsertBefore(binary); binary->SetOperand(ir::CoreBinary::kRhsOperandOffset, masked->Result(0)); - return binary->Result(0); } };
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc index 0eb45f8..ef68d5b 100644 --- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc +++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -147,72 +147,61 @@ // Polyfill the builtin call instructions that we found. for (auto* builtin : worklist) { - ir::Value* replacement = nullptr; switch (builtin->Func()) { case core::BuiltinFn::kClamp: - replacement = ClampInt(builtin); + ClampInt(builtin); break; case core::BuiltinFn::kCountLeadingZeros: - replacement = CountLeadingZeros(builtin); + CountLeadingZeros(builtin); break; case core::BuiltinFn::kCountTrailingZeros: - replacement = CountTrailingZeros(builtin); + CountTrailingZeros(builtin); break; case core::BuiltinFn::kExtractBits: - replacement = ExtractBits(builtin); + ExtractBits(builtin); break; case core::BuiltinFn::kFirstLeadingBit: - replacement = FirstLeadingBit(builtin); + FirstLeadingBit(builtin); break; case core::BuiltinFn::kFirstTrailingBit: - replacement = FirstTrailingBit(builtin); + FirstTrailingBit(builtin); break; case core::BuiltinFn::kInsertBits: - replacement = InsertBits(builtin); + InsertBits(builtin); break; case core::BuiltinFn::kSaturate: - replacement = Saturate(builtin); + Saturate(builtin); break; case core::BuiltinFn::kTextureSampleBaseClampToEdge: - replacement = TextureSampleBaseClampToEdge_2d_f32(builtin); + TextureSampleBaseClampToEdge_2d_f32(builtin); break; case core::BuiltinFn::kDot4I8Packed: - replacement = Dot4I8Packed(builtin); + Dot4I8Packed(builtin); break; case core::BuiltinFn::kDot4U8Packed: - replacement = Dot4U8Packed(builtin); + Dot4U8Packed(builtin); break; case core::BuiltinFn::kPack4XI8: - replacement = Pack4xI8(builtin); + Pack4xI8(builtin); break; case core::BuiltinFn::kPack4XU8: - replacement = Pack4xU8(builtin); + Pack4xU8(builtin); break; case core::BuiltinFn::kPack4XI8Clamp: - replacement = Pack4xI8Clamp(builtin); + Pack4xI8Clamp(builtin); break; case core::BuiltinFn::kPack4XU8Clamp: - replacement = Pack4xU8Clamp(builtin); + Pack4xU8Clamp(builtin); break; case core::BuiltinFn::kUnpack4XI8: - replacement = Unpack4xI8(builtin); + Unpack4xI8(builtin); break; case core::BuiltinFn::kUnpack4XU8: - replacement = Unpack4xU8(builtin); + Unpack4xU8(builtin); break; default: break; } - TINT_ASSERT(replacement); - - if (replacement != builtin->Result(0)) { - // Replace the old builtin call result with the new value. - if (auto name = ir.NameOf(builtin->Result(0))) { - ir.SetName(replacement, name); - } - builtin->Result(0)->ReplaceAllUsesWith(replacement); - builtin->Destroy(); - } } } @@ -243,26 +232,22 @@ /// Polyfill a `clamp()` builtin call for integers. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* ClampInt(ir::CoreBuiltinCall* call) { + void ClampInt(ir::CoreBuiltinCall* call) { auto* type = call->Result(0)->Type(); auto* e = call->Args()[0]; auto* low = call->Args()[1]; auto* high = call->Args()[2]; - Value* result = nullptr; b.InsertBefore(call, [&] { auto* max = b.Call(type, core::BuiltinFn::kMax, e, low); - auto* min = b.Call(type, core::BuiltinFn::kMin, max, high); - result = min->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kMin, max, high); }); - return result; + call->Destroy(); } /// Polyfill a `countLeadingZeros()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* CountLeadingZeros(ir::CoreBuiltinCall* call) { + void CountLeadingZeros(ir::CoreBuiltinCall* call) { auto* input = call->Args()[0]; auto* result_ty = input->Type(); auto* uint_ty = MatchWidth(ty.u32(), result_ty); @@ -271,7 +256,6 @@ // Make an u32 constant with the same component count as result_ty. auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); }; - Value* result = nullptr; b.InsertBefore(call, [&] { // %x = %input; // if (%x is signed) { @@ -309,23 +293,23 @@ b.LessThanEqual(bool_ty, x, V(0x7fffffff))); auto* b0 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0))); - result = b.Add(uint_ty, - b.Or(uint_ty, b16, - b.Or(uint_ty, b8, - b.Or(uint_ty, b4, b.Or(uint_ty, b2, b.Or(uint_ty, b1, b0))))), - b0) - ->Result(0); + Instruction* result = b.Add( + uint_ty, + b.Or( + uint_ty, b16, + b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b.Or(uint_ty, b1, b0))))), + b0); if (result_ty->is_signed_integer_scalar_or_vector()) { - result = b.Bitcast(result_ty, result)->Result(0); + result = b.Bitcast(result_ty, result); } + result->SetResults(Vector{call->DetachResult()}); }); - return result; + call->Destroy(); } /// Polyfill a `countTrailingZeros()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* CountTrailingZeros(ir::CoreBuiltinCall* call) { + void CountTrailingZeros(ir::CoreBuiltinCall* call) { auto* input = call->Args()[0]; auto* result_ty = input->Type(); auto* uint_ty = MatchWidth(ty.u32(), result_ty); @@ -334,7 +318,6 @@ // Make an u32 constant with the same component count as result_ty. auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); }; - Value* result = nullptr; b.InsertBefore(call, [&] { // %x = %input; // if (%x is signed) { @@ -372,22 +355,21 @@ b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000001)), V(0))); auto* b0 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1), b.Equal(bool_ty, x, V(0))); - result = b.Add(uint_ty, - b.Or(uint_ty, b16, - b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))), - b0) - ->Result(0); + Instruction* result = b.Add( + uint_ty, + b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))), + b0); if (result_ty->is_signed_integer_scalar_or_vector()) { - result = b.Bitcast(result_ty, result)->Result(0); + result = b.Bitcast(result_ty, result); } + result->SetResults(Vector{call->DetachResult()}); }); - return result; + call->Destroy(); } /// Polyfill an `extractBits()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* ExtractBits(ir::CoreBuiltinCall* call) { + void ExtractBits(ir::CoreBuiltinCall* call) { auto* offset = call->Args()[1]; auto* count = call->Args()[2]; @@ -406,7 +388,7 @@ call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 1, o->Result(0)); call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 2, c->Result(0)); }); - return call->Result(0); + break; } default: TINT_UNIMPLEMENTED() << "extractBits polyfill level"; @@ -415,8 +397,7 @@ /// Polyfill a `firstLeadingBit()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* FirstLeadingBit(ir::CoreBuiltinCall* call) { + void FirstLeadingBit(ir::CoreBuiltinCall* call) { auto* input = call->Args()[0]; auto* result_ty = input->Type(); auto* uint_ty = MatchWidth(ty.u32(), result_ty); @@ -425,7 +406,6 @@ // Make an u32 constant with the same component count as result_ty. auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); }; - Value* result = nullptr; b.InsertBefore(call, [&] { // %x = %input; // if (%x is signed) { @@ -465,22 +445,21 @@ x = b.ShiftRight(uint_ty, x, b2)->Result(0); auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(1), V(0), b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000002)), V(0))); - result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))) - ->Result(0); + Instruction* result = + b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))); result = b.Call(uint_ty, core::BuiltinFn::kSelect, result, V(0xffffffff), - b.Equal(bool_ty, x, V(0))) - ->Result(0); + b.Equal(bool_ty, x, V(0))); if (result_ty->is_signed_integer_scalar_or_vector()) { - result = b.Bitcast(result_ty, result)->Result(0); + result = b.Bitcast(result_ty, result); } + result->SetResults(Vector{call->DetachResult()}); }); - return result; + call->Destroy(); } /// Polyfill a `firstTrailingBit()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* FirstTrailingBit(ir::CoreBuiltinCall* call) { + void FirstTrailingBit(ir::CoreBuiltinCall* call) { auto* input = call->Args()[0]; auto* result_ty = input->Type(); auto* uint_ty = MatchWidth(ty.u32(), result_ty); @@ -489,7 +468,6 @@ // Make an u32 constant with the same component count as result_ty. auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); }; - Value* result = nullptr; b.InsertBefore(call, [&] { // %x = %input; // if (%x is signed) { @@ -525,22 +503,21 @@ x = b.ShiftRight(uint_ty, x, b2)->Result(0); auto* b1 = b.Call(uint_ty, core::BuiltinFn::kSelect, V(0), V(1), b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000001)), V(0))); - result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))) - ->Result(0); + Instruction* result = + b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1)))); result = b.Call(uint_ty, core::BuiltinFn::kSelect, result, V(0xffffffff), - b.Equal(bool_ty, x, V(0))) - ->Result(0); + b.Equal(bool_ty, x, V(0))); if (result_ty->is_signed_integer_scalar_or_vector()) { - result = b.Bitcast(result_ty, result)->Result(0); + result = b.Bitcast(result_ty, result); } + result->SetResults(Vector{call->DetachResult()}); }); - return result; + call->Destroy(); } /// Polyfill an `insertBits()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* InsertBits(ir::CoreBuiltinCall* call) { + void InsertBits(ir::CoreBuiltinCall* call) { auto* offset = call->Args()[2]; auto* count = call->Args()[3]; @@ -559,7 +536,7 @@ call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 2, o->Result(0)); call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 3, c->Result(0)); }); - return call->Result(0); + break; } default: TINT_UNIMPLEMENTED() << "insertBits polyfill level"; @@ -568,8 +545,7 @@ /// Polyfill a `saturate()` builtin call. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Saturate(ir::CoreBuiltinCall* call) { + void Saturate(ir::CoreBuiltinCall* call) { // Replace `saturate(x)` with `clamp(x, 0., 1.)`. auto* type = call->Result(0)->Type(); ir::Constant* zero = nullptr; @@ -581,21 +557,20 @@ zero = MatchWidth(b.Constant(0_h), type); one = MatchWidth(b.Constant(1_h), type); } - auto* clamp = b.Call(type, core::BuiltinFn::kClamp, Vector{call->Args()[0], zero, one}); + auto* clamp = b.CallWithResult(call->DetachResult(), core::BuiltinFn::kClamp, + Vector{call->Args()[0], zero, one}); clamp->InsertBefore(call); - return clamp->Result(0); + call->Destroy(); } /// Polyfill a `textureSampleBaseClampToEdge()` builtin call for 2D F32 textures. /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* TextureSampleBaseClampToEdge_2d_f32(ir::CoreBuiltinCall* call) { + void TextureSampleBaseClampToEdge_2d_f32(ir::CoreBuiltinCall* call) { // Replace `textureSampleBaseClampToEdge(%texture, %sample, %coords)` with: // %dims = vec2f(textureDimensions(%texture)); // %half_texel = vec2f(0.5) / dims; // %clamped = clamp(%coord, %half_texel, 1.0 - %half_texel); // %result = textureSampleLevel(%texture, %sampler, %clamped, 0); - ir::Value* result = nullptr; auto* texture = call->Args()[0]; auto* sampler = call->Args()[1]; auto* coords = call->Args()[2]; @@ -607,17 +582,15 @@ auto* one_minus_half_texel = b.Subtract(vec2f, b.Splat(vec2f, 1_f, 2), half_texel); auto* clamped = b.Call(vec2f, core::BuiltinFn::kClamp, coords, half_texel, one_minus_half_texel); - result = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureSampleLevel, texture, sampler, - clamped, 0_f) - ->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kTextureSampleLevel, texture, + sampler, clamped, 0_f); }); - return result; + call->Destroy(); } /// Polyfill a `dot4I8Packed()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Dot4I8Packed(ir::CoreBuiltinCall* call) { + void Dot4I8Packed(ir::CoreBuiltinCall* call) { // Replace `dot4I8Packed(%x,%y)` with: // %unpacked_x = unpack4xI8(%x); // %unpacked_y = unpack4xI8(%y); @@ -626,17 +599,15 @@ auto* y = call->Args()[1]; auto* unpacked_x = Unpack4xI8OnValue(call, x); auto* unpacked_y = Unpack4xI8OnValue(call, y); - ir::Value* result = nullptr; b.InsertBefore(call, [&] { - result = b.Call(ty.i32(), core::BuiltinFn::kDot, unpacked_x, unpacked_y)->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, unpacked_x, unpacked_y); }); - return result; + call->Destroy(); } /// Polyfill a `dot4U8Packed()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Dot4U8Packed(ir::CoreBuiltinCall* call) { + void Dot4U8Packed(ir::CoreBuiltinCall* call) { // Replace `dot4U8Packed(%x,%y)` with: // %unpacked_x = unpack4xU8(%x); // %unpacked_y = unpack4xU8(%y); @@ -645,23 +616,20 @@ auto* y = call->Args()[1]; auto* unpacked_x = Unpack4xU8OnValue(call, x); auto* unpacked_y = Unpack4xU8OnValue(call, y); - ir::Value* result = nullptr; b.InsertBefore(call, [&] { - result = b.Call(ty.u32(), core::BuiltinFn::kDot, unpacked_x, unpacked_y)->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, unpacked_x, unpacked_y); }); - return result; + call->Destroy(); } /// Polyfill a `pack4xI8()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Pack4xI8(ir::CoreBuiltinCall* call) { + void Pack4xI8(ir::CoreBuiltinCall* call) { // Replace `pack4xI8(%x)` with: // %n = vec4u(0, 8, 16, 24); // %x_u32 = bitcast<vec4u>(%x) // %x_u8 = (%x_u32 & vec4u(0xff)) << n; // %result = dot(%x_u8, vec4u(1)); - ir::Value* result = nullptr; auto* x = call->Args()[0]; b.InsertBefore(call, [&] { auto* vec4u = ty.vec4<u32>(); @@ -671,22 +639,19 @@ auto* x_u32 = b.Bitcast(vec4u, x); auto* x_u8 = b.ShiftLeft( vec4u, b.And(vec4u, x_u32, b.Construct(vec4u, b.Constant(u32(0xff)))), n); - result = b.Call(ty.u32(), core::BuiltinFn::kDot, x_u8, - b.Construct(vec4u, (b.Constant(u32(1))))) - ->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, x_u8, + b.Construct(vec4u, (b.Constant(u32(1))))); }); - return result; + call->Destroy(); } /// Polyfill a `pack4xU8()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Pack4xU8(ir::CoreBuiltinCall* call) { + void Pack4xU8(ir::CoreBuiltinCall* call) { // Replace `pack4xU8(%x)` with: // %n = vec4u(0, 8, 16, 24); // %x_i8 = (%x & vec4u(0xff)) << %n; // %result = dot(%x_i8, vec4u(1)); - ir::Value* result = nullptr; auto* x = call->Args()[0]; b.InsertBefore(call, [&] { auto* vec4u = ty.vec4<u32>(); @@ -695,17 +660,15 @@ b.Constant(u32(16)), b.Constant(u32(24))); auto* x_u8 = b.ShiftLeft(vec4u, b.And(vec4u, x, b.Construct(vec4u, b.Constant(u32(0xff)))), n); - result = b.Call(ty.u32(), core::BuiltinFn::kDot, x_u8, - b.Construct(vec4u, (b.Constant(u32(1))))) - ->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, x_u8, + b.Construct(vec4u, (b.Constant(u32(1))))); }); - return result; + call->Destroy(); } /// Polyfill a `pack4xI8Clamp()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Pack4xI8Clamp(ir::CoreBuiltinCall* call) { + void Pack4xI8Clamp(ir::CoreBuiltinCall* call) { // Replace `pack4xI8Clamp(%x)` with: // %n = vec4u(0, 8, 16, 24); // %min_i8_vec4 = vec4i(-128); @@ -714,7 +677,6 @@ // %x_u32 = bitcast<vec4u>(%x_clamp); // %x_u8 = (%x_u32 & vec4u(0xff)) << n; // %result = dot(%x_u8, vec4u(1)); - ir::Value* result = nullptr; auto* x = call->Args()[0]; b.InsertBefore(call, [&] { auto* vec4i = ty.vec4<i32>(); @@ -728,17 +690,15 @@ auto* x_u32 = b.Bitcast(vec4u, x_clamp); auto* x_u8 = b.ShiftLeft( vec4u, b.And(vec4u, x_u32, b.Construct(vec4u, b.Constant(u32(0xff)))), n); - result = b.Call(ty.u32(), core::BuiltinFn::kDot, x_u8, - b.Construct(vec4u, (b.Constant(u32(1))))) - ->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, x_u8, + b.Construct(vec4u, (b.Constant(u32(1))))); }); - return result; + call->Destroy(); } /// Polyfill a `pack4xU8Clamp()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Pack4xU8Clamp(ir::CoreBuiltinCall* call) { + void Pack4xU8Clamp(ir::CoreBuiltinCall* call) { // Replace `pack4xU8Clamp(%x)` with: // %n = vec4u(0, 8, 16, 24); // %min_u8_vec4 = vec4u(0); @@ -746,7 +706,6 @@ // %x_clamp = clamp(%x, vec4u(0), vec4u(255)); // %x_u8 = %x_clamp << n; // %result = dot(%x_u8, vec4u(1)); - ir::Value* result = nullptr; auto* x = call->Args()[0]; b.InsertBefore(call, [&] { auto* vec4u = ty.vec4<u32>(); @@ -757,23 +716,22 @@ auto* max_u8_vec4 = b.Construct(vec4u, b.Constant(u32(255))); auto* x_clamp = b.Call(vec4u, core::BuiltinFn::kClamp, x, min_u8_vec4, max_u8_vec4); auto* x_u8 = b.ShiftLeft(vec4u, x_clamp, n); - result = b.Call(ty.u32(), core::BuiltinFn::kDot, x_u8, - b.Construct(vec4u, (b.Constant(u32(1))))) - ->Result(0); + b.CallWithResult(call->DetachResult(), core::BuiltinFn::kDot, x_u8, + b.Construct(vec4u, (b.Constant(u32(1))))); }); - return result; + call->Destroy(); } /// Emit code for `unpack4xI8` on u32 value `x`, before the given call. /// @param call the instruction that should follow the emitted code /// @param x the u32 value to be unpacked - ir::Value* Unpack4xI8OnValue(ir::CoreBuiltinCall* call, ir::Value* x) { + ir::Instruction* Unpack4xI8OnValue(ir::CoreBuiltinCall* call, ir::Value* x) { // Replace `unpack4xI8(%x)` with: // %n = vec4u(24, 16, 8, 0); // %x_splat = vec4u(%x); // splat the scalar to a vector // %x_vec4i = bitcast<vec4i>(%x_splat << n); // %result = %x_vec4i >> vec4u(24); - ir::Value* result = nullptr; + ir::Instruction* result = nullptr; b.InsertBefore(call, [&] { auto* vec4i = ty.vec4<i32>(); auto* vec4u = ty.vec4<u32>(); @@ -782,29 +740,29 @@ b.Constant(u32(8)), b.Constant(u32(0))); auto* x_splat = b.Construct(vec4u, x); auto* x_vec4i = b.Bitcast(vec4i, b.ShiftLeft(vec4u, x_splat, n)); - result = - b.ShiftRight(vec4i, x_vec4i, b.Construct(vec4u, b.Constant(u32(24))))->Result(0); + result = b.ShiftRight(vec4i, x_vec4i, b.Construct(vec4u, b.Constant(u32(24)))); }); return result; } /// Polyfill a `unpack4xI8()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Unpack4xI8(ir::CoreBuiltinCall* call) { - return Unpack4xI8OnValue(call, call->Args()[0]); + void Unpack4xI8(ir::CoreBuiltinCall* call) { + auto* result = Unpack4xI8OnValue(call, call->Args()[0]); + result->SetResults(Vector{call->DetachResult()}); + call->Destroy(); } /// Emit code for `unpack4xU8` on u32 value `x`, before the given call. /// @param call the instruction that should follow the emitted code /// @param x the u32 value to be unpacked - ir::Value* Unpack4xU8OnValue(ir::CoreBuiltinCall* call, ir::Value* x) { + Instruction* Unpack4xU8OnValue(ir::CoreBuiltinCall* call, ir::Value* x) { // Replace `unpack4xU8(%x)` with: // %n = vec4u(0, 8, 16, 24); // %x_splat = vec4u(%x); // splat the scalar to a vector // %x_vec4u = %x_splat >> n; // %result = %x_vec4u & vec4u(0xff); - ir::Value* result = nullptr; + ir::Instruction* result = nullptr; b.InsertBefore(call, [&] { auto* vec4u = ty.vec4<u32>(); @@ -812,16 +770,17 @@ b.Constant(u32(16)), b.Constant(u32(24))); auto* x_splat = b.Construct(vec4u, x); auto* x_vec4u = b.ShiftRight(vec4u, x_splat, n); - result = b.And(vec4u, x_vec4u, b.Construct(vec4u, b.Constant(u32(0xff))))->Result(0); + result = b.And(vec4u, x_vec4u, b.Construct(vec4u, b.Constant(u32(0xff)))); }); return result; } /// Polyfill a `unpack4xU8()` builtin call /// @param call the builtin call instruction - /// @returns the replacement value - ir::Value* Unpack4xU8(ir::CoreBuiltinCall* call) { - return Unpack4xU8OnValue(call, call->Args()[0]); + void Unpack4xU8(ir::CoreBuiltinCall* call) { + auto* result = Unpack4xU8OnValue(call, call->Args()[0]); + result->SetResults(Vector{call->DetachResult()}); + call->Destroy(); } };
diff --git a/src/tint/lang/core/ir/transform/common/BUILD.bazel b/src/tint/lang/core/ir/transform/common/BUILD.bazel new file mode 100644 index 0000000..f05642f --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/BUILD.bazel
@@ -0,0 +1,103 @@ +# Copyright 2024 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.bazel.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +load("//src/tint:flags.bzl", "COPTS") +load("@bazel_skylib//lib:selects.bzl", "selects") +cc_library( + name = "common", + srcs = [ + "referenced_module_vars.cc", + ], + hdrs = [ + "referenced_module_vars.h", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/ir", + "//src/tint/lang/core/type", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + ], + copts = COPTS, + visibility = ["//visibility:public"], +) +cc_library( + name = "test", + alwayslink = True, + srcs = [ + "referenced_module_vars_test.cc", + ], + deps = [ + "//src/tint/api/common", + "//src/tint/lang/core", + "//src/tint/lang/core/constant", + "//src/tint/lang/core/intrinsic", + "//src/tint/lang/core/ir", + "//src/tint/lang/core/ir/transform/common", + "//src/tint/lang/core/ir:test", + "//src/tint/lang/core/type", + "//src/tint/utils/containers", + "//src/tint/utils/diagnostic", + "//src/tint/utils/ice", + "//src/tint/utils/id", + "//src/tint/utils/macros", + "//src/tint/utils/math", + "//src/tint/utils/memory", + "//src/tint/utils/reflection", + "//src/tint/utils/result", + "//src/tint/utils/rtti", + "//src/tint/utils/symbol", + "//src/tint/utils/text", + "//src/tint/utils/traits", + "@gtest", + ], + copts = COPTS, + visibility = ["//visibility:public"], +) +
diff --git a/src/tint/lang/core/ir/transform/common/BUILD.cmake b/src/tint/lang/core/ir/transform/common/BUILD.cmake new file mode 100644 index 0000000..7004d86 --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/BUILD.cmake
@@ -0,0 +1,101 @@ +# Copyright 2024 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.cmake.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +################################################################################ +# Target: tint_lang_core_ir_transform_common +# Kind: lib +################################################################################ +tint_add_target(tint_lang_core_ir_transform_common lib + lang/core/ir/transform/common/referenced_module_vars.cc + lang/core/ir/transform/common/referenced_module_vars.h +) + +tint_target_add_dependencies(tint_lang_core_ir_transform_common lib + tint_api_common + tint_lang_core + tint_lang_core_constant + tint_lang_core_ir + tint_lang_core_type + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +################################################################################ +# Target: tint_lang_core_ir_transform_common_test +# Kind: test +################################################################################ +tint_add_target(tint_lang_core_ir_transform_common_test test + lang/core/ir/transform/common/referenced_module_vars_test.cc +) + +tint_target_add_dependencies(tint_lang_core_ir_transform_common_test test + tint_api_common + tint_lang_core + tint_lang_core_constant + tint_lang_core_intrinsic + tint_lang_core_ir + tint_lang_core_ir_transform_common + tint_lang_core_ir_test + tint_lang_core_type + tint_utils_containers + tint_utils_diagnostic + tint_utils_ice + tint_utils_id + tint_utils_macros + tint_utils_math + tint_utils_memory + tint_utils_reflection + tint_utils_result + tint_utils_rtti + tint_utils_symbol + tint_utils_text + tint_utils_traits +) + +tint_target_add_external_dependencies(tint_lang_core_ir_transform_common_test test + "gtest" +)
diff --git a/src/tint/lang/core/ir/transform/common/BUILD.gn b/src/tint/lang/core/ir/transform/common/BUILD.gn new file mode 100644 index 0000000..d050caf --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/BUILD.gn
@@ -0,0 +1,99 @@ +# Copyright 2024 The Dawn & Tint Authors +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +################################################################################ +# File generated by 'tools/src/cmd/gen' using the template: +# tools/src/cmd/gen/build/BUILD.gn.tmpl +# +# To regenerate run: './tools/run gen' +# +# Do not modify this file directly +################################################################################ + +import("../../../../../../../scripts/tint_overrides_with_defaults.gni") + +import("${tint_src_dir}/tint.gni") + +if (tint_build_unittests || tint_build_benchmarks) { + import("//testing/test.gni") +} + +libtint_source_set("common") { + sources = [ + "referenced_module_vars.cc", + "referenced_module_vars.h", + ] + deps = [ + "${tint_src_dir}/api/common", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] +} +if (tint_build_unittests) { + tint_unittests_source_set("unittests") { + sources = [ "referenced_module_vars_test.cc" ] + deps = [ + "${tint_src_dir}:gmock_and_gtest", + "${tint_src_dir}/api/common", + "${tint_src_dir}/lang/core", + "${tint_src_dir}/lang/core/constant", + "${tint_src_dir}/lang/core/intrinsic", + "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/ir:unittests", + "${tint_src_dir}/lang/core/ir/transform/common", + "${tint_src_dir}/lang/core/type", + "${tint_src_dir}/utils/containers", + "${tint_src_dir}/utils/diagnostic", + "${tint_src_dir}/utils/ice", + "${tint_src_dir}/utils/id", + "${tint_src_dir}/utils/macros", + "${tint_src_dir}/utils/math", + "${tint_src_dir}/utils/memory", + "${tint_src_dir}/utils/reflection", + "${tint_src_dir}/utils/result", + "${tint_src_dir}/utils/rtti", + "${tint_src_dir}/utils/symbol", + "${tint_src_dir}/utils/text", + "${tint_src_dir}/utils/traits", + ] + } +}
diff --git a/src/tint/lang/core/ir/transform/common/referenced_module_vars.cc b/src/tint/lang/core/ir/transform/common/referenced_module_vars.cc new file mode 100644 index 0000000..b97b9dd --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/referenced_module_vars.cc
@@ -0,0 +1,75 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/core/ir/transform/common/referenced_module_vars.h" + +#include "src/tint/lang/core/ir/control_instruction.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/user_call.h" +#include "src/tint/lang/core/ir/var.h" +#include "src/tint/utils/rtti/switch.h" + +namespace tint::core::ir { + +const ReferencedModuleVars::VarSet& ReferencedModuleVars::TransitiveReferences(Function* func) { + return transitive_references_.GetOrAdd(func, [&] { + VarSet vars; + GetTransitiveReferences(func->Block(), vars); + return vars; + }); +} + +/// Get the set of variables transitively referenced by @p block. +/// @param block the block +/// @param vars the set of transitively referenced variables to populate +void ReferencedModuleVars::GetTransitiveReferences(Block* block, VarSet& vars) { + // Add directly referenced vars. + if (auto itr = block_to_direct_vars_.Get(block)) { + for (auto& var : *itr) { + vars.Add(var); + } + } + + // Loop over instructions in the block to find indirectly referenced vars. + for (auto* inst : *block) { + tint::Switch( + inst, + [&](UserCall* call) { + // Get variables referenced by a function called from this block. + const auto& callee_vars = TransitiveReferences(call->Target()); + for (auto* var : callee_vars) { + vars.Add(var); + } + }, + [&](ControlInstruction* ctrl) { + // Recurse into control instructions and gather their referenced vars. + ctrl->ForeachBlock([&](Block* blk) { GetTransitiveReferences(blk, vars); }); + }); + } +} + +} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/transform/common/referenced_module_vars.h b/src/tint/lang/core/ir/transform/common/referenced_module_vars.h new file mode 100644 index 0000000..a48b0a7 --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/referenced_module_vars.h
@@ -0,0 +1,105 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_COMMON_REFERENCED_MODULE_VARS_H_ +#define SRC_TINT_LANG_CORE_IR_TRANSFORM_COMMON_REFERENCED_MODULE_VARS_H_ + +#include <functional> + +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/var.h" +#include "src/tint/utils/containers/hashmap.h" +#include "src/tint/utils/containers/unique_vector.h" + +// Forward declarations. +namespace tint::core::ir { +class Block; +class Function; +} // namespace tint::core::ir + +namespace tint::core::ir { + +/// ReferencedModuleVars is a helper to determine the set of module-scope variables that are +/// transitively referenced by functions in a module. +/// References are determined lazily and cached for future requests. +/// +/// Note: changes to the module can invalidate the cached data. This is intended to be created by +/// a transform that need this information, and discarded when that transform completes. Tracking +/// this information inside the IR module would add overhead any time an instruction is added or +/// removed from the module. Since only a few transforms need this information, we expect it to be +/// more efficient to generate it as and when needed instead. +class ReferencedModuleVars { + public: + /// The signature of a predicate used to filter variables. + /// A predicate function should return `true` when the variable should be added to the set. + using Predicate = std::function<bool(const Var*)>; + + /// A set of a variables referenced by a function (in declaration order). + using VarSet = UniqueVector<Var*, 16>; + + /// Constructor. + /// @param ir the module + /// @param pred an optional predicate function for filtering variables + /// Note: @p pred is not stored by the class, so can be a lambda that captures by reference. + explicit ReferencedModuleVars(Module& ir, Predicate&& pred = {}) : ir_(ir) { + // Loop over module-scope variables, recording the blocks that they are referenced from. + for (auto inst : *ir_.root_block) { + if (auto* var = inst->As<Var>()) { + if (!pred || pred(var)) { + var->Result(0)->ForEachUse([&](const Usage& use) { + block_to_direct_vars_.GetOrAddZero(use.instruction->Block()).Add(var); + }); + } + } + } + } + + /// Get the set of transitively referenced module-scope variables for a function, filtered by + /// the predicate function if provided. + /// @param func the function + /// @returns the set of (possibly filtered) transitively reference module-scope variables + const VarSet& TransitiveReferences(Function* func); + + private: + /// The module. + Module& ir_; + + /// A map from blocks to their directly referenced variables. + Hashmap<Block*, VarSet, 64> block_to_direct_vars_{}; + + /// A map from functions to their transitively referenced variables. + Hashmap<Function*, VarSet, 8> transitive_references_; + + /// Get the set of transitively referenced module-scope variables for a block. + /// @param block the block + /// @param vars the set of transitively reference module-scope variables to populate + void GetTransitiveReferences(Block* block, VarSet& vars); +}; + +} // namespace tint::core::ir + +#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_COMMON_REFERENCED_MODULE_VARS_H_
diff --git a/src/tint/lang/core/ir/transform/common/referenced_module_vars_test.cc b/src/tint/lang/core/ir/transform/common/referenced_module_vars_test.cc new file mode 100644 index 0000000..3a5a032 --- /dev/null +++ b/src/tint/lang/core/ir/transform/common/referenced_module_vars_test.cc
@@ -0,0 +1,422 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/core/ir/transform/common/referenced_module_vars.h" + +#include <string> + +#include "gmock/gmock.h" +#include "src/tint/lang/core/ir/disassembly.h" +#include "src/tint/lang/core/ir/ir_helper_test.h" + +namespace tint::core::ir { +namespace { + +using ::testing::ElementsAre; + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +class IR_ReferencedModuleVarsTest : public IRTestHelper { + protected: + /// @returns the module as a disassembled string + std::string Disassemble() const { return "\n" + ir::Disassemble(mod).Plain(); } +}; + +TEST_F(IR_ReferencedModuleVarsTest, EmptyRootBlock) { + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Return(foo); + }); + + auto* src = R"( +%foo = func():void { + $B1: { + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + auto& foo_vars = vars.TransitiveReferences(foo); + EXPECT_TRUE(foo_vars.IsEmpty()); +} + +TEST_F(IR_ReferencedModuleVarsTest, DirectUse) { + // Referenced. + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b")); + // Not referenced. + mod.root_block->Append(b.Var<workgroup, u32>("c")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Load(var_a); + b.Load(var_b); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<workgroup, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var +} + +%foo = func():void { + $B2: { + %5:u32 = load %a + %6:u32 = load %b + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a, var_b)); +} + +TEST_F(IR_ReferencedModuleVarsTest, DirectUse_DeclarationOrder) { + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b")); + auto* var_c = mod.root_block->Append(b.Var<workgroup, u32>("c")); + auto* var_d = mod.root_block->Append(b.Var<workgroup, u32>("d")); + auto* var_e = mod.root_block->Append(b.Var<workgroup, u32>("e")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Load(var_b); + b.Load(var_e); + b.Load(var_d); + b.Load(var_c); + b.Load(var_a); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<workgroup, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var + %d:ptr<workgroup, u32, read_write> = var + %e:ptr<workgroup, u32, read_write> = var +} + +%foo = func():void { + $B2: { + %7:u32 = load %b + %8:u32 = load %e + %9:u32 = load %d + %10:u32 = load %c + %11:u32 = load %a + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a, var_b, var_c, var_d, var_e)); +} + +TEST_F(IR_ReferencedModuleVarsTest, DirectUse_MultipleFunctions) { + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b")); + auto* var_c = mod.root_block->Append(b.Var<workgroup, u32>("c")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Load(var_a); + b.Load(var_b); + b.Return(foo); + }); + + auto* bar = b.Function("bar", ty.void_()); + b.Append(bar->Block(), [&] { // + b.Load(var_a); + b.Load(var_c); + b.Return(bar); + }); + + auto* zoo = b.Function("zoo", ty.void_()); + b.Append(zoo->Block(), [&] { // + b.Return(zoo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<workgroup, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var +} + +%foo = func():void { + $B2: { + %5:u32 = load %a + %6:u32 = load %b + ret + } +} +%bar = func():void { + $B3: { + %8:u32 = load %a + %9:u32 = load %c + ret + } +} +%zoo = func():void { + $B4: { + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a, var_b)); + EXPECT_THAT(vars.TransitiveReferences(bar), ElementsAre(var_a, var_c)); + EXPECT_TRUE(vars.TransitiveReferences(zoo).IsEmpty()); +} + +TEST_F(IR_ReferencedModuleVarsTest, DirectUse_NestedInControlFlow) { + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b")); + auto* var_c = mod.root_block->Append(b.Var<workgroup, u32>("c")); + auto* var_d = mod.root_block->Append(b.Var<workgroup, u32>("c")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + auto* ifelse = b.If(true); + b.Append(ifelse->True(), [&] { + b.Load(var_a); + b.ExitIf(ifelse); + }); + b.Append(ifelse->False(), [&] { + auto* loop = b.Loop(); + b.Append(loop->Initializer(), [&] { + b.Load(var_b); + b.NextIteration(loop); + }); + b.Append(loop->Body(), [&] { + b.Load(var_c); + b.Continue(loop); + }); + b.Append(loop->Continuing(), [&] { + b.Load(var_d); + b.NextIteration(loop); + }); + b.ExitIf(ifelse); + }); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<workgroup, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var + %c_1:ptr<workgroup, u32, read_write> = var # %c_1: 'c' +} + +%foo = func():void { + $B2: { + if true [t: $B3, f: $B4] { # if_1 + $B3: { # true + %6:u32 = load %a + exit_if # if_1 + } + $B4: { # false + loop [i: $B5, b: $B6, c: $B7] { # loop_1 + $B5: { # initializer + %7:u32 = load %b + next_iteration # -> $B6 + } + $B6: { # body + %8:u32 = load %c + continue # -> $B7 + } + $B7: { # continuing + %9:u32 = load %c_1 + next_iteration # -> $B6 + } + } + exit_if # if_1 + } + } + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a, var_b, var_c, var_d)); +} + +TEST_F(IR_ReferencedModuleVarsTest, IndirectUse) { + // Directly used by foo. + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + // Directly used by bar, called by zoo and foo. + auto* var_b = mod.root_block->Append(b.Var<workgroup, u32>("b")); + // Not used. + mod.root_block->Append(b.Var<workgroup, u32>("c")); + + auto* bar = b.Function("bar", ty.void_()); + b.Append(bar->Block(), [&] { // + b.Load(var_b); + b.Return(bar); + }); + + auto* zoo = b.Function("zoo", ty.void_()); + b.Append(zoo->Block(), [&] { // + b.Call(bar); + b.Return(zoo); + }); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Load(var_a); + b.Call(zoo); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<workgroup, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var +} + +%bar = func():void { + $B2: { + %5:u32 = load %b + ret + } +} +%zoo = func():void { + $B3: { + %7:void = call %bar + ret + } +} +%foo = func():void { + $B4: { + %9:u32 = load %a + %10:void = call %zoo + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(bar), ElementsAre(var_b)); + EXPECT_THAT(vars.TransitiveReferences(zoo), ElementsAre(var_b)); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a, var_b)); +} + +TEST_F(IR_ReferencedModuleVarsTest, NoFunctionVars) { + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + auto* var_b = b.Var<function, u32>("b"); + b.Load(var_a); + b.Load(var_b); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var +} + +%foo = func():void { + $B2: { + %b:ptr<function, u32, read_write> = var + %4:u32 = load %a + %5:u32 = load %b + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_a)); +} + +TEST_F(IR_ReferencedModuleVarsTest, Predicate) { + auto* var_a = mod.root_block->Append(b.Var<workgroup, u32>("a")); + auto* var_b = mod.root_block->Append(b.Var<private_, u32>("b")); + auto* var_c = mod.root_block->Append(b.Var<workgroup, u32>("c")); + auto* var_d = mod.root_block->Append(b.Var<private_, u32>("d")); + auto* var_e = mod.root_block->Append(b.Var<workgroup, u32>("e")); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { // + b.Load(var_a); + b.Load(var_b); + b.Load(var_c); + b.Load(var_d); + b.Load(var_e); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %a:ptr<workgroup, u32, read_write> = var + %b:ptr<private, u32, read_write> = var + %c:ptr<workgroup, u32, read_write> = var + %d:ptr<private, u32, read_write> = var + %e:ptr<workgroup, u32, read_write> = var +} + +%foo = func():void { + $B2: { + %7:u32 = load %a + %8:u32 = load %b + %9:u32 = load %c + %10:u32 = load %d + %11:u32 = load %e + ret + } +} +)"; + EXPECT_EQ(src, Disassemble()); + + ReferencedModuleVars vars(mod, [](const Var* var) { + auto* view = var->Result(0)->Type()->As<type::MemoryView>(); + return view->AddressSpace() == AddressSpace::kPrivate; + }); + EXPECT_THAT(vars.TransitiveReferences(foo), ElementsAre(var_b, var_d)); +} + +} // namespace +} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.cc b/src/tint/lang/core/ir/transform/conversion_polyfill.cc index c5f7bbd..76d1920 100644 --- a/src/tint/lang/core/ir/transform/conversion_polyfill.cc +++ b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
@@ -82,13 +82,7 @@ // Polyfill the conversion instructions that we found. for (auto* convert : ftoi_worklist) { - auto* replacement = ftoi(convert); - - // Replace the old conversion instruction result with the new value. - if (auto name = ir.NameOf(convert->Result(0))) { - ir.SetName(replacement, name); - } - convert->Result(0)->ReplaceAllUsesWith(replacement); + ftoi(convert); convert->Destroy(); } } @@ -96,8 +90,7 @@ /// Replace a conversion instruction with a call to helper function that manually clamps the /// result to within the limit of the destination type. /// @param convert the conversion instruction - /// @returns the replacement value - ir::Value* ftoi(ir::Convert* convert) { + void ftoi(ir::Convert* convert) { auto* res_ty = convert->Result(0)->Type(); auto* src_ty = convert->Args()[0]->Type(); auto* src_el_ty = src_ty->DeepestElement(); @@ -190,9 +183,8 @@ }); // Call the helper function, splatting the arguments to match the target vector width. - auto* call = b.Call(res_ty, helper, convert->Args()[0]); + auto* call = b.CallWithResult(convert->DetachResult(), helper, convert->Args()[0]); call->InsertBefore(convert); - return call->Result(0); } /// Return a type with element type @p type that has the same number of vector components as
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc index d63e3ba..aa0d1dc 100644 --- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc +++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -240,19 +240,18 @@ } // Call the `TextureLoadExternal()` helper function. - auto* helper = b.Call(ty.vec4<f32>(), TextureLoadExternal(), plane_0, - plane_1, params, coords); + auto* helper = b.CallWithResult(call->DetachResult(), TextureLoadExternal(), + plane_0, plane_1, params, coords); helper->InsertBefore(call); - call->Result(0)->ReplaceAllUsesWith(helper->Result(0)); call->Destroy(); } else if (call->Func() == core::BuiltinFn::kTextureSampleBaseClampToEdge) { // Call the `TextureSampleExternal()` helper function. auto* sampler = call->Args()[1]; auto* coords = call->Args()[2]; - auto* helper = b.Call(ty.vec4<f32>(), TextureSampleExternal(), plane_0, - plane_1, params, sampler, coords); + auto* helper = + b.CallWithResult(call->DetachResult(), TextureSampleExternal(), plane_0, + plane_1, params, sampler, coords); helper->InsertBefore(call); - call->Result(0)->ReplaceAllUsesWith(helper->Result(0)); call->Destroy(); } else { TINT_ICE() << "unhandled texture_external builtin call: " << call->Func();
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc index 6597fb8..f032ba8 100644 --- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc +++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
@@ -352,8 +352,8 @@ %6:texture_2d<f32> = load %texture_plane0 %7:texture_2d<f32> = load %texture_plane1 %8:tint_ExternalTextureParams = load %texture_params - %9:vec4<f32> = call %tint_TextureLoadExternal, %6, %7, %8, %coords - ret %9 + %result:vec4<f32> = call %tint_TextureLoadExternal, %6, %7, %8, %coords + ret %result } } %tint_TextureLoadExternal = func(%plane_0:texture_2d<f32>, %plane_1:texture_2d<f32>, %params:tint_ExternalTextureParams, %coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords' @@ -514,8 +514,8 @@ %7:texture_2d<f32> = load %texture_plane1 %8:tint_ExternalTextureParams = load %texture_params %9:vec2<u32> = convert %coords - %10:vec4<f32> = call %tint_TextureLoadExternal, %6, %7, %8, %9 - ret %10 + %result:vec4<f32> = call %tint_TextureLoadExternal, %6, %7, %8, %9 + ret %result } } %tint_TextureLoadExternal = func(%plane_0:texture_2d<f32>, %plane_1:texture_2d<f32>, %params:tint_ExternalTextureParams, %coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords' @@ -677,8 +677,8 @@ %7:texture_2d<f32> = load %texture_plane0 %8:texture_2d<f32> = load %texture_plane1 %9:tint_ExternalTextureParams = load %texture_params - %10:vec4<f32> = call %tint_TextureSampleExternal, %7, %8, %9, %sampler, %coords - ret %10 + %result:vec4<f32> = call %tint_TextureSampleExternal, %7, %8, %9, %sampler, %coords + ret %result } } %tint_TextureSampleExternal = func(%plane_0:texture_2d<f32>, %plane_1:texture_2d<f32>, %params:tint_ExternalTextureParams, %sampler_1:sampler, %coords_1:vec2<f32>):vec4<f32> { # %sampler_1: 'sampler', %coords_1: 'coords' @@ -856,8 +856,8 @@ %foo = func(%texture_plane0_1:texture_2d<f32>, %texture_plane1_1:texture_2d<f32>, %texture_params_1:tint_ExternalTextureParams, %sampler:sampler, %coords:vec2<f32>):vec4<f32> { # %texture_plane0_1: 'texture_plane0', %texture_plane1_1: 'texture_plane1', %texture_params_1: 'texture_params' $B2: { - %10:vec4<f32> = call %tint_TextureSampleExternal, %texture_plane0_1, %texture_plane1_1, %texture_params_1, %sampler, %coords - ret %10 + %result:vec4<f32> = call %tint_TextureSampleExternal, %texture_plane0_1, %texture_plane1_1, %texture_params_1, %sampler, %coords + ret %result } } %bar = func(%sampler_1:sampler, %coords_1:vec2<f32>):vec4<f32> { # %sampler_1: 'sampler', %coords_1: 'coords' @@ -865,8 +865,8 @@ %15:texture_2d<f32> = load %texture_plane0 %16:texture_2d<f32> = load %texture_plane1 %17:tint_ExternalTextureParams = load %texture_params - %result:vec4<f32> = call %foo, %15, %16, %17, %sampler_1, %coords_1 - ret %result + %result_1:vec4<f32> = call %foo, %15, %16, %17, %sampler_1, %coords_1 # %result_1: 'result' + ret %result_1 } } %tint_TextureSampleExternal = func(%plane_0:texture_2d<f32>, %plane_1:texture_2d<f32>, %params:tint_ExternalTextureParams, %sampler_2:sampler, %coords_2:vec2<f32>):vec4<f32> { # %sampler_2: 'sampler', %coords_2: 'coords' @@ -1062,8 +1062,8 @@ %foo = func(%texture_plane0_1:texture_2d<f32>, %texture_plane1_1:texture_2d<f32>, %texture_params_1:tint_ExternalTextureParams, %sampler:sampler, %coords:vec2<f32>):vec4<f32> { # %texture_plane0_1: 'texture_plane0', %texture_plane1_1: 'texture_plane1', %texture_params_1: 'texture_params' $B2: { - %10:vec4<f32> = call %tint_TextureSampleExternal, %texture_plane0_1, %texture_plane1_1, %texture_params_1, %sampler, %coords - ret %10 + %result:vec4<f32> = call %tint_TextureSampleExternal, %texture_plane0_1, %texture_plane1_1, %texture_params_1, %sampler, %coords + ret %result } } %bar = func(%sampler_1:sampler, %coords_1:vec2<f32>):vec4<f32> { # %sampler_1: 'sampler', %coords_1: 'coords'
diff --git a/src/tint/lang/core/ir/transform/std140.cc b/src/tint/lang/core/ir/transform/std140.cc index f2caaf5..a61b8bc 100644 --- a/src/tint/lang/core/ir/transform/std140.cc +++ b/src/tint/lang/core/ir/transform/std140.cc
@@ -27,14 +27,23 @@ #include "src/tint/lang/core/ir/transform/std140.h" +#include <cstdint> #include <utility> +#include "src/tint/lang/core/address_space.h" #include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/function_param.h" #include "src/tint/lang/core/ir/module.h" #include "src/tint/lang/core/ir/validator.h" #include "src/tint/lang/core/type/array.h" #include "src/tint/lang/core/type/matrix.h" +#include "src/tint/lang/core/type/memory_view.h" +#include "src/tint/lang/core/type/pointer.h" #include "src/tint/lang/core/type/struct.h" +#include "src/tint/lang/core/type/type.h" +#include "src/tint/utils/containers/hashmap.h" +#include "src/tint/utils/containers/vector.h" +#include "src/tint/utils/text/string_stream.h" using namespace tint::core::fluent_types; // NOLINT using namespace tint::core::number_suffixes; // NOLINT @@ -73,43 +82,51 @@ } // Find uniform buffers that contain matrices that need to be decomposed. - Vector<Var*, 8> buffer_variables; + Vector<std::pair<Var*, const core::type::Type*>, 8> buffer_variables; for (auto inst : *ir.root_block) { - auto* var = inst->As<Var>(); - if (!var || !var->Alive()) { - continue; - } - auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>(); - if (!ptr || ptr->AddressSpace() != core::AddressSpace::kUniform) { - continue; - } - if (RewriteType(ptr->StoreType()) != ptr->StoreType()) { - buffer_variables.Push(var); + if (auto* var = inst->As<Var>()) { + auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>(); + if (!ptr || ptr->AddressSpace() != core::AddressSpace::kUniform) { + continue; + } + auto* store_type = RewriteType(ptr->StoreType()); + if (store_type != ptr->StoreType()) { + buffer_variables.Push(std::make_pair(var, store_type)); + } } } // Now process the buffer variables, replacing them with new variables that have decomposed // matrices and updating all usages of the variables. - for (auto* var : buffer_variables) { + for (auto var_and_ty : buffer_variables) { // Create a new variable with the modified store type. - const auto& bp = var->BindingPoint(); - auto* store_type = var->Result(0)->Type()->As<core::type::Pointer>()->StoreType(); - auto* new_var = b.Var(ty.ptr(uniform, RewriteType(store_type))); + auto* old_var = var_and_ty.first; + auto* new_var = b.Var(ty.ptr(uniform, var_and_ty.second)); + const auto& bp = old_var->BindingPoint(); new_var->SetBindingPoint(bp->group, bp->binding); - if (auto name = ir.NameOf(var)) { + if (auto name = ir.NameOf(old_var)) { ir.SetName(new_var->Result(0), name); } - // Replace every instruction that uses the original variable. - var->Result(0)->ForEachUse( + // Transform instructions that accessed the variable to use the decomposed var. + old_var->Result(0)->ForEachUse( [&](Usage use) { Replace(use.instruction, new_var->Result(0)); }); // Replace the original variable with the new variable. - var->ReplaceWith(new_var); - var->Destroy(); + old_var->ReplaceWith(new_var); + old_var->Destroy(); } } + /// @param type the type to check + /// @returns the matrix if @p type is a matrix that needs to be decomposed + static const core::type::Matrix* NeedsDecomposing(const core::type::Type* type) { + if (auto* mat = type->As<core::type::Matrix>(); mat && NeedsDecomposing(mat)) { + return mat; + } + return nullptr; + } + /// @param mat the matrix type to check /// @returns true if @p mat needs to be decomposed static bool NeedsDecomposing(const core::type::Matrix* mat) { @@ -125,10 +142,10 @@ /// @param type the type to rewrite /// @returns the new type const core::type::Type* RewriteType(const core::type::Type* type) { - return rewritten_types.GetOrAdd(type, [&]() -> const core::type::Type* { + return rewritten_types.GetOrAdd(type, [&] { return tint::Switch( type, - [&](const core::type::Array* arr) -> const core::type::Type* { + [&](const core::type::Array* arr) { // Create a new array with element type potentially rewritten. return ty.array(RewriteType(arr->ElemType()), arr->ConstantCount().value()); }, @@ -137,8 +154,7 @@ uint32_t member_index = 0; Vector<const core::type::StructMember*, 4> new_members; for (auto* member : str->Members()) { - auto* mat = member->Type()->As<core::type::Matrix>(); - if (mat && NeedsDecomposing(mat)) { + if (auto* mat = NeedsDecomposing(member->Type())) { // Decompose these matrices into a separate member for each column. member_index_map.Add(member, member_index); auto* col = mat->ColumnType(); @@ -182,6 +198,32 @@ } return new_str; }, + [&](const core::type::Matrix* mat) -> const core::type::Type* { + if (!NeedsDecomposing(mat)) { + return mat; + } + StringStream name; + name << "mat" << mat->columns() << "x" << mat->rows() << "_" + << mat->ColumnType()->type()->FriendlyName() << "_std140"; + Vector<core::type::StructMember*, 4> members; + // Decompose these matrices into a separate member for each column. + auto* col = mat->ColumnType(); + uint32_t offset = 0; + for (uint32_t i = 0; i < mat->columns(); i++) { + StringStream ss; + ss << "col" << std::to_string(i); + members.Push(ty.Get<core::type::StructMember>( + sym.New(ss.str()), col, i, offset, col->Align(), col->Size(), + core::type::StructMemberAttributes{})); + offset += col->Align(); + } + + // Create a new struct with the rewritten members. + return ty.Get<core::type::Struct>( + sym.New(name.str()), std::move(members), col->Align(), + col->Align() * mat->columns(), + (col->Align() * (mat->columns() - 1)) + col->Size()); + }, [&](Default) { // This type cannot contain a matrix, so no changes needed. return type; @@ -189,19 +231,26 @@ }); } - /// Load a decomposed matrix from a structure. + /// Reconstructs a column-decomposed matrix. /// @param mat the matrix type /// @param root the root value being accessed into - /// @param indices the access indices that get to the first column of the decomposed matrix + /// @param indices the access indices that index the first column of the matrix. /// @returns the loaded matrix - Value* LoadMatrix(const core::type::Matrix* mat, Value* root, Vector<Value*, 4> indices) { - // Load each column vector from the struct and reconstruct the original matrix type. + Value* RebuildMatrix(const core::type::Matrix* mat, Value* root, VectorRef<Value*> indices) { + // Recombine each column vector from the struct and reconstruct the original matrix type. + bool is_ptr = root->Type()->Is<core::type::Pointer>(); + Vector<Value*, 4> column_indices(std::move(indices)); Vector<Value*, 4> args; auto first_column = indices.Back()->As<Constant>()->Value()->ValueAs<uint32_t>(); for (uint32_t i = 0; i < mat->columns(); i++) { - indices.Back() = b.Constant(u32(first_column + i)); - auto* access = b.Access(ty.ptr(uniform, mat->ColumnType()), root, indices); - args.Push(b.Load(access->Result(0))->Result(0)); + column_indices.Back() = b.Constant(u32(first_column + i)); + if (is_ptr) { + auto* access = b.Access(ty.ptr(uniform, mat->ColumnType()), root, column_indices); + args.Push(b.Load(access)->Result(0)); + } else { + auto* access = b.Access(mat->ColumnType(), root, column_indices); + args.Push(access->Result(0)); + } } return b.Construct(mat, std::move(args))->Result(0); } @@ -228,16 +277,10 @@ uint32_t index = 0; Vector<Value*, 4> args; for (auto* member : str->Members()) { - if (auto* mat = member->Type()->As<core::type::Matrix>(); - mat && NeedsDecomposing(mat)) { - // Extract each decomposed column and reconstruct the matrix. - Vector<Value*, 4> columns; - for (uint32_t i = 0; i < mat->columns(); i++) { - auto* extract = b.Access(mat->ColumnType(), input, u32(index)); - columns.Push(extract->Result(0)); - index++; - } - args.Push(b.Construct(mat, std::move(columns))->Result(0)); + if (auto* mat = NeedsDecomposing(member->Type())) { + args.Push( + RebuildMatrix(mat, input, Vector{b.Constant(u32(index))})); + index += mat->columns(); } else { // Extract and convert the member. auto* type = input_str->Element(index); @@ -268,6 +311,12 @@ }); return b.Load(new_arr)->Result(0); }, + [&](const core::type::Matrix* mat) -> Value* { + if (!NeedsDecomposing(mat)) { + return source; + } + return RebuildMatrix(mat, source, Vector{b.Constant(u32(0))}); + }, [&](Default) { return source; }); } @@ -279,28 +328,75 @@ tint::Switch( inst, // [&](Access* access) { + auto* object_ty = access->Object()->Type()->As<core::type::MemoryView>(); + if (!object_ty || object_ty->AddressSpace() != core::AddressSpace::kUniform) { + // Access to non-uniform memory views does not require transformation. + return; + } + + if (!replacement->Type()->Is<core::type::MemoryView>()) { + // The replacement is a value, in which case the decomposed matrix has + // already been reconstructed. In this situation the access only needs its + // return type updating, and downstream instructions need updating. + access->SetOperand(Access::kObjectOperandOffset, replacement); + auto* result = access->Result(0); + result->SetType(result->Type()->UnwrapPtrOrRef()); + result->ForEachUse([&](Usage use) { Replace(use.instruction, result); }); + return; + } + // Modify the access indices to take decomposed matrices into account. - auto* current_type = access->Object()->Type()->UnwrapPtr(); + auto* current_type = object_ty->StoreType(); Vector<Value*, 4> indices; - for (auto idx : access->Indices()) { - if (auto* str = current_type->As<core::type::Struct>()) { + + if (NeedsDecomposing(current_type)) { + // Decomposed matrices are indexed using their first column vector + indices.Push(b.Constant(0_u)); + } + + for (size_t i = 0, n = access->Indices().Length(); i < n; i++) { + auto* idx = access->Indices()[i]; + + if (auto* mat = NeedsDecomposing(current_type)) { + // Access chain passes through decomposed matrix. + if (auto* const_idx = idx->As<Constant>()) { + // Column vector index is a constant. + // Instead of loading the whole matrix, fold the access of the + // matrix and the constant column index into an single access of + // column vector member. + auto* base_idx = indices.Back()->As<Constant>(); + indices.Back() = + b.Constant(u32(base_idx->Value()->ValueAs<uint32_t>() + + const_idx->Value()->ValueAs<uint32_t>())); + current_type = mat->ColumnType(); + i++; // We've already consumed the column access + } else { + // Column vector index is dynamic. + // Reconstruct the whole matrix and index that. + replacement = RebuildMatrix(mat, replacement, std::move(indices)); + indices.Clear(); + indices.Push(idx); + current_type = mat->ColumnType(); + } + } else if (auto* str = current_type->As<core::type::Struct>()) { + // Remap member index uint32_t old_index = idx->As<Constant>()->Value()->ValueAs<uint32_t>(); uint32_t new_index = *member_index_map.Get(str->Members()[old_index]); - indices.Push(b.Constant(u32(new_index))); current_type = str->Element(old_index); + indices.Push(b.Constant(u32(new_index))); } else { indices.Push(idx); current_type = current_type->Elements().type; + if (NeedsDecomposing(current_type)) { + // Decomposed matrices are indexed using their first column vector + indices.Push(b.Constant(0_u)); + } } + } - // If we've hit a matrix that was decomposed, load the whole matrix. - // Any additional accesses will extract columns instead of producing - // pointers. - if (auto* mat = current_type->As<core::type::Matrix>(); - mat && NeedsDecomposing(mat)) { - replacement = LoadMatrix(mat, replacement, std::move(indices)); - indices.Clear(); - } + if (auto* mat = NeedsDecomposing(current_type)) { + replacement = RebuildMatrix(mat, replacement, std::move(indices)); + indices.Clear(); } if (!indices.IsEmpty()) { @@ -333,9 +429,7 @@ if (!replacement->Type()->Is<core::type::Pointer>()) { // We have loaded a decomposed matrix and reconstructed it, so this is now // extracting from a value type. - auto* access = - b.Access(load->Result(0)->Type(), replacement, load->Index()); - load->Result(0)->ReplaceAllUsesWith(access->Result(0)); + b.AccessWithResult(load->DetachResult(), replacement, load->Index()); load->Destroy(); } else { // There was no decomposed matrix on the path to this instruction so just
diff --git a/src/tint/lang/core/ir/transform/std140.h b/src/tint/lang/core/ir/transform/std140.h index f931546..89852db 100644 --- a/src/tint/lang/core/ir/transform/std140.h +++ b/src/tint/lang/core/ir/transform/std140.h
@@ -41,6 +41,8 @@ /// Std140 is a transform that rewrites matrix types in the uniform address space to conform to /// GLSL's std140 layout rules. +/// @note requires the DirectVariableAccess transform to have been run first to remove uniform +/// pointer parameters. /// @param module the module to transform /// @returns success or failure Result<SuccessType> Std140(Module& module);
diff --git a/src/tint/lang/core/ir/transform/std140_fuzz.cc b/src/tint/lang/core/ir/transform/std140_fuzz.cc index 86e5730..091def8 100644 --- a/src/tint/lang/core/ir/transform/std140_fuzz.cc +++ b/src/tint/lang/core/ir/transform/std140_fuzz.cc
@@ -28,12 +28,31 @@ #include "src/tint/lang/core/ir/transform/std140.h" #include "src/tint/cmd/fuzz/ir/fuzz.h" +#include "src/tint/lang/core/address_space.h" +#include "src/tint/lang/core/ir/module.h" #include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/core/type/pointer.h" namespace tint::core::ir::transform { namespace { +bool CanRun(Module& module) { + for (auto& fn : module.functions) { + for (auto* param : fn->Params()) { + if (auto* ptr = param->Type()->As<core::type::Pointer>(); + ptr && ptr->AddressSpace() == core::AddressSpace::kUniform) { + return false; // Requires the DirectVariableAccess transform + } + } + } + return true; +} + void Std140Fuzzer(Module& module) { + if (!CanRun(module)) { + return; + } + if (auto res = Std140(module); res != Success) { return; }
diff --git a/src/tint/lang/core/ir/transform/std140_test.cc b/src/tint/lang/core/ir/transform/std140_test.cc index dd55108..28a67fa 100644 --- a/src/tint/lang/core/ir/transform/std140_test.cc +++ b/src/tint/lang/core/ir/transform/std140_test.cc
@@ -29,6 +29,8 @@ #include <utility> +#include "src/tint/lang/core/fluent_types.h" +#include "src/tint/lang/core/ir/load_vector_element.h" #include "src/tint/lang/core/ir/transform/helper_test.h" #include "src/tint/lang/core/type/array.h" #include "src/tint/lang/core/type/matrix.h" @@ -148,8 +150,7 @@ EXPECT_EQ(expect, str()); } -// Test that we do not decompose a mat2x2 that is used an array element type. -TEST_F(IR_Std140Test, NoModify_Mat2x2_InsideArray) { +TEST_F(IR_Std140Test, Load_Mat2x2f_InArray) { auto* mat = ty.mat2x2<f32>(); auto* structure = ty.Struct(mod.symbols.New("MyStruct"), { @@ -186,7 +187,35 @@ )"; EXPECT_EQ(src, str()); - auto* expect = src; + auto* expect = R"( +MyStruct = struct @align(8), @block { + arr:array<mat2x2<f32>, 4> @offset(0) +} + +mat2x2_f32_std140 = struct @align(8) { + col0:vec2<f32> @offset(0) + col1:vec2<f32> @offset(8) +} + +MyStruct_std140 = struct @align(8), @block { + arr:array<mat2x2_f32_std140, 4> @offset(0) +} + +$B1: { # root + %buffer:ptr<uniform, MyStruct_std140, read> = var @binding_point(0, 0) +} + +%foo = func():mat2x2<f32> { + $B2: { + %3:ptr<uniform, vec2<f32>, read> = access %buffer, 0u, 2u, 0u + %4:vec2<f32> = load %3 + %5:ptr<uniform, vec2<f32>, read> = access %buffer, 0u, 2u, 1u + %6:vec2<f32> = load %5 + %7:mat2x2<f32> = construct %4, %6 + ret %7 + } +} +)"; Run(Std140); @@ -264,7 +293,7 @@ EXPECT_EQ(expect, str()); } -TEST_F(IR_Std140Test, Mat3x2_LoadColumn) { +TEST_F(IR_Std140Test, Mat3x2_LoadConstantColumn) { auto* mat = ty.mat3x2<f32>(); auto* structure = ty.Struct(mod.symbols.New("MyStruct"), { {mod.symbols.New("a"), mat}, @@ -318,15 +347,83 @@ %foo = func():vec2<f32> { $B2: { - %3:ptr<uniform, vec2<f32>, read> = access %buffer, 0u + %3:ptr<uniform, vec2<f32>, read> = access %buffer, 1u %4:vec2<f32> = load %3 - %5:ptr<uniform, vec2<f32>, read> = access %buffer, 1u - %6:vec2<f32> = load %5 - %7:ptr<uniform, vec2<f32>, read> = access %buffer, 2u - %8:vec2<f32> = load %7 - %9:mat3x2<f32> = construct %4, %6, %8 - %10:vec2<f32> = access %9, 1u - ret %10 + ret %4 + } +} +)"; + + Run(Std140); + + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_Std140Test, Mat3x2_LoadDynamicColumn) { + auto* mat = ty.mat3x2<f32>(); + auto* structure = ty.Struct(mod.symbols.New("MyStruct"), { + {mod.symbols.New("a"), mat}, + }); + structure->SetStructFlag(core::type::kBlock); + + auto* buffer = b.Var("buffer", ty.ptr(uniform, structure)); + buffer->SetBindingPoint(0, 0); + mod.root_block->Append(buffer); + + auto* func = b.Function("foo", mat->ColumnType()); + auto* column = b.FunctionParam<i32>("column"); + func->AppendParam(column); + b.Append(func->Block(), [&] { + auto* access = b.Access(ty.ptr(uniform, mat->ColumnType()), buffer, 0_u, column); + auto* load = b.Load(access); + b.Return(func, load); + }); + + auto* src = R"( +MyStruct = struct @align(8), @block { + a:mat3x2<f32> @offset(0) +} + +$B1: { # root + %buffer:ptr<uniform, MyStruct, read> = var @binding_point(0, 0) +} + +%foo = func(%column:i32):vec2<f32> { + $B2: { + %4:ptr<uniform, vec2<f32>, read> = access %buffer, 0u, %column + %5:vec2<f32> = load %4 + ret %5 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +MyStruct = struct @align(8), @block { + a:mat3x2<f32> @offset(0) +} + +MyStruct_std140 = struct @align(8), @block { + a_col0:vec2<f32> @offset(0) + a_col1:vec2<f32> @offset(8) + a_col2:vec2<f32> @offset(16) +} + +$B1: { # root + %buffer:ptr<uniform, MyStruct_std140, read> = var @binding_point(0, 0) +} + +%foo = func(%column:i32):vec2<f32> { + $B2: { + %4:ptr<uniform, vec2<f32>, read> = access %buffer, 0u + %5:vec2<f32> = load %4 + %6:ptr<uniform, vec2<f32>, read> = access %buffer, 1u + %7:vec2<f32> = load %6 + %8:ptr<uniform, vec2<f32>, read> = access %buffer, 2u + %9:vec2<f32> = load %8 + %10:mat3x2<f32> = construct %5, %7, %9 + %11:vec2<f32> = access %10, %column + ret %11 } } )"; @@ -390,16 +487,9 @@ %foo = func():f32 { $B2: { - %3:ptr<uniform, vec2<f32>, read> = access %buffer, 0u - %4:vec2<f32> = load %3 - %5:ptr<uniform, vec2<f32>, read> = access %buffer, 1u - %6:vec2<f32> = load %5 - %7:ptr<uniform, vec2<f32>, read> = access %buffer, 2u - %8:vec2<f32> = load %7 - %9:mat3x2<f32> = construct %4, %6, %8 - %10:vec2<f32> = access %9, 1u - %11:f32 = access %10, 1u - ret %11 + %3:ptr<uniform, vec2<f32>, read> = access %buffer, 1u + %4:f32 = load_vector_element %3, 1u + ret %4 } } )"; @@ -1709,48 +1799,35 @@ } %load_vec_b = func():f32 { $B7: { - %29:ptr<uniform, vec2<f32>, read> = access %buffer, 1u + %29:ptr<uniform, vec2<f32>, read> = access %buffer, 2u %30:vec2<f32> = load %29 - %31:ptr<uniform, vec2<f32>, read> = access %buffer, 2u - %32:vec2<f32> = load %31 - %33:ptr<uniform, vec2<f32>, read> = access %buffer, 3u - %34:vec2<f32> = load %33 - %35:mat3x2<f32> = construct %30, %32, %34 - %36:vec2<f32> = access %35, 1u - %37:f32 = access %36, 1u - ret %37 + %31:f32 = access %30, 1u + ret %31 } } %lve_a = func():f32 { $B8: { - %39:ptr<uniform, vec4<f32>, read> = access %buffer, 0u, 1u - %40:f32 = load_vector_element %39, 1u - ret %40 + %33:ptr<uniform, vec4<f32>, read> = access %buffer, 0u, 1u + %34:f32 = load_vector_element %33, 1u + ret %34 } } %lve_b = func():f32 { $B9: { - %42:ptr<uniform, vec2<f32>, read> = access %buffer, 1u - %43:vec2<f32> = load %42 - %44:ptr<uniform, vec2<f32>, read> = access %buffer, 2u - %45:vec2<f32> = load %44 - %46:ptr<uniform, vec2<f32>, read> = access %buffer, 3u - %47:vec2<f32> = load %46 - %48:mat3x2<f32> = construct %43, %45, %47 - %49:vec2<f32> = access %48, 1u - %50:f32 = access %49, 1u - ret %50 + %36:ptr<uniform, vec2<f32>, read> = access %buffer, 2u + %37:f32 = load_vector_element %36, 1u + ret %37 } } %convert_MyStruct = func(%input:MyStruct_std140):MyStruct { $B10: { - %52:mat4x4<f32> = access %input, 0u - %53:vec2<f32> = access %input, 1u - %54:vec2<f32> = access %input, 2u - %55:vec2<f32> = access %input, 3u - %56:mat3x2<f32> = construct %53, %54, %55 - %57:MyStruct = construct %52, %56 - ret %57 + %39:mat4x4<f32> = access %input, 0u + %40:vec2<f32> = access %input, 1u + %41:vec2<f32> = access %input, 2u + %42:vec2<f32> = access %input, 3u + %43:mat3x2<f32> = construct %40, %41, %42 + %44:MyStruct = construct %39, %43 + ret %44 } } )"; @@ -1857,48 +1934,295 @@ %14:vec4<f16> = load %13 %15:mat4x4<f16> = construct %8, %10, %12, %14 %mat:mat4x4<f16> = let %15 - %17:ptr<uniform, vec3<f16>, read> = access %buffer, 4u + %17:ptr<uniform, vec3<f16>, read> = access %buffer, 5u %18:vec3<f16> = load %17 - %19:ptr<uniform, vec3<f16>, read> = access %buffer, 5u - %20:vec3<f16> = load %19 - %21:ptr<uniform, vec3<f16>, read> = access %buffer, 6u - %22:vec3<f16> = load %21 - %23:ptr<uniform, vec3<f16>, read> = access %buffer, 7u - %24:vec3<f16> = load %23 - %25:mat4x3<f16> = construct %18, %20, %22, %24 - %26:vec3<f16> = access %25, 1u - %col:vec3<f16> = let %26 - %28:ptr<uniform, vec4<f16>, read> = access %buffer, 2u - %29:vec4<f16> = load %28 - %30:ptr<uniform, vec4<f16>, read> = access %buffer, 3u - %31:vec4<f16> = load %30 - %32:mat2x4<f16> = construct %29, %31 - %33:vec4<f16> = access %32, 0u - %34:f16 = access %33, 3u - %el:f16 = let %34 + %col:vec3<f16> = let %18 + %20:ptr<uniform, vec4<f16>, read> = access %buffer, 2u + %21:f16 = load_vector_element %20, 3u + %el:f16 = let %21 ret } } %convert_MyStruct = func(%input:MyStruct_std140):MyStruct { $B3: { - %37:vec2<f16> = access %input, 0u - %38:vec2<f16> = access %input, 1u - %39:mat2x2<f16> = construct %37, %38 - %40:vec4<f16> = access %input, 2u - %41:vec4<f16> = access %input, 3u - %42:mat2x4<f16> = construct %40, %41 - %43:vec3<f16> = access %input, 4u - %44:vec3<f16> = access %input, 5u - %45:vec3<f16> = access %input, 6u - %46:vec3<f16> = access %input, 7u - %47:mat4x3<f16> = construct %43, %44, %45, %46 - %48:vec4<f16> = access %input, 8u - %49:vec4<f16> = access %input, 9u - %50:vec4<f16> = access %input, 10u - %51:vec4<f16> = access %input, 11u - %52:mat4x4<f16> = construct %48, %49, %50, %51 - %53:MyStruct = construct %39, %42, %47, %52 - ret %53 + %24:vec2<f16> = access %input, 0u + %25:vec2<f16> = access %input, 1u + %26:mat2x2<f16> = construct %24, %25 + %27:vec4<f16> = access %input, 2u + %28:vec4<f16> = access %input, 3u + %29:mat2x4<f16> = construct %27, %28 + %30:vec3<f16> = access %input, 4u + %31:vec3<f16> = access %input, 5u + %32:vec3<f16> = access %input, 6u + %33:vec3<f16> = access %input, 7u + %34:mat4x3<f16> = construct %30, %31, %32, %33 + %35:vec4<f16> = access %input, 8u + %36:vec4<f16> = access %input, 9u + %37:vec4<f16> = access %input, 10u + %38:vec4<f16> = access %input, 11u + %39:mat4x4<f16> = construct %35, %36, %37, %38 + %40:MyStruct = construct %26, %29, %34, %39 + ret %40 + } +} +)"; + + Run(Std140); + + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_Std140Test, Mat3x3f_And_ArrayMat4x3f) { // crbug.com/338727551 + auto* s = + ty.Struct(mod.symbols.New("S"), { + {mod.symbols.New("a"), ty.mat3x3<f32>()}, + {mod.symbols.New("b"), ty.array<mat4x3<f32>, 3>()}, + }); + s->SetStructFlag(core::type::kBlock); + + auto* u = b.Var("u", ty.ptr(uniform, s)); + u->SetBindingPoint(0, 0); + mod.root_block->Append(u); + + auto* f = b.Function("F", ty.f32()); + b.Append(f->Block(), [&] { + auto* p = b.Access<ptr<uniform, vec3<f32>, read>>(u, 1_u, 0_u, 0_u); + auto* x = b.LoadVectorElement(p, 0_u); + b.Return(f, x); + }); + + auto* src = R"( +S = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:array<mat4x3<f32>, 3> @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec3<f32>, read> = access %u, 1u, 0u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +S = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:array<mat4x3<f32>, 3> @offset(48) +} + +mat4x3_f32_std140 = struct @align(16) { + col0:vec3<f32> @offset(0) + col1:vec3<f32> @offset(16) + col2:vec3<f32> @offset(32) + col3:vec3<f32> @offset(48) +} + +S_std140 = struct @align(16), @block { + a_col0:vec3<f32> @offset(0) + a_col1:vec3<f32> @offset(16) + a_col2:vec3<f32> @offset(32) + b:array<mat4x3_f32_std140, 3> @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S_std140, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec3<f32>, read> = access %u, 3u, 0u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 + } +} +)"; + + Run(Std140); + + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_Std140Test, Mat3x3f_And_ArrayStructMat4x3f) { + auto* s1 = + ty.Struct(mod.symbols.New("S1"), { + {mod.symbols.New("c"), ty.mat3x3<f32>()}, + {mod.symbols.New("d"), ty.array<mat4x3<f32>, 3>()}, + }); + auto* s2 = ty.Struct(mod.symbols.New("S2"), { + {mod.symbols.New("a"), ty.mat3x3<f32>()}, + {mod.symbols.New("b"), s1}, + }); + s2->SetStructFlag(core::type::kBlock); + + auto* u = b.Var("u", ty.ptr(uniform, s2)); + u->SetBindingPoint(0, 0); + mod.root_block->Append(u); + + auto* f = b.Function("F", ty.f32()); + b.Append(f->Block(), [&] { + auto* p = b.Access<ptr<uniform, vec3<f32>, read>>(u, 1_u, 1_u, 0_u, 0_u); + auto* x = b.LoadVectorElement(p, 0_u); + b.Return(f, x); + }); + + auto* src = R"( +S1 = struct @align(16) { + c:mat3x3<f32> @offset(0) + d:array<mat4x3<f32>, 3> @offset(48) +} + +S2 = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:S1 @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S2, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec3<f32>, read> = access %u, 1u, 1u, 0u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +S1 = struct @align(16) { + c:mat3x3<f32> @offset(0) + d:array<mat4x3<f32>, 3> @offset(48) +} + +S2 = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:S1 @offset(48) +} + +mat4x3_f32_std140 = struct @align(16) { + col0:vec3<f32> @offset(0) + col1:vec3<f32> @offset(16) + col2:vec3<f32> @offset(32) + col3:vec3<f32> @offset(48) +} + +S1_std140 = struct @align(16) { + c_col0:vec3<f32> @offset(0) + c_col1:vec3<f32> @offset(16) + c_col2:vec3<f32> @offset(32) + d:array<mat4x3_f32_std140, 3> @offset(48) +} + +S2_std140 = struct @align(16), @block { + a_col0:vec3<f32> @offset(0) + a_col1:vec3<f32> @offset(16) + a_col2:vec3<f32> @offset(32) + b:S1_std140 @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S2_std140, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec3<f32>, read> = access %u, 3u, 3u, 0u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 + } +} +)"; + + Run(Std140); + + EXPECT_EQ(expect, str()); +} + +TEST_F(IR_Std140Test, Mat3x3f_And_ArrayStructMat2x2f) { + auto* s1 = ty.Struct(mod.symbols.New("S1"), { + {mod.symbols.New("c"), ty.mat2x2<f32>()}, + }); + auto* s2 = ty.Struct(mod.symbols.New("S2"), { + {mod.symbols.New("a"), ty.mat3x3<f32>()}, + {mod.symbols.New("b"), s1}, + }); + s2->SetStructFlag(core::type::kBlock); + + auto* u = b.Var("u", ty.ptr(uniform, s2)); + u->SetBindingPoint(0, 0); + mod.root_block->Append(u); + + auto* f = b.Function("F", ty.f32()); + b.Append(f->Block(), [&] { + auto* p = b.Access<ptr<uniform, vec2<f32>, read>>(u, 1_u, 0_u, 0_u); + auto* x = b.LoadVectorElement(p, 0_u); + b.Return(f, x); + }); + + auto* src = R"( +S1 = struct @align(8) { + c:mat2x2<f32> @offset(0) +} + +S2 = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:S1 @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S2, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec2<f32>, read> = access %u, 1u, 0u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +S1 = struct @align(8) { + c:mat2x2<f32> @offset(0) +} + +S2 = struct @align(16), @block { + a:mat3x3<f32> @offset(0) + b:S1 @offset(48) +} + +S1_std140 = struct @align(8) { + c_col0:vec2<f32> @offset(0) + c_col1:vec2<f32> @offset(8) +} + +S2_std140 = struct @align(16), @block { + a_col0:vec3<f32> @offset(0) + a_col1:vec3<f32> @offset(16) + a_col2:vec3<f32> @offset(32) + b:S1_std140 @offset(48) +} + +$B1: { # root + %u:ptr<uniform, S2_std140, read> = var @binding_point(0, 0) +} + +%F = func():f32 { + $B2: { + %3:ptr<uniform, vec2<f32>, read> = access %u, 3u, 0u + %4:f32 = load_vector_element %3, 0u + ret %4 } } )";
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc index 37bdb03..99d6be9 100644 --- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc +++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
@@ -86,8 +86,7 @@ } // Construct the matrix from the column vectors and replace the original instruction. - auto* replacement = b.Construct(mat, std::move(columns))->Result(0); - construct->Result(0)->ReplaceAllUsesWith(replacement); + b.ConstructWithResult(construct->DetachResult(), std::move(columns)); construct->Destroy(); } };
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc index 26cbf76..8969c9f 100644 --- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc +++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -32,6 +32,7 @@ #include "src/tint/lang/core/ir/builder.h" #include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/transform/common/referenced_module_vars.h" #include "src/tint/lang/core/ir/validator.h" #include "src/tint/utils/containers/reverse.h" @@ -61,17 +62,12 @@ /// The type manager. core::type::Manager& ty{ir.Types()}; - /// VarSet is a hash set of workgroup variables. - using VarSet = Hashset<Var*, 8>; - - /// A map from variable to an ID used for sorting. - Hashmap<Var*, uint32_t, 8> var_to_id{}; - - /// A map from blocks to their directly referenced workgroup variables. - Hashmap<Block*, VarSet, 64> block_to_direct_vars{}; - - /// A map from functions to their transitively referenced workgroup variables. - Hashmap<Function*, VarSet, 8> function_to_transitive_vars{}; + /// The mapping from functions to their transitively referenced workgroup variables. + ReferencedModuleVars referenced_module_vars_{ + ir, [](const Var* var) { + auto* view = var->Result(0)->Type()->As<type::MemoryView>(); + return view && view->AddressSpace() == AddressSpace::kWorkgroup; + }}; /// ArrayIndex represents a required array index for an access instruction. struct ArrayIndex { @@ -104,22 +100,6 @@ if (ir.root_block->IsEmpty()) { return; } - - // Loop over module-scope variables, looking for workgroup variables. - uint32_t next_id = 0; - for (auto inst : *ir.root_block) { - if (auto* var = inst->As<Var>()) { - auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>(); - if (ptr && ptr->AddressSpace() == core::AddressSpace::kWorkgroup) { - // Record the usage of the variable for each block that references it. - var->Result(0)->ForEachUse([&](const Usage& use) { - block_to_direct_vars.GetOrAddZero(use.instruction->Block()).Add(var); - }); - var_to_id.Add(var, next_id++); - } - } - } - // Process each entry point function. for (auto& func : ir.functions) { if (func->Stage() == Function::PipelineStage::kCompute) { @@ -132,20 +112,14 @@ /// @param func the entry point function void ProcessEntryPoint(Function* func) { // Get list of transitively referenced workgroup variables. - auto vars = GetReferencedVars(func); + const auto& vars = referenced_module_vars_.TransitiveReferences(func); if (vars.IsEmpty()) { return; } - // Sort the variables to get deterministic output in tests. - auto sorted_vars = vars.Vector(); - sorted_vars.Sort([&](Var* first, Var* second) { - return *var_to_id.Get(first) < *var_to_id.Get(second); - }); - // Build list of store descriptors for all workgroup variables. StoreMap stores; - for (auto* var : sorted_vars) { + for (auto* var : vars) { PrepareStores(var, var->Result(0)->Type()->UnwrapPtr(), 1, {}, stores); } @@ -188,46 +162,6 @@ }); } - /// Get the set of workgroup variables transitively referenced by @p func. - /// @param func the function - /// @returns the set of transitively referenced workgroup variables - VarSet GetReferencedVars(Function* func) { - return function_to_transitive_vars.GetOrAdd(func, [&] { - VarSet vars; - GetReferencedVars(func->Block(), vars); - return vars; - }); - } - - /// Get the set of workgroup variables transitively referenced by @p block. - /// @param block the block - /// @param vars the set of transitively referenced workgroup variables to populate - void GetReferencedVars(Block* block, VarSet& vars) { - // Add directly referenced vars. - if (auto itr = block_to_direct_vars.Get(block)) { - for (auto& var : *itr) { - vars.Add(var); - } - } - - // Loop over instructions in the block. - for (auto* inst : *block) { - tint::Switch( - inst, - [&](UserCall* call) { - // Get variables referenced by a function called from this block. - auto callee_vars = GetReferencedVars(call->Target()); - for (auto& var : callee_vars) { - vars.Add(var); - } - }, - [&](ControlInstruction* ctrl) { - // Recurse into control instructions and gather their referenced vars. - ctrl->ForeachBlock([&](Block* blk) { GetReferencedVars(blk, vars); }); - }); - } - } - /// Recursively generate store descriptors for a workgroup variable. /// Determines the combined array iteration count of each inner element. /// @param var the workgroup variable
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc index bf1a82b..1843665 100644 --- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc +++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -102,6 +102,48 @@ EXPECT_EQ(expect, str()); } +TEST_F(IR_ZeroInitWorkgroupMemoryTest, NonWorkgroupVar) { + auto* var = b.Var("pvar", ty.ptr(private_, ty.bool_())); + mod.root_block->Append(var); + + auto* func = MakeEntryPoint("main", 1, 1, 1); + b.Append(func->Block(), [&] { // + b.Load(var); + b.Return(func); + }); + + auto* src = R"( +$B1: { # root + %pvar:ptr<private, bool, read_write> = var +} + +%main = @compute @workgroup_size(1, 1, 1) func():void { + $B2: { + %3:bool = load %pvar + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %pvar:ptr<private, bool, read_write> = var +} + +%main = @compute @workgroup_size(1, 1, 1) func():void { + $B2: { + %3:bool = load %pvar + ret + } +} +)"; + + Run(ZeroInitWorkgroupMemory); + + EXPECT_EQ(expect, str()); +} + TEST_F(IR_ZeroInitWorkgroupMemoryTest, ScalarBool) { auto* var = MakeVar("wgvar", ty.bool_());
diff --git a/src/tint/lang/core/ir/user_call_test.cc b/src/tint/lang/core/ir/user_call_test.cc index 768fea3..252375d 100644 --- a/src/tint/lang/core/ir/user_call_test.cc +++ b/src/tint/lang/core/ir/user_call_test.cc
@@ -58,7 +58,7 @@ } TEST_F(IR_UserCallTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc index e945cc6..3e0e704 100644 --- a/src/tint/lang/core/ir/validator.cc +++ b/src/tint/lang/core/ir/validator.cc
@@ -80,7 +80,6 @@ #include "src/tint/utils/containers/transform.h" #include "src/tint/utils/ice/ice.h" #include "src/tint/utils/macros/defer.h" -#include "src/tint/utils/macros/scoped_assignment.h" #include "src/tint/utils/rtti/switch.h" #include "src/tint/utils/text/styled_text.h" #include "src/tint/utils/text/text_style.h" @@ -448,6 +447,7 @@ } diag::Diagnostic& Validator::AddError(const Instruction* inst) { + diagnostics_.ReserveAdditional(2); // Ensure diagnostics don't resize alive after AddNote() auto src = Disassembly().InstructionSource(inst); auto& diag = AddError(src) << inst->FriendlyName() << ": "; @@ -458,6 +458,7 @@ } diag::Diagnostic& Validator::AddError(const Instruction* inst, size_t idx) { + diagnostics_.ReserveAdditional(2); // Ensure diagnostics don't resize alive after AddNote() auto src = Disassembly().OperandSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)}); auto& diag = AddError(src) << inst->FriendlyName() << ": "; @@ -469,6 +470,7 @@ } diag::Diagnostic& Validator::AddResultError(const Instruction* inst, size_t idx) { + diagnostics_.ReserveAdditional(2); // Ensure diagnostics don't resize alive after AddNote() auto src = Disassembly().ResultSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)}); auto& diag = AddError(src) << inst->FriendlyName() << ": "; @@ -789,7 +791,10 @@ void Validator::CheckVar(const Var* var) { if (var->Result(0) && var->Initializer()) { if (var->Initializer()->Type() != var->Result(0)->Type()->UnwrapPtrOrRef()) { - AddError(var) << "initializer has incorrect type"; + AddError(var) << "initializer type " + << style::Type(var->Initializer()->Type()->FriendlyName()) + << " does not match store type " + << style::Type(var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName()); } } } @@ -799,7 +804,9 @@ if (let->Result(0) && let->Value()) { if (let->Result(0)->Type() != let->Value()->Type()) { - AddError(let) << "result type does not match value type"; + AddError(let) << "result type " << style::Type(let->Result(0)->Type()->FriendlyName()) + << " does not match value type " + << style::Type(let->Value()->Type()->FriendlyName()); } } } @@ -859,8 +866,9 @@ for (size_t i = 0; i < args.Length(); i++) { if (args[i]->Type() != params[i]->Type()) { AddError(call, UserCall::kArgsOperandOffset + i) - << "function parameter " << i << " is of type " << params[i]->Type()->FriendlyName() - << ", but argument is of type " << args[i]->Type()->FriendlyName(); + << "function parameter " << i << " is of type " + << style::Type(params[i]->Type()->FriendlyName()) << ", but argument is of type " + << style::Type(args[i]->Type()->FriendlyName()); } } } @@ -880,13 +888,15 @@ auto desc_of = [&](Kind kind, const core::type::Type* type) { switch (kind) { case kPtr: - return StyledText{} << "ptr<" << obj_view->AddressSpace() << ", " - << type->FriendlyName() << ", " << obj_view->Access() << ">"; + return StyledText{} + << style::Type("ptr<", obj_view->AddressSpace(), ", ", type->FriendlyName(), + ", ", obj_view->Access(), ">"); case kRef: - return StyledText{} << "ref<" << obj_view->AddressSpace() << ", " - << type->FriendlyName() << ", " << obj_view->Access() << ">"; + return StyledText{} + << style::Type("ref<", obj_view->AddressSpace(), ", ", type->FriendlyName(), + ", ", obj_view->Access(), ">"); default: - return StyledText{} << type->FriendlyName(); + return StyledText{} << style::Type(type->FriendlyName()); } }; @@ -957,7 +967,7 @@ if (TINT_UNLIKELY(!ok)) { AddError(a) << "result of access chain is type " << desc_of(in_kind, ty) - << " but instruction type is " << want->FriendlyName(); + << " but instruction type is " << style::Type(want->FriendlyName()); } } @@ -966,11 +976,7 @@ if (b->LHS() && b->RHS()) { auto symbols = SymbolTable::Wrap(mod_.symbols); auto type_mgr = type::Manager::Wrap(mod_.Types()); - intrinsic::Context context{ - b->TableData(), - type_mgr, - symbols, - }; + intrinsic::Context context{b->TableData(), type_mgr, symbols}; auto overload = core::intrinsic::LookupBinary(context, b->Op(), b->LHS()->Type(), b->RHS()->Type(), @@ -982,11 +988,10 @@ if (auto* result = b->Result(0)) { if (overload->return_type != result->Type()) { - StringStream err; - err << "binary instruction result type (" << result->Type()->FriendlyName() - << ") does not match overload result type (" - << overload->return_type->FriendlyName() << ")"; - AddError(b) << err.str(); + AddError(b) << "result value type " << style::Type(result->Type()->FriendlyName()) + << " does not match " + << style::Instruction(Disassembly().NameOf(b->Op())) << " result type " + << style::Type(overload->return_type->FriendlyName()); } } } @@ -997,11 +1002,7 @@ if (u->Val()) { auto symbols = SymbolTable::Wrap(mod_.symbols); auto type_mgr = type::Manager::Wrap(mod_.Types()); - intrinsic::Context context{ - u->TableData(), - type_mgr, - symbols, - }; + intrinsic::Context context{u->TableData(), type_mgr, symbols}; auto overload = core::intrinsic::LookupUnary(context, u->Op(), u->Val()->Type(), core::EvaluationStage::kRuntime); @@ -1012,11 +1013,10 @@ if (auto* result = u->Result(0)) { if (overload->return_type != result->Type()) { - StringStream err; - err << "unary instruction result type (" << result->Type()->FriendlyName() - << ") does not match overload result type (" - << overload->return_type->FriendlyName() << ")"; - AddError(u) << err.str(); + AddError(u) << "result value type " << style::Type(result->Type()->FriendlyName()) + << " does not match " + << style::Instruction(Disassembly().NameOf(u->Op())) << " result type " + << style::Type(overload->return_type->FriendlyName()); } } } @@ -1026,7 +1026,8 @@ CheckOperandNotNull(if_, if_->Condition(), If::kConditionOperandOffset); if (if_->Condition() && !if_->Condition()->Type()->Is<core::type::Bool>()) { - AddError(if_, If::kConditionOperandOffset) << "condition must be a `bool` type"; + AddError(if_, If::kConditionOperandOffset) + << "condition type must be " << style::Type("bool"); } tasks_.Push([this] { control_stack_.Pop(); }); @@ -1114,9 +1115,9 @@ for (size_t i = 0; i < results.Length(); ++i) { if (results[i] && args[i] && results[i]->Type() != args[i]->Type()) { - AddError(e, i) << "argument type (" << results[i]->Type()->FriendlyName() - << ") does not match control instruction type (" - << args[i]->Type()->FriendlyName() << ")"; + AddError(e, i) << "argument type " << style::Type(results[i]->Type()->FriendlyName()) + << " does not match control instruction type " + << style::Type(args[i]->Type()->FriendlyName()); AddNote(e->ControlInstruction()) << "control instruction"; } } @@ -1150,7 +1151,10 @@ if (!ret->Value()) { AddError(ret) << "expected return value"; } else if (ret->Value()->Type() != func->ReturnType()) { - AddError(ret) << "return value type does not match function return type"; + AddError(ret) << "return value type " + << style::Type(ret->Value()->Type()->FriendlyName()) + << " does not match function return type " + << style::Type(func->ReturnType()->FriendlyName()); } } } @@ -1214,7 +1218,10 @@ return; } if (l->Result(0)->Type() != mv->StoreType()) { - AddError(l, Load::kFromOperandOffset) << "result type does not match source store type"; + AddError(l, Load::kFromOperandOffset) + << "result type " << style::Type(l->Result(0)->Type()->FriendlyName()) + << " does not match source store type " + << style::Type(mv->StoreType()->FriendlyName()); } } } @@ -1230,8 +1237,12 @@ << "store target operand is not a memory view"; return; } - if (from->Type() != mv->StoreType()) { - AddError(s, Store::kFromOperandOffset) << "value type does not match store type"; + auto* value_type = from->Type(); + auto* store_type = mv->StoreType(); + if (value_type != store_type) { + AddError(s, Store::kFromOperandOffset) + << "value type " << style::Type(value_type->FriendlyName()) + << " does not match store type " << style::Type(store_type->FriendlyName()); } } } @@ -1245,7 +1256,9 @@ if (auto* res = l->Result(0)) { if (auto* el_ty = GetVectorPtrElementType(l, LoadVectorElement::kFromOperandOffset)) { if (res->Type() != el_ty) { - AddResultError(l, 0) << "result type does not match vector pointer element type"; + AddResultError(l, 0) << "result type " << style::Type(res->Type()->FriendlyName()) + << " does not match vector pointer element type " + << style::Type(el_ty->FriendlyName()); } } } @@ -1260,7 +1273,9 @@ if (auto* el_ty = GetVectorPtrElementType(s, StoreVectorElement::kToOperandOffset)) { if (value->Type() != el_ty) { AddError(s, StoreVectorElement::kValueOperandOffset) - << "value type does not match vector pointer element type"; + << "value type " << style::Type(value->Type()->FriendlyName()) + << " does not match vector pointer element type " + << style::Type(el_ty->FriendlyName()); } } } @@ -1285,7 +1300,8 @@ } } - AddError(inst, idx) << "operand must be a pointer to vector, got " << type->FriendlyName(); + AddError(inst, idx) << "operand must be a pointer to vector, got " + << style::Type(type->FriendlyName()); return nullptr; }
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc index 864e24d..2619338 100644 --- a/src/tint/lang/core/ir/validator_test.cc +++ b/src/tint/lang/core/ir/validator_test.cc
@@ -394,8 +394,9 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), - R"(:8:28 error: call: function parameter 1 is of type i32, but argument is of type f32 + EXPECT_EQ( + res.Failure().reason.Str(), + R"(:8:28 error: call: function parameter 1 is of type 'i32', but argument is of type 'f32' %6:void = call %g, 1i, 2.0f, 3i ^^^^ @@ -631,7 +632,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); EXPECT_EQ(res.Failure().reason.Str(), - R"(:3:29 error: access: index out of bounds for type vec2<f32> + R"(:3:29 error: access: index out of bounds for type 'vec2<f32>' %3:f32 = access %2, 1u, 3u ^^ @@ -667,7 +668,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:55 error: access: index out of bounds for type ptr<private, array<f32, 2>, read_write> + R"(:3:55 error: access: index out of bounds for type 'ptr<private, array<f32, 2>, read_write>' %3:ptr<private, f32, read_write> = access %2, 1u, 3u ^^ @@ -701,7 +702,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), R"(:3:25 error: access: type f32 cannot be indexed + EXPECT_EQ(res.Failure().reason.Str(), R"(:3:25 error: access: type 'f32' cannot be indexed %3:f32 = access %2, 1u ^^ @@ -732,7 +733,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); EXPECT_EQ(res.Failure().reason.Str(), - R"(:3:51 error: access: type ptr<private, f32, read_write> cannot be indexed + R"(:3:51 error: access: type 'ptr<private, f32, read_write>' cannot be indexed %3:ptr<private, f32, read_write> = access %2, 1u ^^ @@ -769,7 +770,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); EXPECT_EQ(res.Failure().reason.Str(), - R"(:8:25 error: access: type MyStruct cannot be dynamically indexed + R"(:8:25 error: access: type 'MyStruct' cannot be dynamically indexed %4:i32 = access %2, %3 ^^ @@ -812,7 +813,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:8:25 error: access: type ptr<private, MyStruct, read_write> cannot be dynamically indexed + R"(:8:25 error: access: type 'ptr<private, MyStruct, read_write>' cannot be dynamically indexed %4:i32 = access %2, %3 ^^ @@ -847,8 +848,9 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), - R"(:3:14 error: access: result of access chain is type f32 but instruction type is i32 + EXPECT_EQ( + res.Failure().reason.Str(), + R"(:3:14 error: access: result of access chain is type 'f32' but instruction type is 'i32' %3:i32 = access %2, 1u, 1u ^^^^^^ @@ -880,7 +882,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:40 error: access: result of access chain is type ptr<private, f32, read_write> but instruction type is ptr<private, i32, read_write> + R"(:3:40 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'ptr<private, i32, read_write>' %3:ptr<private, i32, read_write> = access %2, 1u, 1u ^^^^^^ @@ -912,7 +914,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:14 error: access: result of access chain is type ptr<private, f32, read_write> but instruction type is f32 + R"(:3:14 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'f32' %3:f32 = access %2, 1u, 1u ^^^^^^ @@ -1034,7 +1036,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:34 error: access: result of access chain is type ptr<storage, f32, read> but instruction type is ptr<uniform, f32, read> + R"(:3:34 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<uniform, f32, read>' %3:ptr<uniform, f32, read> = access %2, 1u ^^^^^^ @@ -1066,7 +1068,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:40 error: access: result of access chain is type ptr<storage, f32, read> but instruction type is ptr<storage, f32, read_write> + R"(:3:40 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<storage, f32, read_write>' %3:ptr<storage, f32, read_write> = access %2, 1u ^^^^^^ @@ -1197,7 +1199,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), R"(:3:8 error: if: condition must be a `bool` type + EXPECT_EQ(res.Failure().reason.Str(), R"(:3:8 error: if: condition type must be 'bool' if 1i [t: $B2, f: $B3] { # if_1 ^^ @@ -1400,7 +1402,8 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), R"(:3:41 error: var: initializer has incorrect type + EXPECT_EQ(res.Failure().reason.Str(), + R"(:3:41 error: var: initializer type 'i32' does not match store type 'f32' %2:ptr<function, f32, read_write> = var, 1i ^^^ @@ -1488,7 +1491,8 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), R"(:3:14 error: let: result type does not match value type + EXPECT_EQ(res.Failure().reason.Str(), + R"(:3:14 error: let: result type 'f32' does not match value type 'i32' %2:f32 = let 1i ^^^ @@ -1813,7 +1817,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:3:5 error: unary: unary instruction result type (f32) does not match overload result type (i32) + R"(:3:5 error: unary: result value type 'f32' does not match complement result type 'i32' %2:f32 = complement 2i ^^^^^^^^^^^^^^^^^^^^^^ @@ -1998,7 +2002,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:5:21 error: exit_if: argument type (f32) does not match control instruction type (i32) + R"(:5:21 error: exit_if: argument type 'f32' does not match control instruction type 'i32' exit_if 1i, 2i # if_1 ^^ @@ -2392,7 +2396,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:5:25 error: exit_switch: argument type (f32) does not match control instruction type (i32) + R"(:5:25 error: exit_switch: argument type 'f32' does not match control instruction type 'i32' exit_switch 1i, 2i # switch_1 ^^ @@ -2780,7 +2784,7 @@ ASSERT_NE(res, Success); EXPECT_EQ( res.Failure().reason.Str(), - R"(:5:23 error: exit_loop: argument type (f32) does not match control instruction type (i32) + R"(:5:23 error: exit_loop: argument type 'f32' does not match control instruction type 'i32' exit_loop 1i, 2i # loop_1 ^^ @@ -3300,8 +3304,9 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); - EXPECT_EQ(res.Failure().reason.Str(), - R"(:3:5 error: return: return value type does not match function return type + EXPECT_EQ( + res.Failure().reason.Str(), + R"(:3:5 error: return: return value type 'f32' does not match function return type 'i32' ret 42.0f ^^^^^^^^^ @@ -3392,7 +3397,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); EXPECT_EQ(res.Failure().reason.Str(), - R"(:4:19 error: load: result type does not match source store type + R"(:4:19 error: load: result type 'f32' does not match source store type 'i32' %3:f32 = load %2 ^^ @@ -3512,7 +3517,7 @@ auto res = ir::Validate(mod); ASSERT_NE(res, Success); EXPECT_EQ(res.Failure().reason.Str(), - R"(:4:15 error: store: value type does not match store type + R"(:4:15 error: store: value type 'u32' does not match store type 'i32' store %2, 42u ^^^ @@ -3672,7 +3677,7 @@ $B1: { ^^^ -:4:37 error: store_vector_element: value type does not match vector pointer element type +:4:37 error: store_vector_element: value type 'i32' does not match vector pointer element type 'f32' store_vector_element %2, undef, 2i ^^
diff --git a/src/tint/lang/core/ir/value_test.cc b/src/tint/lang/core/ir/value_test.cc index 43fd900..c8b7954 100644 --- a/src/tint/lang/core/ir/value_test.cc +++ b/src/tint/lang/core/ir/value_test.cc
@@ -66,7 +66,7 @@ } TEST_F(IR_ValueTest, Destroy_HasSource) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/core/ir/var_test.cc b/src/tint/lang/core/ir/var_test.cc index e1759d9..88a1c69 100644 --- a/src/tint/lang/core/ir/var_test.cc +++ b/src/tint/lang/core/ir/var_test.cc
@@ -41,7 +41,7 @@ using IR_VarTest = IRTestHelper; TEST_F(IR_VarTest, Fail_NullType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Module mod; Builder b{mod};
diff --git a/src/tint/lang/msl/validate/BUILD.bazel b/src/tint/lang/msl/validate/BUILD.bazel index 7e5730a..2b76c9d 100644 --- a/src/tint/lang/msl/validate/BUILD.bazel +++ b/src/tint/lang/msl/validate/BUILD.bazel
@@ -50,29 +50,9 @@ "validate.h", ], deps = [ - "//src/tint/lang/core", - "//src/tint/lang/core/constant", - "//src/tint/lang/core/type", - "//src/tint/lang/wgsl", - "//src/tint/lang/wgsl/ast", - "//src/tint/lang/wgsl/features", - "//src/tint/lang/wgsl/program", - "//src/tint/lang/wgsl/sem", "//src/tint/utils/command", - "//src/tint/utils/containers", - "//src/tint/utils/diagnostic", "//src/tint/utils/file", - "//src/tint/utils/ice", - "//src/tint/utils/id", - "//src/tint/utils/macros", - "//src/tint/utils/math", - "//src/tint/utils/memory", - "//src/tint/utils/reflection", - "//src/tint/utils/result", - "//src/tint/utils/rtti", - "//src/tint/utils/symbol", "//src/tint/utils/text", - "//src/tint/utils/traits", ] + select({ ":tint_build_is_mac": [
diff --git a/src/tint/lang/msl/validate/BUILD.cmake b/src/tint/lang/msl/validate/BUILD.cmake index 698e899..3fcdcac 100644 --- a/src/tint/lang/msl/validate/BUILD.cmake +++ b/src/tint/lang/msl/validate/BUILD.cmake
@@ -46,29 +46,9 @@ ) tint_target_add_dependencies(tint_lang_msl_validate lib - tint_lang_core - tint_lang_core_constant - tint_lang_core_type - tint_lang_wgsl - tint_lang_wgsl_ast - tint_lang_wgsl_features - tint_lang_wgsl_program - tint_lang_wgsl_sem tint_utils_command - tint_utils_containers - tint_utils_diagnostic tint_utils_file - tint_utils_ice - tint_utils_id - tint_utils_macros - tint_utils_math - tint_utils_memory - tint_utils_reflection - tint_utils_result - tint_utils_rtti - tint_utils_symbol tint_utils_text - tint_utils_traits ) if(TINT_BUILD_IS_MAC)
diff --git a/src/tint/lang/msl/validate/BUILD.gn b/src/tint/lang/msl/validate/BUILD.gn index cbf7c61..902ed8d 100644 --- a/src/tint/lang/msl/validate/BUILD.gn +++ b/src/tint/lang/msl/validate/BUILD.gn
@@ -44,29 +44,9 @@ "validate.h", ] deps = [ - "${tint_src_dir}/lang/core", - "${tint_src_dir}/lang/core/constant", - "${tint_src_dir}/lang/core/type", - "${tint_src_dir}/lang/wgsl", - "${tint_src_dir}/lang/wgsl/ast", - "${tint_src_dir}/lang/wgsl/features", - "${tint_src_dir}/lang/wgsl/program", - "${tint_src_dir}/lang/wgsl/sem", "${tint_src_dir}/utils/command", - "${tint_src_dir}/utils/containers", - "${tint_src_dir}/utils/diagnostic", "${tint_src_dir}/utils/file", - "${tint_src_dir}/utils/ice", - "${tint_src_dir}/utils/id", - "${tint_src_dir}/utils/macros", - "${tint_src_dir}/utils/math", - "${tint_src_dir}/utils/memory", - "${tint_src_dir}/utils/reflection", - "${tint_src_dir}/utils/result", - "${tint_src_dir}/utils/rtti", - "${tint_src_dir}/utils/symbol", "${tint_src_dir}/utils/text", - "${tint_src_dir}/utils/traits", ] if (tint_build_is_mac) {
diff --git a/src/tint/lang/msl/validate/validate.cc b/src/tint/lang/msl/validate/validate.cc index 2230f54..0c960ed 100644 --- a/src/tint/lang/msl/validate/validate.cc +++ b/src/tint/lang/msl/validate/validate.cc
@@ -27,8 +27,6 @@ #include "src/tint/lang/msl/validate/validate.h" -#include "src/tint/lang/wgsl/ast/module.h" -#include "src/tint/lang/wgsl/program/program.h" #include "src/tint/utils/command/command.h" #include "src/tint/utils/file/tmpfile.h"
diff --git a/src/tint/lang/msl/validate/validate.h b/src/tint/lang/msl/validate/validate.h index d7b8acf..df2108a 100644 --- a/src/tint/lang/msl/validate/validate.h +++ b/src/tint/lang/msl/validate/validate.h
@@ -31,13 +31,6 @@ #include <string> #include <utility> -#include "src/tint/lang/wgsl/ast/pipeline_stage.h" - -// Forward declarations -namespace tint { -class Program; -} // namespace tint - namespace tint::msl::validate { /// The version of MSL to validate against.
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc index e41330f..e44d1a6 100644 --- a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc +++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
@@ -73,42 +73,32 @@ // Replace the builtins that we found. for (auto* builtin : worklist) { - core::ir::Value* replacement = nullptr; switch (builtin->Func()) { case core::BuiltinFn::kStorageBarrier: - replacement = ThreadgroupBarrier(builtin, BarrierType::kDevice); + ThreadgroupBarrier(builtin, BarrierType::kDevice); break; case core::BuiltinFn::kWorkgroupBarrier: - replacement = ThreadgroupBarrier(builtin, BarrierType::kThreadGroup); + ThreadgroupBarrier(builtin, BarrierType::kThreadGroup); break; case core::BuiltinFn::kTextureBarrier: - replacement = ThreadgroupBarrier(builtin, BarrierType::kTexture); + ThreadgroupBarrier(builtin, BarrierType::kTexture); break; default: break; } - TINT_ASSERT(replacement); - - // Replace the old builtin result with the new value. - if (auto name = ir.NameOf(builtin->Result(0))) { - ir.SetName(replacement, name); - } - builtin->Result(0)->ReplaceAllUsesWith(replacement); - builtin->Destroy(); } } /// Replace a barrier builtin with the `threadgroupBarrier()` intrinsic. /// @param builtin the builtin call instruction /// @param type the barrier type - /// @returns the replacement value - core::ir::Value* ThreadgroupBarrier(core::ir::CoreBuiltinCall* builtin, BarrierType type) { + void ThreadgroupBarrier(core::ir::CoreBuiltinCall* builtin, BarrierType type) { // Replace the builtin call with a call to the msl.threadgroup_barrier intrinsic. auto args = Vector<core::ir::Value*, 1>{b.Constant(u32(type))}; - auto* call = b.Call<msl::ir::BuiltinCall>( - builtin->Result(0)->Type(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args)); + auto* call = b.CallWithResult<msl::ir::BuiltinCall>( + builtin->DetachResult(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args)); call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } };
diff --git a/src/tint/lang/spirv/reader/lower/BUILD.bazel b/src/tint/lang/spirv/reader/lower/BUILD.bazel index 01fd572..1348c8d 100644 --- a/src/tint/lang/spirv/reader/lower/BUILD.bazel +++ b/src/tint/lang/spirv/reader/lower/BUILD.bazel
@@ -40,10 +40,12 @@ name = "lower", srcs = [ "lower.cc", + "shader_io.cc", "vector_element_pointer.cc", ], hdrs = [ "lower.h", + "shader_io.h", "vector_element_pointer.h", ], deps = [ @@ -52,6 +54,7 @@ "//src/tint/lang/core/constant", "//src/tint/lang/core/intrinsic", "//src/tint/lang/core/ir", + "//src/tint/lang/core/ir/transform/common", "//src/tint/lang/core/type", "//src/tint/utils/containers", "//src/tint/utils/diagnostic", @@ -74,6 +77,7 @@ name = "test", alwayslink = True, srcs = [ + "shader_io_test.cc", "vector_element_pointer_test.cc", ], deps = [
diff --git a/src/tint/lang/spirv/reader/lower/BUILD.cmake b/src/tint/lang/spirv/reader/lower/BUILD.cmake index 5f0e866..98de660 100644 --- a/src/tint/lang/spirv/reader/lower/BUILD.cmake +++ b/src/tint/lang/spirv/reader/lower/BUILD.cmake
@@ -41,6 +41,8 @@ tint_add_target(tint_lang_spirv_reader_lower lib lang/spirv/reader/lower/lower.cc lang/spirv/reader/lower/lower.h + lang/spirv/reader/lower/shader_io.cc + lang/spirv/reader/lower/shader_io.h lang/spirv/reader/lower/vector_element_pointer.cc lang/spirv/reader/lower/vector_element_pointer.h ) @@ -51,6 +53,7 @@ tint_lang_core_constant tint_lang_core_intrinsic tint_lang_core_ir + tint_lang_core_ir_transform_common tint_lang_core_type tint_utils_containers tint_utils_diagnostic @@ -72,6 +75,7 @@ # Kind: test ################################################################################ tint_add_target(tint_lang_spirv_reader_lower_test test + lang/spirv/reader/lower/shader_io_test.cc lang/spirv/reader/lower/vector_element_pointer_test.cc )
diff --git a/src/tint/lang/spirv/reader/lower/BUILD.gn b/src/tint/lang/spirv/reader/lower/BUILD.gn index b5f0342..37c4709 100644 --- a/src/tint/lang/spirv/reader/lower/BUILD.gn +++ b/src/tint/lang/spirv/reader/lower/BUILD.gn
@@ -46,6 +46,8 @@ sources = [ "lower.cc", "lower.h", + "shader_io.cc", + "shader_io.h", "vector_element_pointer.cc", "vector_element_pointer.h", ] @@ -55,6 +57,7 @@ "${tint_src_dir}/lang/core/constant", "${tint_src_dir}/lang/core/intrinsic", "${tint_src_dir}/lang/core/ir", + "${tint_src_dir}/lang/core/ir/transform/common", "${tint_src_dir}/lang/core/type", "${tint_src_dir}/utils/containers", "${tint_src_dir}/utils/diagnostic", @@ -73,7 +76,10 @@ } if (tint_build_unittests) { tint_unittests_source_set("unittests") { - sources = [ "vector_element_pointer_test.cc" ] + sources = [ + "shader_io_test.cc", + "vector_element_pointer_test.cc", + ] deps = [ "${tint_src_dir}:gmock_and_gtest", "${tint_src_dir}/api/common",
diff --git a/src/tint/lang/spirv/reader/lower/lower.cc b/src/tint/lang/spirv/reader/lower/lower.cc index 25f546c..433d0cd 100644 --- a/src/tint/lang/spirv/reader/lower/lower.cc +++ b/src/tint/lang/spirv/reader/lower/lower.cc
@@ -28,6 +28,7 @@ #include "src/tint/lang/spirv/reader/lower/lower.h" #include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/spirv/reader/lower/shader_io.h" #include "src/tint/lang/spirv/reader/lower/vector_element_pointer.h" namespace tint::spirv::reader { @@ -42,6 +43,7 @@ } while (false) RUN_TRANSFORM(lower::VectorElementPointer, mod); + RUN_TRANSFORM(lower::ShaderIO, mod); if (auto res = core::ir::ValidateAndDumpIfNeeded(mod, "end of lowering from SPIR-V"); res != Success) {
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc new file mode 100644 index 0000000..a786791 --- /dev/null +++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -0,0 +1,426 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/spirv/reader/lower/shader_io.h" + +#include <utility> + +#include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/transform/common/referenced_module_vars.h" +#include "src/tint/lang/core/ir/validator.h" + +namespace tint::spirv::reader::lower { + +namespace { + +using namespace tint::core::fluent_types; // NOLINT + +/// PIMPL state for the transform. +struct State { + /// The IR module. + core::ir::Module& ir; + + /// The IR builder. + core::ir::Builder b{ir}; + + /// The type manager. + core::type::Manager& ty{ir.Types()}; + + /// A map from block to its containing function. + Hashmap<core::ir::Block*, core::ir::Function*, 64> block_to_function{}; + + /// A map from each function to a map from input variable to parameter. + Hashmap<core::ir::Function*, Hashmap<core::ir::Var*, core::ir::Value*, 4>, 8> + function_parameter_map{}; + + /// The set of output variables that have been processed. + Hashset<core::ir::Var*, 4> output_variables{}; + + /// The mapping from functions to their transitively referenced output variables. + core::ir::ReferencedModuleVars referenced_output_vars{ + ir, [](const core::ir::Var* var) { + auto* view = var->Result(0)->Type()->As<core::type::MemoryView>(); + return view && view->AddressSpace() == core::AddressSpace::kOut; + }}; + + /// Process the module. + void Process() { + // Process outputs first, as that may introduce new functions that input variables need to + // be propagated through. + ProcessOutputs(); + ProcessInputs(); + } + + /// Process output variables. + /// Changes output variables to the `private` address space and wraps entry points that produce + /// outputs with new functions that copy the outputs from the private variables to the return + /// value. + void ProcessOutputs() { + // Update entry point functions to return their outputs, using a wrapper function. + // Use a worklist as `ProcessEntryPointOutputs()` will add new functions. + Vector<core::ir::Function*, 4> entry_points; + for (auto& func : ir.functions) { + if (func->Stage() != core::ir::Function::PipelineStage::kUndefined) { + entry_points.Push(func); + } + } + for (auto& ep : entry_points) { + ProcessEntryPointOutputs(ep); + } + + // Remove attributes from all of the original structs and module-scope output variables. + // This is done last as we need to copy attributes during `ProcessEntryPointOutputs()`. + for (auto& var : output_variables) { + var->SetAttributes({}); + if (auto* str = var->Result(0)->Type()->UnwrapPtr()->As<core::type::Struct>()) { + for (auto* member : str->Members()) { + // TODO(crbug.com/tint/745): Remove the const_cast. + const_cast<core::type::StructMember*>(member)->SetAttributes({}); + } + } + } + } + + /// Process input variables. + /// Pass inputs down the call stack as parameters to any functions that need them. + void ProcessInputs() { + // Seed the block-to-function map with the function entry blocks. + for (auto& func : ir.functions) { + block_to_function.Add(func->Block(), func); + } + + // Gather the list of all module-scope input variables. + Vector<core::ir::Var*, 4> inputs; + for (auto* global : *ir.root_block) { + if (auto* var = global->As<core::ir::Var>()) { + auto addrspace = var->Result(0)->Type()->As<core::type::Pointer>()->AddressSpace(); + if (addrspace == core::AddressSpace::kIn) { + inputs.Push(var); + } + } + } + + // Replace the input variables with function parameters. + for (auto* var : inputs) { + ReplaceInputPointerUses(var, var->Result(0)); + var->Destroy(); + } + } + + /// Replace an output pointer address space to make it `private`. + /// @param value the output variable + void ReplaceOutputPointerAddressSpace(core::ir::InstructionResult* value) { + // Change the address space to `private`. + auto* old_ptr_type = value->Type(); + auto* new_ptr_type = ty.ptr(core::AddressSpace::kPrivate, old_ptr_type->UnwrapPtr()); + value->SetType(new_ptr_type); + + // Update all uses of the module-scope variable. + value->ForEachUse([&](core::ir::Usage use) { + if (auto* access = use.instruction->As<core::ir::Access>()) { + ReplaceOutputPointerAddressSpace(access->Result(0)); + } else if (!use.instruction->IsAnyOf<core::ir::Load, core::ir::LoadVectorElement, + core::ir::Store, core::ir::StoreVectorElement>()) { + TINT_UNREACHABLE() + << "unexpected instruction: " << use.instruction->TypeInfo().name; + } + }); + } + + /// Process the outputs of an entry point function, adding a wrapper function to forward outputs + /// through the return value. + /// @param ep the entry point + void ProcessEntryPointOutputs(core::ir::Function* ep) { + const auto& referenced_outputs = referenced_output_vars.TransitiveReferences(ep); + if (referenced_outputs.IsEmpty()) { + return; + } + + // Add a wrapper function to return either a single value or a struct. + auto* wrapper = b.Function(ty.void_(), ep->Stage()); + if (auto name = ir.NameOf(ep)) { + ir.SetName(ep, name.Name() + "_inner"); + ir.SetName(wrapper, name); + } + + // Call the original entry point and make it a regular function. + ep->SetStage(core::ir::Function::PipelineStage::kUndefined); + b.Append(wrapper->Block(), [&] { // + b.Call(ep); + }); + + // Collect all outputs into a list of struct member declarations. + // Also add instructions to load their final values in the wrapper function. + Vector<core::ir::Value*, 4> results; + Vector<core::type::Manager::StructMemberDesc, 4> output_descriptors; + auto add_output = [&](Symbol name, const core::type::Type* type, + core::type::StructMemberAttributes attributes) { + if (!name) { + name = ir.symbols.New(); + } + output_descriptors.Push(core::type::Manager::StructMemberDesc{name, type, attributes}); + }; + for (auto* var : referenced_outputs) { + // Change the address space of the variable to private and update its uses, if we + // haven't already seen this variable. + if (output_variables.Add(var)) { + ReplaceOutputPointerAddressSpace(var->Result(0)); + } + + // Copy the variable attributes to the struct member. + const auto& original_attributes = var->Attributes(); + core::type::StructMemberAttributes var_attributes; + var_attributes.invariant = original_attributes.invariant; + var_attributes.builtin = original_attributes.builtin; + var_attributes.location = original_attributes.location; + var_attributes.interpolation = original_attributes.interpolation; + + auto var_type = var->Result(0)->Type()->UnwrapPtr(); + if (auto* str = var_type->As<core::type::Struct>()) { + // Add an output for each member of the struct. + for (auto* member : str->Members()) { + // Use the base variable attributes if not specified directly on the member. + auto member_attributes = member->Attributes(); + if (auto base_loc = var_attributes.location) { + // Location values increment from the base location value on the variable. + member_attributes.location = base_loc.value() + member->Index(); + } + if (!member_attributes.interpolation) { + member_attributes.interpolation = var_attributes.interpolation; + } + + add_output(member->Name(), member->Type(), std::move(member_attributes)); + + // Load the final result from the member of the original struct variable. + b.Append(wrapper->Block(), [&] { // + auto* access = + b.Access(ty.ptr<private_>(member->Type()), var, u32(member->Index())); + results.Push(b.Load(access)->Result(0)); + }); + } + } else { + // Load the final result from the original variable. + b.Append(wrapper->Block(), [&] { + results.Push(b.Load(var)->Result(0)); + + // If we're dealing with sample_mask, extract the scalar from the array. + if (var_attributes.builtin == core::BuiltinValue::kSampleMask) { + var_type = ty.u32(); + results.Back() = b.Access(ty.u32(), results.Back(), u32(0))->Result(0); + } + }); + add_output(ir.NameOf(var), var_type, std::move(var_attributes)); + } + } + + if (output_descriptors.Length() == 1) { + // Copy the output attributes to the function return. + const auto& attributes = output_descriptors[0].attributes; + wrapper->SetReturnInvariant(attributes.invariant); + if (attributes.builtin) { + wrapper->SetReturnBuiltin(attributes.builtin.value()); + } else if (attributes.location) { + core::ir::Location loc; + loc.value = attributes.location.value(); + loc.interpolation = attributes.interpolation; + wrapper->SetReturnLocation(std::move(loc)); + } + + // Return the output from the wrapper function. + wrapper->SetReturnType(output_descriptors[0].type); + b.Append(wrapper->Block(), [&] { // + b.Return(wrapper, results[0]); + }); + } else { + // Create a struct to hold all of the output values. + auto* str = ty.Struct(ir.symbols.New(), std::move(output_descriptors)); + wrapper->SetReturnType(str); + + // Collect the output values and return them from the wrapper function. + b.Append(wrapper->Block(), [&] { // + b.Return(wrapper, b.Construct(str, std::move(results))); + }); + } + } + + /// Replace a use of an input pointer value. + /// @param var the originating input variable + /// @param value the input pointer value + void ReplaceInputPointerUses(core::ir::Var* var, core::ir::Value* value) { + Vector<core::ir::Instruction*, 8> to_destroy; + value->ForEachUse([&](core::ir::Usage use) { + auto* object = value; + if (object->Type()->Is<core::type::Pointer>()) { + // Get (or create) the function parameter that will replace the variable. + auto* func = ContainingFunction(use.instruction); + object = GetParameter(func, var); + } + + Switch( + use.instruction, + [&](core::ir::Load* l) { + // Fold the load away and replace its uses with the new parameter. + l->Result(0)->ReplaceAllUsesWith(object); + to_destroy.Push(l); + }, + [&](core::ir::LoadVectorElement* lve) { + // Replace the vector element load with an access instruction. + auto* access = b.AccessWithResult(lve->DetachResult(), object, lve->Index()); + access->InsertBefore(lve); + to_destroy.Push(lve); + }, + [&](core::ir::Access* a) { + if (!a->Indices().IsEmpty()) { + // Remove the pointer from the source and destination type. + a->SetOperand(core::ir::Access::kObjectOperandOffset, object); + a->Result(0)->SetType(a->Result(0)->Type()->UnwrapPtr()); + ReplaceInputPointerUses(var, a->Result(0)); + } else { + // Fold the access away and replace its uses. + ReplaceInputPointerUses(var, a->Result(0)); + to_destroy.Push(a); + } + }, + TINT_ICE_ON_NO_MATCH); + }); + + // Clean up orphaned instructions. + for (auto* inst : to_destroy) { + inst->Destroy(); + } + } + + /// Get the function that contains an instruction. + /// @param inst the instruction + /// @returns the function + core::ir::Function* ContainingFunction(core::ir::Instruction* inst) { + return block_to_function.GetOrAdd(inst->Block(), [&] { // + return ContainingFunction(inst->Block()->Parent()); + }); + } + + /// Get or create a function parameter to replace a module-scope variable. + /// @param func the function + /// @param var the module-scope variable + /// @returns the function parameter + core::ir::Value* GetParameter(core::ir::Function* func, core::ir::Var* var) { + return function_parameter_map.GetOrAddZero(func).GetOrAdd(var, [&] { + const bool entry_point = func->Stage() != core::ir::Function::PipelineStage::kUndefined; + auto* var_type = var->Result(0)->Type()->UnwrapPtr(); + + // Use a scalar u32 for sample_mask builtins for entry point parameters. + if (entry_point && var->Attributes().builtin == core::BuiltinValue::kSampleMask) { + TINT_ASSERT(var_type->Is<core::type::Array>()); + TINT_ASSERT(var_type->As<core::type::Array>()->ConstantCount() == 1u); + var_type = ty.u32(); + } + + // Create a new function parameter for the input. + auto* param = b.FunctionParam(var_type); + func->AppendParam(param); + if (auto name = ir.NameOf(var)) { + ir.SetName(param, name); + } + + // Add attributes to the parameter if this is an entry point function. + if (entry_point) { + AddEntryPointParameterAttributes(param, var->Attributes()); + } + + // Update the callsites of this function. + func->ForEachUse([&](core::ir::Usage use) { + if (auto* call = use.instruction->As<core::ir::UserCall>()) { + // Recurse into the calling function. + auto* caller = ContainingFunction(call); + call->AppendArg(GetParameter(caller, var)); + } else if (!use.instruction->Is<core::ir::Return>()) { + TINT_UNREACHABLE() + << "unexpected instruction: " << use.instruction->TypeInfo().name; + } + }); + + core::ir::Value* result = param; + if (entry_point && var->Attributes().builtin == core::BuiltinValue::kSampleMask) { + // Construct an array from the scalar sample_mask builtin value for entry points. + b.Prepend(func->Block(), [&] { // + result = b.Construct(var->Result(0)->Type()->UnwrapPtr(), param)->Result(0); + }); + } + return result; + }); + } + + /// Add attributes to an entry point function parameter. + /// @param param the parameter + /// @param attributes the attributes + void AddEntryPointParameterAttributes(core::ir::FunctionParam* param, + const core::ir::IOAttributes& attributes) { + if (auto* str = param->Type()->UnwrapPtr()->As<core::type::Struct>()) { + for (auto* member : str->Members()) { + // Use the base variable attributes if not specified directly on the member. + auto member_attributes = member->Attributes(); + if (auto base_loc = attributes.location) { + // Location values increment from the base location value on the variable. + member_attributes.location = base_loc.value() + member->Index(); + } + if (!member_attributes.interpolation) { + member_attributes.interpolation = attributes.interpolation; + } + // TODO(crbug.com/tint/745): Remove the const_cast. + const_cast<core::type::StructMember*>(member)->SetAttributes( + std::move(member_attributes)); + } + } else { + // Set attributes directly on the function parameter. + param->SetInvariant(attributes.invariant); + if (attributes.builtin) { + param->SetBuiltin(attributes.builtin.value()); + } else if (attributes.location) { + core::ir::Location loc; + loc.value = attributes.location.value(); + loc.interpolation = attributes.interpolation; + param->SetLocation(std::move(loc)); + } + } + } +}; + +} // namespace + +Result<SuccessType> ShaderIO(core::ir::Module& ir) { + auto result = ValidateAndDumpIfNeeded(ir, "ShaderIO transform"); + if (result != Success) { + return result.Failure(); + } + + State{ir}.Process(); + + return Success; +} + +} // namespace tint::spirv::reader::lower
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.h b/src/tint/lang/spirv/reader/lower/shader_io.h new file mode 100644 index 0000000..8347585 --- /dev/null +++ b/src/tint/lang/spirv/reader/lower/shader_io.h
@@ -0,0 +1,48 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SRC_TINT_LANG_SPIRV_READER_LOWER_SHADER_IO_H_ +#define SRC_TINT_LANG_SPIRV_READER_LOWER_SHADER_IO_H_ + +#include "src/tint/utils/result/result.h" + +// Forward declarations. +namespace tint::core::ir { +class Module; +} + +namespace tint::spirv::reader::lower { + +/// ShaderIO is a transform that converts SPIR-V's style of shader IO (using global variables) into +/// the form expected by Tint's core IR (using function parameters and return values). +/// @param module the module to transform +/// @returns success or failure +Result<SuccessType> ShaderIO(core::ir::Module& module); + +} // namespace tint::spirv::reader::lower + +#endif // SRC_TINT_LANG_SPIRV_READER_LOWER_SHADER_IO_H_
diff --git a/src/tint/lang/spirv/reader/lower/shader_io_test.cc b/src/tint/lang/spirv/reader/lower/shader_io_test.cc new file mode 100644 index 0000000..d004762 --- /dev/null +++ b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
@@ -0,0 +1,2267 @@ +// Copyright 2024 The Dawn & Tint Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "src/tint/lang/spirv/reader/lower/shader_io.h" + +#include <utility> + +#include "src/tint/lang/core/ir/transform/helper_test.h" + +namespace tint::spirv::reader::lower { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +class SpirvReader_ShaderIOTest : public core::ir::transform::TransformTest { + protected: + core::type::StructMemberAttributes BuiltinAttrs(core::BuiltinValue builtin) { + core::type::StructMemberAttributes attrs; + attrs.builtin = builtin; + return attrs; + } + core::type::StructMemberAttributes LocationAttrs( + uint32_t location, + std::optional<core::Interpolation> interpolation = std::nullopt) { + core::type::StructMemberAttributes attrs; + attrs.location = location; + attrs.interpolation = interpolation; + return attrs; + } +}; + +TEST_F(SpirvReader_ShaderIOTest, NoInputsOrOutputs) { + auto* ep = b.Function("foo", ty.void_()); + ep->SetStage(core::ir::Function::PipelineStage::kCompute); + + b.Append(ep->Block(), [&] { // + b.Return(ep); + }); + + auto* src = R"( +%foo = @compute func():void { + $B1: { + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = src; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs) { + auto* front_facing = b.Var("front_facing", ty.ptr(core::AddressSpace::kIn, ty.bool_())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kFrontFacing; + front_facing->SetAttributes(std::move(attributes)); + } + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kIn, ty.f32())); + { + core::ir::IOAttributes attributes; + attributes.location = 0; + color1->SetAttributes(std::move(attributes)); + } + auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kIn, ty.f32())); + { + core::ir::IOAttributes attributes; + attributes.location = 1; + attributes.interpolation = core::Interpolation{core::InterpolationType::kLinear, + core::InterpolationSampling::kSample}; + color2->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(front_facing); + mod.root_block->Append(position); + mod.root_block->Append(color1); + mod.root_block->Append(color2); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + auto* ifelse = b.If(b.Load(front_facing)); + b.Append(ifelse->True(), [&] { + auto* position_value = b.Load(position); + auto* color1_value = b.Load(color1); + auto* color2_value = b.Load(color2); + b.Multiply(ty.vec4<f32>(), position_value, b.Add(ty.f32(), color1_value, color2_value)); + b.ExitIf(ifelse); + }); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %front_facing:ptr<__in, bool, read> = var @builtin(front_facing) + %position:ptr<__in, vec4<f32>, read> = var @invariant @builtin(position) + %color1:ptr<__in, f32, read> = var @location(0) + %color2:ptr<__in, f32, read> = var @location(1) @interpolate(linear, sample) +} + +%foo = @fragment func():void { + $B2: { + %6:bool = load %front_facing + if %6 [t: $B3] { # if_1 + $B3: { # true + %7:vec4<f32> = load %position + %8:f32 = load %color1 + %9:f32 = load %color2 + %10:f32 = add %8, %9 + %11:vec4<f32> = mul %7, %10 + exit_if # if_1 + } + } + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = @fragment func(%front_facing:bool [@front_facing], %position:vec4<f32> [@invariant, @position], %color1:f32 [@location(0)], %color2:f32 [@location(1), @interpolate(linear, sample)]):void { + $B1: { + if %front_facing [t: $B2] { # if_1 + $B2: { # true + %6:f32 = add %color1, %color2 + %7:vec4<f32> = mul %position, %6 + exit_if # if_1 + } + } + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedByHelper) { + auto* front_facing = b.Var("front_facing", ty.ptr(core::AddressSpace::kIn, ty.bool_())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kFrontFacing; + front_facing->SetAttributes(std::move(attributes)); + } + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kIn, ty.f32())); + { + core::ir::IOAttributes attributes; + attributes.location = 0; + color1->SetAttributes(std::move(attributes)); + } + auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kIn, ty.f32())); + { + core::ir::IOAttributes attributes; + attributes.location = 1; + attributes.interpolation = core::Interpolation{core::InterpolationType::kLinear, + core::InterpolationSampling::kSample}; + color2->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(front_facing); + mod.root_block->Append(position); + mod.root_block->Append(color1); + mod.root_block->Append(color2); + + // Inner function has an existing parameter. + auto* param = b.FunctionParam("existing_param", ty.f32()); + auto* foo = b.Function("foo", ty.void_()); + foo->SetParams({param}); + b.Append(foo->Block(), [&] { + auto* ifelse = b.If(b.Load(front_facing)); + b.Append(ifelse->True(), [&] { + auto* position_value = b.Load(position); + auto* color1_value = b.Load(color1); + auto* color2_value = b.Load(color2); + auto* add = b.Add(ty.f32(), color1_value, color2_value); + auto* mul = b.Multiply(ty.vec4<f32>(), position_value, add); + b.Divide(ty.vec4<f32>(), mul, param); + b.ExitIf(ifelse); + }); + b.Return(foo); + }); + + // Intermediate function has no existing parameters. + auto* bar = b.Function("bar", ty.void_()); + b.Append(bar->Block(), [&] { + b.Call(foo, 42_f); + b.Return(bar); + }); + + auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + b.Call(bar); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %front_facing:ptr<__in, bool, read> = var @builtin(front_facing) + %position:ptr<__in, vec4<f32>, read> = var @invariant @builtin(position) + %color1:ptr<__in, f32, read> = var @location(0) + %color2:ptr<__in, f32, read> = var @location(1) @interpolate(linear, sample) +} + +%foo = func(%existing_param:f32):void { + $B2: { + %7:bool = load %front_facing + if %7 [t: $B3] { # if_1 + $B3: { # true + %8:vec4<f32> = load %position + %9:f32 = load %color1 + %10:f32 = load %color2 + %11:f32 = add %9, %10 + %12:vec4<f32> = mul %8, %11 + %13:vec4<f32> = div %12, %existing_param + exit_if # if_1 + } + } + ret + } +} +%bar = func():void { + $B4: { + %15:void = call %foo, 42.0f + ret + } +} +%main = @fragment func():void { + $B5: { + %17:void = call %bar + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = func(%existing_param:f32, %front_facing:bool, %position:vec4<f32>, %color1:f32, %color2:f32):void { + $B1: { + if %front_facing [t: $B2] { # if_1 + $B2: { # true + %7:f32 = add %color1, %color2 + %8:vec4<f32> = mul %position, %7 + %9:vec4<f32> = div %8, %existing_param + exit_if # if_1 + } + } + ret + } +} +%bar = func(%front_facing_1:bool, %position_1:vec4<f32>, %color1_1:f32, %color2_1:f32):void { # %front_facing_1: 'front_facing', %position_1: 'position', %color1_1: 'color1', %color2_1: 'color2' + $B3: { + %15:void = call %foo, 42.0f, %front_facing_1, %position_1, %color1_1, %color2_1 + ret + } +} +%main = @fragment func(%front_facing_2:bool [@front_facing], %position_2:vec4<f32> [@invariant, @position], %color1_2:f32 [@location(0)], %color2_2:f32 [@location(1), @interpolate(linear, sample)]):void { # %front_facing_2: 'front_facing', %position_2: 'position', %color1_2: 'color1', %color2_2: 'color2' + $B4: { + %21:void = call %bar, %front_facing_2, %position_2, %color1_2, %color2_2 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedEntryPointAndHelper) { + auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kGlobalInvocationId; + gid->SetAttributes(std::move(attributes)); + } + auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kLocalInvocationId; + lid->SetAttributes(std::move(attributes)); + } + auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kWorkgroupId; + group_id->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(gid); + mod.root_block->Append(lid); + mod.root_block->Append(group_id); + + // Use a subset of the inputs in the helper. + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* gid_value = b.Load(gid); + auto* lid_value = b.Load(lid); + b.Add(ty.vec3<u32>(), gid_value, lid_value); + b.Return(foo); + }); + + // Use a different subset of the inputs in the entry point. + auto* ep = b.Function("main1", ty.void_(), core::ir::Function::PipelineStage::kCompute); + b.Append(ep->Block(), [&] { + auto* group_value = b.Load(group_id); + auto* gid_value = b.Load(gid); + b.Add(ty.vec3<u32>(), group_value, gid_value); + b.Call(foo); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %gid:ptr<__in, vec3<u32>, read> = var @builtin(global_invocation_id) + %lid:ptr<__in, vec3<u32>, read> = var @builtin(local_invocation_id) + %group_id:ptr<__in, vec3<u32>, read> = var @builtin(workgroup_id) +} + +%foo = func():void { + $B2: { + %5:vec3<u32> = load %gid + %6:vec3<u32> = load %lid + %7:vec3<u32> = add %5, %6 + ret + } +} +%main1 = @compute func():void { + $B3: { + %9:vec3<u32> = load %group_id + %10:vec3<u32> = load %gid + %11:vec3<u32> = add %9, %10 + %12:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = func(%gid:vec3<u32>, %lid:vec3<u32>):void { + $B1: { + %4:vec3<u32> = add %gid, %lid + ret + } +} +%main1 = @compute func(%gid_1:vec3<u32> [@global_invocation_id], %lid_1:vec3<u32> [@local_invocation_id], %group_id:vec3<u32> [@workgroup_id]):void { # %gid_1: 'gid', %lid_1: 'lid' + $B2: { + %9:vec3<u32> = add %group_id, %gid_1 + %10:void = call %foo, %gid_1, %lid_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedEntryPointAndHelper_ForwardReference) { + auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kGlobalInvocationId; + gid->SetAttributes(std::move(attributes)); + } + auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kLocalInvocationId; + lid->SetAttributes(std::move(attributes)); + } + auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kWorkgroupId; + group_id->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(gid); + mod.root_block->Append(lid); + mod.root_block->Append(group_id); + + auto* ep = b.Function("main1", ty.void_(), core::ir::Function::PipelineStage::kCompute); + auto* foo = b.Function("foo", ty.void_()); + + // Use a subset of the inputs in the entry point. + b.Append(ep->Block(), [&] { + auto* group_value = b.Load(group_id); + auto* gid_value = b.Load(gid); + b.Add(ty.vec3<u32>(), group_value, gid_value); + b.Call(foo); + b.Return(ep); + }); + + // Use a different subset of the variables in the helper. + b.Append(foo->Block(), [&] { + auto* gid_value = b.Load(gid); + auto* lid_value = b.Load(lid); + b.Add(ty.vec3<u32>(), gid_value, lid_value); + b.Return(foo); + }); + + auto* src = R"( +$B1: { # root + %gid:ptr<__in, vec3<u32>, read> = var @builtin(global_invocation_id) + %lid:ptr<__in, vec3<u32>, read> = var @builtin(local_invocation_id) + %group_id:ptr<__in, vec3<u32>, read> = var @builtin(workgroup_id) +} + +%main1 = @compute func():void { + $B2: { + %5:vec3<u32> = load %group_id + %6:vec3<u32> = load %gid + %7:vec3<u32> = add %5, %6 + %8:void = call %foo + ret + } +} +%foo = func():void { + $B3: { + %10:vec3<u32> = load %gid + %11:vec3<u32> = load %lid + %12:vec3<u32> = add %10, %11 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%main1 = @compute func(%gid:vec3<u32> [@global_invocation_id], %lid:vec3<u32> [@local_invocation_id], %group_id:vec3<u32> [@workgroup_id]):void { + $B1: { + %5:vec3<u32> = add %group_id, %gid + %6:void = call %foo, %gid, %lid + ret + } +} +%foo = func(%gid_1:vec3<u32>, %lid_1:vec3<u32>):void { # %gid_1: 'gid', %lid_1: 'lid' + $B2: { + %10:vec3<u32> = add %gid_1, %lid_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedByMultipleEntryPoints) { + auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kGlobalInvocationId; + gid->SetAttributes(std::move(attributes)); + } + auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kLocalInvocationId; + lid->SetAttributes(std::move(attributes)); + } + auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kWorkgroupId; + group_id->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(gid); + mod.root_block->Append(lid); + mod.root_block->Append(group_id); + + // Use a subset of the inputs in the helper. + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* gid_value = b.Load(gid); + auto* lid_value = b.Load(lid); + b.Add(ty.vec3<u32>(), gid_value, lid_value); + b.Return(foo); + }); + + // Call the helper without directly referencing any inputs. + auto* ep1 = b.Function("main1", ty.void_(), core::ir::Function::PipelineStage::kCompute); + b.Append(ep1->Block(), [&] { + b.Call(foo); + b.Return(ep1); + }); + + // Reference another input and then call the helper. + auto* ep2 = b.Function("main2", ty.void_(), core::ir::Function::PipelineStage::kCompute); + b.Append(ep2->Block(), [&] { + auto* group_value = b.Load(group_id); + b.Add(ty.vec3<u32>(), group_value, group_value); + b.Call(foo); + b.Return(ep1); + }); + + auto* src = R"( +$B1: { # root + %gid:ptr<__in, vec3<u32>, read> = var @builtin(global_invocation_id) + %lid:ptr<__in, vec3<u32>, read> = var @builtin(local_invocation_id) + %group_id:ptr<__in, vec3<u32>, read> = var @builtin(workgroup_id) +} + +%foo = func():void { + $B2: { + %5:vec3<u32> = load %gid + %6:vec3<u32> = load %lid + %7:vec3<u32> = add %5, %6 + ret + } +} +%main1 = @compute func():void { + $B3: { + %9:void = call %foo + ret + } +} +%main2 = @compute func():void { + $B4: { + %11:vec3<u32> = load %group_id + %12:vec3<u32> = add %11, %11 + %13:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = func(%gid:vec3<u32>, %lid:vec3<u32>):void { + $B1: { + %4:vec3<u32> = add %gid, %lid + ret + } +} +%main1 = @compute func(%gid_1:vec3<u32> [@global_invocation_id], %lid_1:vec3<u32> [@local_invocation_id]):void { # %gid_1: 'gid', %lid_1: 'lid' + $B2: { + %8:void = call %foo, %gid_1, %lid_1 + ret + } +} +%main2 = @compute func(%gid_2:vec3<u32> [@global_invocation_id], %lid_2:vec3<u32> [@local_invocation_id], %group_id:vec3<u32> [@workgroup_id]):void { # %gid_2: 'gid', %lid_2: 'lid' + $B3: { + %13:vec3<u32> = add %group_id, %group_id + %14:void = call %foo, %gid_2, %lid_2 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Input_LoadVectorElement) { + auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kLocalInvocationId; + lid->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(lid); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kCompute); + b.Append(ep->Block(), [&] { + b.LoadVectorElement(lid, 2_u); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %lid:ptr<__in, vec3<u32>, read> = var @builtin(local_invocation_id) +} + +%foo = @compute func():void { + $B2: { + %3:u32 = load_vector_element %lid, 2u + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = @compute func(%lid:vec3<u32> [@local_invocation_id]):void { + $B1: { + %3:u32 = access %lid, 2u + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Input_AccessChains) { + auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kLocalInvocationId; + lid->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(lid); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kCompute); + b.Append(ep->Block(), [&] { + auto* access_1 = b.Access(ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()), lid); + auto* access_2 = b.Access(ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()), access_1); + auto* vec = b.Load(access_2); + auto* z = b.LoadVectorElement(access_2, 2_u); + b.Multiply<vec3<u32>>(vec, z); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %lid:ptr<__in, vec3<u32>, read> = var @builtin(local_invocation_id) +} + +%foo = @compute func():void { + $B2: { + %3:ptr<__in, vec3<u32>, read> = access %lid + %4:ptr<__in, vec3<u32>, read> = access %3 + %5:vec3<u32> = load %4 + %6:u32 = load_vector_element %4, 2u + %7:vec3<u32> = mul %5, %6 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +%foo = @compute func(%lid:vec3<u32> [@local_invocation_id]):void { + $B1: { + %3:u32 = access %lid, 2u + %4:vec3<u32> = mul %lid, %3 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_Struct_LocationOnEachMember) { + auto* colors_str = ty.Struct( + mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + LocationAttrs(1), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + LocationAttrs(2u, core::Interpolation{core::InterpolationType::kLinear, + core::InterpolationSampling::kCentroid}), + }, + }); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str)); + mod.root_block->Append(colors); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* ptr = ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()); + auto* color1_value = b.Load(b.Access(ptr, colors, 0_u)); + auto* color2_z_value = b.LoadVectorElement(b.Access(ptr, colors, 1_u), 2_u); + b.Multiply(ty.vec4<f32>(), color1_value, color2_z_value); + b.Return(foo); + }); + + auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + b.Call(foo); + b.Return(ep); + }); + + auto* src = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, centroid) +} + +$B1: { # root + %colors:ptr<__in, Colors, read> = var +} + +%foo = func():void { + $B2: { + %3:ptr<__in, vec4<f32>, read> = access %colors, 0u + %4:vec4<f32> = load %3 + %5:ptr<__in, vec4<f32>, read> = access %colors, 1u + %6:f32 = load_vector_element %5, 2u + %7:vec4<f32> = mul %4, %6 + ret + } +} +%main = @fragment func():void { + $B3: { + %9:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, centroid) +} + +%foo = func(%colors:Colors):void { + $B1: { + %3:vec4<f32> = access %colors, 0u + %4:vec4<f32> = access %colors, 1u + %5:f32 = access %4, 2u + %6:vec4<f32> = mul %3, %5 + ret + } +} +%main = @fragment func(%colors_1:Colors):void { # %colors_1: 'colors' + $B2: { + %9:void = call %foo, %colors_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_Struct_LocationOnVariable) { + auto* colors_str = + ty.Struct(mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + core::type::StructMemberAttributes{ + /* location */ std::nullopt, + /* index */ std::nullopt, + /* color */ std::nullopt, + /* builtin */ std::nullopt, + /* interpolation */ + core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}, + /* invariant */ false, + }, + }, + }); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str)); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + colors->SetAttributes(attributes); + } + mod.root_block->Append(colors); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* ptr = ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()); + auto* color1_value = b.Load(b.Access(ptr, colors, 0_u)); + auto* color2_z_value = b.LoadVectorElement(b.Access(ptr, colors, 1_u), 2_u); + b.Multiply(ty.vec4<f32>(), color1_value, color2_z_value); + b.Return(foo); + }); + + auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + b.Call(foo); + b.Return(ep); + }); + + auto* src = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0) + color2:vec4<f32> @offset(16), @interpolate(perspective, centroid) +} + +$B1: { # root + %colors:ptr<__in, Colors, read> = var @location(1) +} + +%foo = func():void { + $B2: { + %3:ptr<__in, vec4<f32>, read> = access %colors, 0u + %4:vec4<f32> = load %3 + %5:ptr<__in, vec4<f32>, read> = access %colors, 1u + %6:f32 = load_vector_element %5, 2u + %7:vec4<f32> = mul %4, %6 + ret + } +} +%main = @fragment func():void { + $B3: { + %9:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(perspective, centroid) +} + +%foo = func(%colors:Colors):void { + $B1: { + %3:vec4<f32> = access %colors, 0u + %4:vec4<f32> = access %colors, 1u + %5:f32 = access %4, 2u + %6:vec4<f32> = mul %3, %5 + ret + } +} +%main = @fragment func(%colors_1:Colors):void { # %colors_1: 'colors' + $B2: { + %9:void = call %foo, %colors_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_Struct_InterpolateOnVariable) { + auto* colors_str = ty.Struct( + mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + LocationAttrs(1), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + LocationAttrs(2u, core::Interpolation{core::InterpolationType::kLinear, + core::InterpolationSampling::kSample}), + }, + }); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str)); + { + core::ir::IOAttributes attributes; + attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}; + colors->SetAttributes(attributes); + } + mod.root_block->Append(colors); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* ptr = ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()); + auto* color1_value = b.Load(b.Access(ptr, colors, 0_u)); + auto* color2_z_value = b.LoadVectorElement(b.Access(ptr, colors, 1_u), 2_u); + b.Multiply(ty.vec4<f32>(), color1_value, color2_z_value); + b.Return(foo); + }); + + auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + b.Call(foo); + b.Return(ep); + }); + + auto* src = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, sample) +} + +$B1: { # root + %colors:ptr<__in, Colors, read> = var @interpolate(perspective, centroid) +} + +%foo = func():void { + $B2: { + %3:ptr<__in, vec4<f32>, read> = access %colors, 0u + %4:vec4<f32> = load %3 + %5:ptr<__in, vec4<f32>, read> = access %colors, 1u + %6:f32 = load_vector_element %5, 2u + %7:vec4<f32> = mul %4, %6 + ret + } +} +%main = @fragment func():void { + $B3: { + %9:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1), @interpolate(perspective, centroid) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, sample) +} + +%foo = func(%colors:Colors):void { + $B1: { + %3:vec4<f32> = access %colors, 0u + %4:vec4<f32> = access %colors, 1u + %5:f32 = access %4, 2u + %6:vec4<f32> = mul %3, %5 + ret + } +} +%main = @fragment func(%colors_1:Colors):void { # %colors_1: 'colors' + $B2: { + %9:void = call %foo, %colors_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_Struct_LoadWholeStruct) { + auto* colors_str = ty.Struct( + mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + LocationAttrs(1), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + LocationAttrs(2u, core::Interpolation{core::InterpolationType::kLinear, + core::InterpolationSampling::kCentroid}), + }, + }); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str)); + mod.root_block->Append(colors); + + auto* foo = b.Function("foo", ty.void_()); + b.Append(foo->Block(), [&] { + auto* load = b.Load(colors); + auto* color1_value = b.Access<vec4<f32>>(load, 0_u); + auto* color2_z_value = b.Access<f32>(load, 1_u, 2_u); + b.Multiply(ty.vec4<f32>(), color1_value, color2_z_value); + b.Return(foo); + }); + + auto* ep = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + b.Call(foo); + b.Return(ep); + }); + + auto* src = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, centroid) +} + +$B1: { # root + %colors:ptr<__in, Colors, read> = var +} + +%foo = func():void { + $B2: { + %3:Colors = load %colors + %4:vec4<f32> = access %3, 0u + %5:f32 = access %3, 1u, 2u + %6:vec4<f32> = mul %4, %5 + ret + } +} +%main = @fragment func():void { + $B3: { + %8:void = call %foo + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(linear, centroid) +} + +%foo = func(%colors:Colors):void { + $B1: { + %3:vec4<f32> = access %colors, 0u + %4:f32 = access %colors, 1u, 2u + %5:vec4<f32> = mul %3, %4 + ret + } +} +%main = @fragment func(%colors_1:Colors):void { # %colors_1: 'colors' + $B2: { + %8:void = call %foo, %colors_1 + ret + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Builtin) { + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + position->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(position); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %position:ptr<__out, vec4<f32>, read_write> = var @builtin(position) +} + +%foo = @vertex func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %position:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +%foo = @vertex func():vec4<f32> [@position] { + $B3: { + %4:void = call %foo_inner + %5:vec4<f32> = load %position + ret %5 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Builtin_WithInvariant) { + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(position); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %position:ptr<__out, vec4<f32>, read_write> = var @invariant @builtin(position) +} + +%foo = @vertex func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %position:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +%foo = @vertex func():vec4<f32> [@invariant, @position] { + $B3: { + %4:void = call %foo_inner + %5:vec4<f32> = load %position + ret %5 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Location) { + auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(color); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { // + b.Store(color, b.Splat<vec4<f32>>(1_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %color:ptr<__out, vec4<f32>, read_write> = var @location(1) +} + +%foo = @fragment func():void { + $B2: { + store %color, vec4<f32>(1.0f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %color:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %color, vec4<f32>(1.0f) + ret + } +} +%foo = @fragment func():vec4<f32> [@location(1)] { + $B3: { + %4:void = call %foo_inner + %5:vec4<f32> = load %color + ret %5 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Location_WithInterpolation) { + auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}; + color->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(color); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { // + b.Store(color, b.Splat<vec4<f32>>(1_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %color:ptr<__out, vec4<f32>, read_write> = var @location(1) @interpolate(perspective, centroid) +} + +%foo = @fragment func():void { + $B2: { + store %color, vec4<f32>(1.0f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %color:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %color, vec4<f32>(1.0f) + ret + } +} +%foo = @fragment func():vec4<f32> [@location(1), @interpolate(perspective, centroid)] { + $B3: { + %4:void = call %foo_inner + %5:vec4<f32> = load %color + ret %5 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, MultipleOutputs) { + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color1->SetAttributes(std::move(attributes)); + } + auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}; + color2->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(position); + mod.root_block->Append(color1); + mod.root_block->Append(color2); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Store(color1, b.Splat<vec4<f32>>(0.5_f, 4)); + b.Store(color2, b.Splat<vec4<f32>>(0.25_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %position:ptr<__out, vec4<f32>, read_write> = var @invariant @builtin(position) + %color1:ptr<__out, vec4<f32>, read_write> = var @location(1) + %color2:ptr<__out, vec4<f32>, read_write> = var @location(1) @interpolate(perspective, centroid) +} + +%foo = @vertex func():void { + $B2: { + store %position, vec4<f32>(1.0f) + store %color1, vec4<f32>(0.5f) + store %color2, vec4<f32>(0.25f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +tint_symbol = struct @align(16) { + position:vec4<f32> @offset(0), @invariant, @builtin(position) + color1:vec4<f32> @offset(16), @location(1) + color2:vec4<f32> @offset(32), @location(1), @interpolate(perspective, centroid) +} + +$B1: { # root + %position:ptr<private, vec4<f32>, read_write> = var + %color1:ptr<private, vec4<f32>, read_write> = var + %color2:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %position, vec4<f32>(1.0f) + store %color1, vec4<f32>(0.5f) + store %color2, vec4<f32>(0.25f) + ret + } +} +%foo = @vertex func():tint_symbol { + $B3: { + %6:void = call %foo_inner + %7:vec4<f32> = load %position + %8:vec4<f32> = load %color1 + %9:vec4<f32> = load %color2 + %10:tint_symbol = construct %7, %8, %9 + ret %10 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Outputs_Struct_LocationOnEachMember) { + auto* builtin_str = + ty.Struct(mod.symbols.New("Builtins"), Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("position"), + ty.vec4<f32>(), + BuiltinAttrs(core::BuiltinValue::kPosition), + }, + }); + auto* colors_str = ty.Struct( + mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + LocationAttrs(1), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + LocationAttrs(2u, core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}), + }, + }); + + auto* builtins = b.Var("builtins", ty.ptr(core::AddressSpace::kOut, builtin_str)); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kOut, colors_str)); + mod.root_block->Append(builtins); + mod.root_block->Append(colors); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + auto* ptr = ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()); + b.Store(b.Access(ptr, builtins, 0_u), b.Splat<vec4<f32>>(1_f, 4)); + b.Store(b.Access(ptr, colors, 0_u), b.Splat<vec4<f32>>(0.5_f, 4)); + b.Store(b.Access(ptr, colors, 1_u), b.Splat<vec4<f32>>(0.25_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(1) + color2:vec4<f32> @offset(16), @location(2), @interpolate(perspective, centroid) +} + +$B1: { # root + %builtins:ptr<__out, Builtins, read_write> = var + %colors:ptr<__out, Colors, read_write> = var +} + +%foo = @vertex func():void { + $B2: { + %4:ptr<__out, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<__out, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<__out, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0) + color2:vec4<f32> @offset(16) +} + +tint_symbol = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) + color1:vec4<f32> @offset(16), @location(1) + color2:vec4<f32> @offset(32), @location(2), @interpolate(perspective, centroid) +} + +$B1: { # root + %builtins:ptr<private, Builtins, read_write> = var + %colors:ptr<private, Colors, read_write> = var +} + +%foo_inner = func():void { + $B2: { + %4:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<private, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<private, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +%foo = @vertex func():tint_symbol { + $B3: { + %8:void = call %foo_inner + %9:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + %10:vec4<f32> = load %9 + %11:ptr<private, vec4<f32>, read_write> = access %colors, 0u + %12:vec4<f32> = load %11 + %13:ptr<private, vec4<f32>, read_write> = access %colors, 1u + %14:vec4<f32> = load %13 + %15:tint_symbol = construct %10, %12, %14 + ret %15 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Outputs_Struct_LocationOnVariable) { + auto* builtin_str = + ty.Struct(mod.symbols.New("Builtins"), Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("position"), + ty.vec4<f32>(), + BuiltinAttrs(core::BuiltinValue::kPosition), + }, + }); + auto* colors_str = + ty.Struct(mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + core::type::StructMemberAttributes{ + /* location */ std::nullopt, + /* index */ std::nullopt, + /* color */ std::nullopt, + /* builtin */ std::nullopt, + /* interpolation */ + core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}, + /* invariant */ false, + }, + }, + }); + + auto* builtins = b.Var("builtins", ty.ptr(core::AddressSpace::kOut, builtin_str)); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kOut, colors_str)); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + colors->SetAttributes(attributes); + } + mod.root_block->Append(builtins); + mod.root_block->Append(colors); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + auto* ptr = ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()); + b.Store(b.Access(ptr, builtins, 0_u), b.Splat<vec4<f32>>(1_f, 4)); + b.Store(b.Access(ptr, colors, 0_u), b.Splat<vec4<f32>>(0.5_f, 4)); + b.Store(b.Access(ptr, colors, 1_u), b.Splat<vec4<f32>>(0.25_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0) + color2:vec4<f32> @offset(16), @interpolate(perspective, centroid) +} + +$B1: { # root + %builtins:ptr<__out, Builtins, read_write> = var + %colors:ptr<__out, Colors, read_write> = var @location(1) +} + +%foo = @vertex func():void { + $B2: { + %4:ptr<__out, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<__out, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<__out, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0) + color2:vec4<f32> @offset(16) +} + +tint_symbol = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) + color1:vec4<f32> @offset(16), @location(1) + color2:vec4<f32> @offset(32), @location(2), @interpolate(perspective, centroid) +} + +$B1: { # root + %builtins:ptr<private, Builtins, read_write> = var + %colors:ptr<private, Colors, read_write> = var +} + +%foo_inner = func():void { + $B2: { + %4:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<private, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<private, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +%foo = @vertex func():tint_symbol { + $B3: { + %8:void = call %foo_inner + %9:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + %10:vec4<f32> = load %9 + %11:ptr<private, vec4<f32>, read_write> = access %colors, 0u + %12:vec4<f32> = load %11 + %13:ptr<private, vec4<f32>, read_write> = access %colors, 1u + %14:vec4<f32> = load %13 + %15:tint_symbol = construct %10, %12, %14 + ret %15 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Outputs_Struct_InterpolateOnVariable) { + auto* builtin_str = + ty.Struct(mod.symbols.New("Builtins"), Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("position"), + ty.vec4<f32>(), + BuiltinAttrs(core::BuiltinValue::kPosition), + }, + }); + auto* colors_str = + ty.Struct(mod.symbols.New("Colors"), + Vector{ + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color1"), + ty.vec4<f32>(), + LocationAttrs(2), + }, + core::type::Manager::StructMemberDesc{ + mod.symbols.New("color2"), + ty.vec4<f32>(), + LocationAttrs(3, core::Interpolation{core::InterpolationType::kFlat}), + }, + }); + + auto* builtins = b.Var("builtins", ty.ptr(core::AddressSpace::kOut, builtin_str)); + auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kOut, colors_str)); + { + core::ir::IOAttributes attributes; + attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}; + colors->SetAttributes(attributes); + } + mod.root_block->Append(builtins); + mod.root_block->Append(colors); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep->Block(), [&] { // + auto* ptr = ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()); + b.Store(b.Access(ptr, builtins, 0_u), b.Splat<vec4<f32>>(1_f, 4)); + b.Store(b.Access(ptr, colors, 0_u), b.Splat<vec4<f32>>(0.5_f, 4)); + b.Store(b.Access(ptr, colors, 1_u), b.Splat<vec4<f32>>(0.25_f, 4)); + b.Return(ep); + }); + + auto* src = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0), @location(2) + color2:vec4<f32> @offset(16), @location(3), @interpolate(flat) +} + +$B1: { # root + %builtins:ptr<__out, Builtins, read_write> = var + %colors:ptr<__out, Colors, read_write> = var @interpolate(perspective, centroid) +} + +%foo = @vertex func():void { + $B2: { + %4:ptr<__out, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<__out, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<__out, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +Builtins = struct @align(16) { + position:vec4<f32> @offset(0) +} + +Colors = struct @align(16) { + color1:vec4<f32> @offset(0) + color2:vec4<f32> @offset(16) +} + +tint_symbol = struct @align(16) { + position:vec4<f32> @offset(0), @builtin(position) + color1:vec4<f32> @offset(16), @location(2), @interpolate(perspective, centroid) + color2:vec4<f32> @offset(32), @location(3), @interpolate(flat) +} + +$B1: { # root + %builtins:ptr<private, Builtins, read_write> = var + %colors:ptr<private, Colors, read_write> = var +} + +%foo_inner = func():void { + $B2: { + %4:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + store %4, vec4<f32>(1.0f) + %5:ptr<private, vec4<f32>, read_write> = access %colors, 0u + store %5, vec4<f32>(0.5f) + %6:ptr<private, vec4<f32>, read_write> = access %colors, 1u + store %6, vec4<f32>(0.25f) + ret + } +} +%foo = @vertex func():tint_symbol { + $B3: { + %8:void = call %foo_inner + %9:ptr<private, vec4<f32>, read_write> = access %builtins, 0u + %10:vec4<f32> = load %9 + %11:ptr<private, vec4<f32>, read_write> = access %colors, 0u + %12:vec4<f32> = load %11 + %13:ptr<private, vec4<f32>, read_write> = access %colors, 1u + %14:vec4<f32> = load %13 + %15:tint_symbol = construct %10, %12, %14 + ret %15 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Outputs_UsedByMultipleEntryPoints) { + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color1->SetAttributes(std::move(attributes)); + } + auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective, + core::InterpolationSampling::kCentroid}; + color2->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(position); + mod.root_block->Append(color1); + mod.root_block->Append(color2); + + auto* ep1 = b.Function("main1", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep1->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Return(ep1); + }); + + auto* ep2 = b.Function("main2", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep2->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Store(color1, b.Splat<vec4<f32>>(0.5_f, 4)); + b.Return(ep2); + }); + + auto* ep3 = b.Function("main3", ty.void_(), core::ir::Function::PipelineStage::kVertex); + b.Append(ep3->Block(), [&] { // + b.Store(position, b.Splat<vec4<f32>>(1_f, 4)); + b.Store(color2, b.Splat<vec4<f32>>(0.25_f, 4)); + b.Return(ep3); + }); + + auto* src = R"( +$B1: { # root + %position:ptr<__out, vec4<f32>, read_write> = var @invariant @builtin(position) + %color1:ptr<__out, vec4<f32>, read_write> = var @location(1) + %color2:ptr<__out, vec4<f32>, read_write> = var @location(1) @interpolate(perspective, centroid) +} + +%main1 = @vertex func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +%main2 = @vertex func():void { + $B3: { + store %position, vec4<f32>(1.0f) + store %color1, vec4<f32>(0.5f) + ret + } +} +%main3 = @vertex func():void { + $B4: { + store %position, vec4<f32>(1.0f) + store %color2, vec4<f32>(0.25f) + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +tint_symbol = struct @align(16) { + position:vec4<f32> @offset(0), @invariant, @builtin(position) + color1:vec4<f32> @offset(16), @location(1) +} + +tint_symbol_1 = struct @align(16) { + position:vec4<f32> @offset(0), @invariant, @builtin(position) + color2:vec4<f32> @offset(16), @location(1), @interpolate(perspective, centroid) +} + +$B1: { # root + %position:ptr<private, vec4<f32>, read_write> = var + %color1:ptr<private, vec4<f32>, read_write> = var + %color2:ptr<private, vec4<f32>, read_write> = var +} + +%main1_inner = func():void { + $B2: { + store %position, vec4<f32>(1.0f) + ret + } +} +%main2_inner = func():void { + $B3: { + store %position, vec4<f32>(1.0f) + store %color1, vec4<f32>(0.5f) + ret + } +} +%main3_inner = func():void { + $B4: { + store %position, vec4<f32>(1.0f) + store %color2, vec4<f32>(0.25f) + ret + } +} +%main1 = @vertex func():vec4<f32> [@invariant, @position] { + $B5: { + %8:void = call %main1_inner + %9:vec4<f32> = load %position + ret %9 + } +} +%main2 = @vertex func():tint_symbol { + $B6: { + %11:void = call %main2_inner + %12:vec4<f32> = load %position + %13:vec4<f32> = load %color1 + %14:tint_symbol = construct %12, %13 + ret %14 + } +} +%main3 = @vertex func():tint_symbol_1 { + $B7: { + %16:void = call %main3_inner + %17:vec4<f32> = load %position + %18:vec4<f32> = load %color2 + %19:tint_symbol_1 = construct %17, %18 + ret %19 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Output_LoadAndStore) { + auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(color); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { // + b.Store(color, b.Splat<vec4<f32>>(1_f, 4)); + auto* load = b.Load(color); + auto* mul = b.Multiply<vec4<f32>>(load, 2_f); + b.Store(color, mul); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %color:ptr<__out, vec4<f32>, read_write> = var @location(1) +} + +%foo = @fragment func():void { + $B2: { + store %color, vec4<f32>(1.0f) + %3:vec4<f32> = load %color + %4:vec4<f32> = mul %3, 2.0f + store %color, %4 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %color:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %color, vec4<f32>(1.0f) + %3:vec4<f32> = load %color + %4:vec4<f32> = mul %3, 2.0f + store %color, %4 + ret + } +} +%foo = @fragment func():vec4<f32> [@location(1)] { + $B3: { + %6:void = call %foo_inner + %7:vec4<f32> = load %color + ret %7 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Output_LoadVectorElementAndStoreVectorElement) { + auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(color); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { // + b.Store(color, b.Splat<vec4<f32>>(1_f, 4)); + auto* load = b.LoadVectorElement(color, 2_u); + auto* mul = b.Multiply<f32>(load, 2_f); + b.StoreVectorElement(color, 2_u, mul); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %color:ptr<__out, vec4<f32>, read_write> = var @location(1) +} + +%foo = @fragment func():void { + $B2: { + store %color, vec4<f32>(1.0f) + %3:f32 = load_vector_element %color, 2u + %4:f32 = mul %3, 2.0f + store_vector_element %color, 2u, %4 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %color:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + store %color, vec4<f32>(1.0f) + %3:f32 = load_vector_element %color, 2u + %4:f32 = mul %3, 2.0f + store_vector_element %color, 2u, %4 + ret + } +} +%foo = @fragment func():vec4<f32> [@location(1)] { + $B3: { + %6:void = call %foo_inner + %7:vec4<f32> = load %color + ret %7 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Output_AccessChain) { + auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1u; + color->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(color); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { // + auto* access_1 = b.Access(ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()), color); + auto* access_2 = b.Access(ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()), access_1); + auto* load = b.LoadVectorElement(access_2, 2_u); + auto* mul = b.Multiply<vec4<f32>>(b.Splat<vec4<f32>>(1_f, 4), load); + b.Store(access_2, mul); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %color:ptr<__out, vec4<f32>, read_write> = var @location(1) +} + +%foo = @fragment func():void { + $B2: { + %3:ptr<__out, vec4<f32>, read_write> = access %color + %4:ptr<__out, vec4<f32>, read_write> = access %3 + %5:f32 = load_vector_element %4, 2u + %6:vec4<f32> = mul vec4<f32>(1.0f), %5 + store %4, %6 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %color:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func():void { + $B2: { + %3:ptr<private, vec4<f32>, read_write> = access %color + %4:ptr<private, vec4<f32>, read_write> = access %3 + %5:f32 = load_vector_element %4, 2u + %6:vec4<f32> = mul vec4<f32>(1.0f), %5 + store %4, %6 + ret + } +} +%foo = @fragment func():vec4<f32> [@location(1)] { + $B3: { + %8:void = call %foo_inner + %9:vec4<f32> = load %color + ret %9 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +TEST_F(SpirvReader_ShaderIOTest, Inputs_And_Outputs) { + auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kPosition; + attributes.invariant = true; + position->SetAttributes(std::move(attributes)); + } + auto* color_in = b.Var("color_in", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 0; + color_in->SetAttributes(std::move(attributes)); + } + auto* color_out_1 = b.Var("color_out_1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 1; + color_out_1->SetAttributes(std::move(attributes)); + } + auto* color_out_2 = b.Var("color_out_2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>())); + { + core::ir::IOAttributes attributes; + attributes.location = 2; + color_out_2->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(position); + mod.root_block->Append(color_in); + mod.root_block->Append(color_out_1); + mod.root_block->Append(color_out_2); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + auto* position_value = b.Load(position); + auto* color_in_value = b.Load(color_in); + b.Store(color_out_1, position_value); + b.Store(color_out_2, color_in_value); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %position:ptr<__in, vec4<f32>, read> = var @invariant @builtin(position) + %color_in:ptr<__in, vec4<f32>, read> = var @location(0) + %color_out_1:ptr<__out, vec4<f32>, read_write> = var @location(1) + %color_out_2:ptr<__out, vec4<f32>, read_write> = var @location(2) +} + +%foo = @fragment func():void { + $B2: { + %6:vec4<f32> = load %position + %7:vec4<f32> = load %color_in + store %color_out_1, %6 + store %color_out_2, %7 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +tint_symbol = struct @align(16) { + color_out_1:vec4<f32> @offset(0), @location(1) + color_out_2:vec4<f32> @offset(16), @location(2) +} + +$B1: { # root + %color_out_1:ptr<private, vec4<f32>, read_write> = var + %color_out_2:ptr<private, vec4<f32>, read_write> = var +} + +%foo_inner = func(%position:vec4<f32>, %color_in:vec4<f32>):void { + $B2: { + store %color_out_1, %position + store %color_out_2, %color_in + ret + } +} +%foo = @fragment func(%position_1:vec4<f32> [@invariant, @position], %color_in_1:vec4<f32> [@location(0)]):tint_symbol { # %position_1: 'position', %color_in_1: 'color_in' + $B3: { + %9:void = call %foo_inner, %position_1, %color_in_1 + %10:vec4<f32> = load %color_out_1 + %11:vec4<f32> = load %color_out_2 + %12:tint_symbol = construct %10, %11 + ret %12 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +// Test that a sample mask array is converted to a scalar u32 for the entry point. +TEST_F(SpirvReader_ShaderIOTest, SampleMask) { + auto* arr = ty.array<u32, 1>(); + auto* mask_in = b.Var("mask_in", ty.ptr(core::AddressSpace::kIn, arr)); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kSampleMask; + mask_in->SetAttributes(std::move(attributes)); + } + auto* mask_out = b.Var("mask_out", ty.ptr(core::AddressSpace::kOut, arr)); + { + core::ir::IOAttributes attributes; + attributes.builtin = core::BuiltinValue::kSampleMask; + mask_out->SetAttributes(std::move(attributes)); + } + mod.root_block->Append(mask_in); + mod.root_block->Append(mask_out); + + auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); + b.Append(ep->Block(), [&] { + auto* mask_value = b.Load(mask_in); + auto* doubled = b.Multiply(ty.u32(), b.Access(ty.u32(), mask_value, 0_u), 2_u); + b.Store(mask_out, b.Construct(arr, doubled)); + b.Return(ep); + }); + + auto* src = R"( +$B1: { # root + %mask_in:ptr<__in, array<u32, 1>, read> = var @builtin(sample_mask) + %mask_out:ptr<__out, array<u32, 1>, read_write> = var @builtin(sample_mask) +} + +%foo = @fragment func():void { + $B2: { + %4:array<u32, 1> = load %mask_in + %5:u32 = access %4, 0u + %6:u32 = mul %5, 2u + %7:array<u32, 1> = construct %6 + store %mask_out, %7 + ret + } +} +)"; + EXPECT_EQ(src, str()); + + auto* expect = R"( +$B1: { # root + %mask_out:ptr<private, array<u32, 1>, read_write> = var +} + +%foo_inner = func(%mask_in:array<u32, 1>):void { + $B2: { + %4:u32 = access %mask_in, 0u + %5:u32 = mul %4, 2u + %6:array<u32, 1> = construct %5 + store %mask_out, %6 + ret + } +} +%foo = @fragment func(%mask_in_1:u32 [@sample_mask]):u32 [@sample_mask] { # %mask_in_1: 'mask_in' + $B3: { + %9:array<u32, 1> = construct %mask_in_1 + %10:void = call %foo_inner, %9 + %11:array<u32, 1> = load %mask_out + %12:u32 = access %11, 0u + ret %12 + } +} +)"; + + Run(ShaderIO); + + EXPECT_EQ(expect, str()); +} + +} // namespace +} // namespace tint::spirv::reader::lower
diff --git a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc index daf59df..948104d 100644 --- a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc +++ b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
@@ -131,9 +131,8 @@ Switch( use.instruction, [&](core::ir::Load* load) { - auto* lve = b.LoadVectorElement(object, index); + auto* lve = b.LoadVectorElementWithResult(load->DetachResult(), object, index); lve->InsertBefore(load); - load->Result(0)->ReplaceAllUsesWith(lve->Result(0)); to_destroy.Push(load); }, [&](core::ir::Store* store) {
diff --git a/src/tint/lang/spirv/reader/parser/struct_test.cc b/src/tint/lang/spirv/reader/parser/struct_test.cc index 7c85914..f59236b 100644 --- a/src/tint/lang/spirv/reader/parser/struct_test.cc +++ b/src/tint/lang/spirv/reader/parser/struct_test.cc
@@ -30,7 +30,7 @@ namespace tint::spirv::reader { TEST_F(SpirvParserTest, Struct_Empty) { - EXPECT_DEATH( // + EXPECT_DEATH_IF_SUPPORTED( // { auto assembly = Assemble(R"( OpCapability Shader
diff --git a/src/tint/lang/spirv/reader/reader_test.cc b/src/tint/lang/spirv/reader/reader_test.cc index d8ab351..5242819 100644 --- a/src/tint/lang/spirv/reader/reader_test.cc +++ b/src/tint/lang/spirv/reader/reader_test.cc
@@ -154,5 +154,201 @@ )"); } +TEST_F(SpirvReaderTest, ShaderInputs) { + auto got = Run(R"( + OpCapability Shader + OpCapability SampleRateShading + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %coord %colors + OpExecutionMode %main OriginUpperLeft + OpDecorate %coord BuiltIn FragCoord + OpDecorate %colors Location 1 + OpMemberDecorate %str 1 NoPerspective + %void = OpTypeVoid + %f32 = OpTypeFloat 32 + %vec4f = OpTypeVector %f32 4 + %fn_type = OpTypeFunction %void + %str = OpTypeStruct %vec4f %vec4f + %u32 = OpTypeInt 32 0 + %u32_0 = OpConstant %u32 0 + %u32_1 = OpConstant %u32 1 + +%_ptr_Input_vec4f = OpTypePointer Input %vec4f + %_ptr_Input_str = OpTypePointer Input %str + %coord = OpVariable %_ptr_Input_vec4f Input + %colors = OpVariable %_ptr_Input_str Input + + %main = OpFunction %void None %fn_type + %main_start = OpLabel + %access_a = OpAccessChain %_ptr_Input_vec4f %colors %u32_0 + %access_b = OpAccessChain %_ptr_Input_vec4f %colors %u32_1 + %a = OpLoad %vec4f %access_a + %b = OpLoad %vec4f %access_b + %c = OpLoad %vec4f %coord + %mul = OpFMul %vec4f %a %b + %add = OpFAdd %vec4f %mul %c + OpReturn + OpFunctionEnd +)"); + ASSERT_EQ(got, Success); + EXPECT_EQ(got, R"( +tint_symbol_2 = struct @align(16) { + tint_symbol:vec4<f32> @offset(0), @location(1) + tint_symbol_1:vec4<f32> @offset(16), @location(2), @interpolate(linear, center) +} + +%main = @fragment func(%2:vec4<f32> [@position], %3:tint_symbol_2):void { + $B1: { + %4:vec4<f32> = access %3, 0u + %5:vec4<f32> = access %3, 1u + %6:vec4<f32> = mul %4, %5 + %7:vec4<f32> = add %6, %2 + ret + } +} +)"); +} + +TEST_F(SpirvReaderTest, ShaderOutputs) { + auto got = Run(R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %depth %colors + OpExecutionMode %main OriginUpperLeft + OpExecutionMode %main DepthReplacing + OpDecorate %depth BuiltIn FragDepth + OpDecorate %colors Location 1 + OpMemberDecorate %str 1 NoPerspective + %void = OpTypeVoid + %f32 = OpTypeFloat 32 + %vec4f = OpTypeVector %f32 4 + %fn_type = OpTypeFunction %void + %str = OpTypeStruct %vec4f %vec4f + %u32 = OpTypeInt 32 0 + %u32_0 = OpConstant %u32 0 + %u32_1 = OpConstant %u32 1 + %f32_42 = OpConstant %f32 42.0 + %f32_n1 = OpConstant %f32 -1.0 + %f32_v4_a = OpConstantComposite %vec4f %f32_42 %f32_42 %f32_42 %f32_n1 + %f32_v4_b = OpConstantComposite %vec4f %f32_n1 %f32_n1 %f32_n1 %f32_42 + +%_ptr_Output_f32 = OpTypePointer Output %f32 +%_ptr_Output_vec4f = OpTypePointer Output %vec4f + %_ptr_Output_str = OpTypePointer Output %str + %depth = OpVariable %_ptr_Output_f32 Output + %colors = OpVariable %_ptr_Output_str Output + + %main = OpFunction %void None %fn_type + %main_start = OpLabel + %access_a = OpAccessChain %_ptr_Output_vec4f %colors %u32_0 + %access_b = OpAccessChain %_ptr_Output_vec4f %colors %u32_1 + OpStore %access_a %f32_v4_a + OpStore %access_b %f32_v4_b + OpStore %depth %f32_42 + OpReturn + OpFunctionEnd +)"); + ASSERT_EQ(got, Success); + EXPECT_EQ(got, R"( +tint_symbol_2 = struct @align(16) { + tint_symbol:vec4<f32> @offset(0) + tint_symbol_1:vec4<f32> @offset(16) +} + +tint_symbol_4 = struct @align(16) { + tint_symbol_3:f32 @offset(0), @builtin(frag_depth) + tint_symbol:vec4<f32> @offset(16), @location(1) + tint_symbol_1:vec4<f32> @offset(32), @location(2), @interpolate(linear, center) +} + +$B1: { # root + %1:ptr<private, f32, read_write> = var + %2:ptr<private, tint_symbol_2, read_write> = var +} + +%main_inner = func():void { + $B2: { + %4:ptr<private, vec4<f32>, read_write> = access %2, 0u + %5:ptr<private, vec4<f32>, read_write> = access %2, 1u + store %4, vec4<f32>(42.0f, 42.0f, 42.0f, -1.0f) + store %5, vec4<f32>(-1.0f, -1.0f, -1.0f, 42.0f) + store %1, 42.0f + ret + } +} +%main = @fragment func():tint_symbol_4 { + $B3: { + %7:void = call %main_inner + %8:f32 = load %1 + %9:ptr<private, vec4<f32>, read_write> = access %2, 0u + %10:vec4<f32> = load %9 + %11:ptr<private, vec4<f32>, read_write> = access %2, 1u + %12:vec4<f32> = load %11 + %13:tint_symbol_4 = construct %8, %10, %12 + ret %13 + } +} +)"); +} + +TEST_F(SpirvReaderTest, SampleMask) { + auto got = Run(R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %mask_in %mask_out + OpExecutionMode %main OriginUpperLeft + OpDecorate %mask_in BuiltIn SampleMask + OpDecorate %mask_out BuiltIn SampleMask + %void = OpTypeVoid + %fn_type = OpTypeFunction %void + %u32 = OpTypeInt 32 0 + %u32_0 = OpConstant %u32 0 + %u32_1 = OpConstant %u32 1 + %arr_u32 = OpTypeArray %u32 %u32_1 + +%_ptr_Input_u32 = OpTypePointer Input %u32 +%_ptr_Input_arr_u32 = OpTypePointer Input %arr_u32 +%_ptr_Output_u32 = OpTypePointer Output %u32 +%_ptr_Output_arr_u32 = OpTypePointer Output %arr_u32 + %mask_in = OpVariable %_ptr_Input_arr_u32 Input + %mask_out = OpVariable %_ptr_Output_arr_u32 Output + + %main = OpFunction %void None %fn_type + %main_start = OpLabel + %mask_in_0 = OpAccessChain %_ptr_Input_u32 %mask_in %u32_0 +%mask_in_val = OpLoad %u32 %mask_in_0 + %plus_one = OpIAdd %u32 %mask_in_val %u32_1 + %mask_out_0 = OpAccessChain %_ptr_Output_u32 %mask_out %u32_0 + OpStore %mask_out_0 %plus_one + OpReturn + OpFunctionEnd +)"); + ASSERT_EQ(got, Success); + EXPECT_EQ(got, R"( +$B1: { # root + %1:ptr<private, array<u32, 1>, read_write> = var +} + +%main_inner = func(%3:array<u32, 1>):void { + $B2: { + %4:u32 = access %3, 0u + %5:u32 = add %4, 1u + %6:ptr<private, u32, read_write> = access %1, 0u + store %6, %5 + ret + } +} +%main = @fragment func(%8:u32 [@sample_mask]):u32 [@sample_mask] { + $B3: { + %9:array<u32, 1> = construct %8 + %10:void = call %main_inner, %9 + %11:array<u32, 1> = load %1 + %12:u32 = access %11, 0u + ret %12 + } +} +)"); +} + } // namespace } // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/validate/validate.h b/src/tint/lang/spirv/validate/validate.h index 1e09477..9dab618 100644 --- a/src/tint/lang/spirv/validate/validate.h +++ b/src/tint/lang/spirv/validate/validate.h
@@ -31,11 +31,6 @@ #include "spirv-tools/libspirv.hpp" #include "src/tint/utils/result/result.h" -// Forward declarations -namespace tint { -class Program; -} // namespace tint - namespace tint::spirv::validate { /// Validate checks that the provided SPIR-V passes validation.
diff --git a/src/tint/lang/spirv/writer/ast_printer/assign_test.cc b/src/tint/lang/spirv/writer/ast_printer/assign_test.cc index 24ad65b..7da01f4 100644 --- a/src/tint/lang/spirv/writer/ast_printer/assign_test.cc +++ b/src/tint/lang/spirv/writer/ast_printer/assign_test.cc
@@ -66,7 +66,7 @@ } TEST_F(SpirvASTPrinterTest, Assign_Var_OutsideFunction_IsError) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder pb;
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc index 2404561..f8caeaa 100644 --- a/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc +++ b/src/tint/lang/spirv/writer/ast_printer/ast_if_test.cc
@@ -64,7 +64,7 @@ // if (true) { // } - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder pb;
diff --git a/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc b/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc index 4e6f84b..2105c9f 100644 --- a/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc +++ b/src/tint/lang/spirv/writer/ast_printer/builtin_texture_test.cc
@@ -3774,9 +3774,8 @@ Validate(b); } -// TODO(dsinclair): This generates two fatal errors, but EXPECT_DEATH can only handle 1 -TEST_P(BuiltinTextureTest, DISABLED_OutsideFunction_IsError) { - EXPECT_DEATH( +TEST_P(BuiltinTextureTest, OutsideFunction_IsError) { + EXPECT_DEATH_IF_SUPPORTED( { auto param = GetParam();
diff --git a/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc b/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc index 27e9a6e..7c5fdfb 100644 --- a/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc +++ b/src/tint/lang/spirv/writer/ast_printer/function_attribute_test.cc
@@ -165,7 +165,7 @@ } TEST_F(SpirvASTPrinterTest, Decoration_ExecutionMode_WorkgroupSize_OverridableConst) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder pb; pb.Override("width", pb.ty.i32(), pb.Call<i32>(2_i), pb.Id(7_u)); @@ -185,7 +185,7 @@ } TEST_F(SpirvASTPrinterTest, Decoration_ExecutionMode_WorkgroupSize_LiteralAndConst) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder pb;
diff --git a/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc b/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc index e65e8c9..54e18bb 100644 --- a/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc +++ b/src/tint/lang/spirv/writer/ast_printer/ident_expression_test.cc
@@ -37,7 +37,7 @@ using SpirvASTPrinterTest = TestHelper; TEST_F(SpirvASTPrinterTest, IdentifierExpression_GlobalConst) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder pb;
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc index b45beac..70808f6 100644 --- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc +++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -114,10 +114,9 @@ // Replace the builtins that we found. for (auto* builtin : worklist) { - core::ir::Value* replacement = nullptr; switch (builtin->Func()) { case core::BuiltinFn::kArrayLength: - replacement = ArrayLength(builtin); + ArrayLength(builtin); break; case core::BuiltinFn::kAtomicAdd: case core::BuiltinFn::kAtomicAnd: @@ -130,30 +129,30 @@ case core::BuiltinFn::kAtomicStore: case core::BuiltinFn::kAtomicSub: case core::BuiltinFn::kAtomicXor: - replacement = Atomic(builtin); + Atomic(builtin); break; case core::BuiltinFn::kDot: - replacement = Dot(builtin); + Dot(builtin); break; case core::BuiltinFn::kDot4I8Packed: case core::BuiltinFn::kDot4U8Packed: - replacement = DotPacked4x8(builtin); + DotPacked4x8(builtin); break; case core::BuiltinFn::kSelect: - replacement = Select(builtin); + Select(builtin); break; case core::BuiltinFn::kTextureDimensions: - replacement = TextureDimensions(builtin); + TextureDimensions(builtin); break; case core::BuiltinFn::kTextureGather: case core::BuiltinFn::kTextureGatherCompare: - replacement = TextureGather(builtin); + TextureGather(builtin); break; case core::BuiltinFn::kTextureLoad: - replacement = TextureLoad(builtin); + TextureLoad(builtin); break; case core::BuiltinFn::kTextureNumLayers: - replacement = TextureNumLayers(builtin); + TextureNumLayers(builtin); break; case core::BuiltinFn::kTextureSample: case core::BuiltinFn::kTextureSampleBias: @@ -161,25 +160,17 @@ case core::BuiltinFn::kTextureSampleCompareLevel: case core::BuiltinFn::kTextureSampleGrad: case core::BuiltinFn::kTextureSampleLevel: - replacement = TextureSample(builtin); + TextureSample(builtin); break; case core::BuiltinFn::kTextureStore: - replacement = TextureStore(builtin); + TextureStore(builtin); break; case core::BuiltinFn::kQuantizeToF16: - replacement = QuantizeToF16Vec(builtin); + QuantizeToF16Vec(builtin); break; default: break; } - TINT_ASSERT(replacement); - - // Replace the old builtin result with the new value. - if (auto name = ir.NameOf(builtin->Result(0))) { - ir.SetName(replacement, name); - } - builtin->Result(0)->ReplaceAllUsesWith(replacement); - builtin->Destroy(); } } @@ -192,8 +183,7 @@ /// Handle an `arrayLength()` builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* ArrayLength(core::ir::CoreBuiltinCall* builtin) { + void ArrayLength(core::ir::CoreBuiltinCall* builtin) { // Strip away any let instructions to get to the original struct member access instruction. auto* ptr = builtin->Args()[0]->As<core::ir::InstructionResult>(); while (auto* let = tint::As<core::ir::Let>(ptr->Instruction())) { @@ -208,17 +198,16 @@ auto* const_idx = access->Indices()[0]->As<core::ir::Constant>(); // Replace the builtin call with a call to the spirv.array_length intrinsic. - auto* call = b.Call<spirv::ir::BuiltinCall>( - builtin->Result(0)->Type(), spirv::BuiltinFn::kArrayLength, + auto* call = b.CallWithResult<spirv::ir::BuiltinCall>( + builtin->DetachResult(), spirv::BuiltinFn::kArrayLength, Vector{access->Object(), Literal(u32(const_idx->Value()->ValueAs<uint32_t>()))}); call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } /// Handle an atomic*() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* Atomic(core::ir::CoreBuiltinCall* builtin) { + void Atomic(core::ir::CoreBuiltinCall* builtin) { auto* result_ty = builtin->Result(0)->Type(); auto* pointer = builtin->Args()[0]; @@ -235,27 +224,29 @@ auto* memory_semantics = b.Constant(u32(SpvMemorySemanticsMaskNone)); // Helper to build the builtin call with the common operands. - auto build = [&](const core::type::Type* type, enum spirv::BuiltinFn builtin_fn) { - return b.Call<spirv::ir::BuiltinCall>(type, builtin_fn, pointer, memory, - memory_semantics); + auto build = [&](enum spirv::BuiltinFn builtin_fn) { + return b.CallWithResult<spirv::ir::BuiltinCall>(builtin->DetachResult(), builtin_fn, + pointer, memory, memory_semantics); }; // Create the replacement call instruction. core::ir::Call* call = nullptr; switch (builtin->Func()) { case core::BuiltinFn::kAtomicAdd: - call = build(result_ty, spirv::BuiltinFn::kAtomicIadd); + call = build(spirv::BuiltinFn::kAtomicIadd); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicAnd: - call = build(result_ty, spirv::BuiltinFn::kAtomicAnd); + call = build(spirv::BuiltinFn::kAtomicAnd); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicCompareExchangeWeak: { auto* cmp = builtin->Args()[1]; auto* value = builtin->Args()[2]; auto* int_ty = value->Type(); - call = build(int_ty, spirv::BuiltinFn::kAtomicCompareExchange); + call = + b.Call<spirv::ir::BuiltinCall>(int_ty, spirv::BuiltinFn::kAtomicCompareExchange, + pointer, memory, memory_semantics); call->AppendArg(memory_semantics); call->AppendArg(value); call->AppendArg(cmp); @@ -267,62 +258,60 @@ compare->InsertBefore(builtin); // Construct the atomicCompareExchange result structure. - call = b.Construct( - core::type::CreateAtomicCompareExchangeResult(ty, ir.symbols, int_ty), - Vector{original, compare->Result(0)}); + call = b.ConstructWithResult(builtin->DetachResult(), + Vector{original, compare->Result(0)}); break; } case core::BuiltinFn::kAtomicExchange: - call = build(result_ty, spirv::BuiltinFn::kAtomicExchange); + call = build(spirv::BuiltinFn::kAtomicExchange); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicLoad: - call = build(result_ty, spirv::BuiltinFn::kAtomicLoad); + call = build(spirv::BuiltinFn::kAtomicLoad); break; case core::BuiltinFn::kAtomicOr: - call = build(result_ty, spirv::BuiltinFn::kAtomicOr); + call = build(spirv::BuiltinFn::kAtomicOr); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicMax: if (result_ty->is_signed_integer_scalar()) { - call = build(result_ty, spirv::BuiltinFn::kAtomicSmax); + call = build(spirv::BuiltinFn::kAtomicSmax); } else { - call = build(result_ty, spirv::BuiltinFn::kAtomicUmax); + call = build(spirv::BuiltinFn::kAtomicUmax); } call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicMin: if (result_ty->is_signed_integer_scalar()) { - call = build(result_ty, spirv::BuiltinFn::kAtomicSmin); + call = build(spirv::BuiltinFn::kAtomicSmin); } else { - call = build(result_ty, spirv::BuiltinFn::kAtomicUmin); + call = build(spirv::BuiltinFn::kAtomicUmin); } call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicStore: - call = build(result_ty, spirv::BuiltinFn::kAtomicStore); + call = build(spirv::BuiltinFn::kAtomicStore); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicSub: - call = build(result_ty, spirv::BuiltinFn::kAtomicIsub); + call = build(spirv::BuiltinFn::kAtomicIsub); call->AppendArg(builtin->Args()[1]); break; case core::BuiltinFn::kAtomicXor: - call = build(result_ty, spirv::BuiltinFn::kAtomicXor); + call = build(spirv::BuiltinFn::kAtomicXor); call->AppendArg(builtin->Args()[1]); break; default: - return nullptr; + TINT_UNREACHABLE() << "unhandled atomic builtin"; } call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } /// Handle a `dot()` builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* Dot(core::ir::CoreBuiltinCall* builtin) { + void Dot(core::ir::CoreBuiltinCall* builtin) { // OpDot only supports floating point operands, so we need to polyfill the integer case. // TODO(crbug.com/tint/1267): If SPV_KHR_integer_dot_product is supported, use that instead. if (builtin->Result(0)->Type()->is_integer_scalar()) { @@ -344,38 +333,38 @@ } }); } - return sum->Result(0); + sum->SetResults(Vector{builtin->DetachResult()}); + builtin->Destroy(); + return; } // Replace the builtin call with a call to the spirv.dot intrinsic. auto args = Vector<core::ir::Value*, 4>(builtin->Args()); - auto* call = b.Call<spirv::ir::BuiltinCall>(builtin->Result(0)->Type(), - spirv::BuiltinFn::kDot, std::move(args)); + auto* call = b.CallWithResult<spirv::ir::BuiltinCall>( + builtin->DetachResult(), spirv::BuiltinFn::kDot, std::move(args)); call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } /// Handle a `dot4{I,U}8Packed()` builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* DotPacked4x8(core::ir::CoreBuiltinCall* builtin) { + void DotPacked4x8(core::ir::CoreBuiltinCall* builtin) { // Replace the builtin call with a call to the spirv.{s,u}dot intrinsic. - auto* type = builtin->Result(0)->Type(); auto is_signed = builtin->Func() == core::BuiltinFn::kDot4I8Packed; auto inst = is_signed ? spirv::BuiltinFn::kSdot : spirv::BuiltinFn::kUdot; auto args = Vector<core::ir::Value*, 3>(builtin->Args()); args.Push(Literal(u32(SpvPackedVectorFormatPackedVectorFormat4x8Bit))); - auto* call = b.Call<spirv::ir::BuiltinCall>(type, inst, std::move(args)); + auto* call = b.CallWithResult<spirv::ir::BuiltinCall>(builtin->DetachResult(), inst, + std::move(args)); call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } /// Handle a `select()` builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* Select(core::ir::CoreBuiltinCall* builtin) { + void Select(core::ir::CoreBuiltinCall* builtin) { // Argument order is different in SPIR-V: (condition, true_operand, false_operand). Vector<core::ir::Value*, 4> args = { builtin->Args()[2], @@ -397,10 +386,10 @@ } // Replace the builtin call with a call to the spirv.select intrinsic. - auto* call = b.Call<spirv::ir::BuiltinCall>(builtin->Result(0)->Type(), - spirv::BuiltinFn::kSelect, std::move(args)); + auto* call = b.CallWithResult<spirv::ir::BuiltinCall>( + builtin->DetachResult(), spirv::BuiltinFn::kSelect, std::move(args)); call->InsertBefore(builtin); - return call->Result(0); + builtin->Destroy(); } /// ImageOperands represents the optional image operands for an image instruction. @@ -494,8 +483,7 @@ /// Handle a textureSample*() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureSample(core::ir::CoreBuiltinCall* builtin) { + void TextureSample(core::ir::CoreBuiltinCall* builtin) { // Helper to get the next argument from the call, or nullptr if there are no more arguments. uint32_t arg_idx = 0; auto next_arg = [&]() { @@ -520,7 +508,7 @@ } // Determine which SPIR-V function to use and which optional image operands are needed. - enum spirv::BuiltinFn function; + enum spirv::BuiltinFn function = BuiltinFn::kNone; core::ir::Value* depth = nullptr; ImageOperands operands; switch (builtin->Func()) { @@ -556,7 +544,7 @@ operands.offset = next_arg(); break; default: - return nullptr; + TINT_UNREACHABLE() << "unhandled texture sample builtin"; } // Start building the argument list for the function. @@ -575,28 +563,25 @@ // Call the function. // If this is a depth comparison, the result is always f32, otherwise vec4f. auto* result_ty = depth ? static_cast<const core::type::Type*>(ty.f32()) : ty.vec4<f32>(); - auto* texture_call = + core::ir::Instruction* result = b.Call<spirv::ir::BuiltinCall>(result_ty, function, std::move(function_args)); - texture_call->InsertBefore(builtin); - - auto* result = texture_call->Result(0); + result->InsertBefore(builtin); // If this is not a depth comparison but we are sampling a depth texture, extract the first // component to get the scalar f32 that SPIR-V expects. if (!depth && texture_ty->IsAnyOf<core::type::DepthTexture, core::type::DepthMultisampledTexture>()) { - auto* extract = b.Access(ty.f32(), result, 0_u); - extract->InsertBefore(builtin); - result = extract->Result(0); + result = b.Access(ty.f32(), result, 0_u); + result->InsertBefore(builtin); } - return result; + result->SetResults(Vector{builtin->DetachResult()}); + builtin->Destroy(); } /// Handle a textureGather*() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureGather(core::ir::CoreBuiltinCall* builtin) { + void TextureGather(core::ir::CoreBuiltinCall* builtin) { // Helper to get the next argument from the call, or nullptr if there are no more arguments. uint32_t arg_idx = 0; auto next_arg = [&]() { @@ -628,7 +613,7 @@ } // Determine which SPIR-V function to use and which optional image operands are needed. - enum spirv::BuiltinFn function; + enum spirv::BuiltinFn function = BuiltinFn::kNone; core::ir::Value* depth = nullptr; ImageOperands operands; switch (builtin->Func()) { @@ -642,7 +627,7 @@ operands.offset = next_arg(); break; default: - return nullptr; + TINT_UNIMPLEMENTED() << "unhandled texture gather builtin"; } // Start building the argument list for the function. @@ -661,17 +646,15 @@ AppendImageOperands(operands, function_args, builtin, /* requires_float_lod */ true); // Call the function. - auto* result_ty = builtin->Result(0)->Type(); - auto* texture_call = - b.Call<spirv::ir::BuiltinCall>(result_ty, function, std::move(function_args)); + auto* texture_call = b.CallWithResult<spirv::ir::BuiltinCall>( + builtin->DetachResult(), function, std::move(function_args)); texture_call->InsertBefore(builtin); - return texture_call->Result(0); + builtin->Destroy(); } /// Handle a textureLoad() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureLoad(core::ir::CoreBuiltinCall* builtin) { + void TextureLoad(core::ir::CoreBuiltinCall* builtin) { // Helper to get the next argument from the call, or nullptr if there are no more arguments. uint32_t arg_idx = 0; auto next_arg = [&]() { @@ -713,25 +696,23 @@ } auto kind = texture_ty->Is<core::type::StorageTexture>() ? spirv::BuiltinFn::kImageRead : spirv::BuiltinFn::kImageFetch; - auto* texture_call = + core::ir::Instruction* result = b.Call<spirv::ir::BuiltinCall>(result_ty, kind, std::move(builtin_args)); - texture_call->InsertBefore(builtin); - auto* result = texture_call->Result(0); + result->InsertBefore(builtin); // If we are expecting a scalar result, extract the first component. if (expects_scalar_result) { - auto* extract = b.Access(ty.f32(), result, 0_u); - extract->InsertBefore(builtin); - result = extract->Result(0); + result = b.Access(ty.f32(), result, 0_u); + result->InsertBefore(builtin); } - return result; + result->SetResults(Vector{builtin->DetachResult()}); + builtin->Destroy(); } /// Handle a textureStore() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureStore(core::ir::CoreBuiltinCall* builtin) { + void TextureStore(core::ir::CoreBuiltinCall* builtin) { // Helper to get the next argument from the call, or nullptr if there are no more arguments. uint32_t arg_idx = 0; auto next_arg = [&]() { @@ -764,13 +745,12 @@ auto* texture_call = b.Call<spirv::ir::BuiltinCall>( ty.void_(), spirv::BuiltinFn::kImageWrite, std::move(function_args)); texture_call->InsertBefore(builtin); - return texture_call->Result(0); + builtin->Destroy(); } /// Handle a textureDimensions() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureDimensions(core::ir::CoreBuiltinCall* builtin) { + void TextureDimensions(core::ir::CoreBuiltinCall* builtin) { // Helper to get the next argument from the call, or nullptr if there are no more arguments. uint32_t arg_idx = 0; auto next_arg = [&]() { @@ -807,26 +787,23 @@ } // Call the function. - auto* texture_call = + core::ir::Instruction* result = b.Call<spirv::ir::BuiltinCall>(result_ty, function, std::move(function_args)); - texture_call->InsertBefore(builtin); - - auto* result = texture_call->Result(0); + result->InsertBefore(builtin); // Swizzle the first two components from the result for arrayed textures. if (core::type::IsTextureArray(texture_ty->dim())) { - auto* swizzle = b.Swizzle(builtin->Result(0)->Type(), result, {0, 1}); - swizzle->InsertBefore(builtin); - result = swizzle->Result(0); + result = b.Swizzle(builtin->Result(0)->Type(), result, {0, 1}); + result->InsertBefore(builtin); } - return result; + result->SetResults(Vector{builtin->DetachResult()}); + builtin->Destroy(); } /// Handle a textureNumLayers() builtin. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* TextureNumLayers(core::ir::CoreBuiltinCall* builtin) { + void TextureNumLayers(core::ir::CoreBuiltinCall* builtin) { auto* texture = builtin->Args()[0]; auto* texture_ty = texture->Type()->As<core::type::Texture>(); @@ -850,16 +827,15 @@ texture_call->InsertBefore(builtin); // Extract the third component to get the number of array layers. - auto* extract = b.Access(ty.u32(), texture_call->Result(0), 2_u); + auto* extract = b.AccessWithResult(builtin->DetachResult(), texture_call->Result(0), 2_u); extract->InsertBefore(builtin); - return extract->Result(0); + builtin->Destroy(); } /// Scalarize the vector form of a `quantizeToF16()` builtin. /// See crbug.com/tint/1741. /// @param builtin the builtin call instruction - /// @returns the replacement value - core::ir::Value* QuantizeToF16Vec(core::ir::CoreBuiltinCall* builtin) { + void QuantizeToF16Vec(core::ir::CoreBuiltinCall* builtin) { auto* arg = builtin->Args()[0]; auto* vec = arg->Type()->As<core::type::Vector>(); TINT_ASSERT(vec); @@ -873,9 +849,9 @@ el->InsertBefore(builtin); scalar_call->InsertBefore(builtin); } - auto* construct = b.Construct(vec, std::move(args)); + auto* construct = b.ConstructWithResult(builtin->DetachResult(), std::move(args)); construct->InsertBefore(builtin); - return construct->Result(0); + builtin->Destroy(); } };
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc index 3b650a7..c5359f2 100644 --- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc +++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -107,8 +107,8 @@ auto* result_ty = binary->Result(0)->Type(); if (result_ty->is_float_vector() && binary->Op() == core::BinaryOp::kMultiply) { // Use OpVectorTimesScalar for floating point multiply. - auto* vts = - b.Call<spirv::ir::BuiltinCall>(result_ty, spirv::BuiltinFn::kVectorTimesScalar); + auto* vts = b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kVectorTimesScalar); if (binary->LHS()->Type()->Is<core::type::Scalar>()) { vts->AppendArg(binary->RHS()); vts->AppendArg(binary->LHS()); @@ -119,7 +119,6 @@ if (auto name = ir.NameOf(binary)) { ir.SetName(vts->Result(0), name); } - binary->Result(0)->ReplaceAllUsesWith(vts->Result(0)); binary->ReplaceWith(vts); binary->Destroy(); } else {
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc index 487bac0..3091c09 100644 --- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc +++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
@@ -77,65 +77,63 @@ auto* rhs_ty = rhs->Type(); auto* ty = binary->Result(0)->Type(); - // Helper to replace the instruction with a new one. - auto replace = [&](core::ir::Instruction* inst) { - if (auto name = ir.NameOf(binary)) { - ir.SetName(inst->Result(0), name); - } - binary->Result(0)->ReplaceAllUsesWith(inst->Result(0)); - binary->ReplaceWith(inst); - binary->Destroy(); - }; - - // Helper to replace the instruction with a column-wise operation. - auto column_wise = [&](auto op) { - auto* mat = ty->As<core::type::Matrix>(); - Vector<core::ir::Value*, 4> args; - for (uint32_t col = 0; col < mat->columns(); col++) { - b.InsertBefore(binary, [&] { + b.InsertBefore(binary, [&] { + // Helper to replace the instruction with a column-wise operation. + auto column_wise = [&](auto op) { + auto* mat = ty->As<core::type::Matrix>(); + Vector<core::ir::Value*, 4> args; + for (uint32_t col = 0; col < mat->columns(); col++) { auto* lhs_col = b.Access(mat->ColumnType(), lhs, u32(col)); auto* rhs_col = b.Access(mat->ColumnType(), rhs, u32(col)); auto* add = b.Binary(op, mat->ColumnType(), lhs_col, rhs_col); args.Push(add->Result(0)); - }); - } - replace(b.Construct(ty, std::move(args))); - }; - - switch (binary->Op()) { - case core::BinaryOp::kAdd: - column_wise(core::BinaryOp::kAdd); - break; - case core::BinaryOp::kSubtract: - column_wise(core::BinaryOp::kSubtract); - break; - case core::BinaryOp::kMultiply: - // Select the SPIR-V intrinsic that corresponds to the operation being performed. - if (lhs_ty->Is<core::type::Matrix>()) { - if (rhs_ty->Is<core::type::Scalar>()) { - replace(b.Call<spirv::ir::BuiltinCall>( - ty, spirv::BuiltinFn::kMatrixTimesScalar, lhs, rhs)); - } else if (rhs_ty->Is<core::type::Vector>()) { - replace(b.Call<spirv::ir::BuiltinCall>( - ty, spirv::BuiltinFn::kMatrixTimesVector, lhs, rhs)); - } else if (rhs_ty->Is<core::type::Matrix>()) { - replace(b.Call<spirv::ir::BuiltinCall>( - ty, spirv::BuiltinFn::kMatrixTimesMatrix, lhs, rhs)); - } - } else { - if (lhs_ty->Is<core::type::Scalar>()) { - replace(b.Call<spirv::ir::BuiltinCall>( - ty, spirv::BuiltinFn::kMatrixTimesScalar, rhs, lhs)); - } else if (lhs_ty->Is<core::type::Vector>()) { - replace(b.Call<spirv::ir::BuiltinCall>( - ty, spirv::BuiltinFn::kVectorTimesMatrix, lhs, rhs)); - } } - break; + b.ConstructWithResult(binary->DetachResult(), std::move(args)); + }; - default: - TINT_UNREACHABLE() << "unhandled matrix arithmetic instruction"; - } + switch (binary->Op()) { + case core::BinaryOp::kAdd: + column_wise(core::BinaryOp::kAdd); + break; + case core::BinaryOp::kSubtract: + column_wise(core::BinaryOp::kSubtract); + break; + case core::BinaryOp::kMultiply: + // Select the SPIR-V intrinsic that corresponds to the operation being + // performed. + if (lhs_ty->Is<core::type::Matrix>()) { + if (rhs_ty->Is<core::type::Scalar>()) { + b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kMatrixTimesScalar, lhs, + rhs); + } else if (rhs_ty->Is<core::type::Vector>()) { + b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kMatrixTimesVector, lhs, + rhs); + } else if (rhs_ty->Is<core::type::Matrix>()) { + b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kMatrixTimesMatrix, lhs, + rhs); + } + } else { + if (lhs_ty->Is<core::type::Scalar>()) { + b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kMatrixTimesScalar, rhs, + lhs); + } else if (lhs_ty->Is<core::type::Vector>()) { + b.CallWithResult<spirv::ir::BuiltinCall>( + binary->DetachResult(), spirv::BuiltinFn::kVectorTimesMatrix, lhs, + rhs); + } + } + break; + + default: + TINT_UNREACHABLE() << "unhandled matrix arithmetic instruction"; + } + }); + + binary->Destroy(); } /// Replace a matrix convert instruction. @@ -145,23 +143,19 @@ auto* in_mat = arg->Type()->As<core::type::Matrix>(); auto* out_mat = convert->Result(0)->Type()->As<core::type::Matrix>(); - // Extract and convert each column separately. - Vector<core::ir::Value*, 4> args; - for (uint32_t c = 0; c < out_mat->columns(); c++) { - b.InsertBefore(convert, [&] { + b.InsertBefore(convert, [&] { + // Extract and convert each column separately. + Vector<core::ir::Value*, 4> args; + for (uint32_t c = 0; c < out_mat->columns(); c++) { auto* col = b.Access(in_mat->ColumnType(), arg, u32(c)); auto* new_col = b.Convert(out_mat->ColumnType(), col); args.Push(new_col->Result(0)); - }); - } + } - // Reconstruct the result matrix from the converted columns. - auto* construct = b.Construct(out_mat, std::move(args)); - if (auto name = ir.NameOf(convert)) { - ir.SetName(construct->Result(0), name); - } - convert->Result(0)->ReplaceAllUsesWith(construct->Result(0)); - convert->ReplaceWith(construct); + // Reconstruct the result matrix from the converted columns. + b.ConstructWithResult(convert->DetachResult(), std::move(args)); + }); + convert->Destroy(); } };
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc index 409a52e..49655d5 100644 --- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc +++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -237,13 +237,11 @@ core::ir::Instruction* load = nullptr; if (to_replace.vector_access_type) { - load = b.LoadVectorElement(new_access->Result(0), vector_index); + load = b.LoadVectorElementWithResult(access->DetachResult(), new_access->Result(0), + vector_index); } else { - load = b.Load(new_access); + load = b.LoadWithResult(access->DetachResult(), new_access); } - - // Replace all uses of the old access instruction with the loaded result. - access->Result(0)->ReplaceAllUsesWith(load->Result(0)); access->ReplaceWith(load); access->Destroy(); }
diff --git a/src/tint/lang/spirv/writer/writer_ir_fuzz.cc b/src/tint/lang/spirv/writer/writer_ir_fuzz.cc index dcc848c..483dd77 100644 --- a/src/tint/lang/spirv/writer/writer_ir_fuzz.cc +++ b/src/tint/lang/spirv/writer/writer_ir_fuzz.cc
@@ -28,6 +28,7 @@ #include "src/tint/lang/spirv/writer/writer.h" #include "src/tint/cmd/fuzz/ir/fuzz.h" +#include "src/tint/lang/core/ir/disassembly.h" #include "src/tint/lang/spirv/validate/validate.h" #include "src/tint/lang/spirv/writer/helpers/generate_bindings.h" @@ -43,8 +44,10 @@ auto& spirv = output->spirv; if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1); res != Success) { - TINT_ICE() << "Output of SPIR-V writer failed to validate with SPIR-V Tools\n" - << res.Failure(); + TINT_ICE() << "output of SPIR-V writer failed to validate with SPIR-V Tools\n" + << res.Failure() << "\n\n" + << "IR:\n" + << core::ir::Disassemble(module).Plain(); } }
diff --git a/src/tint/lang/wgsl/ast/assignment_statement_test.cc b/src/tint/lang/wgsl/ast/assignment_statement_test.cc index 6c51920..7ba43d1 100644 --- a/src/tint/lang/wgsl/ast/assignment_statement_test.cc +++ b/src/tint/lang/wgsl/ast/assignment_statement_test.cc
@@ -64,7 +64,7 @@ } TEST_F(AssignmentStatementTest, Assert_Null_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<AssignmentStatement>(nullptr, b.Expr(1_i)); @@ -73,7 +73,7 @@ } TEST_F(AssignmentStatementTest, Assert_Null_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<AssignmentStatement>(b.Expr(1_i), nullptr); @@ -82,7 +82,7 @@ } TEST_F(AssignmentStatementTest, Assert_DifferentGenerationID_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -92,7 +92,7 @@ } TEST_F(AssignmentStatementTest, Assert_DifferentGenerationID_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/binary_expression_test.cc b/src/tint/lang/wgsl/ast/binary_expression_test.cc index 34e6e58..7dffd7b 100644 --- a/src/tint/lang/wgsl/ast/binary_expression_test.cc +++ b/src/tint/lang/wgsl/ast/binary_expression_test.cc
@@ -62,7 +62,7 @@ } TEST_F(BinaryExpressionTest, Assert_Null_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<BinaryExpression>(core::BinaryOp::kEqual, nullptr, b.Expr("rhs")); @@ -71,7 +71,7 @@ } TEST_F(BinaryExpressionTest, Assert_Null_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<BinaryExpression>(core::BinaryOp::kEqual, b.Expr("lhs"), nullptr); @@ -80,7 +80,7 @@ } TEST_F(BinaryExpressionTest, Assert_DifferentGenerationID_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -90,7 +90,7 @@ } TEST_F(BinaryExpressionTest, Assert_DifferentGenerationID_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/block_statement_test.cc b/src/tint/lang/wgsl/ast/block_statement_test.cc index 6e00eaa..8540432 100644 --- a/src/tint/lang/wgsl/ast/block_statement_test.cc +++ b/src/tint/lang/wgsl/ast/block_statement_test.cc
@@ -72,7 +72,7 @@ } TEST_F(BlockStatementTest, Assert_Null_Statement) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<BlockStatement>(tint::Vector<const Statement*, 1>{nullptr}, tint::Empty); @@ -81,7 +81,7 @@ } TEST_F(BlockStatementTest, Assert_DifferentGenerationID_Statement) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/break_if_statement_test.cc b/src/tint/lang/wgsl/ast/break_if_statement_test.cc index 4c59976..c5e3e4b 100644 --- a/src/tint/lang/wgsl/ast/break_if_statement_test.cc +++ b/src/tint/lang/wgsl/ast/break_if_statement_test.cc
@@ -48,7 +48,7 @@ } TEST_F(BreakIfStatementTest, Assert_Null_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.BreakIf(nullptr); @@ -57,7 +57,7 @@ } TEST_F(BreakIfStatementTest, Assert_DifferentGenerationID_Cond) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/builtin_attribute_test.cc b/src/tint/lang/wgsl/ast/builtin_attribute_test.cc index 4bd547b..6a56995 100644 --- a/src/tint/lang/wgsl/ast/builtin_attribute_test.cc +++ b/src/tint/lang/wgsl/ast/builtin_attribute_test.cc
@@ -39,7 +39,7 @@ } TEST_F(BuiltinAttributeTest, Assert_Null_Builtin) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Builtin(nullptr); @@ -48,7 +48,7 @@ } TEST_F(BuiltinAttributeTest, Assert_DifferentGenerationID_Builtin) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/call_expression_test.cc b/src/tint/lang/wgsl/ast/call_expression_test.cc index 7e6cab8..90bbc34 100644 --- a/src/tint/lang/wgsl/ast/call_expression_test.cc +++ b/src/tint/lang/wgsl/ast/call_expression_test.cc
@@ -91,7 +91,7 @@ } TEST_F(CallExpressionTest, Assert_Null_Identifier) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Call(static_cast<Identifier*>(nullptr)); @@ -100,7 +100,7 @@ } TEST_F(CallExpressionTest, Assert_Null_Param) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Call(b.Ident("func"), tint::Vector{ @@ -113,7 +113,7 @@ } TEST_F(CallExpressionTest, Assert_DifferentGenerationID_Identifier) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -123,7 +123,7 @@ } TEST_F(CallExpressionTest, Assert_DifferentGenerationID_Type) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -133,7 +133,7 @@ } TEST_F(CallExpressionTest, Assert_DifferentGenerationID_Param) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/call_statement_test.cc b/src/tint/lang/wgsl/ast/call_statement_test.cc index e9e8169..3c1a47f 100644 --- a/src/tint/lang/wgsl/ast/call_statement_test.cc +++ b/src/tint/lang/wgsl/ast/call_statement_test.cc
@@ -47,7 +47,7 @@ } TEST_F(CallStatementTest, Assert_Null_Call) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.CallStmt(nullptr); @@ -56,7 +56,7 @@ } TEST_F(CallStatementTest, Assert_DifferentGenerationID_Call) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/case_statement_test.cc b/src/tint/lang/wgsl/ast/case_statement_test.cc index 60df861..6425ac2 100644 --- a/src/tint/lang/wgsl/ast/case_statement_test.cc +++ b/src/tint/lang/wgsl/ast/case_statement_test.cc
@@ -99,7 +99,7 @@ } TEST_F(CaseStatementTest, Assert_Null_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<CaseStatement>(tint::Vector{b.DefaultCaseSelector()}, nullptr); @@ -108,7 +108,7 @@ } TEST_F(CaseStatementTest, Assert_Null_Selector) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<CaseStatement>(tint::Vector<const ast::CaseSelector*, 1>{nullptr}, @@ -118,7 +118,7 @@ } TEST_F(CaseStatementTest, Assert_DifferentGenerationID_Call) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -129,7 +129,7 @@ } TEST_F(CaseStatementTest, Assert_DifferentGenerationID_Selector) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/clone_context_test.cc b/src/tint/lang/wgsl/ast/clone_context_test.cc index 6bc1e6e..4d70dbc 100644 --- a/src/tint/lang/wgsl/ast/clone_context_test.cc +++ b/src/tint/lang/wgsl/ast/clone_context_test.cc
@@ -1053,7 +1053,7 @@ TEST_F(ASTCloneContextTestNodeTest, CloneWithReplaceAll_SameTypeTwice) { std::string Testnode_name = TypeInfo::Of<TestNode>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1070,7 +1070,7 @@ std::string Testnode_name = TypeInfo::Of<TestNode>().name; std::string replaceable_name = TypeInfo::Of<Replaceable>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1087,7 +1087,7 @@ std::string Testnode_name = TypeInfo::Of<TestNode>().name; std::string replaceable_name = TypeInfo::Of<Replaceable>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1103,7 +1103,7 @@ using ASTCloneContextTest = ::testing::Test; TEST_F(ASTCloneContextTest, CloneWithReplaceAll_SymbolsTwice) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1181,7 +1181,7 @@ } TEST_F(ASTCloneContextTest, GenerationIDs_Clone_ObjectNotOwnedBySrc) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder dst; Program src(ProgramBuilder{}); @@ -1194,7 +1194,7 @@ } TEST_F(ASTCloneContextTest, GenerationIDs_Clone_ObjectNotOwnedByDst) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder dst; Program src(ProgramBuilder{});
diff --git a/src/tint/lang/wgsl/ast/color_attribute_test.cc b/src/tint/lang/wgsl/ast/color_attribute_test.cc index c8dd840..990ee41 100644 --- a/src/tint/lang/wgsl/ast/color_attribute_test.cc +++ b/src/tint/lang/wgsl/ast/color_attribute_test.cc
@@ -42,7 +42,7 @@ } TEST_F(ColorAttributeTest, Assert_Null_Builtin) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Color(nullptr); @@ -51,7 +51,7 @@ } TEST_F(ColorAttributeTest, Assert_DifferentGenerationID_Color) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/compound_assignment_statement_test.cc b/src/tint/lang/wgsl/ast/compound_assignment_statement_test.cc index 36e302d..ee26804 100644 --- a/src/tint/lang/wgsl/ast/compound_assignment_statement_test.cc +++ b/src/tint/lang/wgsl/ast/compound_assignment_statement_test.cc
@@ -68,7 +68,7 @@ } TEST_F(CompoundAssignmentStatementTest, Assert_Null_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<CompoundAssignmentStatement>(nullptr, b.Expr(1_i), core::BinaryOp::kAdd); @@ -77,7 +77,7 @@ } TEST_F(CompoundAssignmentStatementTest, Assert_Null_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<CompoundAssignmentStatement>(b.Expr(1_i), nullptr, core::BinaryOp::kAdd); @@ -86,7 +86,7 @@ } TEST_F(CompoundAssignmentStatementTest, Assert_DifferentGenerationID_LHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -97,7 +97,7 @@ } TEST_F(CompoundAssignmentStatementTest, Assert_DifferentGenerationID_RHS) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/const_assert_test.cc b/src/tint/lang/wgsl/ast/const_assert_test.cc index 70e9dfd..0f94a51 100644 --- a/src/tint/lang/wgsl/ast/const_assert_test.cc +++ b/src/tint/lang/wgsl/ast/const_assert_test.cc
@@ -59,7 +59,7 @@ } TEST_F(ConstAssertTest, Assert_Null_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.ConstAssert(nullptr); @@ -68,7 +68,7 @@ } TEST_F(ConstAssertTest, Assert_DifferentGenerationID_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/diagnostic_control_test.cc b/src/tint/lang/wgsl/ast/diagnostic_control_test.cc index 514b9ef..9873fb7 100644 --- a/src/tint/lang/wgsl/ast/diagnostic_control_test.cc +++ b/src/tint/lang/wgsl/ast/diagnostic_control_test.cc
@@ -37,7 +37,7 @@ using DiagnosticControlTest = TestHelper; TEST_F(DiagnosticControlTest, Assert_RuleNotNull) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; DiagnosticControl control(wgsl::DiagnosticSeverity::kWarning, nullptr);
diff --git a/src/tint/lang/wgsl/ast/diagnostic_rule_name_test.cc b/src/tint/lang/wgsl/ast/diagnostic_rule_name_test.cc index 94ccc9c..387792c 100644 --- a/src/tint/lang/wgsl/ast/diagnostic_rule_name_test.cc +++ b/src/tint/lang/wgsl/ast/diagnostic_rule_name_test.cc
@@ -41,7 +41,7 @@ } TEST_F(DiagnosticRuleNameTest, Assert_NameNotTemplated) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<ast::DiagnosticRuleName>(b.Ident("name", "a", "b", "c")); @@ -50,7 +50,7 @@ } TEST_F(DiagnosticRuleNameTest, Assert_CategoryNotTemplated) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<ast::DiagnosticRuleName>(b.Ident("name"), b.Ident("category", "a", "b", "c"));
diff --git a/src/tint/lang/wgsl/ast/for_loop_statement_test.cc b/src/tint/lang/wgsl/ast/for_loop_statement_test.cc index a284597..d68ff74 100644 --- a/src/tint/lang/wgsl/ast/for_loop_statement_test.cc +++ b/src/tint/lang/wgsl/ast/for_loop_statement_test.cc
@@ -73,7 +73,7 @@ } TEST_F(ForLoopStatementTest, Assert_Null_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.For(nullptr, nullptr, nullptr, nullptr); @@ -82,7 +82,7 @@ } TEST_F(ForLoopStatementTest, Assert_DifferentGenerationID_Initializer) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -92,7 +92,7 @@ } TEST_F(ForLoopStatementTest, Assert_DifferentGenerationID_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -102,7 +102,7 @@ } TEST_F(ForLoopStatementTest, Assert_DifferentGenerationID_Continuing) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -112,7 +112,7 @@ } TEST_F(ForLoopStatementTest, Assert_DifferentGenerationID_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/function_test.cc b/src/tint/lang/wgsl/ast/function_test.cc index 96f7aa5..316d814 100644 --- a/src/tint/lang/wgsl/ast/function_test.cc +++ b/src/tint/lang/wgsl/ast/function_test.cc
@@ -113,7 +113,7 @@ } TEST_F(FunctionTest, Assert_NullName) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Func(static_cast<Identifier*>(nullptr), tint::Empty, b.ty.void_(), tint::Empty); @@ -122,7 +122,7 @@ } TEST_F(FunctionTest, Assert_TemplatedName) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Func(b.Ident("a", "b"), tint::Empty, b.ty.void_(), tint::Empty); @@ -132,7 +132,7 @@ TEST_F(FunctionTest, Assert_NullParam) { using ParamList = tint::Vector<const Parameter*, 2>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; ParamList params; @@ -144,7 +144,7 @@ } TEST_F(FunctionTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -154,7 +154,7 @@ } TEST_F(FunctionTest, Assert_DifferentGenerationID_Param) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -168,7 +168,7 @@ } TEST_F(FunctionTest, Assert_DifferentGenerationID_Attr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -181,7 +181,7 @@ } TEST_F(FunctionTest, Assert_DifferentGenerationID_ReturnType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -191,7 +191,7 @@ } TEST_F(FunctionTest, Assert_DifferentGenerationID_ReturnAttr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/identifier_expression_test.cc b/src/tint/lang/wgsl/ast/identifier_expression_test.cc index ebf1916..8d3641e 100644 --- a/src/tint/lang/wgsl/ast/identifier_expression_test.cc +++ b/src/tint/lang/wgsl/ast/identifier_expression_test.cc
@@ -57,7 +57,7 @@ } TEST_F(IdentifierExpressionTest, Assert_InvalidSymbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Expr(""); @@ -66,7 +66,7 @@ } TEST_F(IdentifierExpressionTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/identifier_test.cc b/src/tint/lang/wgsl/ast/identifier_test.cc index 8f238e7..2a71304 100644 --- a/src/tint/lang/wgsl/ast/identifier_test.cc +++ b/src/tint/lang/wgsl/ast/identifier_test.cc
@@ -52,7 +52,7 @@ } TEST_F(IdentifierTest, Assert_InvalidSymbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Ident(""); @@ -61,7 +61,7 @@ } TEST_F(IdentifierTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/if_statement_test.cc b/src/tint/lang/wgsl/ast/if_statement_test.cc index 4c738ad..7b37286 100644 --- a/src/tint/lang/wgsl/ast/if_statement_test.cc +++ b/src/tint/lang/wgsl/ast/if_statement_test.cc
@@ -59,7 +59,7 @@ } TEST_F(IfStatementTest, Assert_Null_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.If(nullptr, b.Block()); @@ -68,7 +68,7 @@ } TEST_F(IfStatementTest, Assert_Null_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.If(b.Expr(true), nullptr); @@ -77,7 +77,7 @@ } TEST_F(IfStatementTest, Assert_InvalidElse) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.If(b.Expr(true), b.Block(), b.Else(b.CallStmt(b.Call("foo")))); @@ -86,7 +86,7 @@ } TEST_F(IfStatementTest, Assert_DifferentGenerationID_Cond) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -96,7 +96,7 @@ } TEST_F(IfStatementTest, Assert_DifferentGenerationID_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -106,7 +106,7 @@ } TEST_F(IfStatementTest, Assert_DifferentGenerationID_ElseStatement) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/increment_decrement_statement_test.cc b/src/tint/lang/wgsl/ast/increment_decrement_statement_test.cc index 869b522..70730d0 100644 --- a/src/tint/lang/wgsl/ast/increment_decrement_statement_test.cc +++ b/src/tint/lang/wgsl/ast/increment_decrement_statement_test.cc
@@ -66,7 +66,7 @@ } TEST_F(IncrementDecrementStatementTest, Assert_DifferentGenerationID_Expr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/index_accessor_expression_test.cc b/src/tint/lang/wgsl/ast/index_accessor_expression_test.cc index ff96f2a..7c1527a 100644 --- a/src/tint/lang/wgsl/ast/index_accessor_expression_test.cc +++ b/src/tint/lang/wgsl/ast/index_accessor_expression_test.cc
@@ -60,7 +60,7 @@ } TEST_F(IndexAccessorExpressionTest, Assert_Null_Array) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.IndexAccessor(nullptr, b.Expr("idx")); @@ -69,7 +69,7 @@ } TEST_F(IndexAccessorExpressionTest, Assert_Null_Index) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.IndexAccessor(b.Expr("arr"), nullptr); @@ -78,7 +78,7 @@ } TEST_F(IndexAccessorExpressionTest, Assert_DifferentGenerationID_Array) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -88,7 +88,7 @@ } TEST_F(IndexAccessorExpressionTest, Assert_DifferentGenerationID_Index) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/loop_statement_test.cc b/src/tint/lang/wgsl/ast/loop_statement_test.cc index fc43b31..3afccfa 100644 --- a/src/tint/lang/wgsl/ast/loop_statement_test.cc +++ b/src/tint/lang/wgsl/ast/loop_statement_test.cc
@@ -93,7 +93,7 @@ } TEST_F(LoopStatementTest, Assert_Null_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<LoopStatement>(nullptr, nullptr, tint::Empty); @@ -102,7 +102,7 @@ } TEST_F(LoopStatementTest, Assert_DifferentGenerationID_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -112,7 +112,7 @@ } TEST_F(LoopStatementTest, Assert_DifferentGenerationID_Continuing) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/member_accessor_expression_test.cc b/src/tint/lang/wgsl/ast/member_accessor_expression_test.cc index 22b1c38..28153d1 100644 --- a/src/tint/lang/wgsl/ast/member_accessor_expression_test.cc +++ b/src/tint/lang/wgsl/ast/member_accessor_expression_test.cc
@@ -55,7 +55,7 @@ } TEST_F(MemberAccessorExpressionTest, Assert_Null_Struct) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<MemberAccessorExpression>(nullptr, b.Ident("member")); @@ -64,7 +64,7 @@ } TEST_F(MemberAccessorExpressionTest, Assert_Null_Member) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<MemberAccessorExpression>(b.Expr("struct"), nullptr); @@ -73,7 +73,7 @@ } TEST_F(MemberAccessorExpressionTest, Assert_DifferentGenerationID_Struct) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -83,7 +83,7 @@ } TEST_F(MemberAccessorExpressionTest, Assert_DifferentGenerationID_Member) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -93,7 +93,7 @@ } TEST_F(MemberAccessorExpressionTest, Assert_MemberNotTemplated) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<MemberAccessorExpression>(b.Expr("structure"),
diff --git a/src/tint/lang/wgsl/ast/module_test.cc b/src/tint/lang/wgsl/ast/module_test.cc index e5949a6..36773ed 100644 --- a/src/tint/lang/wgsl/ast/module_test.cc +++ b/src/tint/lang/wgsl/ast/module_test.cc
@@ -52,7 +52,7 @@ } TEST_F(ModuleTest, Assert_Null_GlobalVariable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder builder; builder.AST().AddGlobalVariable(nullptr); @@ -61,7 +61,7 @@ } TEST_F(ModuleTest, Assert_Null_TypeDecl) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder builder; builder.AST().AddTypeDecl(nullptr); @@ -70,7 +70,7 @@ } TEST_F(ModuleTest, Assert_DifferentGenerationID_Function) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -81,7 +81,7 @@ } TEST_F(ModuleTest, Assert_DifferentGenerationID_GlobalVariable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -91,7 +91,7 @@ } TEST_F(ModuleTest, Assert_Null_Function) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder builder; builder.AST().AddFunction(nullptr);
diff --git a/src/tint/lang/wgsl/ast/return_statement_test.cc b/src/tint/lang/wgsl/ast/return_statement_test.cc index ed7c9e0..138a46c 100644 --- a/src/tint/lang/wgsl/ast/return_statement_test.cc +++ b/src/tint/lang/wgsl/ast/return_statement_test.cc
@@ -65,7 +65,7 @@ } TEST_F(ReturnStatementTest, Assert_DifferentGenerationID_Expr) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/struct_member_test.cc b/src/tint/lang/wgsl/ast/struct_member_test.cc index c1e429b..8a76d9f 100644 --- a/src/tint/lang/wgsl/ast/struct_member_test.cc +++ b/src/tint/lang/wgsl/ast/struct_member_test.cc
@@ -58,7 +58,7 @@ } TEST_F(StructMemberTest, Assert_Null_Name) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Member(static_cast<Identifier*>(nullptr), b.ty.i32()); @@ -67,7 +67,7 @@ } TEST_F(StructMemberTest, Assert_Null_Type) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Member("a", Type{}); @@ -76,7 +76,7 @@ } TEST_F(StructMemberTest, Assert_Null_Attribute) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Member("a", b.ty.i32(), tint::Vector{b.MemberSize(4_a), nullptr}); @@ -85,7 +85,7 @@ } TEST_F(StructMemberTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -95,7 +95,7 @@ } TEST_F(StructMemberTest, Assert_DifferentGenerationID_Attribute) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/struct_test.cc b/src/tint/lang/wgsl/ast/struct_test.cc index d0e78de..b47e920 100644 --- a/src/tint/lang/wgsl/ast/struct_test.cc +++ b/src/tint/lang/wgsl/ast/struct_test.cc
@@ -81,7 +81,7 @@ } TEST_F(AstStructTest, Assert_Null_StructMember) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Structure(b.Sym("S"), tint::Vector{b.Member("a", b.ty.i32()), nullptr}, tint::Empty); @@ -90,7 +90,7 @@ } TEST_F(AstStructTest, Assert_Null_Attribute) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Structure(b.Sym("S"), tint::Vector{b.Member("a", b.ty.i32())}, @@ -100,7 +100,7 @@ } TEST_F(AstStructTest, Assert_DifferentGenerationID_StructMember) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -110,7 +110,7 @@ } TEST_F(AstStructTest, Assert_DifferentGenerationID_Attribute) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/switch_statement_test.cc b/src/tint/lang/wgsl/ast/switch_statement_test.cc index fa1fc40..2105a6c 100644 --- a/src/tint/lang/wgsl/ast/switch_statement_test.cc +++ b/src/tint/lang/wgsl/ast/switch_statement_test.cc
@@ -88,7 +88,7 @@ TEST_F(SwitchStatementTest, Assert_Null_Condition) { using CaseStatementList = tint::Vector<const CaseStatement*, 2>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; CaseStatementList cases; @@ -101,7 +101,7 @@ TEST_F(SwitchStatementTest, Assert_Null_CaseStatement) { using CaseStatementList = tint::Vector<const CaseStatement*, 2>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<SwitchStatement>(b.Expr(true), CaseStatementList{nullptr}, tint::Empty, @@ -111,7 +111,7 @@ } TEST_F(SwitchStatementTest, Assert_DifferentGenerationID_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -129,7 +129,7 @@ } TEST_F(SwitchStatementTest, Assert_DifferentGenerationID_CaseStatement) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/templated_identifier_test.cc b/src/tint/lang/wgsl/ast/templated_identifier_test.cc index ded0e81..a47a905 100644 --- a/src/tint/lang/wgsl/ast/templated_identifier_test.cc +++ b/src/tint/lang/wgsl/ast/templated_identifier_test.cc
@@ -63,7 +63,7 @@ } TEST_F(TemplatedIdentifierTest, Assert_InvalidSymbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Expr(""); @@ -72,7 +72,7 @@ } TEST_F(TemplatedIdentifierTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -82,7 +82,7 @@ } TEST_F(TemplatedIdentifierTest, Assert_DifferentGenerationID_TemplateArg) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/transform/add_block_attribute_fuzz.cc b/src/tint/lang/wgsl/ast/transform/add_block_attribute_fuzz.cc index 7898243..b9a5389 100644 --- a/src/tint/lang/wgsl/ast/transform/add_block_attribute_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/add_block_attribute_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = AddBlockAttribute{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "AddBlockAttribute returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "AddBlockAttribute returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point_fuzz.cc b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point_fuzz.cc index cee5702..ff81441 100644 --- a/src/tint/lang/wgsl/ast/transform/add_empty_entry_point_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/add_empty_entry_point_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = AddEmptyEntryPoint{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "AddEmptyEntryPoint returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "AddEmptyEntryPoint returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc index 525f57e..7b1f693 100644 --- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc
@@ -72,6 +72,7 @@ if (auto result = ArrayLengthFromUniform{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { TINT_ICE() << "ArrayLengthFromUniform returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/binding_remapper_fuzz.cc b/src/tint/lang/wgsl/ast/transform/binding_remapper_fuzz.cc index 2f27549..071e83d 100644 --- a/src/tint/lang/wgsl/ast/transform/binding_remapper_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/binding_remapper_fuzz.cc
@@ -78,7 +78,9 @@ DataMap outputs; if (auto result = BindingRemapper{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "BindingRemapper returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "BindingRemapper returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/builtin_polyfill_fuzz.cc b/src/tint/lang/wgsl/ast/transform/builtin_polyfill_fuzz.cc index adce548..8694e3b 100644 --- a/src/tint/lang/wgsl/ast/transform/builtin_polyfill_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/builtin_polyfill_fuzz.cc
@@ -38,7 +38,9 @@ DataMap outputs; if (auto result = BuiltinPolyfill{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "BuiltinPolyfill returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "BuiltinPolyfill returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_fuzz.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_fuzz.cc index 136291a..671a35b 100644 --- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io_fuzz.cc
@@ -40,6 +40,7 @@ if (auto result = CanonicalizeEntryPointIO{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { TINT_ICE() << "CanonicalizeEntryPointIO returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc index 2854240..6de6553 100644 --- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc
@@ -27,6 +27,7 @@ #include "src/tint/cmd/fuzz/wgsl/fuzz.h" #include "src/tint/lang/wgsl/ast/transform/clamp_frag_depth.h" +#include "src/tint/lang/wgsl/program/program.h" namespace tint::ast::transform { namespace { @@ -49,7 +50,9 @@ DataMap outputs; if (auto result = ClampFragDepth{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "ClampFragDepth returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "ClampFragDepth returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/demote_to_helper_fuzz.cc b/src/tint/lang/wgsl/ast/transform/demote_to_helper_fuzz.cc index 180ab67..16e3833 100644 --- a/src/tint/lang/wgsl/ast/transform/demote_to_helper_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/demote_to_helper_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = DemoteToHelper{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "DemoteToHelper returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "DemoteToHelper returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access_fuzz.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access_fuzz.cc index 5123798..e1d1893 100644 --- a/src/tint/lang/wgsl/ast/transform/direct_variable_access_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access_fuzz.cc
@@ -40,6 +40,7 @@ if (auto result = DirectVariableAccess{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { TINT_ICE() << "DirectVariableAccess returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis_fuzz.cc b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis_fuzz.cc index 378eb5f..1f5e153 100644 --- a/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/disable_uniformity_analysis_fuzz.cc
@@ -36,6 +36,7 @@ if (auto result = DisableUniformityAnalysis{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "DisableUniformityAnalysis returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_fuzz.cc b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_fuzz.cc index d017dbb..7776590 100644 --- a/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/expand_compound_assignment_fuzz.cc
@@ -36,6 +36,7 @@ if (auto result = ExpandCompoundAssignment{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "ExpandCompoundAssignment returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/first_index_offset_fuzz.cc b/src/tint/lang/wgsl/ast/transform/first_index_offset_fuzz.cc index b7ed12e..2f3a607 100644 --- a/src/tint/lang/wgsl/ast/transform/first_index_offset_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/first_index_offset_fuzz.cc
@@ -59,7 +59,9 @@ DataMap outputs; if (auto result = FirstIndexOffset{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "FirstIndexOffset returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "FirstIndexOffset returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/fold_constants_fuzz.cc b/src/tint/lang/wgsl/ast/transform/fold_constants_fuzz.cc index 4c48a90..668f7a5 100644 --- a/src/tint/lang/wgsl/ast/transform/fold_constants_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/fold_constants_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = FoldConstants{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "FoldConstants returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "FoldConstants returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc index caddb35..4c6efeb 100644 --- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_fuzz.cc
@@ -95,6 +95,7 @@ if (auto result = MultiplanarExternalTexture{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { TINT_ICE() << "MultiplanarExternalTexture returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/offset_first_index_fuzz.cc b/src/tint/lang/wgsl/ast/transform/offset_first_index_fuzz.cc index 04c13a2..f4c9faf 100644 --- a/src/tint/lang/wgsl/ast/transform/offset_first_index_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/offset_first_index_fuzz.cc
@@ -55,7 +55,9 @@ DataMap outputs; if (auto result = OffsetFirstIndex{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "OffsetFirstIndex returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "OffsetFirstIndex returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/preserve_padding_fuzz.cc b/src/tint/lang/wgsl/ast/transform/preserve_padding_fuzz.cc index 486a65a..963da24 100644 --- a/src/tint/lang/wgsl/ast/transform/preserve_padding_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/preserve_padding_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = PreservePadding{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "PreservePadding returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "PreservePadding returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let_fuzz.cc b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let_fuzz.cc index bf928e6..ed3518f 100644 --- a/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/promote_initializers_to_let_fuzz.cc
@@ -36,6 +36,7 @@ if (auto result = PromoteInitializersToLet{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "PromoteInitializersToLet returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_fuzz.cc b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_fuzz.cc index 57a15f8..423d3ed 100644 --- a/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/promote_side_effects_to_decl_fuzz.cc
@@ -54,6 +54,7 @@ if (auto result = PromoteSideEffectsToDecl{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "PromoteSideEffectsToDecl returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/remove_continue_in_switch_fuzz.cc b/src/tint/lang/wgsl/ast/transform/remove_continue_in_switch_fuzz.cc index 049b57c..b49f1ed 100644 --- a/src/tint/lang/wgsl/ast/transform/remove_continue_in_switch_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/remove_continue_in_switch_fuzz.cc
@@ -36,6 +36,7 @@ if (auto result = RemoveContinueInSwitch{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "RemoveContinueInSwitch returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/remove_phonies_fuzz.cc b/src/tint/lang/wgsl/ast/transform/remove_phonies_fuzz.cc index 92faf38..823fc6f 100644 --- a/src/tint/lang/wgsl/ast/transform/remove_phonies_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/remove_phonies_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = RemovePhonies{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "RemovePhonies returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "RemovePhonies returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements_fuzz.cc b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements_fuzz.cc index c61d8aa..c803823 100644 --- a/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/remove_unreachable_statements_fuzz.cc
@@ -52,6 +52,7 @@ if (auto result = RemoveUnreachableStatements{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "RemoveUnreachableStatements returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/single_entry_point_fuzz.cc b/src/tint/lang/wgsl/ast/transform/single_entry_point_fuzz.cc index 5cec2bb..5df7964 100644 --- a/src/tint/lang/wgsl/ast/transform/single_entry_point_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/single_entry_point_fuzz.cc
@@ -56,7 +56,9 @@ DataMap outputs; if (auto result = SingleEntryPoint{}.Apply(program, inputs, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "SingleEntryPoint returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "SingleEntryPoint returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/std140_fuzz.cc b/src/tint/lang/wgsl/ast/transform/std140_fuzz.cc index aedf74c..3606f96 100644 --- a/src/tint/lang/wgsl/ast/transform/std140_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/std140_fuzz.cc
@@ -57,7 +57,9 @@ DataMap outputs; if (auto result = Std140{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "Std140 returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "Std140 returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/unshadow_fuzz.cc b/src/tint/lang/wgsl/ast/transform/unshadow_fuzz.cc index 064ffaf..752a501 100644 --- a/src/tint/lang/wgsl/ast/transform/unshadow_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/unshadow_fuzz.cc
@@ -35,7 +35,9 @@ DataMap outputs; if (auto result = Unshadow{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { - TINT_ICE() << "Unshadow returned invalid program:\n" << result->Diagnostics(); + TINT_ICE() << "Unshadow returned invalid program:\n" + << Program::printer(*result) << "\n" + << result->Diagnostics(); } } }
diff --git a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_fuzz.cc b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_fuzz.cc index 407538c..f1c6e2d 100644 --- a/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/vectorize_scalar_matrix_initializers_fuzz.cc
@@ -36,6 +36,7 @@ if (auto result = VectorizeScalarMatrixInitializers{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "VectorizeScalarMatrixInitializers returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory_fuzz.cc b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory_fuzz.cc index 2e3650d..b9c569d 100644 --- a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory_fuzz.cc +++ b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory_fuzz.cc
@@ -41,6 +41,7 @@ if (auto result = ZeroInitWorkgroupMemory{}.Apply(program, DataMap{}, outputs)) { if (!result->IsValid()) { TINT_ICE() << "ZeroInitWorkgroupMemory returned invalid program:\n" + << Program::printer(*result) << "\n" << result->Diagnostics(); } }
diff --git a/src/tint/lang/wgsl/ast/unary_op_expression_test.cc b/src/tint/lang/wgsl/ast/unary_op_expression_test.cc index 3ce46c9..45b2c52 100644 --- a/src/tint/lang/wgsl/ast/unary_op_expression_test.cc +++ b/src/tint/lang/wgsl/ast/unary_op_expression_test.cc
@@ -58,7 +58,7 @@ } TEST_F(UnaryOpExpressionTest, Assert_Null_Expression) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<UnaryOpExpression>(core::UnaryOp::kNot, nullptr); @@ -67,7 +67,7 @@ } TEST_F(UnaryOpExpressionTest, Assert_DifferentGenerationID_Expression) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/variable_decl_statement_test.cc b/src/tint/lang/wgsl/ast/variable_decl_statement_test.cc index 1e3588d..3b4640c 100644 --- a/src/tint/lang/wgsl/ast/variable_decl_statement_test.cc +++ b/src/tint/lang/wgsl/ast/variable_decl_statement_test.cc
@@ -58,7 +58,7 @@ } TEST_F(VariableDeclStatementTest, Assert_Null_Variable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.create<VariableDeclStatement>(nullptr); @@ -67,7 +67,7 @@ } TEST_F(VariableDeclStatementTest, Assert_DifferentGenerationID_Variable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/variable_test.cc b/src/tint/lang/wgsl/ast/variable_test.cc index c85ec93..c24ab00 100644 --- a/src/tint/lang/wgsl/ast/variable_test.cc +++ b/src/tint/lang/wgsl/ast/variable_test.cc
@@ -76,7 +76,7 @@ } TEST_F(VariableTest, Assert_Null_Name) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Var(static_cast<Identifier*>(nullptr), b.ty.i32()); @@ -85,7 +85,7 @@ } TEST_F(VariableTest, Assert_DifferentGenerationID_Symbol) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -95,7 +95,7 @@ } TEST_F(VariableTest, Assert_DifferentGenerationID_Initializer) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/ast/while_statement_test.cc b/src/tint/lang/wgsl/ast/while_statement_test.cc index e678d12..be9e07e 100644 --- a/src/tint/lang/wgsl/ast/while_statement_test.cc +++ b/src/tint/lang/wgsl/ast/while_statement_test.cc
@@ -65,7 +65,7 @@ } TEST_F(WhileStatementTest, Assert_Null_Cond) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; auto* body = b.Block(); @@ -75,7 +75,7 @@ } TEST_F(WhileStatementTest, Assert_Null_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; auto* cond = @@ -86,7 +86,7 @@ } TEST_F(WhileStatementTest, Assert_DifferentGenerationID_Condition) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2; @@ -96,7 +96,7 @@ } TEST_F(WhileStatementTest, Assert_DifferentGenerationID_Body) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b1; ProgramBuilder b2;
diff --git a/src/tint/lang/wgsl/program/clone_context_test.cc b/src/tint/lang/wgsl/program/clone_context_test.cc index 936cb6b..527a6a2 100644 --- a/src/tint/lang/wgsl/program/clone_context_test.cc +++ b/src/tint/lang/wgsl/program/clone_context_test.cc
@@ -1051,7 +1051,7 @@ TEST_F(ProgramCloneContextNodeTest, CloneWithReplaceAll_SameTypeTwice) { std::string node_name = TypeInfo::Of<Node>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1068,7 +1068,7 @@ std::string node_name = TypeInfo::Of<Node>().name; std::string replaceable_name = TypeInfo::Of<Replaceable>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1085,7 +1085,7 @@ std::string node_name = TypeInfo::Of<Node>().name; std::string replaceable_name = TypeInfo::Of<Replaceable>().name; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1101,7 +1101,7 @@ using ProgramCloneContextTest = ::testing::Test; TEST_F(ProgramCloneContextTest, CloneWithReplaceAll_SymbolsTwice) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder cloned; Program original; @@ -1207,7 +1207,7 @@ } TEST_F(ProgramCloneContextTest, GenerationIDs_Clone_ObjectNotOwnedBySrc) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder dst; Program src(ProgramBuilder{}); @@ -1220,7 +1220,7 @@ } TEST_F(ProgramCloneContextTest, GenerationIDs_Clone_ObjectNotOwnedByDst) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder dst; Program src(ProgramBuilder{});
diff --git a/src/tint/lang/wgsl/program/program_test.cc b/src/tint/lang/wgsl/program/program_test.cc index dff5b23..4c91d2d 100644 --- a/src/tint/lang/wgsl/program/program_test.cc +++ b/src/tint/lang/wgsl/program/program_test.cc
@@ -65,7 +65,7 @@ } TEST_F(ProgramTest, Assert_NullGlobalVariable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.AST().AddGlobalVariable(nullptr); @@ -74,7 +74,7 @@ } TEST_F(ProgramTest, Assert_NullTypeDecl) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.AST().AddTypeDecl(nullptr); @@ -83,7 +83,7 @@ } TEST_F(ProgramTest, Assert_Null_Function) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.AST().AddFunction(nullptr);
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc index 0752652..33f1999 100644 --- a/src/tint/lang/wgsl/reader/lower/lower.cc +++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -198,19 +198,16 @@ // call workgroupBarrier b.InsertBefore(call, [&] { b.Call(ty.void_(), core::BuiltinFn::kWorkgroupBarrier); - auto* load = b.Load(call->Args()[0]); - call->Result(0)->ReplaceAllUsesWith(load->Result(0)); + b.LoadWithResult(call->DetachResult(), call->Args()[0]); b.Call(ty.void_(), core::BuiltinFn::kWorkgroupBarrier); }); break; } default: { Vector<core::ir::Value*, 8> args(call->Args()); - auto* replacement = - mod.allocators.instructions.Create<core::ir::CoreBuiltinCall>( - call->Result(0), Convert(call->Func()), std::move(args)); + auto* replacement = b.CallWithResult(call->DetachResult(), + Convert(call->Func()), std::move(args)); call->ReplaceWith(replacement); - call->ClearResults(); break; } }
diff --git a/src/tint/lang/wgsl/resolver/resolver_test.cc b/src/tint/lang/wgsl/resolver/resolver_test.cc index 152cbbe..c73a75b 100644 --- a/src/tint/lang/wgsl/resolver/resolver_test.cc +++ b/src/tint/lang/wgsl/resolver/resolver_test.cc
@@ -2087,7 +2087,7 @@ } TEST_F(ResolverTest, ASTNodeNotReached) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.Ident("ident"); @@ -2098,7 +2098,7 @@ } TEST_F(ResolverTest, ASTNodeReachedTwice) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; auto* expr = b.Expr(1_i);
diff --git a/src/tint/lang/wgsl/resolver/validation_test.cc b/src/tint/lang/wgsl/resolver/validation_test.cc index 5d0f51c..9c306f7 100644 --- a/src/tint/lang/wgsl/resolver/validation_test.cc +++ b/src/tint/lang/wgsl/resolver/validation_test.cc
@@ -133,7 +133,7 @@ } TEST_F(ResolverValidationTest, UnhandledStmt) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.WrapInFunction(b.create<FakeStmt>()); @@ -164,7 +164,7 @@ } TEST_F(ResolverValidationTest, Expr_ErrUnknownExprType) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { ProgramBuilder b; b.WrapInFunction(b.create<FakeExpr>());
diff --git a/src/tint/lang/wgsl/writer/raise/raise.cc b/src/tint/lang/wgsl/writer/raise/raise.cc index c576ac4..d06cb72 100644 --- a/src/tint/lang/wgsl/writer/raise/raise.cc +++ b/src/tint/lang/wgsl/writer/raise/raise.cc
@@ -30,6 +30,7 @@ #include <utility> #include "src/tint/lang/core/builtin_fn.h" +#include "src/tint/lang/core/ir/builder.h" #include "src/tint/lang/core/ir/core_builtin_call.h" #include "src/tint/lang/core/ir/load.h" #include "src/tint/lang/core/type/pointer.h" @@ -175,16 +176,15 @@ TINT_ICE() << "unhandled builtin function: " << fn; } -void ReplaceBuiltinFnCall(core::ir::Module& mod, core::ir::CoreBuiltinCall* call) { +void ReplaceBuiltinFnCall(core::ir::Builder& b, core::ir::CoreBuiltinCall* call) { Vector<core::ir::Value*, 8> args(call->Args()); - auto* replacement = mod.allocators.instructions.Create<wgsl::ir::BuiltinCall>( - call->Result(0), Convert(call->Func()), std::move(args)); + auto* replacement = b.CallWithResult<wgsl::ir::BuiltinCall>( + call->DetachResult(), Convert(call->Func()), std::move(args)); call->ReplaceWith(replacement); - call->ClearResults(); call->Destroy(); } -void ReplaceWorkgroupBarrier(core::ir::Module& mod, core::ir::CoreBuiltinCall* call) { +void ReplaceWorkgroupBarrier(core::ir::Builder& b, core::ir::CoreBuiltinCall* call) { // Pattern match: // call workgroupBarrier // %value = load &ptr @@ -196,14 +196,14 @@ if (!load || load->From()->Type()->As<core::type::Pointer>()->AddressSpace() != core::AddressSpace::kWorkgroup) { // No match - ReplaceBuiltinFnCall(mod, call); + ReplaceBuiltinFnCall(b, call); return; } auto* post_load = As<core::ir::CoreBuiltinCall>(load->next.Get()); if (!post_load || post_load->Func() != core::BuiltinFn::kWorkgroupBarrier) { // No match - ReplaceBuiltinFnCall(mod, call); + ReplaceBuiltinFnCall(b, call); return; } @@ -212,24 +212,24 @@ call->Destroy(); // Replace load with workgroupUniformLoad - auto* replacement = mod.allocators.instructions.Create<wgsl::ir::BuiltinCall>( - load->Result(0), wgsl::BuiltinFn::kWorkgroupUniformLoad, Vector{load->From()}); + auto* replacement = b.CallWithResult<wgsl::ir::BuiltinCall>( + load->DetachResult(), wgsl::BuiltinFn::kWorkgroupUniformLoad, Vector{load->From()}); load->ReplaceWith(replacement); - load->ClearResults(); load->Destroy(); } } // namespace Result<SuccessType> Raise(core::ir::Module& mod) { + core::ir::Builder b{mod}; for (auto* inst : mod.Instructions()) { if (auto* call = inst->As<core::ir::CoreBuiltinCall>()) { switch (call->Func()) { case core::BuiltinFn::kWorkgroupBarrier: - ReplaceWorkgroupBarrier(mod, call); + ReplaceWorkgroupBarrier(b, call); break; default: - ReplaceBuiltinFnCall(mod, call); + ReplaceBuiltinFnCall(b, call); break; } }
diff --git a/src/tint/lang/wgsl/writer/writer.cc b/src/tint/lang/wgsl/writer/writer.cc index 92819c0..4013848 100644 --- a/src/tint/lang/wgsl/writer/writer.cc +++ b/src/tint/lang/wgsl/writer/writer.cc
@@ -68,6 +68,14 @@ } Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options) { + auto res = ProgramFromIR(module, options); + if (res != Success) { + return res.Failure(); + } + return Generate(res.Move(), Options{}); +} + +Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options) { // core-dialect -> WGSL-dialect if (auto res = Raise(module); res != Success) { return res.Failure(); @@ -78,7 +86,7 @@ return Failure{program.Diagnostics()}; } - return Generate(program, Options{}); + return program; } } // namespace tint::wgsl::writer
diff --git a/src/tint/lang/wgsl/writer/writer.h b/src/tint/lang/wgsl/writer/writer.h index 604ace2..a79ea4d 100644 --- a/src/tint/lang/wgsl/writer/writer.h +++ b/src/tint/lang/wgsl/writer/writer.h
@@ -56,6 +56,11 @@ /// @returns the resulting WGSL, or failure Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options); +/// Generate a Program from a core-dialect ir::Module. +/// @param module the core-dialect ir::Module. +/// @returns the resulting Program, or failure +Result<Program> ProgramFromIR(core::ir::Module& module, const ProgramOptions& options); + } // namespace tint::wgsl::writer #endif // SRC_TINT_LANG_WGSL_WRITER_WRITER_H_
diff --git a/src/tint/utils/containers/vector_test.cc b/src/tint/utils/containers/vector_test.cc index f437ddb..c509cbb 100644 --- a/src/tint/utils/containers/vector_test.cc +++ b/src/tint/utils/containers/vector_test.cc
@@ -2110,7 +2110,7 @@ } TEST(TintVectorTest, AssertOOBs) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Vector vec{1}; [[maybe_unused]] int i = vec[1]; @@ -2121,7 +2121,7 @@ #if TINT_VECTOR_MUTATION_CHECKS_ENABLED TEST(TintVectorTest, AssertPushWhileIterating) { using V = Vector<int, 4>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { V vec; vec.Push(1); @@ -2136,7 +2136,7 @@ TEST(TintVectorTest, AssertPopWhileIterating) { using V = Vector<int, 4>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { V vec; vec.Push(1); @@ -2151,7 +2151,7 @@ TEST(TintVectorTest, AssertClearWhileIterating) { using V = Vector<int, 4>; - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { V vec; vec.Push(1); @@ -2446,7 +2446,7 @@ } TEST(TintVectorRefTest, AssertOOBs) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Vector vec{1}; const VectorRef<int> vec_ref(vec);
diff --git a/src/tint/utils/diagnostic/diagnostic.h b/src/tint/utils/diagnostic/diagnostic.h index 6471b5a..4b627df 100644 --- a/src/tint/utils/diagnostic/diagnostic.h +++ b/src/tint/utils/diagnostic/diagnostic.h
@@ -185,6 +185,11 @@ return Add(std::move(error)); } + /// Ensures that the diagnostic list can fit an additional @p count diagnostics without + /// resizing. This is useful for ensuring that a reference returned by the AddX() methods is not + /// invalidated after another Add(). + void ReserveAdditional(size_t count) { entries_.Reserve(entries_.Length() + count); } + /// @returns true iff the diagnostic list contains errors diagnostics (or of /// higher severity). bool ContainsErrors() const { return error_count_ > 0; }
diff --git a/src/tint/utils/ice/ice_test.cc b/src/tint/utils/ice/ice_test.cc index 0afa5de..d0a992c 100644 --- a/src/tint/utils/ice/ice_test.cc +++ b/src/tint/utils/ice/ice_test.cc
@@ -33,7 +33,7 @@ namespace { TEST(ICETest_AssertTrue_Test, Unreachable) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { if ((true)) { TINT_UNREACHABLE(); @@ -47,7 +47,7 @@ } TEST(ICETest_AssertTrue_Test, AssertFalse) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { if ((true)) { TINT_ASSERT(false);
diff --git a/src/tint/utils/rtti/switch_test.cc b/src/tint/utils/rtti/switch_test.cc index 4ae0f95..e4241ff 100644 --- a/src/tint/utils/rtti/switch_test.cc +++ b/src/tint/utils/rtti/switch_test.cc
@@ -230,7 +230,7 @@ } TEST(Castable, SwitchMustMatch_NoMatchWithoutReturnValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { std::unique_ptr<Animal> frog = std::make_unique<Frog>(); Switch( @@ -243,7 +243,7 @@ } TEST(Castable, SwitchMustMatch_NoMatchWithReturnValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { std::unique_ptr<Animal> frog = std::make_unique<Frog>(); int res = Switch( @@ -257,7 +257,7 @@ } TEST(Castable, SwitchMustMatch_NullptrWithoutReturnValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { Switch( static_cast<CastableBase*>(nullptr), // @@ -269,7 +269,7 @@ } TEST(Castable, SwitchMustMatch_NullptrWithReturnValue) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { int res = Switch( static_cast<CastableBase*>(nullptr), //
diff --git a/src/tint/utils/symbol/symbol_table_test.cc b/src/tint/utils/symbol/symbol_table_test.cc index 8421e1f..9328640 100644 --- a/src/tint/utils/symbol/symbol_table_test.cc +++ b/src/tint/utils/symbol/symbol_table_test.cc
@@ -50,7 +50,7 @@ } TEST_F(SymbolTableTest, AssertsForBlankString) { - EXPECT_DEATH( + EXPECT_DEATH_IF_SUPPORTED( { auto generation_id = GenerationID::New(); SymbolTable s{generation_id};