[ir] Add an evaluator for constant expressions.
This CL adds an `Evaluator` into the IR in order to allow evaluating
constant expressions (to be used for evaluating `Override` expressions).
Bug: 374971092
Change-Id: Ifdfaa89126189f37cdcf9bde9ea1750cc78be1e8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/213434
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 b12f3ee..74c159a 100644
--- a/src/tint/lang/core/ir/BUILD.bazel
+++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -59,6 +59,7 @@
"core_unary.cc",
"disassembler.cc",
"discard.cc",
+ "evaluator.cc",
"exit.cc",
"exit_if.cc",
"exit_loop.cc",
@@ -114,6 +115,7 @@
"core_unary.h",
"disassembler.h",
"discard.h",
+ "evaluator.h",
"exit.h",
"exit_if.h",
"exit_loop.h",
@@ -194,6 +196,7 @@
"core_builtin_call_test.cc",
"core_unary_test.cc",
"discard_test.cc",
+ "evaluator_test.cc",
"exit_if_test.cc",
"exit_loop_test.cc",
"exit_switch_test.cc",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake
index c833689..3af8189 100644
--- a/src/tint/lang/core/ir/BUILD.cmake
+++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -82,6 +82,8 @@
lang/core/ir/disassembler.h
lang/core/ir/discard.cc
lang/core/ir/discard.h
+ lang/core/ir/evaluator.cc
+ lang/core/ir/evaluator.h
lang/core/ir/exit.cc
lang/core/ir/exit.h
lang/core/ir/exit_if.cc
@@ -198,6 +200,7 @@
lang/core/ir/core_builtin_call_test.cc
lang/core/ir/core_unary_test.cc
lang/core/ir/discard_test.cc
+ lang/core/ir/evaluator_test.cc
lang/core/ir/exit_if_test.cc
lang/core/ir/exit_loop_test.cc
lang/core/ir/exit_switch_test.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn
index c4c2f29..15a78d0 100644
--- a/src/tint/lang/core/ir/BUILD.gn
+++ b/src/tint/lang/core/ir/BUILD.gn
@@ -85,6 +85,8 @@
"disassembler.h",
"discard.cc",
"discard.h",
+ "evaluator.cc",
+ "evaluator.h",
"exit.cc",
"exit.h",
"exit_if.cc",
@@ -195,6 +197,7 @@
"core_builtin_call_test.cc",
"core_unary_test.cc",
"discard_test.cc",
+ "evaluator_test.cc",
"exit_if_test.cc",
"exit_loop_test.cc",
"exit_switch_test.cc",
diff --git a/src/tint/lang/core/ir/evaluator.cc b/src/tint/lang/core/ir/evaluator.cc
new file mode 100644
index 0000000..397736d
--- /dev/null
+++ b/src/tint/lang/core/ir/evaluator.cc
@@ -0,0 +1,372 @@
+// 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/evaluator.h"
+#include "src/tint/lang/core/ir/constant.h"
+#include "src/tint/lang/core/type/matrix.h"
+#include "src/tint/utils/rtti/switch.h"
+
+namespace tint::core::ir {
+namespace eval {
+
+Result<core::ir::Constant*> Eval(core::ir::Builder& b, core::ir::Instruction* inst) {
+ return Eval(b, inst->Result(0));
+}
+
+Result<core::ir::Constant*> Eval(core::ir::Builder& b, core::ir::Value* val) {
+ ir::Evaluator e(b);
+ return e.Evaluate(val);
+}
+
+} // namespace eval
+
+Evaluator::Evaluator(ir::Builder& builder)
+ : b_(builder), const_eval_(b_.ir.constant_values, diagnostics_) {}
+
+Evaluator::~Evaluator() = default;
+
+Result<core::ir::Constant*> Evaluator::Evaluate(core::ir::Value* src) {
+ auto res = EvalValue(src);
+ if (res != Success) {
+ return Failure(diagnostics_);
+ }
+ if (!res.Get()) {
+ return nullptr;
+ }
+
+ return b_.Constant(res.Get());
+}
+
+diag::Diagnostic& Evaluator::AddError(Source src) {
+ diag::Diagnostic diag;
+ diag.source = src;
+ return diagnostics_.Add(diag);
+}
+
+Source Evaluator::SourceOf(core::ir::Instruction* val) {
+ return b_.ir.SourceOf(val);
+}
+
+Evaluator::EvalResult Evaluator::EvalValue(core::ir::Value* val) {
+ return tint::Switch(
+ val, //
+ [&](core::ir::Constant* c) { return c->Value(); },
+ [&](core::ir::InstructionResult* r) {
+ return tint::Switch(
+ r->Instruction(), //
+ [&](core::ir::Bitcast* bc) { return EvalBitcast(bc); },
+ [&](core::ir::Access* a) { return EvalAccess(a); },
+ [&](core::ir::Construct* c) { return EvalConstruct(c); },
+ [&](core::ir::Convert* c) { return EvalConvert(c); },
+ [&](core::ir::CoreBinary* cb) { return EvalBinary(cb); },
+ [&](core::ir::CoreBuiltinCall* c) { return EvalCoreBuiltinCall(c); },
+ [&](core::ir::CoreUnary* u) { return EvalUnary(u); },
+ [&](core::ir::Swizzle* s) { return EvalSwizzle(s); }, //
+ TINT_ICE_ON_NO_MATCH);
+ },
+ TINT_ICE_ON_NO_MATCH);
+}
+
+Evaluator::EvalResult Evaluator::EvalBitcast(core::ir::Bitcast* bc) {
+ auto val = EvalValue(bc->Val());
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+
+ auto r = const_eval_.bitcast(bc->Result(0)->Type(), Vector{val.Get()}, SourceOf(bc));
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalAccess(core::ir::Access* a) {
+ auto obj_res = EvalValue(a->Object());
+ if (obj_res != Success) {
+ return obj_res;
+ }
+ // Check if the object could be evaluated
+ if (!obj_res.Get()) {
+ return nullptr;
+ }
+ auto* obj = obj_res.Get();
+
+ for (auto* idx : a->Indices()) {
+ auto val = EvalValue(idx);
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+ TINT_ASSERT(val.Get()->Is<core::constant::Value>());
+
+ auto res = const_eval_.Index(obj, a->Result(0)->Type(), val.Get(), SourceOf(a));
+ if (res != Success) {
+ return Failure();
+ }
+ obj = res.Get();
+ }
+
+ return obj;
+}
+
+Evaluator::EvalResult Evaluator::EvalConstruct(core::ir::Construct* c) {
+ auto table = core::intrinsic::Table<core::intrinsic::Dialect>(b_.ir.Types(), b_.ir.symbols);
+
+ auto result_ty = c->Result(0)->Type();
+
+ Vector<const core::type::Type*, 4> arg_types;
+ arg_types.Reserve(c->Args().Length());
+ Vector<const core::constant::Value*, 4> arg_values;
+ arg_values.Reserve(c->Args().Length());
+
+ for (auto* arg : c->Args()) {
+ arg_types.Push(arg->Type());
+
+ auto val = EvalValue(arg);
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+ arg_values.Push(val.Get());
+ }
+
+ auto mat_vec = [&](const core::type::Type* type,
+ core::intrinsic::CtorConv intrinsic) -> constant::Eval::Result {
+ auto op =
+ table.Lookup(intrinsic, Vector{type}, arg_types, core::EvaluationStage::kOverride);
+ if (op != Success) {
+ AddError(SourceOf(c)) << "unable to find intrinsic for construct: " << op.Failure();
+ return constant::Eval::Error();
+ }
+ if (!op->const_eval_fn) {
+ AddError(SourceOf(c)) << "unhandled type constructor";
+ return constant::Eval::Error();
+ }
+ auto r = (const_eval_.*op->const_eval_fn)(result_ty, arg_values, SourceOf(c));
+ if (r != Success) {
+ return constant::Eval::Error();
+ }
+ return r.Get();
+ };
+
+ // Dispatch to the appropriate const eval function.
+ auto r = tint::Switch(
+ result_ty, //
+ [&](const core::type::Array*) {
+ return const_eval_.ArrayOrStructCtor(result_ty, arg_values);
+ },
+ [&](const core::type::Struct*) {
+ return const_eval_.ArrayOrStructCtor(result_ty, arg_values);
+ },
+ [&](const core::type::Vector* vec) {
+ return mat_vec(vec->Type(), core::intrinsic::VectorCtorConv(vec->Width()));
+ },
+ [&](const core::type::Matrix* mat) {
+ return mat_vec(mat->Type(),
+ core::intrinsic::MatrixCtorConv(mat->Columns(), mat->Rows()));
+ },
+ [&](Default) {
+ if (!result_ty->Is<core::type::Scalar>()) {
+ AddError(SourceOf(c)) << "unhandled type constructor";
+ return core::constant::Eval::Result(nullptr);
+ }
+ // For scalars, this must be an identity constructor.
+ if (arg_values[0]->Type() != result_ty) {
+ AddError(SourceOf(c)) << "invalid type constructor";
+ return core::constant::Eval::Result(nullptr);
+ }
+ return const_eval_.Identity(result_ty, arg_values, SourceOf(c));
+ });
+
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalConvert(core::ir::Convert* c) {
+ auto val = EvalValue(c->Args()[0]);
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+ auto r = const_eval_.Convert(c->Result(0)->Type(), val.Get(), SourceOf(c));
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalSwizzle(core::ir::Swizzle* s) {
+ auto val = EvalValue(s->Object());
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+
+ auto r = const_eval_.Swizzle(s->Result(0)->Type(), val.Get(), s->Indices());
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalUnary(core::ir::CoreUnary* u) {
+ intrinsic::Context context{u->TableData(), b_.ir.Types(), b_.ir.symbols};
+
+ auto overload = core::intrinsic::LookupUnary(context, u->Op(), u->Val()->Type(),
+ core::EvaluationStage::kOverride);
+ if (overload != Success) {
+ AddError(SourceOf(u)) << overload.Failure().Plain();
+ return Failure();
+ }
+
+ auto const_eval_fn = overload->const_eval_fn;
+ if (!const_eval_fn) {
+ AddError(SourceOf(u)) << "invalid unary expression";
+ return Failure();
+ }
+
+ auto val = EvalValue(u->Val());
+ if (val != Success) {
+ return Failure();
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+
+ auto r = (const_eval_.*const_eval_fn)(u->Result(0)->Type(), Vector{val.Get()}, SourceOf(u));
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalBinary(core::ir::CoreBinary* cb) {
+ intrinsic::Context context{cb->TableData(), b_.ir.Types(), b_.ir.symbols};
+
+ auto overload =
+ core::intrinsic::LookupBinary(context, cb->Op(), cb->LHS()->Type(), cb->RHS()->Type(),
+ core::EvaluationStage::kOverride, /* is_compound */ false);
+ if (overload != Success) {
+ AddError(SourceOf(cb)) << overload.Failure().Plain();
+ return Failure();
+ }
+
+ auto const_eval_fn = overload->const_eval_fn;
+ if (!const_eval_fn) {
+ AddError(SourceOf(cb)) << "invalid binary expression";
+ return Failure();
+ }
+
+ auto lhs = EvalValue(cb->LHS());
+ if (lhs != Success) {
+ return lhs;
+ }
+ // Check LHS could be evaluated
+ if (!lhs.Get()) {
+ return nullptr;
+ }
+
+ auto rhs = EvalValue(cb->RHS());
+ if (rhs != Success) {
+ return rhs;
+ }
+ // Check RHS could be evaluated
+ if (!rhs.Get()) {
+ return nullptr;
+ }
+
+ auto r = (const_eval_.*const_eval_fn)(cb->Result(0)->Type(), Vector{lhs.Get(), rhs.Get()},
+ SourceOf(cb));
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+Evaluator::EvalResult Evaluator::EvalCoreBuiltinCall(core::ir::CoreBuiltinCall* c) {
+ intrinsic::Context context{c->TableData(), b_.ir.Types(), b_.ir.symbols};
+
+ Vector<const core::type::Type*, 0> arg_types;
+ arg_types.Reserve(c->Args().Length());
+ Vector<const core::constant::Value*, 0> args;
+ args.Reserve(c->Args().Length());
+ for (auto* arg : c->Args()) {
+ arg_types.Push(arg->Type());
+
+ auto val = EvalValue(arg);
+ if (val != Success) {
+ return val;
+ }
+ // Check if the value could be evaluated
+ if (!val.Get()) {
+ return nullptr;
+ }
+ args.Push(val.Get());
+ }
+
+ auto overload = core::intrinsic::LookupFn(context, c->FriendlyName().c_str(), c->FuncId(),
+ Empty, arg_types, core::EvaluationStage::kOverride);
+ if (overload != Success) {
+ AddError(SourceOf(c)) << overload.Failure();
+ return Failure();
+ }
+
+ // If there is no `@const` override, we don't fail the eval, we return a nullptr. This is
+ // because we can call eval for things like `dpdx` which is not overridable but that's not an
+ // eval failure, we just don't eval.
+ auto const_eval_fn = overload->const_eval_fn;
+ if (!const_eval_fn) {
+ return nullptr;
+ }
+
+ auto r = (const_eval_.*const_eval_fn)(c->Result(0)->Type(), args, SourceOf(c));
+ if (r != Success) {
+ return Failure();
+ }
+ return r.Get();
+}
+
+} // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/evaluator.h b/src/tint/lang/core/ir/evaluator.h
new file mode 100644
index 0000000..b21511e
--- /dev/null
+++ b/src/tint/lang/core/ir/evaluator.h
@@ -0,0 +1,95 @@
+// 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_EVALUATOR_H_
+#define SRC_TINT_LANG_CORE_IR_EVALUATOR_H_
+
+#include "src/tint/lang/core/constant/eval.h"
+#include "src/tint/lang/core/intrinsic/table.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/convert.h"
+#include "src/tint/lang/core/ir/core_binary.h"
+#include "src/tint/lang/core/ir/core_unary.h"
+#include "src/tint/lang/core/ir/swizzle.h"
+#include "src/tint/lang/core/ir/value.h"
+
+namespace tint::core::ir {
+
+/// An evaluator to take a given `ir::Value` and return the result of evaluating the expression.
+class Evaluator {
+ public:
+ /// Constructor
+ /// @param builder the ir builder
+ explicit Evaluator(ir::Builder& builder);
+ /// Destructor
+ ~Evaluator();
+
+ /// Evaluate the given `src` expression.
+ /// @param src the source expression
+ /// @returns the generated constant or a failure result.
+ Result<core::ir::Constant*> Evaluate(core::ir::Value* src);
+
+ private:
+ using EvalResult = Result<const core::constant::Value*>;
+
+ diag::Diagnostic& AddError(Source src);
+ Source SourceOf(core::ir::Instruction* val);
+
+ EvalResult EvalBitcast(core::ir::Bitcast* bc);
+ EvalResult EvalValue(core::ir::Value* val);
+ EvalResult EvalAccess(core::ir::Access* a);
+ EvalResult EvalConvert(core::ir::Convert* c);
+ EvalResult EvalConstruct(core::ir::Construct* c);
+ EvalResult EvalSwizzle(core::ir::Swizzle* s);
+ EvalResult EvalUnary(core::ir::CoreUnary* u);
+ EvalResult EvalBinary(core::ir::CoreBinary* cb);
+ EvalResult EvalCoreBuiltinCall(core::ir::CoreBuiltinCall* c);
+
+ ir::Builder& b_;
+ diag::List diagnostics_;
+ core::constant::Eval const_eval_;
+};
+
+namespace eval {
+
+/// Evaluate the given `inst` with the provided `b`.
+/// @param b the builder
+/// @param inst the instruction
+/// @returns the evaluated constant for `inst` or a `Failure` otherwise.
+Result<core::ir::Constant*> Eval(core::ir::Builder& b, core::ir::Instruction* inst);
+
+/// Evaluate the given `val` with the provided `b`.
+/// @param b the builder
+/// @param val the value
+/// @returns the evaluated constant for `val` or a `Failure` otherwise.
+Result<core::ir::Constant*> Eval(core::ir::Builder& b, core::ir::Value* val);
+
+} // namespace eval
+
+} // namespace tint::core::ir
+
+#endif // SRC_TINT_LANG_CORE_IR_EVALUATOR_H_
diff --git a/src/tint/lang/core/ir/evaluator_test.cc b/src/tint/lang/core/ir/evaluator_test.cc
new file mode 100644
index 0000000..5143b98
--- /dev/null
+++ b/src/tint/lang/core/ir/evaluator_test.cc
@@ -0,0 +1,332 @@
+// 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 "gmock/gmock.h"
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/evaluator.h"
+#include "src/tint/lang/core/ir/instruction.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+
+using namespace tint::core::number_suffixes; // NOLINT
+using namespace tint::core::fluent_types; // NOLINT
+
+namespace tint::core::ir::eval::compile_time {
+namespace {
+
+using IR_EvaluatorTest = ir::IRTestHelper;
+
+TEST_F(IR_EvaluatorTest, InvalidExpression) {
+ auto* inst = b.Negation(mod.Types().u32(), 4_u);
+ b.ir.SetSource(inst, Source{{2, 3}});
+ auto res = Eval(b, inst);
+ ASSERT_NE(res, Success);
+
+ EXPECT_EQ(res.Failure().reason.Str(), R"(2:3 error: no matching overload for 'operator - (u32)'
+
+2 candidate operators:
+ • 'operator - (T ✗ ) -> T' where:
+ ✗ 'T' is 'f32', 'i32' or 'f16'
+ • 'operator - (vecN<T> ✗ ) -> vecN<T>' where:
+ ✗ 'T' is 'f32', 'i32' or 'f16'
+)");
+}
+
+TEST_F(IR_EvaluatorTest, Bitcast) {
+ auto* inst = b.Bitcast(mod.Types().i32(), 4_u);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_TRUE(c->Type()->Is<core::type::I32>());
+ EXPECT_EQ(4, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, Unary) {
+ auto* inst = b.Complement(mod.Types().i32(), 4_i);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(~4, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, Binary) {
+ auto* inst = b.Add(mod.Types().i32(), 1_i, 2_i);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(3, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructScalar) {
+ auto* inst = b.Construct(ty.i32(), 1_i);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(1, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructArray_Access) {
+ auto* obj = b.Construct(ty.array<i32, 3>(), 1_i, 2_i, 3_i);
+ auto* inst = b.Access(mod.Types().i32(), obj, 1_u);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(2, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructStruct_Access) {
+ auto* S = ty.Struct(mod.symbols.New("S"),
+ {{mod.symbols.New("a"), ty.i32()}, {mod.symbols.New("b"), ty.f32()}});
+
+ auto* obj = b.Construct(S, 1_i, 2_f);
+ auto* inst = b.Access(mod.Types().i32(), obj, 1_u);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_FLOAT_EQ(2, c->Value()->ValueAs<float>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructVector_Swizzle) {
+ auto* obj = b.Construct(ty.vec3<i32>(), 1_i, 2_i, 3_i);
+ auto* inst = b.Swizzle(mod.Types().i32(), obj, {1});
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(2, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructVector_Access) {
+ auto* obj = b.Construct(ty.vec3<i32>(), 1_i, 2_i, 3_i);
+ auto* inst = b.Access(mod.Types().i32(), obj, 1_u);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success);
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(2, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, ConstructMat_Swizzle) {
+ auto* obj = b.Construct(ty.mat2x2<f32>(), 1_f, 2_f, 3_f, 4_f);
+ auto* outer = b.Swizzle(mod.Types().vec2<f32>(), obj, {1});
+ auto* inst = b.Swizzle(mod.Types().f32(), outer, {1});
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_FLOAT_EQ(4.f, c->Value()->ValueAs<float>());
+}
+
+TEST_F(IR_EvaluatorTest, Convert) {
+ auto* obj = b.Construct(ty.i32(), 1_i);
+ auto* inst = b.Convert(ty.u32(), obj);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ ASSERT_EQ(ty.u32(), c->Type());
+ EXPECT_EQ(1u, c->Value()->ValueAs<uint32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, BuiltinCall) {
+ auto* inst = b.Call(ty.i32(), core::BuiltinFn::kAbs, -1_i);
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(1, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, MultiExpression) {
+ auto* abs = b.Call(ty.i32(), core::BuiltinFn::kAbs, -1_i);
+ auto* mul = b.Multiply(ty.i32(), abs, 5_i);
+ auto* cons = b.Construct(ty.vec2<i32>(), mul, mul);
+ auto* inst = b.Swizzle(ty.i32(), cons, {1});
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_NE(val, nullptr);
+ auto* c = val->As<core::ir::Constant>();
+ ASSERT_NE(c, nullptr);
+ EXPECT_EQ(5, c->Value()->ValueAs<int32_t>());
+}
+
+TEST_F(IR_EvaluatorTest, NonConstBuiltinCall) {
+ auto* inst = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstBuiltinCallArg) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Call(ty.f32(), core::BuiltinFn::kAbs, dpdx);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideUnary) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Unary(core::UnaryOp::kNegation, ty.f32(), dpdx);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideBinaryRHS) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Add(ty.f32(), 1.0_f, dpdx);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideBinaryLHS) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Add(ty.f32(), dpdx, 1.0_f);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideSwizzle) {
+ auto* dpdx =
+ b.Call(ty.vec2<f32>(), core::BuiltinFn::kDpdx, b.Construct(ty.vec2<f32>(), 2.0_f, 2.0_f));
+ auto* inst = b.Swizzle(ty.f32(), dpdx, {1});
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideConstruct) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Construct(ty.vec2<f32>(), dpdx, 2_f);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideConvert) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Convert(ty.u32(), dpdx);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideBitcast) {
+ auto* dpdx = b.Call(ty.f32(), core::BuiltinFn::kDpdx, 2.0_f);
+ auto* inst = b.Bitcast(ty.u32(), dpdx);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+TEST_F(IR_EvaluatorTest, NonConstCallInsideAccessObject) {
+ auto* dpdx =
+ b.Call(ty.vec2<f32>(), core::BuiltinFn::kDpdx, b.Construct(ty.vec2<f32>(), 2.0_f, 2.0_f));
+ auto* inst = b.Access(ty.f32(), dpdx, 0_u);
+
+ auto res = Eval(b, inst);
+ ASSERT_EQ(res, Success) << res.Failure().reason.Str();
+
+ auto* val = res.Get();
+ ASSERT_EQ(val, nullptr);
+}
+
+} // namespace
+} // namespace tint::core::ir::eval::compile_time