[spirv-reader][ir] Make sure `position` is used.

In SPIR-V, it's possible to create a `position` var, set it into the
`OpEntryPoint` interface, and then never store to `position`. This will
produce undefined values into the position.

In IR, we _must_ reference the `position` in order for it to be
considered part of the interface for the vertex shader. So, if we detect
that `position` is setup in `OpEntryPoint`, but never referenced, we'll
inject a store of `(0, 0, 0, 0)` into the `position` variable to satisfy
the interface rules.

Bug: 42250952
Change-Id: Ic37a4d6e34842bfd305dca22380df7527c469bed
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245615
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/var.h b/src/tint/lang/core/ir/var.h
index e361bd0..cf503fd 100644
--- a/src/tint/lang/core/ir/var.h
+++ b/src/tint/lang/core/ir/var.h
@@ -118,6 +118,8 @@
         TINT_ASSERT(!attributes_.builtin.has_value());
         attributes_.builtin = val;
     }
+    /// Returns the builtin information, if available
+    std::optional<core::BuiltinValue> Builtin() const { return attributes_.builtin; }
 
     /// Resets the IO attributes
     void ResetAttributes() { attributes_ = {}; }
diff --git a/src/tint/lang/spirv/reader/parser/function_test.cc b/src/tint/lang/spirv/reader/parser/function_test.cc
index c4df303..2b53807 100644
--- a/src/tint/lang/spirv/reader/parser/function_test.cc
+++ b/src/tint/lang/spirv/reader/parser/function_test.cc
@@ -130,21 +130,130 @@
 )");
 }
 
-TEST_F(SpirvParserTest, DISABLED_VertexShader) {
+TEST_F(SpirvParserTest, VertexShader_PositionUnused) {
     EXPECT_IR(R"(
                OpCapability Shader
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Vertex %main "main"
+               OpEntryPoint Vertex %main "main" %position
+               OpName %position "position"
+               OpDecorate %position BuiltIn Position
        %void = OpTypeVoid
+      %float = OpTypeFloat 32
     %ep_type = OpTypeFunction %void
+    %v4float = OpTypeVector %float 4
+        %ptr = OpTypePointer Output %v4float
+   %position = OpVariable %ptr Output
        %main = OpFunction %void None %ep_type
  %main_start = OpLabel
                OpReturn
                OpFunctionEnd
 )",
               R"(
+$B1: {  # root
+  %position:ptr<__out, vec4<f32>, read_write> = var undef @builtin(position)
+}
+
 %main = @vertex func():void {
-  $B1: {
+  $B2: {
+    store %position, vec4<f32>(0.0f)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, VertexShader_PositionUsed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %position
+               OpName %position "position"
+               OpDecorate %position BuiltIn Position
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %ep_type = OpTypeFunction %void
+    %v4float = OpTypeVector %float 4
+        %ptr = OpTypePointer Output %v4float
+         %f1 = OpConstant %float 1
+         %f2 = OpConstant %float 2
+         %f3 = OpConstant %float 3
+         %f4 = OpConstant %float 4
+         %v4 = OpConstantComposite %v4float %f1 %f2 %f3 %f4
+   %position = OpVariable %ptr Output
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpStore %position %v4
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+$B1: {  # root
+  %position:ptr<__out, vec4<f32>, read_write> = var undef @builtin(position)
+}
+
+%main = @vertex func():void {
+  $B2: {
+    store %position, vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, VertexShader_PositionUsed_Transitive) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %position
+               OpName %position "position"
+               OpDecorate %position BuiltIn Position
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %ep_type = OpTypeFunction %void
+    %v4float = OpTypeVector %float 4
+        %ptr = OpTypePointer Output %v4float
+         %f1 = OpConstant %float 1
+         %f2 = OpConstant %float 2
+         %f3 = OpConstant %float 3
+         %f4 = OpConstant %float 4
+         %v4 = OpConstantComposite %v4float %f1 %f2 %f3 %f4
+   %position = OpVariable %ptr Output
+          %c = OpFunction %void None %ep_type
+         %c1 = OpLabel
+               OpStore %position %v4
+               OpReturn
+               OpFunctionEnd
+          %b = OpFunction %void None %ep_type
+         %b1 = OpLabel
+         %b2 = OpFunctionCall %void %c
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+         %a1 = OpFunctionCall %void %b
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+$B1: {  # root
+  %position:ptr<__out, vec4<f32>, read_write> = var undef @builtin(position)
+}
+
+%2 = func():void {
+  $B2: {
+    store %position, vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f)
+    ret
+  }
+}
+%3 = func():void {
+  $B3: {
+    %4:void = call %2
+    ret
+  }
+}
+%main = @vertex func():void {
+  $B4: {
+    %6:void = call %3
     ret
   }
 }
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index fe9c4e8..d718701 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -52,6 +52,7 @@
 
 #include "src/tint/lang/core/ir/builder.h"
 #include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/referenced_module_vars.h"
 #include "src/tint/lang/core/type/builtin_structs.h"
 #include "src/tint/lang/spirv/builtin_fn.h"
 #include "src/tint/lang/spirv/ir/builtin_call.h"
@@ -992,6 +993,41 @@
 
             // Set the entry point name.
             ir_.SetName(func, entry_point.GetOperand(2).AsString());
+
+            // For vertex shaders, must make sure the `position` builtin is referenced. In SPIR-V it
+            // can be referenced in the `OpEntryPoint` without any usages. When translating we need
+            // to make sure we insert a fake write if there are no usages.
+            if (func->IsVertex()) {
+                core::ir::ReferencedModuleVars<const core::ir::Module> referenced(ir_);
+                for (uint32_t i = 3; i < entry_point.NumInOperands(); ++i) {
+                    auto interface_id = entry_point.GetSingleWordInOperand(i);
+                    auto* val = Value(interface_id);
+                    TINT_ASSERT(val);
+
+                    auto* inst_result = val->As<core::ir::InstructionResult>();
+                    TINT_ASSERT(inst_result);
+
+                    auto* var = inst_result->Instruction()->As<core::ir::Var>();
+                    TINT_ASSERT(var);
+
+                    auto builtin = var->Builtin();
+                    if (!builtin.has_value() || builtin.value() != core::BuiltinValue::kPosition) {
+                        continue;
+                    }
+
+                    auto refs = referenced.TransitiveReferences(func);
+                    // The referenced variables does not contain the var which has the `position`
+                    // builtin, then we need to create a fake store.
+                    if (!refs.Contains(var)) {
+                        auto* store = b_.Store(var, b_.Zero(var->Result()->Type()->UnwrapPtr()));
+                        store->InsertBefore(func->Block()->Front());
+                    }
+
+                    // There should only be one `position` so we're done if we've processed the
+                    // first one found.
+                    break;
+                }
+            }
         }
 
         // Handle OpExecutionMode declarations.