[spirv-reader][shader-io] Fold away lets to var's.

If there is a `let` of a `var` (which can happen from an `OpCopyObject`)
then we need to fold away the let and use the var directly in ShaderIO.
The var then folds away and becomes an input parameter.

Add support for folding through `lets`.

Bug: 42250952
Change-Id: I91db43a07cafcb4d1f1d9b50019316883e36db79
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245594
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc
index 84382b4..ceaccf0 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -365,6 +365,11 @@
                     a->Result()->SetType(a->Result()->Type()->UnwrapPtr());
                     ReplaceInputPointerUses(var, a->Result());
                 },
+                [&](core::ir::Let* l) {
+                    // Fold away
+                    ReplaceInputPointerUses(var, l->Result());
+                    to_destroy.Push(l);
+                },
                 TINT_ICE_ON_NO_MATCH);
         });
 
diff --git a/src/tint/lang/spirv/reader/lower/shader_io_test.cc b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
index 5471411..d1515a3 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io_test.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
@@ -286,6 +286,100 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(SpirvReader_ShaderIOTest, Inputs_Copied) {
+    auto* val = b.Var("val", ty.ptr(core::AddressSpace::kIn, ty.u32()));
+    val->SetBuiltin(core::BuiltinValue::kSampleIndex);
+    mod.root_block->Append(val);
+
+    auto* f = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(f->Block(), [&] {
+        auto* c = b.Let("copy", val);
+        b.Let("res", b.Load(c));
+        b.Return(f);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %val:ptr<__in, u32, read> = var undef @builtin(sample_index)
+}
+
+%main = @fragment func():void {
+  $B2: {
+    %copy:ptr<__in, u32, read> = let %val
+    %4:u32 = load %copy
+    %res:u32 = let %4
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%main = @fragment func(%val:u32 [@sample_index]):void {
+  $B1: {
+    %res:u32 = let %val
+    ret
+  }
+}
+)";
+
+    Run(ShaderIO);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvReader_ShaderIOTest, Inputs_Copied_Chain) {
+    auto* val = b.Var("val", ty.ptr(core::AddressSpace::kIn, ty.u32()));
+    val->SetBuiltin(core::BuiltinValue::kSampleIndex);
+    mod.root_block->Append(val);
+
+    auto* f = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(f->Block(), [&] {
+        auto* c1 = b.Let("c1", val);
+        auto* c2 = b.Let("c2", c1);
+        auto* c3 = b.Let("c3", c2);
+        auto* c4 = b.Let("c4", c3);
+        auto* c5 = b.Let("c5", c4);
+        auto* c6 = b.Let("c6", c5);
+        b.Let("res", b.Load(c6));
+        b.Return(f);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %val:ptr<__in, u32, read> = var undef @builtin(sample_index)
+}
+
+%main = @fragment func():void {
+  $B2: {
+    %c1:ptr<__in, u32, read> = let %val
+    %c2:ptr<__in, u32, read> = let %c1
+    %c3:ptr<__in, u32, read> = let %c2
+    %c4:ptr<__in, u32, read> = let %c3
+    %c5:ptr<__in, u32, read> = let %c4
+    %c6:ptr<__in, u32, read> = let %c5
+    %9:u32 = load %c6
+    %res:u32 = let %9
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%main = @fragment func(%val:u32 [@sample_index]):void {
+  $B1: {
+    %res:u32 = let %val
+    ret
+  }
+}
+)";
+
+    Run(ShaderIO);
+
+    EXPECT_EQ(expect, str());
+}
+
 TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedEntryPointAndHelper) {
     auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
     gid->SetBuiltin(core::BuiltinValue::kGlobalInvocationId);