Add sem::Expression::HasSideEffects()

Will be used to implement order of execution.

Bug: tint:1300
Change-Id: I027295e482da7a3f9d7ca930b5303e8f89d7fe09
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/79824
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
Antonio Maiorano 2022-02-09 18:30:27 +00:00 committed by Tint LUCI CQ
parent 35cc663130
commit dba215cd16
18 changed files with 484 additions and 41 deletions

View File

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

View File

@ -1540,6 +1540,15 @@ class ProgramBuilder {
Expr(std::forward<EXPR>(expr))); Expr(std::forward<EXPR>(expr)));
} }
/// @param expr the expression to perform a unary not on
/// @return an ast::UnaryOpExpression that is the unary not of the input
/// expression
template <typename EXPR>
const ast::UnaryOpExpression* Not(EXPR&& expr) {
return create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
Expr(std::forward<EXPR>(expr)));
}
/// @param source the source information /// @param source the source information
/// @param func the function name /// @param func the function name
/// @param args the function call arguments /// @param args the function call arguments

View File

@ -1140,7 +1140,7 @@ sem::Expression* Resolver::Expression(const ast::Expression* root) {
} else if (expr->Is<ast::PhonyExpression>()) { } else if (expr->Is<ast::PhonyExpression>()) {
sem_expr = builder_->create<sem::Expression>( sem_expr = builder_->create<sem::Expression>(
expr, builder_->create<sem::Void>(), current_statement_, expr, builder_->create<sem::Void>(), current_statement_,
sem::Constant{}); sem::Constant{}, /* has_side_effects */ false);
} else { } else {
TINT_ICE(Resolver, diagnostics_) TINT_ICE(Resolver, diagnostics_)
<< "unhandled expression type: " << expr->TypeInfo().name; << "unhandled expression type: " << expr->TypeInfo().name;
@ -1193,8 +1193,9 @@ sem::Expression* Resolver::IndexAccessor(
} }
auto val = EvaluateConstantValue(expr, ty); auto val = EvaluateConstantValue(expr, ty);
auto* sem = bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
builder_->create<sem::Expression>(expr, ty, current_statement_, val); auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
val, has_side_effects);
sem->Behaviors() = idx->Behaviors() + obj->Behaviors(); sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
return sem; return sem;
} }
@ -1207,8 +1208,8 @@ sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
} }
auto val = EvaluateConstantValue(expr, ty); auto val = EvaluateConstantValue(expr, ty);
auto* sem = auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
builder_->create<sem::Expression>(expr, ty, current_statement_, val); val, inner->HasSideEffects());
sem->Behaviors() = inner->Behaviors(); sem->Behaviors() = inner->Behaviors();
@ -1382,8 +1383,13 @@ sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
AddWarning("use of deprecated builtin", expr->source); AddWarning("use of deprecated builtin", expr->source);
} }
bool has_side_effects = builtin->HasSideEffects() ||
std::any_of(args.begin(), args.end(), [](auto* e) {
return e->HasSideEffects();
});
auto* call = builder_->create<sem::Call>(expr, builtin, std::move(args), auto* call = builder_->create<sem::Call>(expr, builtin, std::move(args),
current_statement_, sem::Constant{}); current_statement_, sem::Constant{},
has_side_effects);
current_function_->AddDirectlyCalledBuiltin(builtin); current_function_->AddDirectlyCalledBuiltin(builtin);
@ -1427,8 +1433,12 @@ sem::Call* Resolver::FunctionCall(
auto sym = expr->target.name->symbol; auto sym = expr->target.name->symbol;
auto name = builder_->Symbols().NameFor(sym); auto name = builder_->Symbols().NameFor(sym);
// 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), auto* call = builder_->create<sem::Call>(expr, target, std::move(args),
current_statement_, sem::Constant{}); current_statement_, sem::Constant{},
has_side_effects);
if (current_function_) { if (current_function_) {
// Note: Requires called functions to be resolved first. // Note: Requires called functions to be resolved first.
@ -1530,9 +1540,10 @@ sem::Call* Resolver::TypeConversion(const ast::CallExpression* expr,
} }
auto val = EvaluateConstantValue(expr, target); auto val = EvaluateConstantValue(expr, target);
bool has_side_effects = arg->HasSideEffects();
return builder_->create<sem::Call>(expr, call_target, return builder_->create<sem::Call>(expr, call_target,
std::vector<const sem::Expression*>{arg}, std::vector<const sem::Expression*>{arg},
current_statement_, val); current_statement_, val, has_side_effects);
} }
sem::Call* Resolver::TypeConstructor( sem::Call* Resolver::TypeConstructor(
@ -1590,8 +1601,10 @@ sem::Call* Resolver::TypeConstructor(
} }
auto val = EvaluateConstantValue(expr, ty); auto val = EvaluateConstantValue(expr, ty);
bool has_side_effects = std::any_of(
args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
return builder_->create<sem::Call>(expr, call_target, std::move(args), return builder_->create<sem::Call>(expr, call_target, std::move(args),
current_statement_, val); current_statement_, val, has_side_effects);
} }
sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) { sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
@ -1601,8 +1614,8 @@ sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
} }
auto val = EvaluateConstantValue(literal, ty); auto val = EvaluateConstantValue(literal, ty);
return builder_->create<sem::Expression>(literal, ty, current_statement_, return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
val); /* has_side_effects */ false);
} }
sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) { sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
@ -1715,8 +1728,12 @@ sem::Expression* Resolver::MemberAccessor(
ref->Access()); ref->Access());
} }
// Structure may be a side-effecting expression (e.g. function call).
auto* sem_structure = Sem(expr->structure);
bool has_side_effects = sem_structure && sem_structure->HasSideEffects();
return builder_->create<sem::StructMemberAccess>( return builder_->create<sem::StructMemberAccess>(
expr, ret, current_statement_, member); expr, ret, current_statement_, member, has_side_effects);
} }
if (auto* vec = storage_ty->As<sem::Vector>()) { if (auto* vec = storage_ty->As<sem::Vector>()) {
@ -1827,8 +1844,9 @@ sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
auto build = [&](const sem::Type* ty) { auto build = [&](const sem::Type* ty) {
auto val = EvaluateConstantValue(expr, ty); auto val = EvaluateConstantValue(expr, ty);
auto* sem = bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
builder_->create<sem::Expression>(expr, ty, current_statement_, val); auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
val, has_side_effects);
sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors(); sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
return sem; return sem;
}; };
@ -2075,8 +2093,8 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
} }
auto val = EvaluateConstantValue(unary, ty); auto val = EvaluateConstantValue(unary, ty);
auto* sem = auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_,
builder_->create<sem::Expression>(unary, ty, current_statement_, val); val, expr->HasSideEffects());
sem->Behaviors() = expr->Behaviors(); sem->Behaviors() = expr->Behaviors();
return sem; return sem;
} }

View File

@ -0,0 +1,371 @@
// 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/resolver/resolver.h"
#include "gtest/gtest.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/expression.h"
#include "src/sem/member_accessor_expression.h"
namespace tint::resolver {
namespace {
struct SideEffectsTest : ResolverTest {
template <typename T>
void MakeSideEffectFunc(const char* name) {
auto global = Sym();
Global(global, ty.Of<T>(), ast::StorageClass::kPrivate);
auto local = Sym();
Func(name, {}, ty.Of<T>(),
{
Decl(Var(local, ty.Of<T>())),
Assign(global, local),
Return(global),
});
}
template <typename MAKE_TYPE_FUNC>
void MakeSideEffectFunc(const char* name, MAKE_TYPE_FUNC make_type) {
auto global = Sym();
Global(global, make_type(), ast::StorageClass::kPrivate);
auto local = Sym();
Func(name, {}, make_type(),
{
Decl(Var(local, make_type())),
Assign(global, local),
Return(global),
});
}
};
TEST_F(SideEffectsTest, Phony) {
auto* expr = Phony();
auto* body = Assign(expr, 1);
WrapInFunction(body);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Literal) {
auto* expr = Expr(1);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, VariableUser) {
auto* var = Decl(Var("a", ty.i32()));
auto* expr = Expr("a");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::VariableUser>());
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_Builtin_NoSE) {
Global("a", ty.f32(), ast::StorageClass::kPrivate);
auto* expr = Call("dpdx", "a");
Func("f", {}, ty.void_(), {Ignore(expr)},
{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_Builtin_NoSE_WithSEArg) {
MakeSideEffectFunc<f32>("se");
auto* expr = Call("dpdx", Call("se"));
Func("f", {}, ty.void_(), {Ignore(expr)},
{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_Builtin_SE) {
Global("a", ty.atomic(ty.i32()), ast::StorageClass::kWorkgroup);
auto* expr = Call("atomicAdd", AddressOf("a"), 1);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_Function) {
Func("f", {}, ty.i32(), {Return(1)});
auto* expr = Call("f");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_TypeConversion_NoSE) {
auto* var = Decl(Var("a", ty.i32()));
auto* expr = Construct(ty.f32(), "a");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_TypeConversion_SE) {
MakeSideEffectFunc<i32>("se");
auto* expr = Construct(ty.f32(), Call("se"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_TypeConstructor_NoSE) {
auto* var = Decl(Var("a", ty.f32()));
auto* expr = Construct(ty.f32(), "a");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Call_TypeConstructor_SE) {
MakeSideEffectFunc<f32>("se");
auto* expr = Construct(ty.f32(), Call("se"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->Is<sem::Call>());
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, MemberAccessor_Struct_NoSE) {
auto* s = Structure("S", {Member("m", ty.i32())});
auto* var = Decl(Var("a", ty.Of(s)));
auto* expr = MemberAccessor("a", "m");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, MemberAccessor_Struct_SE) {
auto* s = Structure("S", {Member("m", ty.i32())});
MakeSideEffectFunc("se", [&] { return ty.Of(s); });
auto* expr = MemberAccessor(Call("se"), "m");
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, MemberAccessor_Vector) {
auto* var = Decl(Var("a", ty.vec4<f32>()));
auto* expr = MemberAccessor("a", "x");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_TRUE(sem->Is<sem::MemberAccessorExpression>());
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, MemberAccessor_VectorSwizzle) {
auto* var = Decl(Var("a", ty.vec4<f32>()));
auto* expr = MemberAccessor("a", "xzyw");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_TRUE(sem->Is<sem::Swizzle>());
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Binary_NoSE) {
auto* a = Decl(Var("a", ty.i32()));
auto* b = Decl(Var("b", ty.i32()));
auto* expr = Add("a", "b");
WrapInFunction(a, b, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Binary_LeftSE) {
MakeSideEffectFunc<i32>("se");
auto* b = Decl(Var("b", ty.i32()));
auto* expr = Add(Call("se"), "b");
WrapInFunction(b, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Binary_RightSE) {
MakeSideEffectFunc<i32>("se");
auto* a = Decl(Var("a", ty.i32()));
auto* expr = Add("a", Call("se"));
WrapInFunction(a, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Binary_BothSE) {
MakeSideEffectFunc<i32>("se1");
MakeSideEffectFunc<i32>("se2");
auto* expr = Add(Call("se1"), Call("se2"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Unary_NoSE) {
auto* var = Decl(Var("a", ty.bool_()));
auto* expr = Not("a");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Unary_SE) {
MakeSideEffectFunc<bool>("se");
auto* expr = Not(Call("se"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, IndexAccessor_NoSE) {
auto* var = Decl(Var("a", ty.array<i32, 10>()));
auto* expr = IndexAccessor("a", 0);
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, IndexAccessor_ObjSE) {
MakeSideEffectFunc("se", [&] { return ty.array<i32, 10>(); });
auto* expr = IndexAccessor(Call("se"), 0);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, IndexAccessor_IndexSE) {
MakeSideEffectFunc<i32>("se");
auto* var = Decl(Var("a", ty.array<i32, 10>()));
auto* expr = IndexAccessor("a", Call("se"));
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, IndexAccessor_BothSE) {
MakeSideEffectFunc("se1", [&] { return ty.array<i32, 10>(); });
MakeSideEffectFunc<i32>("se2");
auto* expr = IndexAccessor(Call("se1"), Call("se2"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Bitcast_NoSE) {
auto* var = Decl(Var("a", ty.i32()));
auto* expr = Bitcast<f32>("a");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->HasSideEffects());
}
TEST_F(SideEffectsTest, Bitcast_SE) {
MakeSideEffectFunc<i32>("se");
auto* expr = Bitcast<f32>(Call("se"));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
ASSERT_NE(sem, nullptr);
EXPECT_TRUE(sem->HasSideEffects());
}
} // namespace
} // namespace tint::resolver

View File

@ -160,6 +160,16 @@ bool Builtin::IsAtomic() const {
return IsAtomicBuiltin(type_); return IsAtomicBuiltin(type_);
} }
bool Builtin::HasSideEffects() const {
if (IsAtomic() && type_ != sem::BuiltinType::kAtomicLoad) {
return true;
}
if (type_ == sem::BuiltinType::kTextureStore) {
return true;
}
return false;
}
} // namespace sem } // namespace sem
} // namespace tint } // namespace tint

View File

@ -139,6 +139,10 @@ class Builtin : public Castable<Builtin, CallTarget> {
/// @returns true if builtin is a atomic builtin /// @returns true if builtin is a atomic builtin
bool IsAtomic() const; bool IsAtomic() const;
/// @returns true if intrinsic may have side-effects (i.e. writes to at least
/// one of its inputs)
bool HasSideEffects() const;
private: private:
const BuiltinType type_; const BuiltinType type_;
const PipelineStageSet supported_stages_; const PipelineStageSet supported_stages_;

View File

@ -26,8 +26,13 @@ Call::Call(const ast::CallExpression* declaration,
const CallTarget* target, const CallTarget* target,
std::vector<const sem::Expression*> arguments, std::vector<const sem::Expression*> arguments,
const Statement* statement, const Statement* statement,
Constant constant) Constant constant,
: Base(declaration, target->ReturnType(), statement, std::move(constant)), bool has_side_effects)
: Base(declaration,
target->ReturnType(),
statement,
std::move(constant),
has_side_effects),
target_(target), target_(target),
arguments_(std::move(arguments)) {} arguments_(std::move(arguments)) {}

View File

@ -33,11 +33,13 @@ class Call : public Castable<Call, Expression> {
/// @param arguments the call arguments /// @param arguments the call arguments
/// @param statement the statement that owns this expression /// @param statement the statement that owns this expression
/// @param constant the constant value of 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, Call(const ast::CallExpression* declaration,
const CallTarget* target, const CallTarget* target,
std::vector<const sem::Expression*> arguments, std::vector<const sem::Expression*> arguments,
const Statement* statement, const Statement* statement,
Constant constant); Constant constant,
bool has_side_effects);
/// Destructor /// Destructor
~Call() override; ~Call() override;

View File

@ -24,11 +24,13 @@ namespace sem {
Expression::Expression(const ast::Expression* declaration, Expression::Expression(const ast::Expression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement, const Statement* statement,
Constant constant) Constant constant,
bool has_side_effects)
: declaration_(declaration), : declaration_(declaration),
type_(type), type_(type),
statement_(statement), statement_(statement),
constant_(std::move(constant)) { constant_(std::move(constant)),
has_side_effects_(has_side_effects) {
TINT_ASSERT(Semantic, type_); TINT_ASSERT(Semantic, type_);
} }

View File

@ -34,10 +34,12 @@ class Expression : public Castable<Expression, Node> {
/// @param type the resolved type of the expression /// @param type the resolved type of the expression
/// @param statement the statement that owns this expression /// @param statement the statement that owns this expression
/// @param constant the constant value of the expression. May be invalid /// @param constant the constant value of the expression. May be invalid
/// @param has_side_effects true if this expression may have side-effects
Expression(const ast::Expression* declaration, Expression(const ast::Expression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement, const Statement* statement,
Constant constant); Constant constant,
bool has_side_effects);
/// Destructor /// Destructor
~Expression() override; ~Expression() override;
@ -60,6 +62,9 @@ class Expression : public Castable<Expression, Node> {
/// @return the behaviors of this statement /// @return the behaviors of this statement
sem::Behaviors& Behaviors() { return behaviors_; } sem::Behaviors& Behaviors() { return behaviors_; }
/// @return true of this expression may have side effects
bool HasSideEffects() const { return has_side_effects_; }
protected: protected:
/// The AST expression node for this semantic expression /// The AST expression node for this semantic expression
const ast::Expression* const declaration_; const ast::Expression* const declaration_;
@ -69,6 +74,7 @@ class Expression : public Castable<Expression, Node> {
const Statement* const statement_; const Statement* const statement_;
const Constant constant_; const Constant constant_;
sem::Behaviors behaviors_{sem::Behavior::kNext}; sem::Behaviors behaviors_{sem::Behavior::kNext};
const bool has_side_effects_;
}; };
} // namespace sem } // namespace sem

View File

@ -27,8 +27,9 @@ namespace sem {
MemberAccessorExpression::MemberAccessorExpression( MemberAccessorExpression::MemberAccessorExpression(
const ast::MemberAccessorExpression* declaration, const ast::MemberAccessorExpression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement) const Statement* statement,
: Base(declaration, type, statement, Constant{}) {} bool has_side_effects)
: Base(declaration, type, statement, Constant{}, has_side_effects) {}
MemberAccessorExpression::~MemberAccessorExpression() = default; MemberAccessorExpression::~MemberAccessorExpression() = default;
@ -36,8 +37,9 @@ StructMemberAccess::StructMemberAccess(
const ast::MemberAccessorExpression* declaration, const ast::MemberAccessorExpression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement, const Statement* statement,
const StructMember* member) const StructMember* member,
: Base(declaration, type, statement), member_(member) {} bool has_side_effects)
: Base(declaration, type, statement, has_side_effects), member_(member) {}
StructMemberAccess::~StructMemberAccess() = default; StructMemberAccess::~StructMemberAccess() = default;
@ -45,7 +47,8 @@ Swizzle::Swizzle(const ast::MemberAccessorExpression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement, const Statement* statement,
std::vector<uint32_t> indices) std::vector<uint32_t> indices)
: Base(declaration, type, statement), indices_(std::move(indices)) {} : Base(declaration, type, statement, /* has_side_effects */ false),
indices_(std::move(indices)) {}
Swizzle::~Swizzle() = default; Swizzle::~Swizzle() = default;

View File

@ -41,9 +41,11 @@ class MemberAccessorExpression
/// @param declaration the AST node /// @param declaration the AST node
/// @param type the resolved type of the expression /// @param type the resolved type of the expression
/// @param statement the statement that owns this expression /// @param statement the statement that owns this expression
/// @param has_side_effects whether this expression may have side effects
MemberAccessorExpression(const ast::MemberAccessorExpression* declaration, MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement); const Statement* statement,
bool has_side_effects);
/// Destructor /// Destructor
~MemberAccessorExpression() override; ~MemberAccessorExpression() override;
@ -60,10 +62,12 @@ class StructMemberAccess
/// @param type the resolved type of the expression /// @param type the resolved type of the expression
/// @param statement the statement that owns this expression /// @param statement the statement that owns this expression
/// @param member the structure member /// @param member the structure member
/// @param has_side_effects whether this expression may have side effects
StructMemberAccess(const ast::MemberAccessorExpression* declaration, StructMemberAccess(const ast::MemberAccessorExpression* declaration,
const sem::Type* type, const sem::Type* type,
const Statement* statement, const Statement* statement,
const StructMember* member); const StructMember* member,
bool has_side_effects);
/// Destructor /// Destructor
~StructMemberAccess() override; ~StructMemberAccess() override;

View File

@ -78,7 +78,11 @@ Parameter::~Parameter() = default;
VariableUser::VariableUser(const ast::IdentifierExpression* declaration, VariableUser::VariableUser(const ast::IdentifierExpression* declaration,
Statement* statement, Statement* statement,
sem::Variable* variable) sem::Variable* variable)
: Base(declaration, variable->Type(), statement, variable->ConstantValue()), : Base(declaration,
variable->Type(),
statement,
variable->ConstantValue(),
/* has_side_effects */ false),
variable_(variable) {} variable_(variable) {}
} // namespace sem } // namespace sem

View File

@ -61,7 +61,8 @@ const sem::Expression* Zero(ProgramBuilder& b,
<< "unsupported vector element type: " << ty->TypeInfo().name; << "unsupported vector element type: " << ty->TypeInfo().name;
return nullptr; return nullptr;
} }
auto* sem = b.create<sem::Expression>(expr, ty, stmt, sem::Constant{}); auto* sem = b.create<sem::Expression>(expr, ty, stmt, sem::Constant{},
/* has_side_effects */ false);
b.Sem().Add(expr, sem); b.Sem().Add(expr, sem);
return sem; return sem;
} }
@ -140,10 +141,10 @@ const sem::Call* AppendVector(ProgramBuilder* b,
b->create<sem::Parameter>(nullptr, 0, scalar_sem->Type()->UnwrapRef(), b->create<sem::Parameter>(nullptr, 0, scalar_sem->Type()->UnwrapRef(),
ast::StorageClass::kNone, ast::StorageClass::kNone,
ast::Access::kUndefined)); ast::Access::kUndefined));
auto* scalar_cast_sem = auto* scalar_cast_sem = b->create<sem::Call>(
b->create<sem::Call>(scalar_cast_ast, scalar_cast_target, scalar_cast_ast, scalar_cast_target,
std::vector<const sem::Expression*>{scalar_sem}, std::vector<const sem::Expression*>{scalar_sem}, statement,
statement, sem::Constant{}); sem::Constant{}, /* has_side_effects */ false);
b->Sem().Add(scalar_cast_ast, scalar_cast_sem); b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
packed.emplace_back(scalar_cast_sem); packed.emplace_back(scalar_cast_sem);
} else { } else {
@ -165,7 +166,8 @@ const sem::Call* AppendVector(ProgramBuilder* b,
ast::Access::kUndefined); ast::Access::kUndefined);
})); }));
auto* constructor_sem = b->create<sem::Call>( auto* constructor_sem = b->create<sem::Call>(
constructor_ast, constructor_target, packed, statement, sem::Constant{}); constructor_ast, constructor_target, packed, statement, sem::Constant{},
/* has_side_effects */ false);
b->Sem().Add(constructor_ast, constructor_sem); b->Sem().Add(constructor_ast, constructor_sem);
return constructor_sem; return constructor_sem;
} }

View File

@ -1336,8 +1336,8 @@ bool GeneratorImpl::EmitTextureCall(std::ostream& out,
auto* f32 = builder_.create<sem::F32>(); auto* f32 = builder_.create<sem::F32>();
auto* zero = builder_.Expr(0.0f); auto* zero = builder_.Expr(0.0f);
auto* stmt = builder_.Sem().Get(param_coords)->Stmt(); auto* stmt = builder_.Sem().Get(param_coords)->Stmt();
auto* sem_zero = auto* sem_zero = builder_.create<sem::Expression>(
builder_.create<sem::Expression>(zero, f32, stmt, sem::Constant{}); zero, f32, stmt, sem::Constant{}, /* has_side_effects */ false);
builder_.Sem().Add(zero, sem_zero); builder_.Sem().Add(zero, sem_zero);
param_coords = AppendVector(&builder_, param_coords, zero)->Declaration(); param_coords = AppendVector(&builder_, param_coords, zero)->Declaration();
} }

View File

@ -2405,8 +2405,9 @@ bool GeneratorImpl::EmitTextureCall(std::ostream& out,
auto* i32 = builder_.create<sem::I32>(); auto* i32 = builder_.create<sem::I32>();
auto* zero = builder_.Expr(0); auto* zero = builder_.Expr(0);
auto* stmt = builder_.Sem().Get(vector)->Stmt(); auto* stmt = builder_.Sem().Get(vector)->Stmt();
builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt, builder_.Sem().Add(
sem::Constant{})); zero, builder_.create<sem::Expression>(zero, i32, stmt, sem::Constant{},
/* has_side_effects */ false));
auto* packed = AppendVector(&builder_, vector, zero); auto* packed = AppendVector(&builder_, vector, zero);
return EmitExpression(out, packed->Declaration()); return EmitExpression(out, packed->Declaration());
}; };

View File

@ -181,7 +181,7 @@ TEST_F(MslGeneratorImplTest, EmitConstructor_Type_Struct_Empty) {
ASSERT_TRUE(gen.Generate()) << gen.error(); ASSERT_TRUE(gen.Generate()) << gen.error();
EXPECT_THAT(gen.result(), HasSubstr("{}")); EXPECT_THAT(gen.result(), HasSubstr("{}"));
EXPECT_THAT(gen.result(), Not(HasSubstr("{{}}"))); EXPECT_THAT(gen.result(), testing::Not(HasSubstr("{{}}")));
} }
} // namespace } // namespace

View File

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