[IRToProgram] Allow read-only references to be re-used.

Update the `Bind` method to be able to create re-usable bindings. This
allows things like loads of read-only textures/samplers to be re-used
and not require extra loads.

Change-Id: Ib48d7f8ac59c64702930692ecccd6868fbabc738
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245534
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index a941f19..b0a5a7a 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -158,6 +158,11 @@
         Symbol name;  // Name of the variable
     };
 
+    /// The structure for a reusable value
+    struct ReusableValue {
+        const core::ir::Value* expr = nullptr;
+    };
+
     /// The structure for an inlined value
     struct InlinedValue {
         const ast::Expression* expr = nullptr;
@@ -168,7 +173,7 @@
     /// ICE.
     struct ConsumedValue {};
 
-    using ValueBinding = std::variant<VariableValue, InlinedValue, ConsumedValue>;
+    using ValueBinding = std::variant<VariableValue, InlinedValue, ConsumedValue, ReusableValue>;
 
     /// IR values to their representation
     Hashmap<const core::ir::Value*, ValueBinding, 32> bindings_;
@@ -755,7 +760,18 @@
             TINT_ICE_ON_NO_MATCH);
     }
 
-    void Load(const core::ir::Load* l) { Bind(l->Result(), Expr(l->From())); }
+    void Load(const core::ir::Load* l) {
+        bool reusable = false;
+        // Read-only pointer is reusable inline
+        if (auto* ptr = l->From()->Type()->As<core::type::Reference>()) {
+            reusable = ptr->Access() == core::Access::kRead;
+        }
+        if (reusable) {
+            Bind(l->Result(), l->From());
+        } else {
+            Bind(l->Result(), Expr(l->From()));
+        }
+    }
 
     void LoadVectorElement(const core::ir::LoadVectorElement* l) {
         auto* vec = Expr(l->From());
@@ -924,6 +940,9 @@
                         if constexpr (std::is_same_v<T, VariableValue>) {
                             return b.Expr(got.name);
                         }
+                        if constexpr (std::is_same_v<T, ReusableValue>) {
+                            return Expr(got.expr);
+                        }
 
                         if constexpr (std::is_same_v<T, InlinedValue>) {
                             auto result = got.expr;
@@ -1231,6 +1250,15 @@
         });
     }
 
+    void Bind(const core::ir::Value* value, const core::ir::Value* expr) {
+        TINT_ASSERT(value);
+        if (value->IsUsed()) {
+            bindings_.Replace(value, ReusableValue{expr});
+        } else {
+            Append(b.Assign(b.Phony(), Expr(expr)));
+        }
+    }
+
     /// Associates the IR value @p value with the AST expression @p expr if it is used, otherwise
     /// creates a phony assignment with @p expr.
     void Bind(const core::ir::Value* value, const ast::Expression* expr) {
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index a6b7673..6ce4c2d 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -34,6 +34,7 @@
 #include "src/tint/lang/core/address_space.h"
 #include "src/tint/lang/core/ir/disassembler.h"
 #include "src/tint/lang/core/texel_format.h"
+#include "src/tint/lang/core/type/sampled_texture.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/texture_dimension.h"
 #include "src/tint/lang/wgsl/ir/builtin_call.h"
@@ -1952,6 +1953,46 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Load
+////////////////////////////////////////////////////////////////////////////////
+TEST_F(IRToProgramTest, Load_Reused) {
+    auto im = b.Var(
+        "im",
+        ty.ref(handle, ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), read));
+    im->SetBindingPoint(0, 0);
+    auto sampler = b.Var("sampler", ty.ref(handle, ty.sampler(), read));
+    sampler->SetBindingPoint(0, 1);
+
+    b.ir.root_block->Append(im);
+    b.ir.root_block->Append(sampler);
+
+    auto* fn = b.Function("f", ty.void_());
+    b.Append(fn->Block(), [&] {  //
+        auto* tl = b.Load(im);
+        auto* sl = b.Load(sampler);
+
+        b.Phony(b.Call<wgsl::ir::BuiltinCall>(ty.vec4<f32>(), wgsl::BuiltinFn::kTextureSample, tl,
+                                              sl, b.Splat(ty.vec2<f32>(), 0_f)));
+        b.Phony(b.Call<wgsl::ir::BuiltinCall>(ty.vec4<f32>(), wgsl::BuiltinFn::kTextureSample, tl,
+                                              sl, b.Splat(ty.vec2<f32>(), 0_f)));
+        b.Return(fn);
+    });
+
+    EXPECT_WGSL(R"(
+diagnostic(off, derivative_uniformity);
+
+@group(0u) @binding(0u) var im : texture_2d<f32>;
+
+@group(0u) @binding(1u) var v : sampler;
+
+fn f() {
+  _ = textureSample(im, v, vec2<f32>());
+  _ = textureSample(im, v, vec2<f32>());
+}
+)");
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Function-scope var
 ////////////////////////////////////////////////////////////////////////////////
 TEST_F(IRToProgramTest, FunctionScopeVar_i32) {