spirv-reader: instance_index must have u32 store type

Fixed: tint:485
Change-Id: I73613ae916b2d86b25470f292e5bf5cd5c7f288b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/40582
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 58fa33f..0767da6 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -2040,6 +2040,11 @@
           << "unhandled use of a pointer to the VertexIndex builtin, with ID: "
           << id;
       return {};
+    case SkipReason::kInstanceIndexBuiltinPointer:
+      Fail() << "unhandled use of a pointer to the InstanceIndex builtin, with "
+                "ID: "
+             << id;
+      return {};
     case SkipReason::kSampleMaskInBuiltinPointer:
       Fail()
           << "unhandled use of a pointer to the SampleMask builtin, with ID: "
@@ -3048,13 +3053,15 @@
               SkipReason::kPointSizeBuiltinValue;
           return true;
         case SkipReason::kSampleIdBuiltinPointer:
-        case SkipReason::kVertexIndexBuiltinPointer: {
+        case SkipReason::kVertexIndexBuiltinPointer:
+        case SkipReason::kInstanceIndexBuiltinPointer: {
           // The SPIR-V variable is i32, but WGSL requires u32.
-          auto var_id = parser_impl_.IdForSpecialBuiltIn(
-              (skip_reason == SkipReason::kSampleIdBuiltinPointer)
-                  ? SpvBuiltInSampleId
-                  : SpvBuiltInVertexIndex);
-          auto name = namer_.Name(var_id);
+          auto name = NameForSpecialInputBuiltin(skip_reason);
+          if (name.empty()) {
+            return Fail() << "internal error: unhandled special input builtin "
+                             "variable: "
+                          << inst.PrettyPrint();
+          }
           ast::Expression* id_expr = create<ast::IdentifierExpression>(
               Source{}, builder_.Symbols().Register(name));
           auto expr = TypedExpression{
@@ -3133,6 +3140,28 @@
                 << inst.PrettyPrint();
 }
 
+std::string FunctionEmitter::NameForSpecialInputBuiltin(
+    SkipReason skip_reason) {
+  SpvBuiltIn spv_builtin = SpvBuiltIn(0);
+  switch (skip_reason) {
+    case SkipReason::kSampleIdBuiltinPointer:
+      spv_builtin = SpvBuiltInSampleId;
+      break;
+    case SkipReason::kVertexIndexBuiltinPointer:
+      spv_builtin = SpvBuiltInVertexIndex;
+      break;
+    case SkipReason::kInstanceIndexBuiltinPointer:
+      spv_builtin = SpvBuiltInInstanceIndex;
+      break;
+    default:
+      // Invalid. Issue the error in the caller.
+      return "";
+  }
+  // The SPIR-V variable is i32, but WGSL requires u32.
+  auto var_id = parser_impl_.IdForSpecialBuiltIn(spv_builtin);
+  return namer_.Name(var_id);
+}
+
 TypedExpression FunctionEmitter::MakeOperand(
     const spvtools::opt::Instruction& inst,
     uint32_t operand_index) {
@@ -3725,6 +3754,9 @@
       case SpvBuiltInVertexIndex:
         def->skip = SkipReason::kVertexIndexBuiltinPointer;
         break;
+      case SpvBuiltInInstanceIndex:
+        def->skip = SkipReason::kInstanceIndexBuiltinPointer;
+        break;
       case SpvBuiltInSampleMask: {
         // Distinguish between input and output variable.
         const auto storage_class =
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 27ab12a..b562968 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -233,6 +233,11 @@
   /// builtin variable.  Don't generate its address.
   kVertexIndexBuiltinPointer,
 
+  /// `kInstanceIndexBuiltinPointer`: the value is a pointer to the
+  /// InstanceIndex
+  /// builtin variable.  Don't generate its address.
+  kInstanceIndexBuiltinPointer,
+
   /// `kSampleMaskInBuiltinPointer`: the value is a pointer to the SampleMaskIn
   /// builtin input variable.  Don't generate its address.
   kSampleMaskInBuiltinPointer,
@@ -351,6 +356,9 @@
     case SkipReason::kVertexIndexBuiltinPointer:
       o << " skip:vertexindex_pointer";
       break;
+    case SkipReason::kInstanceIndexBuiltinPointer:
+      o << " skip:instanceindex_pointer";
+      break;
     case SkipReason::kSampleMaskInBuiltinPointer:
       o << " skip:samplemaskin_pointer";
       break;
@@ -762,6 +770,13 @@
     return SkipReason::kDontSkip;
   }
 
+  /// Returns the WGSL variable name for an input builtin variable whose
+  /// translation is managed via the SkipReason mechanism.
+  /// @param skip_reason the skip reason for the special variable
+  /// @returns the variable name for a special builtin variable
+  /// that is handled via the "skip" mechanism.
+  std::string NameForSpecialInputBuiltin(SkipReason skip_reason);
+
   /// Returns the most deeply nested structured construct which encloses the
   /// WGSL scopes of names declared in both block positions. Each position must
   /// be a valid index into the function block order array.
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index e4a212e..ebd2ce8 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1294,6 +1294,7 @@
           return nullptr;
         case SpvBuiltInSampleId:
         case SpvBuiltInVertexIndex:
+        case SpvBuiltInInstanceIndex:
           // The SPIR-V variable is likely to be signed (because GLSL
           // requires signed), but WGSL requires unsigned.  Handle specially
           // so we always perform the conversion at load and store.
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index 4431509..49e81c3 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -3349,6 +3349,384 @@
 })")) << module_str;
 }
 
+// Returns the start of a shader for testing InstanceIndex,
+// parameterized by store type of %int or %uint
+std::string InstanceIndexPreamble(std::string store_type) {
+  return R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Vertex %main "main" %1
+    OpDecorate %1 BuiltIn InstanceIndex
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+    %ptr_ty = OpTypePointer Input )" +
+         store_type + R"(
+    %1 = OpVariable %ptr_ty Input
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_Direct) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %int %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __i32
+        {
+          TypeConstructor[not set]{
+            __i32
+            Identifier[not set]{x_1}
+          }
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_CopyObject) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __i32
+        {
+          TypeConstructor[not set]{
+            __i32
+            Identifier[not set]{x_1}
+          }
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_Load_AccessChain) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %int %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __i32
+        {
+          TypeConstructor[not set]{
+            __i32
+            Identifier[not set]{x_1}
+          }
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_I32_FunctParam) {
+  const std::string assembly = InstanceIndexPreamble("%int") + R"(
+    %helper_ty = OpTypeFunction %int %ptr_ty
+    %helper = OpFunction %int None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %int %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %int %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  // TODO(dneto): We can handle this if we make a shadow variable and mutate
+  // the parameter type.
+  ASSERT_FALSE(p->BuildAndParseInternalModule());
+  EXPECT_THAT(p->error(), HasSubstr("unhandled use of a pointer to the "
+                                    "InstanceIndex builtin, with ID: 1"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_Direct) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %2 = OpLoad %uint %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __u32
+        {
+          Identifier[not set]{x_1}
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_CopyObject) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpCopyObject %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_11
+        none
+        __ptr_in__u32
+        {
+          Identifier[not set]{x_1}
+        }
+      }
+    }
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __u32
+        {
+          Identifier[not set]{x_11}
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_Load_AccessChain) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %copy_ptr = OpAccessChain %ptr_ty %1
+    %2 = OpLoad %uint %copy_ptr
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct body
+  EXPECT_THAT(module_str, HasSubstr(R"(
+    VariableDeclStatement{
+      VariableConst{
+        x_2
+        none
+        __u32
+        {
+          Identifier[not set]{x_1}
+        }
+      }
+    })"))
+      << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, InstanceIndex_U32_FunctParam) {
+  const std::string assembly = InstanceIndexPreamble("%uint") + R"(
+    %helper_ty = OpTypeFunction %uint %ptr_ty
+    %helper = OpFunction %uint None %helper_ty
+    %param = OpFunctionParameter %ptr_ty
+    %helper_entry = OpLabel
+    %3 = OpLoad %uint %param
+    OpReturnValue %3
+    OpFunctionEnd
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %result = OpFunctionCall %uint %helper %1
+    OpReturn
+    OpFunctionEnd
+ )";
+  auto p = parser(test::Assemble(assembly));
+  // TODO(dneto): We can handle this if we make a shadow variable and mutate
+  // the parameter type.
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty());
+  const auto module_str = p->program().to_str();
+  // Correct declaration
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Variable{
+    Decorations{
+      BuiltinDecoration{instance_index}
+    }
+    x_1
+    in
+    __u32
+  })"));
+
+  // Correct bodies
+  EXPECT_THAT(module_str, HasSubstr(R"(
+  Function x_11 -> __u32
+  (
+    VariableConst{
+      x_12
+      none
+      __ptr_in__u32
+    }
+  )
+  {
+    VariableDeclStatement{
+      VariableConst{
+        x_3
+        none
+        __u32
+        {
+          Identifier[not set]{x_12}
+        }
+      }
+    }
+    Return{
+      {
+        Identifier[not set]{x_3}
+      }
+    }
+  }
+  Function main -> __void
+  StageDecoration{vertex}
+  ()
+  {
+    VariableDeclStatement{
+      VariableConst{
+        x_15
+        none
+        __u32
+        {
+          Call[not set]{
+            Identifier[not set]{x_11}
+            (
+              Identifier[not set]{x_1}
+            )
+          }
+        }
+      }
+    }
+    Return{}
+  }
+})")) << module_str;
+}
+
 // TODO(dneto): Test passing pointer to SampleMask as function parameter,
 // both input case and output case.