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