[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 ac8d967..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;
}