[ir] Add concept of overrides to the IR. This CL adds an `override` instruction into the IR. The `program_to_ir` will convert an `Override` as needed into the given instruction. Note, this will then immediately fail validation as only `var` is currently permitted in the root block. Bug: 374971092 Change-Id: I40b4bd33e020a222707a8cb9c3584abe8b327112 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/212094 Reviewed-by: James Price <jrprice@google.com> Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/BUILD.bazel b/src/tint/lang/core/ir/BUILD.bazel index fe29cfa..b12f3ee 100644 --- a/src/tint/lang/core/ir/BUILD.bazel +++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -77,6 +77,7 @@ "multi_in_block.cc", "next_iteration.cc", "operand_instruction.cc", + "override.cc", "return.cc", "store.cc", "store_vector_element.cc", @@ -132,6 +133,7 @@ "multi_in_block.h", "next_iteration.h", "operand_instruction.h", + "override.h", "referenced_functions.h", "referenced_module_vars.h", "return.h", @@ -209,6 +211,7 @@ "multi_in_block_test.cc", "next_iteration_test.cc", "operand_instruction_test.cc", + "override_test.cc", "referenced_functions_test.cc", "referenced_module_vars_test.cc", "return_test.cc",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake index 3fec311..c833689 100644 --- a/src/tint/lang/core/ir/BUILD.cmake +++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -119,6 +119,8 @@ lang/core/ir/next_iteration.h lang/core/ir/operand_instruction.cc lang/core/ir/operand_instruction.h + lang/core/ir/override.cc + lang/core/ir/override.h lang/core/ir/referenced_functions.h lang/core/ir/referenced_module_vars.h lang/core/ir/return.cc @@ -213,6 +215,7 @@ lang/core/ir/multi_in_block_test.cc lang/core/ir/next_iteration_test.cc lang/core/ir/operand_instruction_test.cc + lang/core/ir/override_test.cc lang/core/ir/referenced_functions_test.cc lang/core/ir/referenced_module_vars_test.cc lang/core/ir/return_test.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn index 6e2695c..c4c2f29 100644 --- a/src/tint/lang/core/ir/BUILD.gn +++ b/src/tint/lang/core/ir/BUILD.gn
@@ -122,6 +122,8 @@ "next_iteration.h", "operand_instruction.cc", "operand_instruction.h", + "override.cc", + "override.h", "referenced_functions.h", "referenced_module_vars.h", "return.cc", @@ -210,6 +212,7 @@ "multi_in_block_test.cc", "next_iteration_test.cc", "operand_instruction_test.cc", + "override_test.cc", "referenced_functions_test.cc", "referenced_module_vars_test.cc", "return_test.cc",
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h index a72b4f9..ec9b23a 100644 --- a/src/tint/lang/core/ir/builder.h +++ b/src/tint/lang/core/ir/builder.h
@@ -59,6 +59,7 @@ #include "src/tint/lang/core/ir/module.h" #include "src/tint/lang/core/ir/multi_in_block.h" #include "src/tint/lang/core/ir/next_iteration.h" +#include "src/tint/lang/core/ir/override.h" #include "src/tint/lang/core/ir/return.h" #include "src/tint/lang/core/ir/store.h" #include "src/tint/lang/core/ir/store_vector_element.h" @@ -1744,6 +1745,32 @@ }); } + /// Creates a new `override` declaration + /// @param name the override name + /// @param value the override value + /// @returns the instruction + template <typename VALUE> + ir::Override* Override(std::string_view name, VALUE&& value) { + auto* val = Value(std::forward<VALUE>(value)); + if (DAWN_UNLIKELY(!val)) { + TINT_ASSERT(val); + return nullptr; + } + auto* override = Append(ir.CreateInstruction<ir::Override>(InstructionResult(val->Type()))); + override->SetInitializer(val); + ir.SetName(override->Result(0), name); + return override; + } + + /// Creates a new `override` declaration, with an unassigned value + /// @param type the override type + /// @returns the instruction + ir::Override* Override(const type::Type* type) { + auto* override = ir.CreateInstruction<ir::Override>(InstructionResult(type)); + Append(override); + return override; + } + /// The IR module. Module& ir;
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc index dac70e0..4d9091a 100644 --- a/src/tint/lang/core/ir/disassembler.cc +++ b/src/tint/lang/core/ir/disassembler.cc
@@ -53,6 +53,7 @@ #include "src/tint/lang/core/ir/member_builtin_call.h" #include "src/tint/lang/core/ir/multi_in_block.h" #include "src/tint/lang/core/ir/next_iteration.h" +#include "src/tint/lang/core/ir/override.h" #include "src/tint/lang/core/ir/return.h" #include "src/tint/lang/core/ir/store.h" #include "src/tint/lang/core/ir/store_vector_element.h" @@ -515,6 +516,16 @@ EmitOperandList(c, UserCall::kArgsOperandOffset); } }, + [&](const Override* o) { + EmitValueWithType(o); + out_ << " = "; + EmitInstructionName(o); + if (o->Initializer()) { + out_ << ", "; + EmitOperand(o, Var::kInitializerOperandOffset); + } + out_ << " @id(" << o->OverrideId().value << ")"; + }, [&](const Var* v) { EmitValueWithType(v); out_ << " = ";
diff --git a/src/tint/lang/core/ir/instruction_result.h b/src/tint/lang/core/ir/instruction_result.h index 2b6b442..dc48935 100644 --- a/src/tint/lang/core/ir/instruction_result.h +++ b/src/tint/lang/core/ir/instruction_result.h
@@ -29,7 +29,6 @@ #define SRC_TINT_LANG_CORE_IR_INSTRUCTION_RESULT_H_ #include "src/tint/lang/core/ir/value.h" -#include "src/tint/utils/text/string_stream.h" namespace tint::core::ir { @@ -46,9 +45,6 @@ /// @copydoc Value::Destroy void Destroy() override; - /// @returns the type of the value - const core::type::Type* Type() const override { return type_; } - /// @copydoc Value::Clone() InstructionResult* Clone(CloneContext& ctx) override; @@ -56,6 +52,9 @@ /// @param type the new type of the value void SetType(const core::type::Type* type) { type_ = type; } + /// @returns the type of the value + const core::type::Type* Type() const override { return type_; } + /// Sets the instruction for this value /// @param inst the instruction to set void SetInstruction(Instruction* inst) { instruction_ = inst; }
diff --git a/src/tint/lang/core/ir/override.cc b/src/tint/lang/core/ir/override.cc new file mode 100644 index 0000000..cee6ede --- /dev/null +++ b/src/tint/lang/core/ir/override.cc
@@ -0,0 +1,74 @@ +// 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/override.h" + +#include "src/tint/lang/core/ir/clone_context.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/store.h" +#include "src/tint/lang/core/type/pointer.h" +#include "src/tint/utils/ice/ice.h" + +TINT_INSTANTIATE_TYPEINFO(tint::core::ir::Override); + +namespace tint::core::ir { + +Override::Override(Id id) : Base(id) {} + +Override::Override(Id id, InstructionResult* result) : Base(id) { + // Default to no initializer. + AddOperand(Override::kInitializerOperandOffset, nullptr); + AddResult(result); +} + +Override::~Override() = default; + +void Override::SetInitializer(Value* initializer) { + SetOperand(Override::kInitializerOperandOffset, initializer); +} + +Override* Override::Clone(CloneContext& ctx) { + auto* new_result = ctx.Clone(Result(0)); + TINT_ASSERT(new_result); + + auto* new_override = ctx.ir.CreateInstruction<Override>(new_result); + + new_override->id_ = id_; + + if (auto* init = Initializer()) { + new_override->SetInitializer(ctx.Clone(init)); + } + new_override->SetOverrideId(override_id_); + + auto name = ctx.ir.NameOf(this); + if (name.IsValid()) { + ctx.ir.SetName(new_override, name.Name()); + } + return new_override; +} + +} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/override.h b/src/tint/lang/core/ir/override.h new file mode 100644 index 0000000..f69cc46 --- /dev/null +++ b/src/tint/lang/core/ir/override.h
@@ -0,0 +1,85 @@ +// 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_OVERRIDE_H_ +#define SRC_TINT_LANG_CORE_IR_OVERRIDE_H_ + +#include <string> + +#include "src/tint/api/common/override_id.h" +#include "src/tint/lang/core/ir/operand_instruction.h" +#include "src/tint/utils/rtti/castable.h" + +namespace tint::core::ir { + +/// A var instruction in the IR. +class Override final : public Castable<Override, OperandInstruction<1, 1>> { + public: + /// The offset in Operands() for the initializer + static constexpr size_t kInitializerOperandOffset = 0; + + /// The fixed number of results returned by this instruction + static constexpr size_t kNumResults = 1; + + /// Constructor (no results, no operands) + /// @param id the instruction id + explicit Override(Id id); + + /// Constructor + /// @param id the instruction id + /// @param result the result value + Override(Id id, InstructionResult* result); + + ~Override() override; + + /// @copydoc Instruction::Clone() + Override* Clone(CloneContext& ctx) override; + + /// Sets the var initializer + /// @param initializer the initializer + void SetInitializer(Value* initializer); + /// @returns the initializer + Value* Initializer() { return Operand(kInitializerOperandOffset); } + /// @returns the initializer + const Value* Initializer() const { return Operand(kInitializerOperandOffset); } + + /// Sets the override Id attribute + /// @param id the override id + void SetOverrideId(OverrideId id) { override_id_ = id; } + /// @returns the override ID + tint::OverrideId OverrideId() const { return override_id_; } + + /// @returns the friendly name for the instruction + std::string FriendlyName() const override { return "override"; } + + private: + tint::OverrideId override_id_; +}; + +} // namespace tint::core::ir + +#endif // SRC_TINT_LANG_CORE_IR_OVERRIDE_H_
diff --git a/src/tint/lang/core/ir/override_test.cc b/src/tint/lang/core/ir/override_test.cc new file mode 100644 index 0000000..638a14b --- /dev/null +++ b/src/tint/lang/core/ir/override_test.cc
@@ -0,0 +1,100 @@ +// 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/var.h" + +#include "gmock/gmock.h" +#include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/instruction.h" +#include "src/tint/lang/core/ir/ir_helper_test.h" + +namespace tint::core::ir { +namespace { + +using namespace tint::core::fluent_types; // NOLINT +using namespace tint::core::number_suffixes; // NOLINT + +using IR_OverrideTest = IRTestHelper; +using IR_OverrideDeathTest = IR_OverrideTest; + +TEST_F(IR_OverrideDeathTest, Fail_NullType) { + EXPECT_DEATH_IF_SUPPORTED( + { + Module mod; + Builder b{mod}; + b.Override(nullptr); + }, + "internal compiler error"); +} + +TEST_F(IR_OverrideTest, Results) { + auto* var = b.Override(ty.i32()); + EXPECT_EQ(var->Results().Length(), 1u); + EXPECT_TRUE(var->Result(0)->Is<InstructionResult>()); + EXPECT_EQ(var->Result(0)->Instruction(), var); +} + +TEST_F(IR_OverrideTest, Initializer_Usage) { + Module mod; + Builder b{mod}; + auto* var = b.Override(ty.f32()); + auto* init = b.Constant(1_f); + var->SetInitializer(init); + + EXPECT_THAT(init->UsagesUnsorted(), testing::UnorderedElementsAre(Usage{var, 0u})); + var->SetInitializer(nullptr); + EXPECT_FALSE(init->IsUsed()); +} + +TEST_F(IR_OverrideTest, Clone) { + auto* v = b.Override(ty.f32()); + v->SetInitializer(b.Constant(4_f)); + v->SetOverrideId(OverrideId{2}); + + auto* new_v = clone_ctx.Clone(v); + + EXPECT_NE(v, new_v); + ASSERT_NE(nullptr, new_v->Result(0)); + EXPECT_NE(v->Result(0), new_v->Result(0)); + EXPECT_EQ(new_v->Result(0)->Type(), mod.Types().f32()); + + ASSERT_NE(nullptr, new_v->Initializer()); + auto new_val = new_v->Initializer()->As<Constant>()->Value(); + ASSERT_TRUE(new_val->Is<core::constant::Scalar<f32>>()); + EXPECT_FLOAT_EQ(4_f, new_val->As<core::constant::Scalar<f32>>()->ValueAs<f32>()); + EXPECT_EQ(OverrideId{2u}, new_v->OverrideId()); +} + +TEST_F(IR_OverrideTest, CloneWithName) { + auto* v = b.Override("v", b.Constant(1_f)); + auto* new_v = clone_ctx.Clone(v); + + EXPECT_EQ(std::string("v"), mod.NameOf(new_v).Name()); +} + +} // namespace +} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc index 6cc6af1..5c3b6f5 100644 --- a/src/tint/lang/core/ir/validator.cc +++ b/src/tint/lang/core/ir/validator.cc
@@ -64,6 +64,7 @@ #include "src/tint/lang/core/ir/member_builtin_call.h" #include "src/tint/lang/core/ir/multi_in_block.h" #include "src/tint/lang/core/ir/next_iteration.h" +#include "src/tint/lang/core/ir/override.h" #include "src/tint/lang/core/ir/referenced_module_vars.h" #include "src/tint/lang/core/ir/return.h" #include "src/tint/lang/core/ir/store.h" @@ -874,6 +875,10 @@ /// @param blk the block void CheckRootBlock(const Block* blk); + /// Validates the given instruction is only used in the root block. + /// @param inst the instruction + void CheckOnlyUsedInRootBlock(const Instruction* inst); + /// Validates the given function /// @param func the function to validate void CheckFunction(const Function* func); @@ -974,6 +979,10 @@ /// @param inst the instruction to validate void CheckInstruction(const Instruction* inst); + /// Validates the given override + /// @param o the override to validate + void CheckOverride(const Override* o); + /// Validates the given var /// @param var the var to validate void CheckVar(const Var* var); @@ -1221,6 +1230,7 @@ Hashmap<const ir::Function*, Hashset<const ir::UserCall*, 4>, 4> user_func_calls_; Hashset<const ir::Discard*, 4> discards_; core::ir::ReferencedModuleVars<const Module> referenced_module_vars_; + Hashset<OverrideId, 8> seen_override_ids_; Hashset<ValidatedType, 16> validated_types_{}; }; @@ -1731,6 +1741,13 @@ tint::Switch( inst, // + [&](const core::ir::Override* o) { + if (capabilities_.Contains(Capability::kAllowOverrides)) { + CheckInstruction(o); + } else { + AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name; + } + }, [&](const core::ir::Var* var) { CheckInstruction(var); }, [&](const core::ir::Let* let) { if (capabilities_.Contains(Capability::kAllowModuleScopeLets)) { @@ -1747,11 +1764,31 @@ } }, [&](Default) { - AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name; + // Note, this validation is looser then it could be. There are only certain + // expressions and builtins which can be used in an override. We can tighten this up + // later to a constrained set of instructions and builtins if necessary. + if (capabilities_.Contains(Capability::kAllowOverrides)) { + // If overrides are allowed we can have regular instructions in the root block, + // with the caveat that those instructions can _only_ be used in the root block. + CheckOnlyUsedInRootBlock(inst); + } else { + AddError(inst) << "root block: invalid instruction: " << inst->TypeInfo().name; + } }); } } +void Validator::CheckOnlyUsedInRootBlock(const Instruction* inst) { + for (auto& usage : inst->Result(0)->UsagesSorted()) { + if (usage.instruction->Block() != mod_.root_block) { + AddError(inst) << "root block: instruction used outside of root block " + << inst->TypeInfo().name; + } + } + + CheckInstruction(inst); +} + void Validator::CheckFunction(const Function* func) { // Scope holds the parameters and block scope_stack_.Push(); @@ -2083,6 +2120,7 @@ [&](const Swizzle* s) { CheckSwizzle(s); }, // [&](const Terminator* b) { CheckTerminator(b); }, // [&](const Unary* u) { CheckUnary(u); }, // + [&](const Override* o) { CheckOverride(o); }, // [&](const Var* var) { CheckVar(var); }, // [&](const Default) { AddError(inst) << "missing validation"; }); @@ -2091,6 +2129,36 @@ } } +void Validator::CheckOverride(const Override* o) { + // Intentionally not checking operands, since Override may have a null operand + if (!CheckResults(o, Override::kNumResults)) { + return; + } + + if (!seen_override_ids_.Add(o->OverrideId())) { + AddError(o) << "duplicate override id encountered: " << o->OverrideId().value; + return; + } + + if (!o->Result(0)->Type()->IsScalar()) { + AddError(o) << "override type " << style::Type(o->Result(0)->Type()->FriendlyName()) + << " is not a scalar"; + return; + } + + if (o->Initializer()) { + if (!CheckOperand(o, ir::Var::kInitializerOperandOffset)) { + return; + } + if (o->Initializer()->Type() != o->Result(0)->Type()) { + AddError(o) << "override type " << style::Type(o->Result(0)->Type()->FriendlyName()) + << " does not match initializer type " + << style::Type(o->Initializer()->Type()->FriendlyName()); + return; + } + } +} + void Validator::CheckVar(const Var* var) { // Intentionally not checking operands, since Var may have a null operand if (!CheckResults(var, Var::kNumResults)) {
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h index 4681e03..cf4809a 100644 --- a/src/tint/lang/core/ir/validator.h +++ b/src/tint/lang/core/ir/validator.h
@@ -44,18 +44,20 @@ enum class Capability : uint8_t { /// Allows 8-bit integer types. kAllow8BitIntegers, - /// Allows access instructions to create pointers to vector elements. - kAllowVectorElementPointer, - /// Allows ref types - kAllowRefTypes, - /// Allows module scoped lets - kAllowModuleScopeLets, - /// Allows pointers inside structures. - kAllowPointersInStructures, - /// Allows handle vars to not have binding points - kAllowHandleVarsWithoutBindings, /// Allows ClipDistances on f32 parameters kAllowClipDistancesOnF32, + /// Allows handle vars to not have binding points + kAllowHandleVarsWithoutBindings, + /// Allows module scoped lets + kAllowModuleScopeLets, + /// Allow overrides + kAllowOverrides, + /// Allows pointers inside structures. + kAllowPointersInStructures, + /// Allows ref types + kAllowRefTypes, + /// Allows access instructions to create pointers to vector elements. + kAllowVectorElementPointer, }; /// Capabilities is a set of Capability
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc index e944db2..3060f7c 100644 --- a/src/tint/lang/core/ir/validator_test.cc +++ b/src/tint/lang/core/ir/validator_test.cc
@@ -9660,5 +9660,189 @@ )"); } +TEST_F(IR_ValidatorTest, OverrideWithoutCapability) { + b.Append(mod.root_block, [&] { b.Override("a", 1_u); }); + + auto res = ir::Validate(mod); + ASSERT_NE(res, Success); + EXPECT_EQ(res.Failure().reason.Str(), + R"(:2:12 error: override: root block: invalid instruction: tint::core::ir::Override + %a:u32 = override, 1u @id(0) + ^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %a:u32 = override, 1u @id(0) +} + +)"); +} + +TEST_F(IR_ValidatorTest, InstructionInRootBlockWithoutOverrideCap) { + b.Append(mod.root_block, [&] { b.Add(ty.u32(), 3_u, 2_u); }); + + auto res = ir::Validate(mod); + ASSERT_NE(res, Success); + EXPECT_EQ(res.Failure().reason.Str(), + R"(:2:3 error: binary: root block: invalid instruction: tint::core::ir::CoreBinary + %1:u32 = add 3u, 2u + ^^^^^^^^^^^^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %1:u32 = add 3u, 2u +} + +)"); +} + +TEST_F(IR_ValidatorTest, OverrideWithCapability) { + b.Append(mod.root_block, [&] { b.Override(ty.u32()); }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_EQ(res, Success); +} + +TEST_F(IR_ValidatorTest, OverrideWithValue) { + b.Append(mod.root_block, [&] { + auto* z = b.Override(ty.u32()); + z->SetOverrideId(OverrideId{2}); + auto* init = b.Add(ty.u32(), z, 2_u); + + b.Override("a", init); + }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_EQ(res, Success); +} + +TEST_F(IR_ValidatorTest, OverrideWithInvalidType) { + b.Append(mod.root_block, [&] { b.Override(ty.vec3<u32>()); }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_NE(res, Success); + EXPECT_EQ(res.Failure().reason.Str(), + R"(:2:18 error: override: override type 'vec3<u32>' is not a scalar + %1:vec3<u32> = override @id(0) + ^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %1:vec3<u32> = override @id(0) +} + +)"); +} + +TEST_F(IR_ValidatorTest, OverrideWithMismatchedInitializerType) { + b.Append(mod.root_block, [&] { + auto* init = b.Constant(1_i); + auto* o = b.Override(ty.u32()); + o->SetInitializer(init); + }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_NE(res, Success); + EXPECT_EQ(res.Failure().reason.Str(), + R"(:2:12 error: override: override type 'u32' does not match initializer type 'i32' + %1:u32 = override, 1i @id(0) + ^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %1:u32 = override, 1i @id(0) +} + +)"); +} + +TEST_F(IR_ValidatorTest, OverrideDuplicateId) { + b.Append(mod.root_block, [&] { + auto* o = b.Override(ty.u32()); + o->SetOverrideId(OverrideId{2}); + + auto* o2 = b.Override(ty.i32()); + o2->SetOverrideId(OverrideId{2}); + }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_NE(res, Success); + EXPECT_EQ(res.Failure().reason.Str(), + R"(:3:12 error: override: duplicate override id encountered: 2 + %2:i32 = override @id(2) + ^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %1:u32 = override @id(2) + %2:i32 = override @id(2) +} + +)"); +} + +TEST_F(IR_ValidatorTest, InstructionInRootBlockOnlyUsedInRootBlock) { + core::ir::Value* init = nullptr; + b.Append(mod.root_block, [&] { + auto* z = b.Override(ty.u32()); + z->SetOverrideId(OverrideId{2}); + init = b.Add(ty.u32(), z, 2_u)->Result(0); + b.Override("a", init); + }); + + auto* f = b.Function("my_func", ty.void_()); + b.Append(f->Block(), [&] { + b.Add(ty.u32(), init, 2_u); + b.Return(f); + }); + + auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + ASSERT_NE(res, Success); + EXPECT_EQ( + res.Failure().reason.Str(), + R"(:3:3 error: binary: root block: instruction used outside of root block tint::core::ir::CoreBinary + %2:u32 = add %1, 2u + ^^^^^^^^^^^^^^^^^^^ + +:1:1 note: in block +$B1: { # root +^^^ + +note: # Disassembly +$B1: { # root + %1:u32 = override @id(2) + %2:u32 = add %1, 2u + %a:u32 = override, %2 @id(0) +} + +%my_func = func():void { + $B2: { + %5:u32 = add %2, 2u + ret + } +} +)"); +} + } // namespace } // namespace tint::core::ir
diff --git a/src/tint/lang/core/type/type.cc b/src/tint/lang/core/type/type.cc index 29069bd..cf77682 100644 --- a/src/tint/lang/core/type/type.cc +++ b/src/tint/lang/core/type/type.cc
@@ -88,6 +88,10 @@ return 0; } +bool Type::IsScalar() const { + return IsFloatScalar() || IsIntegerScalar() || IsAnyOf<AbstractInt, Bool>(); +} + bool Type::IsFloatScalar() const { return IsAnyOf<F16, F32, AbstractFloat>(); }
diff --git a/src/tint/lang/core/type/type.h b/src/tint/lang/core/type/type.h index d263428..d4b7647 100644 --- a/src/tint/lang/core/type/type.h +++ b/src/tint/lang/core/type/type.h
@@ -129,6 +129,8 @@ /// @see https://www.w3.org/TR/WGSL/#fixed-footprint-types inline bool HasFixedFootprint() const { return flags_.Contains(Flag::kFixedFootprint); } + /// @returns true if the type is a scalar + bool IsScalar() const; /// @returns true if this type is a float scalar bool IsFloatScalar() const; /// @returns true if this type is a float matrix
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc index 02ba26e..2fe327e 100644 --- a/src/tint/lang/wgsl/reader/lower/lower.cc +++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -35,6 +35,7 @@ #include "src/tint/lang/core/ir/validator.h" #include "src/tint/lang/wgsl/builtin_fn.h" #include "src/tint/lang/wgsl/ir/builtin_call.h" +#include "src/tint/utils/diagnostic/diagnostic.h" #include "src/tint/utils/ice/ice.h" namespace tint::wgsl::reader { @@ -204,7 +205,9 @@ } // namespace Result<SuccessType> Lower(core::ir::Module& mod) { - if (auto res = core::ir::ValidateAndDumpIfNeeded(mod, "wgsl.Lower"); res != Success) { + auto res = core::ir::ValidateAndDumpIfNeeded( + mod, "wgsl.Lower", core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + if (res != Success) { return res.Failure(); }
diff --git a/src/tint/lang/wgsl/reader/lower/lower.h b/src/tint/lang/wgsl/reader/lower/lower.h index 91a647e..b11a139 100644 --- a/src/tint/lang/wgsl/reader/lower/lower.h +++ b/src/tint/lang/wgsl/reader/lower/lower.h
@@ -29,7 +29,6 @@ #define SRC_TINT_LANG_WGSL_READER_LOWER_LOWER_H_ #include "src/tint/lang/core/ir/module.h" -#include "src/tint/utils/diagnostic/diagnostic.h" #include "src/tint/utils/result/result.h" namespace tint::wgsl::reader {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h index 7bdf81a..2ce447c 100644 --- a/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h +++ b/src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h
@@ -66,7 +66,9 @@ return lower.Failure(); } - if (auto validate = core::ir::Validate(result.Get()); validate != Success) { + auto validate = core::ir::Validate( + result.Get(), core::ir::Capabilities{core::ir::Capability::kAllowOverrides}); + if (validate != Success) { return validate.Failure(); } return result;
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc index 0baa2a4..673889e 100644 --- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc +++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -1218,19 +1218,34 @@ // Store the results of the initialization scopes_.Set(l->name->symbol, let->Result(0)); }, - [&](const ast::Override*) { - AddError(var->source) << "found an `Override` variable. The SubstituteOverrides " - "transform must be run before converting to IR"; + [&](const ast::Override* o) { + auto* o_sem = program_.Sem().Get(o); + auto* ty = sem->Type()->Clone(clone_ctx_.type_ctx); + + auto* override = builder_.Override(ty); + if (o->initializer) { + auto init = EmitValueExpression(o->initializer); + if (!init) { + return; + } + override->SetInitializer(init); + } + current_block_->Append(override); + + TINT_ASSERT(o_sem->Attributes().override_id.has_value()); + override->SetOverrideId(o_sem->Attributes().override_id.value()); + + // Store the declaration so we can get the instruction to store too + scopes_.Set(o->name->symbol, override->Result(0)); + + // Record the original name of the var + builder_.ir.SetName(override, o->name->symbol.Name()); }, [&](const ast::Const*) { // Skip. This should be handled by const-eval already, so the const will be a // `core::constant::` value at the usage sites. Can just ignore the `const` variable // as it should never be used. - // - // TODO(dsinclair): Probably want to store the const variable somewhere and then - // in identifier expression log an error if we ever see a const identifier. Add - // this when identifiers and variables are supported. - }, // + }, TINT_ICE_ON_NO_MATCH); }
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc index 1b12473..57457f5 100644 --- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc +++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
@@ -32,6 +32,7 @@ #include "src/tint/lang/core/ir/if.h" #include "src/tint/lang/core/ir/loop.h" #include "src/tint/lang/core/ir/multi_in_block.h" +#include "src/tint/lang/core/ir/override.h" #include "src/tint/lang/core/ir/switch.h" #include "src/tint/lang/wgsl/reader/program_to_ir/ir_program_test.h" @@ -1160,9 +1161,6 @@ )"); } -//////////////////////////////////////////////////////////////////////////////// -// Bugs -//////////////////////////////////////////////////////////////////////////////// TEST_F(IR_FromProgramTest, BugChromium324466107) { Func("f", Empty, ty.void_(), Vector{ @@ -1182,5 +1180,87 @@ )"); } +TEST_F(IR_FromProgramTest, OverrideNoInitializer) { + Override("a", ty.i32()); + + auto res = Build(); + ASSERT_EQ(res, Success); + + auto m = res.Move(); + auto* overide = FindSingleInstruction<core::ir::Override>(m); + + ASSERT_NE(overide, nullptr); + ASSERT_EQ(overide->Initializer(), nullptr); + + EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: { # root + %a:i32 = override @id(0) +} + +)"); +} + +TEST_F(IR_FromProgramTest, OverrideWithConstantInitializer) { + Override("a", Expr(1_f)); + + auto res = Build(); + ASSERT_EQ(res, Success); + + auto m = res.Move(); + auto* override = FindSingleInstruction<core::ir::Override>(m); + + ASSERT_NE(override, nullptr); + ASSERT_NE(override->Initializer(), nullptr); + + auto* init = override->Initializer()->As<core::ir::Constant>(); + ASSERT_NE(init, nullptr); + EXPECT_FLOAT_EQ(1.0f, init->Value()->ValueAs<float>()); + + EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: { # root + %a:f32 = override, 1.0f @id(0) +} + +)"); +} + +TEST_F(IR_FromProgramTest, OverrideWithAddInitializer) { + Override("a", Add(1_u, 2_u)); + + auto res = Build(); + ASSERT_EQ(res, Success); + + auto m = res.Move(); + auto* override = FindSingleInstruction<core::ir::Override>(m); + + ASSERT_NE(override, nullptr); + ASSERT_NE(override->Initializer(), nullptr); + + auto* init = override->Initializer()->As<core::ir::Constant>(); + ASSERT_NE(init, nullptr); + EXPECT_EQ(3u, init->Value()->ValueAs<uint32_t>()); + + EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: { # root + %a:u32 = override, 3u @id(0) +} + +)"); +} + +TEST_F(IR_FromProgramTest, OverrideWithOverrideAddInitializer) { + auto* z = Override("z", ty.u32()); + Override("a", Add(z, 2_u)); + + auto res = Build(); + ASSERT_EQ(res, Success); + + auto m = res.Move(); + EXPECT_EQ(core::ir::Disassembler(m).Plain(), R"($B1: { # root + %z:u32 = override @id(0) + %2:u32 = add %z, 2u + %a:u32 = override, %2 @id(1) +} + +)"); +} + } // namespace } // namespace tint::wgsl::reader
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc index ac8d9679..4cbaaf6 100644 --- a/src/tint/lang/wgsl/reader/reader.cc +++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -51,29 +51,20 @@ Result<core::ir::Module> WgslToIR(const Source::File* file, const Options& options) { Program program = Parse(file, options); - auto module = ProgramToIR(program); - if (module != Success) { - return module.Failure(); - } - // WGSL-dialect -> core-dialect - if (auto res = Lower(module.Get()); res != Success) { - return res.Failure(); - } - return module; + return ProgramToLoweredIR(program); } tint::Result<core::ir::Module> ProgramToLoweredIR(const Program& program) { - auto ir = tint::wgsl::reader::ProgramToIR(program); + auto ir = ProgramToIR(program); if (ir != Success) { return ir.Failure(); } // Lower from WGSL-dialect to core-dialect - auto res = tint::wgsl::reader::Lower(ir.Get()); + auto res = Lower(ir.Get()); if (res != Success) { return res.Failure(); } - return ir; }