spirv-reader: infer function storage class on OpAccessChain without indices

Fixed: tint:941
Bug: tint:807
Change-Id: I37bec630c1b83dbb56d2f8937322a5b5be81f407
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/56660
Auto-Submit: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 03f45d9..a3a9fe8 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -3786,6 +3786,21 @@
   return parser_impl_.RectifyOperandSignedness(inst, std::move(expr));
 }
 
+TypedExpression FunctionEmitter::InferFunctionStorageClass(
+    TypedExpression expr) {
+  TypedExpression result(expr);
+  if (const auto* ref = expr.type->UnwrapAlias()->As<Reference>()) {
+    if (ref->storage_class == ast::StorageClass::kNone) {
+      expr.type = ty_.Reference(ref->type, ast::StorageClass::kFunction);
+    }
+  } else if (const auto* ptr = expr.type->UnwrapAlias()->As<Pointer>()) {
+    if (ptr->storage_class == ast::StorageClass::kNone) {
+      expr.type = ty_.Pointer(ptr->type, ast::StorageClass::kFunction);
+    }
+  }
+  return expr;
+}
+
 TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
     const spvtools::opt::Instruction& inst) {
   if (inst.result_id() == 0) {
@@ -4118,7 +4133,7 @@
   // ever-deeper nested indexing expressions. Start off with an expression
   // for the base, and then bury that inside nested indexing expressions.
   if (!current_expr) {
-    current_expr = MakeOperand(inst, 0);
+    current_expr = InferFunctionStorageClass(MakeOperand(inst, 0));
     if (current_expr.type->Is<Pointer>()) {
       current_expr = Dereference(current_expr);
     }
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 06f0450..8bfa7db 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -964,6 +964,13 @@
   TypedExpression MakeOperand(const spvtools::opt::Instruction& inst,
                               uint32_t operand_index);
 
+  /// Copies a typed expression to the result, but when the type is a pointer
+  /// or reference type, ensures the storage class is not defaulted.  That is,
+  /// it changes a storage class of "none" to "function".
+  /// @param expr a typed expression
+  /// @results a copy of the expression, with possibly updated type
+  TypedExpression InferFunctionStorageClass(TypedExpression expr);
+
   /// Returns an expression for a SPIR-V OpAccessChain or OpInBoundsAccessChain
   /// instruction.
   /// @param inst the SPIR-V instruction
diff --git a/src/reader/spirv/function_memory_test.cc b/src/reader/spirv/function_memory_test.cc
index 1b30749..bb16d54 100644
--- a/src/reader/spirv/function_memory_test.cc
+++ b/src/reader/spirv/function_memory_test.cc
@@ -903,6 +903,76 @@
   EXPECT_EQ(got, expected) << got;
 }
 
+TEST_F(SpvParserMemoryTest,
+       EmitStatement_AccessChain_InferFunctionStorageClass) {
+  // An access chain can have no indices. When the base is a Function variable,
+  // the reference type has no explicit storage class in the AST representation.
+  // But the pointer type for the let declaration must have an explicit
+  // 'function' storage class. From crbug.com/tint/807
+  const std::string assembly = R"(
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+
+%uint = OpTypeInt 32 0
+%ptr_ty = OpTypePointer Function %uint
+
+  %void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+  %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+     %1 = OpVariable %ptr_ty Function
+     %2 = OpAccessChain %ptr_ty %1
+          OpReturn
+          OpFunctionEnd
+)";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << assembly;
+  const auto got = p->program().to_str();
+  const std::string expected = R"(Module{
+  Function main_1 -> __void
+  ()
+  {
+    VariableDeclStatement{
+      Variable{
+        x_1
+        none
+        undefined
+        __u32
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        undefined
+        __ptr_function__u32
+        {
+          UnaryOp[not set]{
+            address-of
+            Identifier[not set]{x_1}
+          }
+        }
+      }
+    }
+    Return{}
+  }
+  Function main -> __void
+  StageDecoration{fragment}
+  ()
+  {
+    Call[not set]{
+      Identifier[not set]{main_1}
+      (
+      )
+    }
+  }
+}
+)";
+  EXPECT_EQ(got, expected) << got;
+}
+
 std::string OldStorageBufferPreamble() {
   return Preamble() + R"(
      OpName %myvar "myvar"