tint/resolver: Track evaluation stage
Keep track of the earliest evaluation point for an expression.
Required to properly track what can be assigned to a `const`, `override`, `let`, `var`.
Bug: tint:1601
Bug: chromium:1343242
Change-Id: I301eec21b71e9036dc1bf6c9af8079317d724762
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/95949
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 1b2e7d5..c0c0b62 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -420,6 +420,7 @@
"sem/constant.h",
"sem/depth_multisampled_texture.h",
"sem/depth_texture.h",
+ "sem/evaluation_stage.h",
"sem/expression.h",
"sem/external_texture.h",
"sem/f16.h",
@@ -621,6 +622,7 @@
"sem/depth_multisampled_texture.h",
"sem/depth_texture.cc",
"sem/depth_texture.h",
+ "sem/evaluation_stage.h",
"sem/expression.cc",
"sem/expression.h",
"sem/external_texture.cc",
@@ -1093,6 +1095,7 @@
"resolver/control_block_validation_test.cc",
"resolver/dependency_graph_test.cc",
"resolver/entry_point_validation_test.cc",
+ "resolver/evaluation_stage_test.cc",
"resolver/function_validation_test.cc",
"resolver/host_shareable_validation_test.cc",
"resolver/increment_decrement_validation_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 1d22340..6c611c3 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -298,6 +298,7 @@
sem/depth_multisampled_texture.h
sem/depth_texture.cc
sem/depth_texture.h
+ sem/evaluation_stage.h
sem/expression.cc
sem/expression.h
sem/external_texture.cc
@@ -777,6 +778,7 @@
resolver/control_block_validation_test.cc
resolver/dependency_graph_test.cc
resolver/entry_point_validation_test.cc
+ resolver/evaluation_stage_test.cc
resolver/function_validation_test.cc
resolver/host_shareable_validation_test.cc
resolver/increment_decrement_validation_test.cc
diff --git a/src/tint/resolver/evaluation_stage_test.cc b/src/tint/resolver/evaluation_stage_test.cc
new file mode 100644
index 0000000..236e4b3
--- /dev/null
+++ b/src/tint/resolver/evaluation_stage_test.cc
@@ -0,0 +1,297 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/resolver/resolver.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/resolver/resolver_test_helper.h"
+
+using namespace tint::number_suffixes; // NOLINT
+
+namespace tint::resolver {
+namespace {
+
+using ResolverEvaluationStageTest = ResolverTest;
+
+TEST_F(ResolverEvaluationStageTest, Literal_i32) {
+ auto* expr = Expr(123_i);
+ WrapInFunction(expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Literal_f32) {
+ auto* expr = Expr(123_f);
+ WrapInFunction(expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Vector_Ctor) {
+ auto* expr = vec3<f32>();
+ WrapInFunction(expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Vector_Ctor_Const_Const) {
+ // const f = 1.f;
+ // vec2<f32>(f, f);
+ auto* f = Const("f", nullptr, Expr(1_f));
+ auto* expr = vec2<f32>(f, f);
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Vector_Ctor_Runtime_Runtime) {
+ // var f = 1.f;
+ // vec2<f32>(f, f);
+ auto* f = Var("f", nullptr, Expr(1_f));
+ auto* expr = vec2<f32>(f, f);
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, Vector_Conv_Const) {
+ // const f = 1.f;
+ // vec2<u32>(vec2<f32>(f));
+ auto* f = Const("f", nullptr, Expr(1_f));
+ auto* expr = vec2<u32>(vec2<f32>(f));
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Vector_Conv_Runtime) {
+ // var f = 1.f;
+ // vec2<u32>(vec2<f32>(f));
+ auto* f = Var("f", nullptr, Expr(1_f));
+ auto* expr = vec2<u32>(vec2<f32>(f));
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, Matrix_Ctor) {
+ auto* expr = mat2x2<f32>();
+ WrapInFunction(expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor) {
+ auto* expr = array<f32, 3>();
+ WrapInFunction(expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor_Const_Const) {
+ // const f = 1.f;
+ // array<f32, 2>(f, f);
+ auto* f = Const("f", nullptr, Expr(1_f));
+ auto* expr = Construct(ty.array<f32, 2>(), f, f);
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor_Const_Override) {
+ // const f1 = 1.f;
+ // override f2 = 2.f;
+ // array<f32, 2>(f1, f2);
+ auto* f1 = Const("f1", nullptr, Expr(1_f));
+ auto* f2 = Override("f2", nullptr, Expr(2_f));
+ auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
+ WrapInFunction(f1, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f1)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(f2)->Stage(), sem::EvaluationStage::kOverride);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kOverride);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor_Override_Runtime) {
+ // override f1 = 1.f;
+ // var f2 = 2.f;
+ // array<f32, 2>(f1, f2);
+ auto* f1 = Override("f1", nullptr, Expr(1_f));
+ auto* f2 = Var("f2", nullptr, Expr(2_f));
+ auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
+ WrapInFunction(f2, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f1)->Stage(), sem::EvaluationStage::kOverride);
+ EXPECT_EQ(Sem().Get(f2)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor_Const_Runtime) {
+ // const f1 = 1.f;
+ // var f2 = 2.f;
+ // array<f32, 2>(f1, f2);
+ auto* f1 = Const("f1", nullptr, Expr(1_f));
+ auto* f2 = Var("f2", nullptr, Expr(2_f));
+ auto* expr = Construct(ty.array<f32, 2>(), f1, f2);
+ WrapInFunction(f1, f2, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f1)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(f2)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, Array_Ctor_Runtime_Runtime) {
+ // var f = 1.f;
+ // array<f32, 2>(f, f);
+ auto* f = Var("f", nullptr, Expr(1_f));
+ auto* expr = Construct(ty.array<f32, 2>(), f, f);
+ WrapInFunction(f, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(f)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, IndexAccessor_Const_Const) {
+ // const vec = vec4<f32>();
+ // const idx = 1_i;
+ // vec[idx]
+ auto* vec = Const("vec", nullptr, vec4<f32>());
+ auto* idx = Const("idx", nullptr, Expr(1_i));
+ auto* expr = IndexAccessor(vec, idx);
+ WrapInFunction(vec, idx, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(idx)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, IndexAccessor_Runtime_Const) {
+ // var vec = vec4<f32>();
+ // const idx = 1_i;
+ // vec[idx]
+ auto* vec = Var("vec", nullptr, vec4<f32>());
+ auto* idx = Const("idx", nullptr, Expr(1_i));
+ auto* expr = IndexAccessor(vec, idx);
+ WrapInFunction(vec, idx, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(idx)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, IndexAccessor_Const_Override) {
+ // const vec = vec4<f32>();
+ // override idx = 1_i;
+ // vec[idx]
+ auto* vec = Const("vec", nullptr, vec4<f32>());
+ auto* idx = Override("idx", nullptr, Expr(1_i));
+ auto* expr = IndexAccessor(vec, idx);
+ WrapInFunction(vec, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(idx)->Stage(), sem::EvaluationStage::kOverride);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kOverride);
+}
+
+TEST_F(ResolverEvaluationStageTest, IndexAccessor_Const_Runtime) {
+ // const vec = vec4<f32>();
+ // let idx = 1_i;
+ // vec[idx]
+ auto* vec = Const("vec", nullptr, vec4<f32>());
+ auto* idx = Let("idx", nullptr, Expr(1_i));
+ auto* expr = IndexAccessor(vec, idx);
+ WrapInFunction(vec, idx, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(idx)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, Swizzle_Const) {
+ // const vec = S();
+ // vec.m
+ auto* vec = Const("vec", nullptr, vec4<f32>());
+ auto* expr = MemberAccessor(vec, "xz");
+ WrapInFunction(vec, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, Swizzle_Runtime) {
+ // var vec = S();
+ // vec.m
+ auto* vec = Var("vec", nullptr, vec4<f32>());
+ auto* expr = MemberAccessor(vec, "rg");
+ WrapInFunction(vec, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(vec)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+TEST_F(ResolverEvaluationStageTest, MemberAccessor_Const) {
+ // struct S { m : i32 };
+ // const str = S();
+ // str.m
+ Structure("S", {Member("m", ty.i32())});
+ auto* str = Const("str", nullptr, Construct(ty.type_name("S")));
+ auto* expr = MemberAccessor(str, "m");
+ WrapInFunction(str, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(str)->Stage(), sem::EvaluationStage::kConstant);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kConstant);
+}
+
+TEST_F(ResolverEvaluationStageTest, MemberAccessor_Runtime) {
+ // struct S { m : i32 };
+ // var str = S();
+ // str.m
+ Structure("S", {Member("m", ty.i32())});
+ auto* str = Var("str", nullptr, Construct(ty.type_name("S")));
+ auto* expr = MemberAccessor(str, "m");
+ WrapInFunction(str, expr);
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ EXPECT_EQ(Sem().Get(str)->Stage(), sem::EvaluationStage::kRuntime);
+ EXPECT_EQ(Sem().Get(expr)->Stage(), sem::EvaluationStage::kRuntime);
+}
+
+} // namespace
+} // namespace tint::resolver
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index bd7dc30..68a1068 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -1119,8 +1119,10 @@
if (match.overload->flags.Contains(OverloadFlag::kSupportsComputePipeline)) {
supported_stages.Add(ast::PipelineStage::kCompute);
}
+ auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
+ : sem::EvaluationStage::kRuntime;
return builder.create<sem::Builtin>(
- builtin_type, match.return_type, std::move(params), supported_stages,
+ builtin_type, match.return_type, std::move(params), eval_stage, supported_stages,
match.overload->flags.Contains(OverloadFlag::kIsDeprecated));
});
return Builtin{sem, match.overload->const_eval_fn};
@@ -1292,8 +1294,11 @@
nullptr, static_cast<uint32_t>(params.size()), p.type, ast::StorageClass::kNone,
ast::Access::kUndefined, p.usage));
}
+ auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
+ : sem::EvaluationStage::kRuntime;
auto* target = utils::GetOrCreate(constructors, match, [&]() {
- return builder.create<sem::TypeConstructor>(match.return_type, std::move(params));
+ return builder.create<sem::TypeConstructor>(match.return_type, std::move(params),
+ eval_stage);
});
return CtorOrConv{target, match.overload->const_eval_fn};
}
@@ -1303,7 +1308,9 @@
auto param = builder.create<sem::Parameter>(
nullptr, 0u, match.parameters[0].type, ast::StorageClass::kNone,
ast::Access::kUndefined, match.parameters[0].usage);
- return builder.create<sem::TypeConversion>(match.return_type, param);
+ auto eval_stage = match.overload->const_eval_fn ? sem::EvaluationStage::kConstant
+ : sem::EvaluationStage::kRuntime;
+ return builder.create<sem::TypeConversion>(match.return_type, param, eval_stage);
});
return CtorOrConv{target, match.overload->const_eval_fn};
}
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 8d09d19..7fc09cc 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -367,10 +367,11 @@
sem::Variable* sem = nullptr;
if (is_global) {
sem = builder_->create<sem::GlobalVariable>(
- v, ty, ast::StorageClass::kNone, ast::Access::kUndefined, /* constant_value */ nullptr,
- sem::BindingPoint{});
+ v, ty, sem::EvaluationStage::kRuntime, ast::StorageClass::kNone,
+ ast::Access::kUndefined, /* constant_value */ nullptr, sem::BindingPoint{});
} else {
- sem = builder_->create<sem::LocalVariable>(v, ty, ast::StorageClass::kNone,
+ sem = builder_->create<sem::LocalVariable>(v, ty, sem::EvaluationStage::kRuntime,
+ ast::StorageClass::kNone,
ast::Access::kUndefined, current_statement_,
/* constant_value */ nullptr);
}
@@ -421,8 +422,8 @@
}
auto* sem = builder_->create<sem::GlobalVariable>(
- v, ty, ast::StorageClass::kNone, ast::Access::kUndefined, /* constant_value */ nullptr,
- sem::BindingPoint{});
+ v, ty, sem::EvaluationStage::kOverride, ast::StorageClass::kNone, ast::Access::kUndefined,
+ /* constant_value */ nullptr, sem::BindingPoint{});
if (auto* id = ast::GetAttribute<ast::IdAttribute>(v->attributes)) {
sem->SetConstantId(static_cast<uint16_t>(id->value));
@@ -482,11 +483,11 @@
}
auto* sem = is_global ? static_cast<sem::Variable*>(builder_->create<sem::GlobalVariable>(
- c, ty, ast::StorageClass::kNone, ast::Access::kUndefined, value,
- sem::BindingPoint{}))
+ c, ty, sem::EvaluationStage::kConstant, ast::StorageClass::kNone,
+ ast::Access::kUndefined, value, sem::BindingPoint{}))
: static_cast<sem::Variable*>(builder_->create<sem::LocalVariable>(
- c, ty, ast::StorageClass::kNone, ast::Access::kUndefined,
- current_statement_, value));
+ c, ty, sem::EvaluationStage::kConstant, ast::StorageClass::kNone,
+ ast::Access::kUndefined, current_statement_, value));
sem->SetConstructor(rhs);
builder_->Sem().Add(c, sem);
@@ -567,12 +568,14 @@
if (auto bp = var->BindingPoint()) {
binding_point = {bp.group->value, bp.binding->value};
}
- sem = builder_->create<sem::GlobalVariable>(var, var_ty, storage_class, access,
+ sem = builder_->create<sem::GlobalVariable>(var, var_ty, sem::EvaluationStage::kRuntime,
+ storage_class, access,
/* constant_value */ nullptr, binding_point);
} else {
- sem = builder_->create<sem::LocalVariable>(
- var, var_ty, storage_class, access, current_statement_, /* constant_value */ nullptr);
+ sem = builder_->create<sem::LocalVariable>(var, var_ty, sem::EvaluationStage::kRuntime,
+ storage_class, access, current_statement_,
+ /* constant_value */ nullptr);
}
sem->SetConstructor(rhs);
@@ -1270,6 +1273,7 @@
[&](const ast::UnaryOpExpression* unary) -> sem::Expression* { return UnaryOp(unary); },
[&](const ast::PhonyExpression*) -> sem::Expression* {
return builder_->create<sem::Expression>(expr, builder_->create<sem::Void>(),
+ sem::EvaluationStage::kRuntime,
current_statement_,
/* constant_value */ nullptr,
/* has_side_effects */ false);
@@ -1423,10 +1427,11 @@
ty = builder_->create<sem::Reference>(ty, ref->StorageClass(), ref->Access());
}
+ auto stage = sem::EarliestStage(obj->Stage(), idx->Stage());
auto val = const_eval_.Index(obj, idx);
bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
auto* sem = builder_->create<sem::IndexAccessorExpression>(
- expr, ty, obj, idx, current_statement_, std::move(val), has_side_effects,
+ expr, ty, stage, obj, idx, current_statement_, std::move(val), has_side_effects,
obj->SourceVariable());
sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
return sem;
@@ -1443,8 +1448,9 @@
}
auto val = const_eval_.Bitcast(ty, inner);
- auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, std::move(val),
- inner->HasSideEffects());
+ auto stage = sem::EvaluationStage::kRuntime; // TODO(crbug.com/tint/1581)
+ auto* sem = builder_->create<sem::Expression>(expr, ty, stage, current_statement_,
+ std::move(val), inner->HasSideEffects());
sem->Behaviors() = inner->Behaviors();
@@ -1464,6 +1470,7 @@
// Resolve all of the arguments, their types and the set of behaviors.
std::vector<const sem::Expression*> args(expr->args.size());
+ auto args_stage = sem::EvaluationStage::kConstant;
sem::Behaviors arg_behaviors;
for (size_t i = 0; i < expr->args.size(); i++) {
auto* arg = sem_.Get(expr->args[i]);
@@ -1471,6 +1478,7 @@
return nullptr;
}
args[i] = arg;
+ args_stage = sem::EarliestStage(args_stage, arg->Stage());
arg_behaviors.Add(arg->Behaviors());
}
arg_behaviors.Remove(sem::Behavior::kNext);
@@ -1491,14 +1499,38 @@
return nullptr;
}
const sem::Constant* value = nullptr;
- if (ctor_or_conv.const_eval_fn) {
+ auto stage = sem::EarliestStage(ctor_or_conv.target->Stage(), args_stage);
+ if (stage == sem::EvaluationStage::kConstant) {
value = (const_eval_.*ctor_or_conv.const_eval_fn)(ctor_or_conv.target->ReturnType(),
args.data(), args.size());
}
- return builder_->create<sem::Call>(expr, ctor_or_conv.target, std::move(args),
+ return builder_->create<sem::Call>(expr, ctor_or_conv.target, stage, std::move(args),
current_statement_, value, has_side_effects);
};
+ // ct_ctor_or_conv is a helper for building a sem::TypeConstructor for an array or structure
+ // constructor call target.
+ auto arr_or_str_ctor = [&](const sem::Type* ty,
+ const sem::CallTarget* call_target) -> sem::Call* {
+ if (!MaterializeArguments(args, call_target)) {
+ return nullptr;
+ }
+
+ auto stage = args_stage; // The evaluation stage of the call
+ const sem::Constant* value = nullptr; // The constant value for the call
+ if (stage == sem::EvaluationStage::kConstant) {
+ value = const_eval_.ArrayOrStructCtor(ty, args);
+ if (!value) {
+ // Constant evaluation failed.
+ // Can happen for expressions that will fail validation (later).
+ stage = sem::EvaluationStage::kRuntime;
+ }
+ }
+
+ return builder_->create<sem::Call>(expr, call_target, stage, std::move(args),
+ current_statement_, std::move(value), has_side_effects);
+ };
+
// ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
// call for the given semantic type.
auto ty_ctor_or_conv = [&](const sem::Type* ty) {
@@ -1517,7 +1549,7 @@
[&](const sem::Bool*) { return ct_ctor_or_conv(CtorConvIntrinsic::kBool, nullptr); },
[&](const sem::Array* arr) -> sem::Call* {
auto* call_target = utils::GetOrCreate(
- array_ctors_, ArrayConstructorSig{{arr, args.size()}},
+ array_ctors_, ArrayConstructorSig{{arr, args.size(), args_stage}},
[&]() -> sem::TypeConstructor* {
sem::ParameterList params(args.size());
for (size_t i = 0; i < args.size(); i++) {
@@ -1528,18 +1560,14 @@
ast::StorageClass::kNone, // storage_class
ast::Access::kUndefined); // access
}
- return builder_->create<sem::TypeConstructor>(arr, std::move(params));
+ return builder_->create<sem::TypeConstructor>(arr, std::move(params),
+ args_stage);
});
- if (!MaterializeArguments(args, call_target)) {
- return nullptr;
- }
- auto val = const_eval_.ArrayOrStructCtor(arr, args);
- return builder_->create<sem::Call>(expr, call_target, std::move(args),
- current_statement_, val, has_side_effects);
+ return arr_or_str_ctor(arr, call_target);
},
[&](const sem::Struct* str) -> sem::Call* {
auto* call_target = utils::GetOrCreate(
- struct_ctors_, StructConstructorSig{{str, args.size()}},
+ struct_ctors_, StructConstructorSig{{str, args.size(), args_stage}},
[&]() -> sem::TypeConstructor* {
sem::ParameterList params(std::min(args.size(), str->Members().size()));
for (size_t i = 0, n = params.size(); i < n; i++) {
@@ -1550,15 +1578,10 @@
ast::StorageClass::kNone, // storage_class
ast::Access::kUndefined); // access
}
- return builder_->create<sem::TypeConstructor>(str, std::move(params));
+ return builder_->create<sem::TypeConstructor>(str, std::move(params),
+ args_stage);
});
- if (!MaterializeArguments(args, call_target)) {
- return nullptr;
- }
- auto val = const_eval_.ArrayOrStructCtor(str, args);
- return builder_->create<sem::Call>(expr, call_target, std::move(args),
- current_statement_, std::move(val),
- has_side_effects);
+ return arr_or_str_ctor(str, call_target);
},
[&](Default) {
AddError("type is not constructible", expr->source);
@@ -1686,9 +1709,16 @@
AddWarning("use of deprecated builtin", expr->source);
}
+ auto stage = builtin.sem->Stage();
+ if (stage == sem::EvaluationStage::kConstant) {
+ for (auto* arg : args) {
+ stage = sem::EarliestStage(stage, arg->Stage());
+ }
+ }
+
// If the builtin is @const, and all arguments have constant values, evaluate the builtin now.
const sem::Constant* value = nullptr;
- if (builtin.const_eval_fn) {
+ if (stage == sem::EvaluationStage::kConstant && builtin.const_eval_fn) {
value = (const_eval_.*builtin.const_eval_fn)(builtin.sem->ReturnType(), args.data(),
args.size());
}
@@ -1696,8 +1726,8 @@
bool has_side_effects =
builtin.sem->HasSideEffects() ||
std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
- auto* call = builder_->create<sem::Call>(expr, builtin.sem, std::move(args), current_statement_,
- value, has_side_effects);
+ auto* call = builder_->create<sem::Call>(expr, builtin.sem, stage, std::move(args),
+ current_statement_, value, has_side_effects);
if (current_function_) {
current_function_->AddDirectlyCalledBuiltin(builtin.sem);
@@ -1755,7 +1785,8 @@
// TODO(crbug.com/tint/1420): For now, assume all function calls have side
// effects.
bool has_side_effects = true;
- auto* call = builder_->create<sem::Call>(expr, target, std::move(args), current_statement_,
+ auto* call = builder_->create<sem::Call>(expr, target, sem::EvaluationStage::kRuntime,
+ std::move(args), current_statement_,
/* constant_value */ nullptr, has_side_effects);
target->AddCallSite(call);
@@ -1851,7 +1882,8 @@
}
auto val = const_eval_.Literal(ty, literal);
- return builder_->create<sem::Expression>(literal, ty, current_statement_, std::move(val),
+ return builder_->create<sem::Expression>(literal, ty, sem::EvaluationStage::kConstant,
+ current_statement_, std::move(val),
/* has_side_effects */ false);
}
@@ -2054,6 +2086,7 @@
const auto* rhs = sem_.Get(expr->rhs);
auto* lhs_ty = lhs->Type()->UnwrapRef();
auto* rhs_ty = rhs->Type()->UnwrapRef();
+ auto stage = sem::EvaluationStage::kRuntime; // TODO(crbug.com/tint/1581)
auto op = intrinsic_table_->Lookup(expr->op, lhs_ty, rhs_ty, expr->source, false);
if (!op.result) {
@@ -2079,7 +2112,7 @@
}
bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
- auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, value,
+ auto* sem = builder_->create<sem::Expression>(expr, op.result, stage, current_statement_, value,
has_side_effects);
sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
@@ -2096,6 +2129,7 @@
const sem::Type* ty = nullptr;
const sem::Variable* source_var = nullptr;
const sem::Constant* value = nullptr;
+ auto stage = sem::EvaluationStage::kRuntime; // TODO(crbug.com/tint/1581)
switch (unary->op) {
case ast::UnaryOp::kAddressOf:
@@ -2155,7 +2189,7 @@
}
}
- auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, value,
+ auto* sem = builder_->create<sem::Expression>(unary, ty, stage, current_statement_, value,
expr->HasSideEffects(), source_var);
sem->Behaviors() = expr->Behaviors();
return sem;
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 2455127..f3aa38b 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -400,12 +400,15 @@
bool IsBuiltin(Symbol) const;
// ArrayConstructorSig represents a unique array constructor signature.
- // It is a tuple of the array type and number of arguments provided.
- using ArrayConstructorSig = utils::UnorderedKeyWrapper<std::tuple<const sem::Array*, size_t>>;
+ // It is a tuple of the array type, number of arguments provided and earliest evaluation stage.
+ using ArrayConstructorSig =
+ utils::UnorderedKeyWrapper<std::tuple<const sem::Array*, size_t, sem::EvaluationStage>>;
// StructConstructorSig represents a unique structure constructor signature.
- // It is a tuple of the structure type and number of arguments provided.
- using StructConstructorSig = utils::UnorderedKeyWrapper<std::tuple<const sem::Struct*, size_t>>;
+ // It is a tuple of the structure type, number of arguments provided and earliest evaluation
+ // stage.
+ using StructConstructorSig =
+ utils::UnorderedKeyWrapper<std::tuple<const sem::Struct*, size_t, sem::EvaluationStage>>;
ProgramBuilder* const builder_;
diag::List& diagnostics_;
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index c7afb54..29e06a2 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -547,10 +547,9 @@
}
}
return Var(v);
- }, //
- [&](const ast::Let*) { return Let(v); }, //
- [&](const ast::Override*) { return Override(v); }, //
- [&](const ast::Const*) { return true; }, //
+ }, //
+ [&](const ast::Let*) { return Let(v); }, //
+ [&](const ast::Const*) { return true; }, //
[&](Default) {
TINT_ICE(Resolver, diagnostics_)
<< "Validator::Variable() called with a unknown variable type: "
@@ -567,6 +566,13 @@
bool ok = Switch(
decl, //
[&](const ast::Var* var) {
+ if (auto* init = global->Constructor();
+ init && init->Stage() > sem::EvaluationStage::kOverride) {
+ AddError("module-scope 'var' initializer must be a constant or override expression",
+ init->Declaration()->source);
+ return false;
+ }
+
if (global->StorageClass() == ast::StorageClass::kNone) {
AddError("module-scope 'var' declaration must have a storage class", decl->source);
return false;
@@ -619,31 +625,7 @@
return Var(global);
},
- [&](const ast::Override*) {
- for (auto* attr : decl->attributes) {
- if (auto* id_attr = attr->As<ast::IdAttribute>()) {
- uint32_t id = id_attr->value;
- auto it = constant_ids.find(id);
- if (it != constant_ids.end() && it->second != global) {
- AddError("pipeline constant IDs must be unique", attr->source);
- AddNote("a pipeline constant with an ID of " + std::to_string(id) +
- " was previously declared here:",
- ast::GetAttribute<ast::IdAttribute>(
- it->second->Declaration()->attributes)
- ->source);
- return false;
- }
- if (id > 65535) {
- AddError("pipeline constant IDs must be between 0 and 65535", attr->source);
- return false;
- }
- } else {
- AddError("attribute is not valid for 'override' declaration", attr->source);
- return false;
- }
- }
- return Override(global);
- },
+ [&](const ast::Override*) { return Override(global, constant_ids); },
[&](const ast::Const*) {
if (!decl->attributes.empty()) {
AddError("attribute is not valid for module-scope 'const' declaration",
@@ -777,10 +759,39 @@
return true;
}
-bool Validator::Override(const sem::Variable* v) const {
+bool Validator::Override(const sem::Variable* v,
+ std::unordered_map<uint32_t, const sem::Variable*> constant_ids) const {
auto* decl = v->Declaration();
auto* storage_ty = v->Type()->UnwrapRef();
+ if (auto* init = v->Constructor(); init && init->Stage() > sem::EvaluationStage::kOverride) {
+ AddError("'override' initializer must be an override expression",
+ init->Declaration()->source);
+ return false;
+ }
+
+ for (auto* attr : decl->attributes) {
+ if (auto* id_attr = attr->As<ast::IdAttribute>()) {
+ uint32_t id = id_attr->value;
+ auto it = constant_ids.find(id);
+ if (it != constant_ids.end() && it->second != v) {
+ AddError("pipeline constant IDs must be unique", attr->source);
+ AddNote("a pipeline constant with an ID of " + std::to_string(id) +
+ " was previously declared here:",
+ ast::GetAttribute<ast::IdAttribute>(it->second->Declaration()->attributes)
+ ->source);
+ return false;
+ }
+ if (id > 65535) {
+ AddError("pipeline constant IDs must be between 0 and 65535", attr->source);
+ return false;
+ }
+ } else {
+ AddError("attribute is not valid for 'override' declaration", attr->source);
+ return false;
+ }
+ }
+
auto name = symbols_.NameFor(decl->symbol);
if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
AddError("'" + name + "' is a builtin and cannot be redeclared as a 'override'",
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 4d41959..1f15a9e 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -371,8 +371,10 @@
/// Validates a 'override' variable declaration
/// @param v the variable to validate
+ /// @param constant_ids the set of constant ids in the module
/// @returns true on success, false otherwise.
- bool Override(const sem::Variable* v) const;
+ bool Override(const sem::Variable* v,
+ std::unordered_map<uint32_t, const sem::Variable*> constant_ids) const;
/// Validates a 'const' variable declaration
/// @param v the variable to validate
diff --git a/src/tint/sem/builtin.cc b/src/tint/sem/builtin.cc
index bb2878b..c688c4c 100644
--- a/src/tint/sem/builtin.cc
+++ b/src/tint/sem/builtin.cc
@@ -90,9 +90,10 @@
Builtin::Builtin(BuiltinType type,
const sem::Type* return_type,
std::vector<Parameter*> parameters,
+ EvaluationStage eval_stage,
PipelineStageSet supported_stages,
bool is_deprecated)
- : Base(return_type, utils::ToConstPtrVec(parameters)),
+ : Base(return_type, utils::ToConstPtrVec(parameters), eval_stage),
type_(type),
supported_stages_(supported_stages),
is_deprecated_(is_deprecated) {
diff --git a/src/tint/sem/builtin.h b/src/tint/sem/builtin.h
index 1dc61ad..783e6ca 100644
--- a/src/tint/sem/builtin.h
+++ b/src/tint/sem/builtin.h
@@ -83,6 +83,7 @@
/// @param type the builtin type
/// @param return_type the return type for the builtin call
/// @param parameters the parameters for the builtin overload
+ /// @param eval_stage the earliest evaluation stage for a call to the builtin
/// @param supported_stages the pipeline stages that this builtin can be
/// used in
/// @param is_deprecated true if the particular overload is considered
@@ -90,6 +91,7 @@
Builtin(BuiltinType type,
const sem::Type* return_type,
std::vector<Parameter*> parameters,
+ EvaluationStage eval_stage,
PipelineStageSet supported_stages,
bool is_deprecated);
diff --git a/src/tint/sem/call.cc b/src/tint/sem/call.cc
index bfce3b1..a20649d 100644
--- a/src/tint/sem/call.cc
+++ b/src/tint/sem/call.cc
@@ -23,13 +23,17 @@
Call::Call(const ast::CallExpression* declaration,
const CallTarget* target,
+ EvaluationStage stage,
std::vector<const sem::Expression*> arguments,
const Statement* statement,
const Constant* constant,
bool has_side_effects)
- : Base(declaration, target->ReturnType(), statement, constant, has_side_effects),
+ : Base(declaration, target->ReturnType(), stage, statement, constant, has_side_effects),
target_(target),
- arguments_(std::move(arguments)) {}
+ arguments_(std::move(arguments)) {
+ // Check that the stage is no earlier than the target supports
+ TINT_ASSERT(Semantic, target->Stage() <= stage);
+}
Call::~Call() = default;
diff --git a/src/tint/sem/call.h b/src/tint/sem/call.h
index 1955bf9..2d82306 100644
--- a/src/tint/sem/call.h
+++ b/src/tint/sem/call.h
@@ -30,12 +30,14 @@
/// Constructor
/// @param declaration the AST node
/// @param target the call target
+ /// @param stage the earliest evaluation stage for the expression
/// @param arguments the call arguments
/// @param statement the statement that owns this expression
/// @param constant the constant value of this expression
/// @param has_side_effects whether this expression may have side effects
Call(const ast::CallExpression* declaration,
const CallTarget* target,
+ EvaluationStage stage,
std::vector<const sem::Expression*> arguments,
const Statement* statement,
const Constant* constant,
diff --git a/src/tint/sem/call_target.cc b/src/tint/sem/call_target.cc
index 67bde0e..f8bcdd8 100644
--- a/src/tint/sem/call_target.cc
+++ b/src/tint/sem/call_target.cc
@@ -21,8 +21,10 @@
namespace tint::sem {
-CallTarget::CallTarget(const sem::Type* return_type, const ParameterList& parameters)
- : signature_{return_type, parameters} {
+CallTarget::CallTarget(const sem::Type* return_type,
+ const ParameterList& parameters,
+ EvaluationStage stage)
+ : signature_{return_type, parameters}, stage_(stage) {
TINT_ASSERT(Semantic, return_type);
}
diff --git a/src/tint/sem/call_target.h b/src/tint/sem/call_target.h
index 64716b2..be1b96a 100644
--- a/src/tint/sem/call_target.h
+++ b/src/tint/sem/call_target.h
@@ -63,9 +63,12 @@
class CallTarget : public Castable<CallTarget, Node> {
public:
/// Constructor
+ /// @param stage the earliest evaluation stage for a call to this target
/// @param return_type the return type of the call target
/// @param parameters the parameters for the call target
- CallTarget(const sem::Type* return_type, const ParameterList& parameters);
+ CallTarget(const sem::Type* return_type,
+ const ParameterList& parameters,
+ EvaluationStage stage);
/// Copy constructor
CallTarget(const CallTarget&);
@@ -82,8 +85,12 @@
/// @return the signature of the call target
const CallTargetSignature& Signature() const { return signature_; }
+ /// @return the earliest evaluation stage for a call to this target
+ EvaluationStage Stage() const { return stage_; }
+
private:
CallTargetSignature signature_;
+ EvaluationStage stage_;
};
} // namespace tint::sem
diff --git a/src/tint/sem/evaluation_stage.h b/src/tint/sem/evaluation_stage.h
new file mode 100644
index 0000000..b5e554d
--- /dev/null
+++ b/src/tint/sem/evaluation_stage.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_SEM_EVALUATION_STAGE_H_
+#define SRC_TINT_SEM_EVALUATION_STAGE_H_
+
+#include <algorithm>
+#include <initializer_list>
+
+namespace tint::sem {
+
+/// The earliest point in time that an expression can be evaluated
+enum class EvaluationStage {
+ /// Expression can be evaluated at shader creation time
+ kConstant,
+ /// Expression can be evaluated at pipeline creation time
+ kOverride,
+ /// Expression can be evaluated at runtime
+ kRuntime,
+};
+
+/// @returns true if stage `a` comes earlier than stage `b`
+inline bool operator<(EvaluationStage a, EvaluationStage b) {
+ return static_cast<int>(a) < static_cast<int>(b);
+}
+
+/// @returns true if stage `a` comes later than stage `b`
+inline bool operator>(EvaluationStage a, EvaluationStage b) {
+ return static_cast<int>(a) > static_cast<int>(b);
+}
+
+/// @param stages a list of EvaluationStage.
+/// @returns the earliest stage supported by all the provided stages
+inline EvaluationStage EarliestStage(std::initializer_list<EvaluationStage> stages) {
+ auto earliest = EvaluationStage::kConstant;
+ for (auto stage : stages) {
+ earliest = std::max(stage, earliest);
+ }
+ return static_cast<EvaluationStage>(earliest);
+}
+
+template <typename... ARGS>
+inline EvaluationStage EarliestStage(ARGS... args) {
+ return EarliestStage({args...});
+}
+
+} // namespace tint::sem
+
+#endif // SRC_TINT_SEM_EVALUATION_STAGE_H_
diff --git a/src/tint/sem/expression.cc b/src/tint/sem/expression.cc
index 4415db5..cdaa2bc 100644
--- a/src/tint/sem/expression.cc
+++ b/src/tint/sem/expression.cc
@@ -24,6 +24,7 @@
Expression::Expression(const ast::Expression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Statement* statement,
const Constant* constant,
bool has_side_effects,
@@ -31,10 +32,12 @@
: declaration_(declaration),
source_variable_(source_var),
type_(type),
+ stage_(stage),
statement_(statement),
constant_(std::move(constant)),
has_side_effects_(has_side_effects) {
TINT_ASSERT(Semantic, type_);
+ TINT_ASSERT(Semantic, (constant != nullptr) == (stage == EvaluationStage::kConstant));
}
Expression::~Expression() = default;
diff --git a/src/tint/sem/expression.h b/src/tint/sem/expression.h
index 45a67ca..5942a71 100644
--- a/src/tint/sem/expression.h
+++ b/src/tint/sem/expression.h
@@ -18,6 +18,7 @@
#include "src/tint/ast/expression.h"
#include "src/tint/sem/behavior.h"
#include "src/tint/sem/constant.h"
+#include "src/tint/sem/evaluation_stage.h"
#include "src/tint/sem/node.h"
// Forward declarations
@@ -28,18 +29,21 @@
} // namespace tint::sem
namespace tint::sem {
+
/// Expression holds the semantic information for expression nodes.
class Expression : public Castable<Expression, Node> {
public:
/// Constructor
/// @param declaration the AST node
/// @param type the resolved type of the expression
+ /// @param stage the earliest evaluation stage for the expression
/// @param statement the statement that owns this expression
/// @param constant the constant value of the expression. May be null
/// @param has_side_effects true if this expression may have side-effects
/// @param source_var the (optional) source variable for this expression
Expression(const ast::Expression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Statement* statement,
const Constant* constant,
bool has_side_effects,
@@ -54,6 +58,9 @@
/// @return the resolved type of the expression
const sem::Type* Type() const { return type_; }
+ /// @return the earliest evaluation stage for the expression
+ EvaluationStage Stage() const { return stage_; }
+
/// @return the statement that owns this expression
const Statement* Stmt() const { return statement_; }
@@ -87,6 +94,7 @@
private:
const sem::Type* const type_;
+ const EvaluationStage stage_;
const Statement* const statement_;
const Constant* const constant_;
sem::Behaviors behaviors_{sem::Behavior::kNext};
diff --git a/src/tint/sem/expression_test.cc b/src/tint/sem/expression_test.cc
index cc4bf0e..63e01fb 100644
--- a/src/tint/sem/expression_test.cc
+++ b/src/tint/sem/expression_test.cc
@@ -43,7 +43,8 @@
TEST_F(ExpressionTest, UnwrapMaterialize) {
MockConstant c(create<I32>());
- auto* a = create<Expression>(/* declaration */ nullptr, create<I32>(), /* statement */ nullptr,
+ auto* a = create<Expression>(/* declaration */ nullptr, create<I32>(),
+ sem::EvaluationStage::kRuntime, /* statement */ nullptr,
/* constant_value */ nullptr,
/* has_side_effects */ false, /* source_var */ nullptr);
auto* b = create<Materialize>(a, /* statement */ nullptr, &c);
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
index cd34eb7..dcc80b0 100644
--- a/src/tint/sem/function.cc
+++ b/src/tint/sem/function.cc
@@ -30,7 +30,7 @@
Function::Function(const ast::Function* declaration,
Type* return_type,
std::vector<Parameter*> parameters)
- : Base(return_type, utils::ToConstPtrVec(parameters)),
+ : Base(return_type, utils::ToConstPtrVec(parameters), EvaluationStage::kRuntime),
declaration_(declaration),
workgroup_size_{WorkgroupDimension{1}, WorkgroupDimension{1}, WorkgroupDimension{1}} {
for (auto* parameter : parameters) {
diff --git a/src/tint/sem/index_accessor_expression.cc b/src/tint/sem/index_accessor_expression.cc
index cd74201..70d1d5f 100644
--- a/src/tint/sem/index_accessor_expression.cc
+++ b/src/tint/sem/index_accessor_expression.cc
@@ -24,13 +24,14 @@
IndexAccessorExpression::IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Expression* object,
const Expression* index,
const Statement* statement,
const Constant* constant,
bool has_side_effects,
const Variable* source_var /* = nullptr */)
- : Base(declaration, type, statement, constant, has_side_effects, source_var),
+ : Base(declaration, type, stage, statement, constant, has_side_effects, source_var),
object_(object),
index_(index) {}
diff --git a/src/tint/sem/index_accessor_expression.h b/src/tint/sem/index_accessor_expression.h
index 3ba10ea..ea93df7 100644
--- a/src/tint/sem/index_accessor_expression.h
+++ b/src/tint/sem/index_accessor_expression.h
@@ -32,6 +32,7 @@
/// Constructor
/// @param declaration the AST node
/// @param type the resolved type of the expression
+ /// @param stage the earliest evaluation stage for the expression
/// @param object the object expression that is being indexed
/// @param index the index expression
/// @param statement the statement that owns this expression
@@ -40,6 +41,7 @@
/// @param source_var the (optional) source variable for this expression
IndexAccessorExpression(const ast::IndexAccessorExpression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Expression* object,
const Expression* index,
const Statement* statement,
diff --git a/src/tint/sem/materialize.cc b/src/tint/sem/materialize.cc
index 739b0ce..15c31ae 100644
--- a/src/tint/sem/materialize.cc
+++ b/src/tint/sem/materialize.cc
@@ -22,6 +22,7 @@
const Constant* constant)
: Base(/* declaration */ expr->Declaration(),
/* type */ constant->Type(),
+ /* stage */ EvaluationStage::kConstant, // Abstract can only be const-expr
/* statement */ statement,
/* constant */ constant,
/* has_side_effects */ false,
diff --git a/src/tint/sem/member_accessor_expression.cc b/src/tint/sem/member_accessor_expression.cc
index 7c31b0e..74b4833 100644
--- a/src/tint/sem/member_accessor_expression.cc
+++ b/src/tint/sem/member_accessor_expression.cc
@@ -25,12 +25,14 @@
MemberAccessorExpression::MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Statement* statement,
const Constant* constant,
const Expression* object,
bool has_side_effects,
const Variable* source_var /* = nullptr */)
- : Base(declaration, type, statement, constant, has_side_effects, source_var), object_(object) {}
+ : Base(declaration, type, stage, statement, constant, has_side_effects, source_var),
+ object_(object) {}
MemberAccessorExpression::~MemberAccessorExpression() = default;
@@ -42,7 +44,14 @@
const StructMember* member,
bool has_side_effects,
const Variable* source_var /* = nullptr */)
- : Base(declaration, type, statement, constant, object, has_side_effects, source_var),
+ : Base(declaration,
+ type,
+ object->Stage(),
+ statement,
+ constant,
+ object,
+ has_side_effects,
+ source_var),
member_(member) {}
StructMemberAccess::~StructMemberAccess() = default;
@@ -55,7 +64,14 @@
std::vector<uint32_t> indices,
bool has_side_effects,
const Variable* source_var /* = nullptr */)
- : Base(declaration, type, statement, constant, object, has_side_effects, source_var),
+ : Base(declaration,
+ type,
+ object->Stage(),
+ statement,
+ constant,
+ object,
+ has_side_effects,
+ source_var),
indices_(std::move(indices)) {}
Swizzle::~Swizzle() = default;
diff --git a/src/tint/sem/member_accessor_expression.h b/src/tint/sem/member_accessor_expression.h
index d8484a8..43e1466 100644
--- a/src/tint/sem/member_accessor_expression.h
+++ b/src/tint/sem/member_accessor_expression.h
@@ -37,6 +37,7 @@
/// Constructor
/// @param declaration the AST node
/// @param type the resolved type of the expression
+ /// @param stage the earliest evaluation stage for the expression
/// @param statement the statement that owns this expression
/// @param constant the constant value of the expression. May be null.
/// @param object the object that holds the member being accessed
@@ -44,6 +45,7 @@
/// @param source_var the (optional) source variable for this expression
MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
+ EvaluationStage stage,
const Statement* statement,
const Constant* constant,
const Expression* object,
diff --git a/src/tint/sem/type_constructor.cc b/src/tint/sem/type_constructor.cc
index 34f6e2a..d85e1be 100644
--- a/src/tint/sem/type_constructor.cc
+++ b/src/tint/sem/type_constructor.cc
@@ -18,8 +18,10 @@
namespace tint::sem {
-TypeConstructor::TypeConstructor(const sem::Type* type, const ParameterList& parameters)
- : Base(type, parameters) {}
+TypeConstructor::TypeConstructor(const sem::Type* type,
+ const ParameterList& parameters,
+ EvaluationStage stage)
+ : Base(type, parameters, stage) {}
TypeConstructor::~TypeConstructor() = default;
diff --git a/src/tint/sem/type_constructor.h b/src/tint/sem/type_constructor.h
index f3d4221..74b7858 100644
--- a/src/tint/sem/type_constructor.h
+++ b/src/tint/sem/type_constructor.h
@@ -25,7 +25,8 @@
/// Constructor
/// @param type the type that's being constructed
/// @param parameters the type constructor parameters
- TypeConstructor(const sem::Type* type, const ParameterList& parameters);
+ /// @param stage the earliest evaluation stage for the expression
+ TypeConstructor(const sem::Type* type, const ParameterList& parameters, EvaluationStage stage);
/// Destructor
~TypeConstructor() override;
diff --git a/src/tint/sem/type_conversion.cc b/src/tint/sem/type_conversion.cc
index 5da2928..262e3a0 100644
--- a/src/tint/sem/type_conversion.cc
+++ b/src/tint/sem/type_conversion.cc
@@ -18,8 +18,10 @@
namespace tint::sem {
-TypeConversion::TypeConversion(const sem::Type* type, const sem::Parameter* parameter)
- : Base(type, ParameterList{parameter}) {}
+TypeConversion::TypeConversion(const sem::Type* type,
+ const sem::Parameter* parameter,
+ EvaluationStage stage)
+ : Base(type, ParameterList{parameter}, stage) {}
TypeConversion::~TypeConversion() = default;
diff --git a/src/tint/sem/type_conversion.h b/src/tint/sem/type_conversion.h
index e400565..5584b36 100644
--- a/src/tint/sem/type_conversion.h
+++ b/src/tint/sem/type_conversion.h
@@ -25,7 +25,8 @@
/// Constructor
/// @param type the target type of the cast
/// @param parameter the type cast parameter
- TypeConversion(const sem::Type* type, const sem::Parameter* parameter);
+ /// @param stage the earliest evaluation stage for the expression
+ TypeConversion(const sem::Type* type, const sem::Parameter* parameter, EvaluationStage stage);
/// Destructor
~TypeConversion() override;
diff --git a/src/tint/sem/variable.cc b/src/tint/sem/variable.cc
index 3849807..28a373b 100644
--- a/src/tint/sem/variable.cc
+++ b/src/tint/sem/variable.cc
@@ -28,14 +28,15 @@
TINT_INSTANTIATE_TYPEINFO(tint::sem::VariableUser);
namespace tint::sem {
-
Variable::Variable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const Constant* constant_value)
: declaration_(declaration),
type_(type),
+ stage_(stage),
storage_class_(storage_class),
access_(access),
constant_value_(constant_value) {}
@@ -44,21 +45,24 @@
LocalVariable::LocalVariable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const sem::Statement* statement,
const Constant* constant_value)
- : Base(declaration, type, storage_class, access, constant_value), statement_(statement) {}
+ : Base(declaration, type, stage, storage_class, access, constant_value),
+ statement_(statement) {}
LocalVariable::~LocalVariable() = default;
GlobalVariable::GlobalVariable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const Constant* constant_value,
sem::BindingPoint binding_point)
- : Base(declaration, type, storage_class, access, constant_value),
+ : Base(declaration, type, stage, storage_class, access, constant_value),
binding_point_(binding_point) {}
GlobalVariable::~GlobalVariable() = default;
@@ -69,7 +73,9 @@
ast::StorageClass storage_class,
ast::Access access,
const ParameterUsage usage /* = ParameterUsage::kNone */)
- : Base(declaration, type, storage_class, access, nullptr), index_(index), usage_(usage) {}
+ : Base(declaration, type, EvaluationStage::kRuntime, storage_class, access, nullptr),
+ index_(index),
+ usage_(usage) {}
Parameter::~Parameter() = default;
@@ -78,6 +84,7 @@
sem::Variable* variable)
: Base(declaration,
variable->Type(),
+ variable->Stage(),
statement,
variable->ConstantValue(),
/* has_side_effects */ false),
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index e5a5cec..fe54f50 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -45,11 +45,13 @@
/// Constructor
/// @param declaration the AST declaration node
/// @param type the variable type
+ /// @param stage the evaluation stage for an expression of this variable type
/// @param storage_class the variable storage class
/// @param access the variable access control type
/// @param constant_value the constant value for the variable. May be null
Variable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const Constant* constant_value);
@@ -63,6 +65,9 @@
/// @returns the canonical type for the variable
const sem::Type* Type() const { return type_; }
+ /// @returns the evaluation stage for an expression of this variable type
+ EvaluationStage Stage() const { return stage_; }
+
/// @returns the storage class for the variable
ast::StorageClass StorageClass() const { return storage_class_; }
@@ -89,6 +94,7 @@
private:
const ast::Variable* const declaration_;
const sem::Type* const type_;
+ const EvaluationStage stage_;
const ast::StorageClass storage_class_;
const ast::Access access_;
const Constant* constant_value_;
@@ -102,12 +108,14 @@
/// Constructor
/// @param declaration the AST declaration node
/// @param type the variable type
+ /// @param stage the evaluation stage for an expression of this variable type
/// @param storage_class the variable storage class
/// @param access the variable access control type
/// @param statement the statement that declared this local variable
/// @param constant_value the constant value for the variable. May be null
LocalVariable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const sem::Statement* statement,
@@ -137,12 +145,14 @@
/// Constructor
/// @param declaration the AST declaration node
/// @param type the variable type
+ /// @param stage the evaluation stage for an expression of this variable type
/// @param storage_class the variable storage class
/// @param access the variable access control type
/// @param constant_value the constant value for the variable. May be null
/// @param binding_point the optional resource binding point of the variable
GlobalVariable(const ast::Variable* declaration,
const sem::Type* type,
+ EvaluationStage stage,
ast::StorageClass storage_class,
ast::Access access,
const Constant* constant_value,
diff --git a/src/tint/writer/append_vector.cc b/src/tint/writer/append_vector.cc
index c5f184d..28845d9 100644
--- a/src/tint/writer/append_vector.cc
+++ b/src/tint/writer/append_vector.cc
@@ -59,7 +59,8 @@
<< "unsupported vector element type: " << ty->TypeInfo().name;
return nullptr;
}
- auto* sem = b.create<sem::Expression>(expr, ty, stmt, /* constant_value */ nullptr,
+ auto* sem = b.create<sem::Expression>(expr, ty, sem::EvaluationStage::kRuntime, stmt,
+ /* constant_value */ nullptr,
/* has_side_effects */ false);
b.Sem().Add(expr, sem);
return sem;
@@ -136,10 +137,12 @@
auto* scalar_cast_target = b->create<sem::TypeConversion>(
packed_el_sem_ty,
b->create<sem::Parameter>(nullptr, 0u, scalar_sem->Type()->UnwrapRef(),
- ast::StorageClass::kNone, ast::Access::kUndefined));
+ ast::StorageClass::kNone, ast::Access::kUndefined),
+ sem::EvaluationStage::kRuntime);
auto* scalar_cast_sem = b->create<sem::Call>(
- scalar_cast_ast, scalar_cast_target, std::vector<const sem::Expression*>{scalar_sem},
- statement, /* constant_value */ nullptr, /* has_side_effects */ false);
+ scalar_cast_ast, scalar_cast_target, sem::EvaluationStage::kRuntime,
+ std::vector<const sem::Expression*>{scalar_sem}, statement,
+ /* constant_value */ nullptr, /* has_side_effects */ false);
b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
packed.emplace_back(scalar_cast_sem);
} else {
@@ -151,15 +154,17 @@
utils::Transform(packed, [&](const sem::Expression* expr) { return expr->Declaration(); }));
auto* constructor_target = b->create<sem::TypeConstructor>(
packed_sem_ty,
- utils::Transform(
- packed, [&](const tint::sem::Expression* arg, size_t i) -> const sem::Parameter* {
- return b->create<sem::Parameter>(nullptr, static_cast<uint32_t>(i),
- arg->Type()->UnwrapRef(), ast::StorageClass::kNone,
- ast::Access::kUndefined);
- }));
- auto* constructor_sem = b->create<sem::Call>(constructor_ast, constructor_target, packed,
- statement, /* constant_value */ nullptr,
- /* has_side_effects */ false);
+ utils::Transform(packed,
+ [&](const tint::sem::Expression* arg, size_t i) -> const sem::Parameter* {
+ return b->create<sem::Parameter>(
+ nullptr, static_cast<uint32_t>(i), arg->Type()->UnwrapRef(),
+ ast::StorageClass::kNone, ast::Access::kUndefined);
+ }),
+ sem::EvaluationStage::kRuntime);
+ auto* constructor_sem =
+ b->create<sem::Call>(constructor_ast, constructor_target, sem::EvaluationStage::kRuntime,
+ packed, statement, /* constant_value */ nullptr,
+ /* has_side_effects */ false);
b->Sem().Add(constructor_ast, constructor_sem);
return constructor_sem;
}
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 1a2b5dc..d28f3f4 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -1338,7 +1338,8 @@
const ast::Expression* GeneratorImpl::CreateF32Zero(const sem::Statement* stmt) {
auto* zero = builder_.Expr(0_f);
auto* f32 = builder_.create<sem::F32>();
- auto* sem_zero = builder_.create<sem::Expression>(zero, f32, stmt, /* constant_value */ nullptr,
+ auto* sem_zero = builder_.create<sem::Expression>(zero, f32, sem::EvaluationStage::kRuntime,
+ stmt, /* constant_value */ nullptr,
/* has_side_effects */ false);
builder_.Sem().Add(zero, sem_zero);
return zero;
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 0a6a196..e8ab2c7 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -2392,7 +2392,8 @@
auto* zero = builder_.Expr(0_i);
auto* stmt = builder_.Sem().Get(vector)->Stmt();
builder_.Sem().Add(
- zero, builder_.create<sem::Expression>(zero, i32, stmt, /* constant_value */ nullptr,
+ zero, builder_.create<sem::Expression>(zero, i32, sem::EvaluationStage::kRuntime, stmt,
+ /* constant_value */ nullptr,
/* has_side_effects */ false));
auto* packed = AppendVector(&builder_, vector, zero);
return EmitExpression(out, packed->Declaration());