tint: Implement sem::Load
The resolver now wraps sem::Expression objects with a sem::Load object
anywhere that the load rule is invoked. sem::Expression provides an
`UnwrapLoad()` method that returns the inner expression (or
passthrough, if no load is present), which is analaguous to
Type::UnwrapRef().
The logic for alias analysis in `RegisterLoadIfNeeded` has been folded
into the new `Resolver::Load` method.
Fixed up many transforms and tests. The only difference in output is
for a single SPIR-V backend test, where some IDs have changed due to
slight re-ordering of when expressions are generated.
There may be further clean-ups possible (e.g. removing unnecessary
calls to `UnwrapRef`, and simplifying places in the SPIR-V writer or
transforms that deal with memory accesses), but these can be addressed
in future patches.
Fixed: tint:1654
Change-Id: I69adecfe9251faae46546b64d0cdc29eea26cd4e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/99706
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/load_test.cc b/src/tint/resolver/load_test.cc
new file mode 100644
index 0000000..fea60ff5
--- /dev/null
+++ b/src/tint/resolver/load_test.cc
@@ -0,0 +1,370 @@
+// 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/test_helper.h"
+
+#include "src/tint/sem/load.h"
+#include "src/tint/type/reference.h"
+
+#include "gmock/gmock.h"
+
+using namespace tint::number_suffixes; // NOLINT
+
+namespace tint::resolver {
+namespace {
+
+using ResolverLoadTest = ResolverTest;
+
+TEST_F(ResolverLoadTest, VarInitializer) {
+ // var ref = 1i;
+ // var v = ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Var("v", ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, LetInitializer) {
+ // var ref = 1i;
+ // let l = ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Let("l", ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, Assignment) {
+ // var ref = 1i;
+ // var v : i32;
+ // v = ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Var("v", ty.i32()), //
+ Assign("v", ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, CompoundAssignment) {
+ // var ref = 1i;
+ // var v : i32;
+ // v += ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Var("v", ty.i32()), //
+ CompoundAssign("v", ident, ast::BinaryOp::kAdd));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, UnaryOp) {
+ // var ref = 1i;
+ // var v = -ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Var("v", Negation(ident)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, UnaryOp_NoLoad) {
+ // var ref = 1i;
+ // let v = &ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Let("v", AddressOf(ident)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* var_user = Sem().Get<sem::VariableUser>(ident);
+ ASSERT_NE(var_user, nullptr);
+ EXPECT_TRUE(var_user->Type()->Is<type::Reference>());
+ EXPECT_TRUE(var_user->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, BinaryOp) {
+ // var ref = 1i;
+ // var v = ref * 1i;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Var("v", Mul(ident, 1_i)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, Index) {
+ // var ref = 1i;
+ // var v = array<i32, 3>(1i, 2i, 3i)[ref];
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ IndexAccessor(array<i32, 3>(1_i, 2_i, 3_i), ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, MultiComponentSwizzle) {
+ // var ref = vec4(1);
+ // var v = ref.xyz;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Construct(ty.vec4<i32>(), 1_i)), //
+ Var("v", MemberAccessor(ident, "xyz")));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Vector>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Vector>());
+}
+
+TEST_F(ResolverLoadTest, Bitcast) {
+ // var ref = 1f;
+ // var v = bitcast<i32>(ref);
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_f)), //
+ Bitcast<i32>(ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::F32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::F32>());
+}
+
+TEST_F(ResolverLoadTest, BuiltinArg) {
+ // var ref = 1f;
+ // var v = abs(ref);
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_f)), //
+ Call("abs", ident));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::F32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::F32>());
+}
+
+TEST_F(ResolverLoadTest, FunctionArg) {
+ // fn f(x : f32) {}
+ // var ref = 1f;
+ // f(ref);
+ Func("f", utils::Vector{Param("x", ty.f32())}, ty.void_(), utils::Empty);
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_f)), //
+ CallStmt(Call("f", ident)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::F32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::F32>());
+}
+
+TEST_F(ResolverLoadTest, FunctionArg_Handles) {
+ // @group(0) @binding(0) var t : texture_2d<f32>;
+ // @group(0) @binding(1) var s : sampler;
+ // fn f(tp : texture_2d<f32>, sp : sampler) -> vec4<f32> {
+ // return textureSampleLevel(tp, sp, vec2(), 0);
+ // }
+ // f(t, s);
+ GlobalVar("t", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
+ utils::Vector{Group(0_a), Binding(0_a)});
+ GlobalVar("s", ty.sampler(ast::SamplerKind::kSampler), utils::Vector{Group(0_a), Binding(1_a)});
+ Func("f",
+ utils::Vector{
+ Param("tp", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32())),
+ Param("sp", ty.sampler(ast::SamplerKind::kSampler)),
+ },
+ ty.vec4<f32>(),
+ utils::Vector{
+ Return(Call("textureSampleLevel", "tp", "sp", Construct(ty.vec2<f32>()), 0_a)),
+ });
+ auto* t_ident = Expr("t");
+ auto* s_ident = Expr("s");
+ WrapInFunction(CallStmt(Call("f", t_ident, s_ident)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+ {
+ auto* load = Sem().Get<sem::Load>(t_ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::SampledTexture>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::SampledTexture>());
+ }
+ {
+ auto* load = Sem().Get<sem::Load>(s_ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Sampler>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Sampler>());
+ }
+}
+
+TEST_F(ResolverLoadTest, FunctionReturn) {
+ // var ref = 1f;
+ // return ref;
+ auto* ident = Expr("ref");
+ Func("f", utils::Empty, ty.f32(),
+ utils::Vector{
+ Decl(Var("ref", Expr(1_f))),
+ Return(ident),
+ });
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::F32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::F32>());
+}
+
+TEST_F(ResolverLoadTest, IfCond) {
+ // var ref = false;
+ // if (ref) {}
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(false)), //
+ If(ident, Block()));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Bool>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Bool>());
+}
+
+TEST_F(ResolverLoadTest, Switch) {
+ // var ref = 1i;
+ // switch (ref) {
+ // default:
+ // }
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Switch(ident, DefaultCase()));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::I32>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::I32>());
+}
+
+TEST_F(ResolverLoadTest, BreakIfCond) {
+ // var ref = false;
+ // loop {
+ // continuing {
+ // break if (ref);
+ // }
+ // }
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(false)), //
+ Loop(Block(), Block(BreakIf(ident))));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Bool>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Bool>());
+}
+
+TEST_F(ResolverLoadTest, ForCond) {
+ // var ref = false;
+ // for (; ref; ) {}
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(false)), //
+ For(nullptr, ident, nullptr, Block()));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Bool>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Bool>());
+}
+
+TEST_F(ResolverLoadTest, WhileCond) {
+ // var ref = false;
+ // while (ref) {}
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(false)), //
+ While(ident, Block()));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* load = Sem().Get<sem::Load>(ident);
+ ASSERT_NE(load, nullptr);
+ EXPECT_TRUE(load->Type()->Is<type::Bool>());
+ EXPECT_TRUE(load->Reference()->Type()->Is<type::Reference>());
+ EXPECT_TRUE(load->Reference()->Type()->UnwrapRef()->Is<type::Bool>());
+}
+
+TEST_F(ResolverLoadTest, AddressOf) {
+ // var ref = 1i;
+ // let l = &ref;
+ auto* ident = Expr("ref");
+ WrapInFunction(Var("ref", Expr(1_i)), //
+ Let("l", AddressOf(ident)));
+
+ ASSERT_TRUE(r()->Resolve()) << r()->error();
+ auto* no_load = Sem().Get(ident);
+ ASSERT_NE(no_load, nullptr);
+ EXPECT_TRUE(no_load->Type()->Is<type::Reference>()); // No load
+}
+
+} // namespace
+} // namespace tint::resolver