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>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 533186b..a5bc183 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -730,6 +730,7 @@
resolver/resolver_test_helper.cc
resolver/resolver_test_helper.h
resolver/resolver_test.cc
+ resolver/side_effects_test.cc
resolver/storage_class_layout_validation_test.cc
resolver/storage_class_validation_test.cc
resolver/struct_layout_test.cc
diff --git a/src/program_builder.h b/src/program_builder.h
index c072b55..ebc39b1 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -1540,6 +1540,15 @@
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 func the function name
/// @param args the function call arguments
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 0bb8670..ca1e700 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1140,7 +1140,7 @@
} else if (expr->Is<ast::PhonyExpression>()) {
sem_expr = builder_->create<sem::Expression>(
expr, builder_->create<sem::Void>(), current_statement_,
- sem::Constant{});
+ sem::Constant{}, /* has_side_effects */ false);
} else {
TINT_ICE(Resolver, diagnostics_)
<< "unhandled expression type: " << expr->TypeInfo().name;
@@ -1193,8 +1193,9 @@
}
auto val = EvaluateConstantValue(expr, ty);
- auto* sem =
- builder_->create<sem::Expression>(expr, ty, current_statement_, val);
+ bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
+ auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+ val, has_side_effects);
sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
return sem;
}
@@ -1207,8 +1208,8 @@
}
auto val = EvaluateConstantValue(expr, ty);
- auto* sem =
- builder_->create<sem::Expression>(expr, ty, current_statement_, val);
+ auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+ val, inner->HasSideEffects());
sem->Behaviors() = inner->Behaviors();
@@ -1382,8 +1383,13 @@
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),
- current_statement_, sem::Constant{});
+ current_statement_, sem::Constant{},
+ has_side_effects);
current_function_->AddDirectlyCalledBuiltin(builtin);
@@ -1427,8 +1433,12 @@
auto sym = expr->target.name->symbol;
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),
- current_statement_, sem::Constant{});
+ current_statement_, sem::Constant{},
+ has_side_effects);
if (current_function_) {
// Note: Requires called functions to be resolved first.
@@ -1530,9 +1540,10 @@
}
auto val = EvaluateConstantValue(expr, target);
+ bool has_side_effects = arg->HasSideEffects();
return builder_->create<sem::Call>(expr, call_target,
std::vector<const sem::Expression*>{arg},
- current_statement_, val);
+ current_statement_, val, has_side_effects);
}
sem::Call* Resolver::TypeConstructor(
@@ -1590,8 +1601,10 @@
}
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),
- current_statement_, val);
+ current_statement_, val, has_side_effects);
}
sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
@@ -1601,8 +1614,8 @@
}
auto val = EvaluateConstantValue(literal, ty);
- return builder_->create<sem::Expression>(literal, ty, current_statement_,
- val);
+ return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
+ /* has_side_effects */ false);
}
sem::Expression* Resolver::Identifier(const ast::IdentifierExpression* expr) {
@@ -1715,8 +1728,12 @@
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>(
- expr, ret, current_statement_, member);
+ expr, ret, current_statement_, member, has_side_effects);
}
if (auto* vec = storage_ty->As<sem::Vector>()) {
@@ -1827,8 +1844,9 @@
auto build = [&](const sem::Type* ty) {
auto val = EvaluateConstantValue(expr, ty);
- auto* sem =
- builder_->create<sem::Expression>(expr, ty, current_statement_, val);
+ bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
+ auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_,
+ val, has_side_effects);
sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
return sem;
};
@@ -2075,8 +2093,8 @@
}
auto val = EvaluateConstantValue(unary, ty);
- auto* sem =
- builder_->create<sem::Expression>(unary, ty, current_statement_, val);
+ auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_,
+ val, expr->HasSideEffects());
sem->Behaviors() = expr->Behaviors();
return sem;
}
diff --git a/src/resolver/side_effects_test.cc b/src/resolver/side_effects_test.cc
new file mode 100644
index 0000000..fc20cc1
--- /dev/null
+++ b/src/resolver/side_effects_test.cc
@@ -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
diff --git a/src/sem/builtin.cc b/src/sem/builtin.cc
index 917fee9..d96cf87 100644
--- a/src/sem/builtin.cc
+++ b/src/sem/builtin.cc
@@ -160,6 +160,16 @@
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 tint
diff --git a/src/sem/builtin.h b/src/sem/builtin.h
index cb5dc30..be9ccaa 100644
--- a/src/sem/builtin.h
+++ b/src/sem/builtin.h
@@ -139,6 +139,10 @@
/// @returns true if builtin is a atomic builtin
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:
const BuiltinType type_;
const PipelineStageSet supported_stages_;
diff --git a/src/sem/call.cc b/src/sem/call.cc
index 9432fee..63297a0 100644
--- a/src/sem/call.cc
+++ b/src/sem/call.cc
@@ -26,8 +26,13 @@
const CallTarget* target,
std::vector<const sem::Expression*> arguments,
const Statement* statement,
- Constant constant)
- : Base(declaration, target->ReturnType(), statement, std::move(constant)),
+ Constant constant,
+ bool has_side_effects)
+ : Base(declaration,
+ target->ReturnType(),
+ statement,
+ std::move(constant),
+ has_side_effects),
target_(target),
arguments_(std::move(arguments)) {}
diff --git a/src/sem/call.h b/src/sem/call.h
index bcf8689..00c9467 100644
--- a/src/sem/call.h
+++ b/src/sem/call.h
@@ -33,11 +33,13 @@
/// @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,
std::vector<const sem::Expression*> arguments,
const Statement* statement,
- Constant constant);
+ Constant constant,
+ bool has_side_effects);
/// Destructor
~Call() override;
diff --git a/src/sem/expression.cc b/src/sem/expression.cc
index 6cb318a..95a2e40 100644
--- a/src/sem/expression.cc
+++ b/src/sem/expression.cc
@@ -24,11 +24,13 @@
Expression::Expression(const ast::Expression* declaration,
const sem::Type* type,
const Statement* statement,
- Constant constant)
+ Constant constant,
+ bool has_side_effects)
: declaration_(declaration),
type_(type),
statement_(statement),
- constant_(std::move(constant)) {
+ constant_(std::move(constant)),
+ has_side_effects_(has_side_effects) {
TINT_ASSERT(Semantic, type_);
}
diff --git a/src/sem/expression.h b/src/sem/expression.h
index b2ff4ac..a3b0d38 100644
--- a/src/sem/expression.h
+++ b/src/sem/expression.h
@@ -34,10 +34,12 @@
/// @param type the resolved type of the expression
/// @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
Expression(const ast::Expression* declaration,
const sem::Type* type,
const Statement* statement,
- Constant constant);
+ Constant constant,
+ bool has_side_effects);
/// Destructor
~Expression() override;
@@ -60,6 +62,9 @@
/// @return the behaviors of this statement
sem::Behaviors& Behaviors() { return behaviors_; }
+ /// @return true of this expression may have side effects
+ bool HasSideEffects() const { return has_side_effects_; }
+
protected:
/// The AST expression node for this semantic expression
const ast::Expression* const declaration_;
@@ -69,6 +74,7 @@
const Statement* const statement_;
const Constant constant_;
sem::Behaviors behaviors_{sem::Behavior::kNext};
+ const bool has_side_effects_;
};
} // namespace sem
diff --git a/src/sem/member_accessor_expression.cc b/src/sem/member_accessor_expression.cc
index d8dcd69..7af2e7b 100644
--- a/src/sem/member_accessor_expression.cc
+++ b/src/sem/member_accessor_expression.cc
@@ -27,8 +27,9 @@
MemberAccessorExpression::MemberAccessorExpression(
const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
- const Statement* statement)
- : Base(declaration, type, statement, Constant{}) {}
+ const Statement* statement,
+ bool has_side_effects)
+ : Base(declaration, type, statement, Constant{}, has_side_effects) {}
MemberAccessorExpression::~MemberAccessorExpression() = default;
@@ -36,8 +37,9 @@
const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
- const StructMember* member)
- : Base(declaration, type, statement), member_(member) {}
+ const StructMember* member,
+ bool has_side_effects)
+ : Base(declaration, type, statement, has_side_effects), member_(member) {}
StructMemberAccess::~StructMemberAccess() = default;
@@ -45,7 +47,8 @@
const sem::Type* type,
const Statement* statement,
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;
diff --git a/src/sem/member_accessor_expression.h b/src/sem/member_accessor_expression.h
index 6d444f0..a123a88 100644
--- a/src/sem/member_accessor_expression.h
+++ b/src/sem/member_accessor_expression.h
@@ -41,9 +41,11 @@
/// @param declaration the AST node
/// @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
MemberAccessorExpression(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
- const Statement* statement);
+ const Statement* statement,
+ bool has_side_effects);
/// Destructor
~MemberAccessorExpression() override;
@@ -60,10 +62,12 @@
/// @param type the resolved type of the expression
/// @param statement the statement that owns this expression
/// @param member the structure member
+ /// @param has_side_effects whether this expression may have side effects
StructMemberAccess(const ast::MemberAccessorExpression* declaration,
const sem::Type* type,
const Statement* statement,
- const StructMember* member);
+ const StructMember* member,
+ bool has_side_effects);
/// Destructor
~StructMemberAccess() override;
diff --git a/src/sem/variable.cc b/src/sem/variable.cc
index 364a3cb..96375f7 100644
--- a/src/sem/variable.cc
+++ b/src/sem/variable.cc
@@ -78,7 +78,11 @@
VariableUser::VariableUser(const ast::IdentifierExpression* declaration,
Statement* statement,
sem::Variable* variable)
- : Base(declaration, variable->Type(), statement, variable->ConstantValue()),
+ : Base(declaration,
+ variable->Type(),
+ statement,
+ variable->ConstantValue(),
+ /* has_side_effects */ false),
variable_(variable) {}
} // namespace sem
diff --git a/src/writer/append_vector.cc b/src/writer/append_vector.cc
index cad3183..4f461d9 100644
--- a/src/writer/append_vector.cc
+++ b/src/writer/append_vector.cc
@@ -61,7 +61,8 @@
<< "unsupported vector element type: " << ty->TypeInfo().name;
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);
return sem;
}
@@ -140,10 +141,10 @@
b->create<sem::Parameter>(nullptr, 0, scalar_sem->Type()->UnwrapRef(),
ast::StorageClass::kNone,
ast::Access::kUndefined));
- auto* scalar_cast_sem =
- b->create<sem::Call>(scalar_cast_ast, scalar_cast_target,
- std::vector<const sem::Expression*>{scalar_sem},
- statement, sem::Constant{});
+ auto* scalar_cast_sem = b->create<sem::Call>(
+ scalar_cast_ast, scalar_cast_target,
+ std::vector<const sem::Expression*>{scalar_sem}, statement,
+ sem::Constant{}, /* has_side_effects */ false);
b->Sem().Add(scalar_cast_ast, scalar_cast_sem);
packed.emplace_back(scalar_cast_sem);
} else {
@@ -165,7 +166,8 @@
ast::Access::kUndefined);
}));
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);
return constructor_sem;
}
diff --git a/src/writer/glsl/generator_impl.cc b/src/writer/glsl/generator_impl.cc
index a6bab5e..7192a8b 100644
--- a/src/writer/glsl/generator_impl.cc
+++ b/src/writer/glsl/generator_impl.cc
@@ -1336,8 +1336,8 @@
auto* f32 = builder_.create<sem::F32>();
auto* zero = builder_.Expr(0.0f);
auto* stmt = builder_.Sem().Get(param_coords)->Stmt();
- auto* sem_zero =
- builder_.create<sem::Expression>(zero, f32, stmt, sem::Constant{});
+ auto* sem_zero = builder_.create<sem::Expression>(
+ zero, f32, stmt, sem::Constant{}, /* has_side_effects */ false);
builder_.Sem().Add(zero, sem_zero);
param_coords = AppendVector(&builder_, param_coords, zero)->Declaration();
}
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 8502427..8861592 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -2405,8 +2405,9 @@
auto* i32 = builder_.create<sem::I32>();
auto* zero = builder_.Expr(0);
auto* stmt = builder_.Sem().Get(vector)->Stmt();
- builder_.Sem().Add(zero, builder_.create<sem::Expression>(zero, i32, stmt,
- sem::Constant{}));
+ builder_.Sem().Add(
+ zero, builder_.create<sem::Expression>(zero, i32, stmt, sem::Constant{},
+ /* has_side_effects */ false));
auto* packed = AppendVector(&builder_, vector, zero);
return EmitExpression(out, packed->Declaration());
};
diff --git a/src/writer/msl/generator_impl_constructor_test.cc b/src/writer/msl/generator_impl_constructor_test.cc
index 4781c42..bf4646f 100644
--- a/src/writer/msl/generator_impl_constructor_test.cc
+++ b/src/writer/msl/generator_impl_constructor_test.cc
@@ -181,7 +181,7 @@
ASSERT_TRUE(gen.Generate()) << gen.error();
EXPECT_THAT(gen.result(), HasSubstr("{}"));
- EXPECT_THAT(gen.result(), Not(HasSubstr("{{}}")));
+ EXPECT_THAT(gen.result(), testing::Not(HasSubstr("{{}}")));
}
} // namespace
diff --git a/test/BUILD.gn b/test/BUILD.gn
index a0952bd..fd880da 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -260,6 +260,7 @@
"../src/resolver/resolver_test.cc",
"../src/resolver/resolver_test_helper.cc",
"../src/resolver/resolver_test_helper.h",
+ "../src/resolver/side_effects_test.cc",
"../src/resolver/storage_class_layout_validation_test.cc",
"../src/resolver/storage_class_validation_test.cc",
"../src/resolver/struct_layout_test.cc",