tint: Track originating variables in the resolver

Attach the source variable to the sem::Expression nodes.

Bug: tint:1341
Change-Id: I71d8fcf5920c4daab9035c7a5615b34378889dac
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/87606
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
James Price 2022-04-22 17:40:33 +00:00
parent 0690b9623e
commit 98eb169692
9 changed files with 355 additions and 17 deletions

View File

@ -763,6 +763,7 @@ if(TINT_BUILD_TESTS)
resolver/resolver_test_helper.h
resolver/resolver_test.cc
resolver/side_effects_test.cc
resolver/source_variable_test.cc
resolver/storage_class_layout_validation_test.cc
resolver/storage_class_validation_test.cc
resolver/struct_layout_test.cc

View File

@ -1209,7 +1209,8 @@ sem::Expression* Resolver::IndexAccessor(
auto val = EvaluateConstantValue(expr, ty);
bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
val, has_side_effects);
val, has_side_effects,
obj->SourceVariable());
sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
return sem;
}
@ -1731,6 +1732,7 @@ sem::Expression* Resolver::MemberAccessor(
const ast::MemberAccessorExpression* expr) {
auto* structure = sem_.TypeOf(expr->structure);
auto* storage_ty = structure->UnwrapRef();
auto* source_var = sem_.Get(expr->structure)->SourceVariable();
const sem::Type* ret = nullptr;
std::vector<uint32_t> swizzle;
@ -1766,7 +1768,7 @@ sem::Expression* Resolver::MemberAccessor(
}
return builder_->create<sem::StructMemberAccess>(
expr, ret, current_statement_, member, has_side_effects);
expr, ret, current_statement_, member, has_side_effects, source_var);
}
if (auto* vec = storage_ty->As<sem::Vector>()) {
@ -1839,7 +1841,8 @@ sem::Expression* Resolver::MemberAccessor(
static_cast<uint32_t>(size));
}
return builder_->create<sem::Swizzle>(expr, ret, current_statement_,
std::move(swizzle), has_side_effects);
std::move(swizzle), has_side_effects,
source_var);
}
AddError(
@ -2047,6 +2050,7 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
}
const sem::Type* ty = nullptr;
const sem::Variable* source_var = nullptr;
switch (unary->op) {
case ast::UnaryOp::kNot:
@ -2105,6 +2109,8 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
ty = builder_->create<sem::Pointer>(ref->StoreType(),
ref->StorageClass(), ref->Access());
source_var = expr->SourceVariable();
} else {
AddError("cannot take the address of expression", unary->expr->source);
return nullptr;
@ -2115,6 +2121,7 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
if (auto* ptr = expr_ty->As<sem::Pointer>()) {
ty = builder_->create<sem::Reference>(
ptr->StoreType(), ptr->StorageClass(), ptr->Access());
source_var = expr->SourceVariable();
} else {
AddError("cannot dereference expression of type '" +
sem_.TypeNameOf(expr_ty) + "'",
@ -2125,8 +2132,8 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
}
auto val = EvaluateConstantValue(unary, ty);
auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_,
val, expr->HasSideEffects());
auto* sem = builder_->create<sem::Expression>(
unary, ty, current_statement_, val, expr->HasSideEffects(), source_var);
sem->Behaviors() = expr->Behaviors();
return sem;
}

View File

@ -0,0 +1,293 @@
// 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 "src/tint/resolver/resolver_test_helper.h"
#include "src/tint/sem/member_accessor_expression.h"
namespace tint::resolver {
namespace {
class ResolverSourceVariableTest : public ResolverTest {};
TEST_F(ResolverSourceVariableTest, GlobalPrivateVar) {
auto* a = Global("a", ty.f32(), ast::StorageClass::kPrivate);
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalWorkgroupVar) {
auto* a = Global("a", ty.f32(), ast::StorageClass::kWorkgroup);
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalStorageVar) {
auto* a =
Global("a", ty.f32(), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalUniformVar) {
auto* a =
Global("a", ty.f32(), ast::StorageClass::kUniform, GroupAndBinding(0, 0));
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalTextureVar) {
auto* a =
Global("a", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
ast::StorageClass::kNone, GroupAndBinding(0, 0));
auto* expr = Expr(a);
WrapInFunction(Call("textureDimensions", expr));
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalOverride) {
auto* a = Override("a", ty.f32(), Expr(1.f));
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, GlobalLet) {
auto* a = GlobalConst("a", ty.f32(), Expr(1.f));
auto* expr = Expr(a);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, FunctionVar) {
auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* expr = Expr(a);
WrapInFunction(a, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, FunctionLet) {
auto* a = Const("a", ty.f32(), Expr(1.f));
auto* expr = Expr(a);
WrapInFunction(a, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, Parameter) {
auto* a = Param("a", ty.f32());
auto* expr = Expr(a);
Func("foo", {a}, ty.void_(), {WrapInStatement(expr)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, PointerParameter) {
// fn foo(a : ptr<function, f32>)
// {
// let b = a;
// }
auto* param = Param("a", ty.pointer(ty.f32(), ast::StorageClass::kFunction));
auto* expr_param = Expr(param);
auto* let = Const("b", nullptr, expr_param);
auto* expr_let = Expr("b");
Func("foo", {param}, ty.void_(),
{WrapInStatement(let), WrapInStatement(expr_let)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_param = Sem().Get(param);
EXPECT_EQ(Sem().Get(expr_param)->SourceVariable(), sem_param);
EXPECT_EQ(Sem().Get(expr_let)->SourceVariable(), sem_param);
}
TEST_F(ResolverSourceVariableTest, VarCopyVar) {
// {
// var a : f32;
// var b = a;
// }
auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* expr_a = Expr(a);
auto* b = Var("b", ty.f32(), ast::StorageClass::kNone, expr_a);
auto* expr_b = Expr(b);
WrapInFunction(a, b, expr_b);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
auto* sem_b = Sem().Get(b);
EXPECT_EQ(Sem().Get(expr_a)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(expr_b)->SourceVariable(), sem_b);
}
TEST_F(ResolverSourceVariableTest, LetCopyVar) {
// {
// var a : f32;
// let b = a;
// }
auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* expr_a = Expr(a);
auto* b = Const("b", ty.f32(), expr_a);
auto* expr_b = Expr(b);
WrapInFunction(a, b, expr_b);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
auto* sem_b = Sem().Get(b);
EXPECT_EQ(Sem().Get(expr_a)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(expr_b)->SourceVariable(), sem_b);
}
TEST_F(ResolverSourceVariableTest, ThroughIndexAccessor) {
// var<private> a : array<f32, 4>;
// {
// a[2]
// }
auto* a = Global("a", ty.array(ty.f32(), 4), ast::StorageClass::kPrivate);
auto* expr = IndexAccessor(a, 2);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, ThroughMemberAccessor) {
// struct S { f : f32 }
// var<private> a : S;
// {
// a.f
// }
auto* S = Structure("S", {Member("f", ty.f32())});
auto* a = Global("a", ty.Of(S), ast::StorageClass::kPrivate);
auto* expr = MemberAccessor(a, "f");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, ThroughPointers) {
// var<private> a : f32;
// {
// let a_ptr1 = &*&a;
// let a_ptr2 = &*a_ptr1;
// }
auto* a = Global("a", ty.f32(), ast::StorageClass::kPrivate);
auto* address_of_1 = AddressOf(a);
auto* deref_1 = Deref(address_of_1);
auto* address_of_2 = AddressOf(deref_1);
auto* a_ptr1 = Const("a_ptr1", nullptr, address_of_2);
auto* deref_2 = Deref(a_ptr1);
auto* address_of_3 = AddressOf(deref_2);
auto* a_ptr2 = Const("a_ptr2", nullptr, address_of_3);
WrapInFunction(a_ptr1, a_ptr2);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem_a = Sem().Get(a);
EXPECT_EQ(Sem().Get(address_of_1)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(address_of_2)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(address_of_3)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(deref_1)->SourceVariable(), sem_a);
EXPECT_EQ(Sem().Get(deref_2)->SourceVariable(), sem_a);
}
TEST_F(ResolverSourceVariableTest, Literal) {
auto* expr = Expr(1.f);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), nullptr);
}
TEST_F(ResolverSourceVariableTest, FunctionReturnValue) {
auto* expr = Call("min", 1.f, 2.f);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), nullptr);
}
TEST_F(ResolverSourceVariableTest, BinaryExpression) {
auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* expr = Add(a, Expr(1.f));
WrapInFunction(a, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), nullptr);
}
TEST_F(ResolverSourceVariableTest, UnaryExpression) {
auto* a = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation, Expr(a));
WrapInFunction(a, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(Sem().Get(expr)->SourceVariable(), nullptr);
}
} // namespace
} // namespace tint::resolver

View File

@ -24,8 +24,10 @@ Expression::Expression(const ast::Expression* declaration,
const sem::Type* type,
const Statement* statement,
Constant constant,
bool has_side_effects)
bool has_side_effects,
const Variable* source_var /* = nullptr */)
: declaration_(declaration),
source_variable_(source_var),
type_(type),
statement_(statement),
constant_(std::move(constant)),

View File

@ -24,6 +24,7 @@
namespace tint::sem {
class Statement;
class Type;
class Variable;
} // namespace tint::sem
namespace tint::sem {
@ -36,11 +37,13 @@ class Expression : public Castable<Expression, Node> {
/// @param statement the statement that owns this expression
/// @param constant the constant value of the expression. May be invalid
/// @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,
const Statement* statement,
Constant constant,
bool has_side_effects);
bool has_side_effects,
const Variable* source_var = nullptr);
/// Destructor
~Expression() override;
@ -57,6 +60,13 @@ class Expression : public Castable<Expression, Node> {
/// @return the constant value of this expression
const Constant& ConstantValue() const { return constant_; }
/// Returns the variable or parameter that this expression derives from.
/// For reference and pointer expressions, this will either be the originating
/// variable or a function parameter. For other types of expressions, it will
/// either be the parameter or constant declaration, or nullptr.
/// @return the source variable of this expression, or nullptr
const Variable* SourceVariable() const { return source_variable_; }
/// @return the behaviors of this statement
const sem::Behaviors& Behaviors() const { return behaviors_; }
@ -69,6 +79,8 @@ class Expression : public Castable<Expression, Node> {
protected:
/// The AST expression node for this semantic expression
const ast::Expression* const declaration_;
/// The source variable for this semantic expression, or nullptr
const Variable* source_variable_;
private:
const sem::Type* const type_;

View File

@ -27,8 +27,14 @@ MemberAccessorExpression::MemberAccessorExpression(
const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
bool has_side_effects)
: Base(declaration, type, statement, Constant{}, has_side_effects) {}
bool has_side_effects,
const Variable* source_var /* = nullptr */)
: Base(declaration,
type,
statement,
Constant{},
has_side_effects,
source_var) {}
MemberAccessorExpression::~MemberAccessorExpression() = default;
@ -37,8 +43,10 @@ StructMemberAccess::StructMemberAccess(
const sem::Type* type,
const Statement* statement,
const StructMember* member,
bool has_side_effects)
: Base(declaration, type, statement, has_side_effects), member_(member) {}
bool has_side_effects,
const Variable* source_var /* = nullptr */)
: Base(declaration, type, statement, has_side_effects, source_var),
member_(member) {}
StructMemberAccess::~StructMemberAccess() = default;
@ -46,8 +54,9 @@ Swizzle::Swizzle(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
std::vector<uint32_t> indices,
bool has_side_effects)
: Base(declaration, type, statement, has_side_effects),
bool has_side_effects,
const Variable* source_var /* = nullptr */)
: Base(declaration, type, statement, has_side_effects, source_var),
indices_(std::move(indices)) {}
Swizzle::~Swizzle() = default;

View File

@ -40,10 +40,12 @@ class MemberAccessorExpression
/// @param type the resolved type of the expression
/// @param statement the statement that owns this expression
/// @param has_side_effects whether this expression may have side effects
/// @param source_var the (optional) source variable for this expression
MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
bool has_side_effects);
bool has_side_effects,
const Variable* source_var = nullptr);
/// Destructor
~MemberAccessorExpression() override;
@ -61,11 +63,13 @@ class StructMemberAccess final
/// @param statement the statement that owns this expression
/// @param member the structure member
/// @param has_side_effects whether this expression may have side effects
/// @param source_var the (optional) source variable for this expression
StructMemberAccess(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
const StructMember* member,
bool has_side_effects);
bool has_side_effects,
const Variable* source_var = nullptr);
/// Destructor
~StructMemberAccess() override;
@ -87,11 +91,13 @@ class Swizzle final : public Castable<Swizzle, MemberAccessorExpression> {
/// @param statement the statement that owns this expression
/// @param indices the swizzle indices
/// @param has_side_effects whether this expression may have side effects
/// @param source_var the (optional) source variable for this expression
Swizzle(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
std::vector<uint32_t> indices,
bool has_side_effects);
bool has_side_effects,
const Variable* source_var = nullptr);
/// Destructor
~Swizzle() override;

View File

@ -82,6 +82,13 @@ VariableUser::VariableUser(const ast::IdentifierExpression* declaration,
statement,
variable->ConstantValue(),
/* has_side_effects */ false),
variable_(variable) {}
variable_(variable) {
auto* type = variable->Type();
if (type->Is<sem::Pointer>() && variable->Constructor()) {
source_variable_ = variable->Constructor()->SourceVariable();
} else {
source_variable_ = variable;
}
}
} // namespace tint::sem

View File

@ -265,6 +265,7 @@ tint_unittests_source_set("tint_unittests_resolver_src") {
"../../src/tint/resolver/resolver_test_helper.cc",
"../../src/tint/resolver/resolver_test_helper.h",
"../../src/tint/resolver/side_effects_test.cc",
"../../src/tint/resolver/source_variable_test.cc",
"../../src/tint/resolver/storage_class_layout_validation_test.cc",
"../../src/tint/resolver/storage_class_validation_test.cc",
"../../src/tint/resolver/struct_layout_test.cc",