tint/resolver: Improve errors for expr eval-stages
Raise the error on the inner-most expression that violates the required evaluation stage. Fixed: tint:1655 Change-Id: I82186e72ed6efa1cd6d4456c04446da18e9f1850 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105640 Commit-Queue: Ben Clayton <bclayton@google.com> Auto-Submit: Ben Clayton <bclayton@google.com> Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
559a248233
commit
c84d06e860
|
@ -239,7 +239,8 @@ TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidAccess) {
|
|||
EXPECT_EQ(t.value, nullptr);
|
||||
EXPECT_FALSE(t.matched);
|
||||
EXPECT_TRUE(t.errored);
|
||||
EXPECT_EQ(p->error(), R"(1:30: expected access control for storage texture type. Did you mean 'read'?
|
||||
EXPECT_EQ(p->error(),
|
||||
R"(1:30: expected access control for storage texture type. Did you mean 'read'?
|
||||
Possible values: 'read', 'read_write', 'write')");
|
||||
}
|
||||
|
||||
|
|
|
@ -738,7 +738,9 @@ TEST_F(StructMemberAttributeTest, Align_Attribute_Override) {
|
|||
Structure("mystruct", utils::Vector{Member(
|
||||
"a", ty.f32(), utils::Vector{MemberAlign(Source{{12, 34}}, "val")})});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(12:34 error: 'align' must be an i32 or u32 value)");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: @align requires a const-expression, but expression is an override-expression)");
|
||||
}
|
||||
|
||||
} // namespace StructAndStructMemberTests
|
||||
|
|
|
@ -67,7 +67,9 @@ TEST_F(ResolverBuiltinTest, ModuleScopeUsage) {
|
|||
|
||||
// TODO(crbug.com/tint/1581): Once 'abs' is implemented as @const, this will no longer be an
|
||||
// error.
|
||||
EXPECT_EQ(r()->error(), R"(12:34 error: 'const' initializer must be const-expression)");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is a runtime-expression)");
|
||||
}
|
||||
|
||||
// Tests for Logical builtins
|
||||
|
|
|
@ -416,6 +416,8 @@ sem::Variable* Resolver::Override(const ast::Override* v) {
|
|||
|
||||
// Does the variable have a constructor?
|
||||
if (v->constructor) {
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kOverride, "override initializer"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
rhs = Materialize(Expression(v->constructor), ty);
|
||||
if (!rhs) {
|
||||
return nullptr;
|
||||
|
@ -453,8 +455,8 @@ sem::Variable* Resolver::Override(const ast::Override* v) {
|
|||
}
|
||||
auto* c = materialize->ConstantValue();
|
||||
if (!c) {
|
||||
// TODO(crbug.com/tint/1633): Handle invalid materialization when expressions
|
||||
// are supported.
|
||||
// TODO(crbug.com/tint/1633): Handle invalid materialization when expressions are
|
||||
// supported.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -493,9 +495,14 @@ sem::Variable* Resolver::Const(const ast::Const* c, bool is_global) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const auto* rhs = Expression(c->constructor);
|
||||
if (!rhs) {
|
||||
return nullptr;
|
||||
const sem::Expression* rhs = nullptr;
|
||||
{
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "const initializer"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
rhs = Expression(c->constructor);
|
||||
if (!rhs) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (ty) {
|
||||
|
@ -509,12 +516,6 @@ sem::Variable* Resolver::Const(const ast::Const* c, bool is_global) {
|
|||
ty = rhs->Type();
|
||||
}
|
||||
|
||||
const auto value = rhs->ConstantValue();
|
||||
if (!value) {
|
||||
AddError("'const' initializer must be const-expression", c->constructor->source);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!validator_.VariableInitializer(c, ast::AddressSpace::kNone, ty, rhs)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -525,6 +526,7 @@ sem::Variable* Resolver::Const(const ast::Const* c, bool is_global) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const auto value = rhs->ConstantValue();
|
||||
auto* sem = is_global ? static_cast<sem::Variable*>(builder_->create<sem::GlobalVariable>(
|
||||
c, ty, sem::EvaluationStage::kConstant, ast::AddressSpace::kNone,
|
||||
ast::Access::kUndefined, value, sem::BindingPoint{}, std::nullopt))
|
||||
|
@ -552,6 +554,12 @@ sem::Variable* Resolver::Var(const ast::Var* var, bool is_global) {
|
|||
|
||||
// Does the variable have a constructor?
|
||||
if (var->constructor) {
|
||||
ExprEvalStageConstraint constraint{
|
||||
is_global ? sem::EvaluationStage::kOverride : sem::EvaluationStage::kRuntime,
|
||||
"var initializer",
|
||||
};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
|
||||
rhs = Materialize(Expression(var->constructor), storage_ty);
|
||||
if (!rhs) {
|
||||
return nullptr;
|
||||
|
@ -858,16 +866,13 @@ sem::GlobalVariable* Resolver::GlobalVariable(const ast::Variable* v) {
|
|||
}
|
||||
|
||||
sem::Statement* Resolver::StaticAssert(const ast::StaticAssert* assertion) {
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "static assertion"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
auto* expr = Expression(assertion->condition);
|
||||
if (!expr) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* cond = expr->ConstantValue();
|
||||
if (!cond) {
|
||||
AddError("static assertion condition must be a const-expression",
|
||||
assertion->condition->source);
|
||||
return nullptr;
|
||||
}
|
||||
if (auto* ty = cond->Type(); !ty->Is<sem::Bool>()) {
|
||||
AddError(
|
||||
"static assertion condition must be a bool, got '" + builder_->FriendlyName(ty) + "'",
|
||||
|
@ -1458,6 +1463,13 @@ sem::Expression* Resolver::Expression(const ast::Expression* root) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto* constraint = expr_eval_stage_constraint_.constraint) {
|
||||
if (!validator_.EvaluationStage(sem_expr, expr_eval_stage_constraint_.stage,
|
||||
constraint)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
builder_->Sem().Add(expr, sem_expr);
|
||||
if (expr == root) {
|
||||
return sem_expr;
|
||||
|
@ -2818,13 +2830,17 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
|||
// Offset attributes are not part of the WGSL spec, but are emitted
|
||||
// by the SPIR-V reader.
|
||||
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant,
|
||||
"@offset value"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
|
||||
auto* materialized = Materialize(Expression(o->expr));
|
||||
if (!materialized) {
|
||||
return nullptr;
|
||||
}
|
||||
auto const_value = materialized->ConstantValue();
|
||||
if (!const_value) {
|
||||
AddError("'offset' must be const-expression", o->expr->source);
|
||||
AddError("'offset' must be constant expression", o->expr->source);
|
||||
return nullptr;
|
||||
}
|
||||
offset = const_value->As<uint64_t>();
|
||||
|
@ -2836,6 +2852,9 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
|||
align = 1;
|
||||
has_offset_attr = true;
|
||||
} else if (auto* a = attr->As<ast::StructMemberAlignAttribute>()) {
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@align"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
|
||||
auto* materialized = Materialize(Expression(a->expr));
|
||||
if (!materialized) {
|
||||
return nullptr;
|
||||
|
@ -2847,7 +2866,7 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
|||
|
||||
auto const_value = materialized->ConstantValue();
|
||||
if (!const_value) {
|
||||
AddError("'align' must be const-expression", a->source);
|
||||
AddError("'align' must be constant expression", a->source);
|
||||
return nullptr;
|
||||
}
|
||||
auto value = const_value->As<AInt>();
|
||||
|
@ -2856,16 +2875,19 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
|||
AddError("'align' value must be a positive, power-of-two integer", a->source);
|
||||
return nullptr;
|
||||
}
|
||||
align = const_value->As<u32>();
|
||||
align = u32(value);
|
||||
has_align_attr = true;
|
||||
} else if (auto* s = attr->As<ast::StructMemberSizeAttribute>()) {
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@size"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
|
||||
auto* materialized = Materialize(Expression(s->expr));
|
||||
if (!materialized) {
|
||||
return nullptr;
|
||||
}
|
||||
auto const_value = materialized->ConstantValue();
|
||||
if (!const_value) {
|
||||
AddError("'size' must be const-expression", s->expr->source);
|
||||
AddError("'size' must be constant expression", s->expr->source);
|
||||
return nullptr;
|
||||
}
|
||||
auto value = const_value->As<uint64_t>();
|
||||
|
@ -2876,9 +2898,12 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
|||
s->source);
|
||||
return nullptr;
|
||||
}
|
||||
size = const_value->As<u32>();
|
||||
size = u32(value);
|
||||
has_size_attr = true;
|
||||
} else if (auto* l = attr->As<ast::LocationAttribute>()) {
|
||||
ExprEvalStageConstraint constraint{sem::EvaluationStage::kConstant, "@location"};
|
||||
TINT_SCOPED_ASSIGNMENT(expr_eval_stage_constraint_, constraint);
|
||||
|
||||
auto* materialize = Materialize(Expression(l->expr));
|
||||
if (!materialize) {
|
||||
return nullptr;
|
||||
|
|
|
@ -414,6 +414,15 @@ class Resolver {
|
|||
using StructConstructorSig =
|
||||
utils::UnorderedKeyWrapper<std::tuple<const sem::Struct*, size_t, sem::EvaluationStage>>;
|
||||
|
||||
/// ExprEvalStageConstraint describes a constraint on when expressions can be evaluated.
|
||||
struct ExprEvalStageConstraint {
|
||||
/// The latest stage that the expression can be evaluated
|
||||
sem::EvaluationStage stage = sem::EvaluationStage::kRuntime;
|
||||
/// The 'thing' that is imposing the contraint. e.g. "var declaration"
|
||||
/// If nullptr, then there is no constraint
|
||||
const char* constraint = nullptr;
|
||||
};
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
diag::List& diagnostics_;
|
||||
ConstEval const_eval_;
|
||||
|
@ -425,10 +434,10 @@ class Resolver {
|
|||
std::vector<sem::Function*> entry_points_;
|
||||
std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
|
||||
utils::Bitset<0> marked_;
|
||||
ExprEvalStageConstraint expr_eval_stage_constraint_;
|
||||
std::unordered_map<OverrideId, const sem::Variable*> override_ids_;
|
||||
std::unordered_map<ArrayConstructorSig, sem::CallTarget*> array_ctors_;
|
||||
std::unordered_map<StructConstructorSig, sem::CallTarget*> struct_ctors_;
|
||||
|
||||
sem::Function* current_function_ = nullptr;
|
||||
sem::Statement* current_statement_ = nullptr;
|
||||
sem::CompoundStatement* current_compound_statement_ = nullptr;
|
||||
|
|
|
@ -88,7 +88,8 @@ TEST_F(ResolverStaticAssertTest, Local_NonConst) {
|
|||
WrapInFunction(StaticAssert(Expr(Source{{12, 34}}, "V")));
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: static assertion condition must be a const-expression");
|
||||
"12:34 error: static assertion requires a const-expression, but expression is a "
|
||||
"runtime-expression");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStaticAssertTest, Local_LessThan_Pass) {
|
||||
|
|
|
@ -1391,6 +1391,30 @@ bool Validator::EntryPoint(const sem::Function* func, ast::PipelineStage stage)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Validator::EvaluationStage(const sem::Expression* expr,
|
||||
sem::EvaluationStage latest_stage,
|
||||
std::string_view constraint) const {
|
||||
if (expr->Stage() > latest_stage) {
|
||||
auto stage_name = [](sem::EvaluationStage stage) -> std::string {
|
||||
switch (stage) {
|
||||
case sem::EvaluationStage::kRuntime:
|
||||
return "a runtime-expression";
|
||||
case sem::EvaluationStage::kOverride:
|
||||
return "an override-expression";
|
||||
case sem::EvaluationStage::kConstant:
|
||||
return "a const-expression";
|
||||
}
|
||||
return "<unknown>";
|
||||
};
|
||||
|
||||
AddError(std::string(constraint) + " requires " + stage_name(latest_stage) +
|
||||
", but expression is " + stage_name(expr->Stage()),
|
||||
expr->Declaration()->source);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Validator::Statements(utils::VectorRef<const ast::Statement*> stmts) const {
|
||||
for (auto* stmt : stmts) {
|
||||
if (!sem_.Get(stmt)->IsReachable()) {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "src/tint/ast/pipeline_stage.h"
|
||||
#include "src/tint/program_builder.h"
|
||||
#include "src/tint/resolver/sem_helper.h"
|
||||
#include "src/tint/sem/evaluation_stage.h"
|
||||
#include "src/tint/source.h"
|
||||
|
||||
// Forward declarations
|
||||
|
@ -209,6 +210,15 @@ class Validator {
|
|||
/// @returns true on success, false otherwise
|
||||
bool EntryPoint(const sem::Function* func, ast::PipelineStage stage) const;
|
||||
|
||||
/// Validates that the expression must not be evaluated any later than @p latest_stage
|
||||
/// @param expr the expression to check
|
||||
/// @param latest_stage the latest evaluation stage that the expression can be evaluated
|
||||
/// @param constraint the 'thing' that is imposing the contraint. e.g. "var declaration"
|
||||
/// @returns true if @p expr is evaluated in or before @p latest_stage, false otherwise
|
||||
bool EvaluationStage(const sem::Expression* expr,
|
||||
sem::EvaluationStage latest_stage,
|
||||
std::string_view constraint) const;
|
||||
|
||||
/// Validates a for loop
|
||||
/// @param stmt the for loop statement to validate
|
||||
/// @returns true on success, false otherwise
|
||||
|
|
|
@ -423,7 +423,9 @@ TEST_F(ResolverVariableValidationTest, ConstInitWithVar) {
|
|||
WrapInFunction(v, c);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(12:34 error: 'const' initializer must be const-expression)");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is a runtime-expression)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVariableValidationTest, ConstInitWithOverride) {
|
||||
|
@ -432,7 +434,9 @@ TEST_F(ResolverVariableValidationTest, ConstInitWithOverride) {
|
|||
WrapInFunction(c);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(12:34 error: 'const' initializer must be const-expression)");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is an override-expression)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVariableValidationTest, ConstInitWithLet) {
|
||||
|
@ -441,7 +445,30 @@ TEST_F(ResolverVariableValidationTest, ConstInitWithLet) {
|
|||
WrapInFunction(l, c);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(12:34 error: 'const' initializer must be const-expression)");
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is a runtime-expression)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVariableValidationTest, ConstInitWithRuntimeExpr) {
|
||||
// const c = clamp(2, dpdx(0.5), 3);
|
||||
WrapInFunction(Const("c", Call("clamp", 2_a, Call(Source{{12, 34}}, "dpdx", 0.5_a), 3_a)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is a runtime-expression)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVariableValidationTest, ConstInitWithOverrideExpr) {
|
||||
auto* o = Override("v", Expr(1_i));
|
||||
auto* c = Const("c", Add(10_a, Expr(Source{{12, 34}}, o)));
|
||||
WrapInFunction(c);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: const initializer requires a const-expression, but expression is an override-expression)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -1311,7 +1311,8 @@ fn f() {
|
|||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(error: array size is an override-expression, when expected a constant-expression.
|
||||
auto* expect =
|
||||
R"(error: array size is an override-expression, when expected a constant-expression.
|
||||
Was the SubstituteOverride transform run?)";
|
||||
|
||||
auto got = Run<Robustness>(src);
|
||||
|
|
Loading…
Reference in New Issue