[spirv-reader][ir] Support unused position in a structure.

Make sure to emit a default write to the `position` builtin if it's in a
structure and unused.

Bug: 42250952
Change-Id: If02a5babd91cf22ddacb4eb8935e549ad5fa985e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/246074
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/spirv/reader/parser/function_test.cc b/src/tint/lang/spirv/reader/parser/function_test.cc
index 98a2aea..0a835dd 100644
--- a/src/tint/lang/spirv/reader/parser/function_test.cc
+++ b/src/tint/lang/spirv/reader/parser/function_test.cc
@@ -225,6 +225,92 @@
 )");
 }
 
+TEST_F(SpirvParserTest, VertexShader_PositionUnused_Struct) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %1
+               OpDecorate %str Block
+               OpMemberDecorate %str 0 BuiltIn Position
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %ep_type = OpTypeFunction %void
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+    %str_ptr = OpTypePointer Output %str
+          %1 = OpVariable %str_ptr Output
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+tint_symbol_1 = struct @align(16) {
+  tint_symbol:vec4<f32> @offset(0), @builtin(position)
+}
+
+$B1: {  # root
+  %1:ptr<__out, tint_symbol_1, read_write> = var undef
+}
+
+%main = @vertex func():void {
+  $B2: {
+    %3:ptr<__out, vec4<f32>, read_write> = access %1, 0u
+    store %3, vec4<f32>(0.0f)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, VertexShader_PositionUsed_Struct) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %1
+               OpDecorate %str Block
+               OpMemberDecorate %str 0 BuiltIn Position
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+    %ep_type = OpTypeFunction %void
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+    %str_ptr = OpTypePointer Output %str
+%ptr_v4float = OpTypePointer Output %v4float
+     %uint_0 = OpConstant %uint 0
+        %one = OpConstant %float 1
+        %two = OpConstant %float 2
+      %three = OpConstant %float 3
+       %four = OpConstant %float 4
+          %3 = OpConstantComposite %v4float %one %two %three %four
+          %1 = OpVariable %str_ptr Output
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+          %2 = OpAccessChain %ptr_v4float %1 %uint_0
+               OpStore %2 %3
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+tint_symbol_1 = struct @align(16) {
+  tint_symbol:vec4<f32> @offset(0), @builtin(position)
+}
+
+$B1: {  # root
+  %1:ptr<__out, tint_symbol_1, read_write> = var undef
+}
+
+%main = @vertex func():void {
+  $B2: {
+    %3:ptr<__out, vec4<f32>, read_write> = access %1, 0u
+    store %3, vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f)
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvParserTest, VertexShader_PositionUnused) {
     EXPECT_IR(R"(
                OpCapability Shader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 6a82af2..a725e62 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -1125,6 +1125,16 @@
         current_spirv_function_ = nullptr;
     }
 
+    std::optional<uint32_t> Position(const core::type::Struct* str) {
+        for (auto* mem : str->Members()) {
+            auto builtin = mem->Attributes().builtin;
+            if (builtin.has_value() && builtin.value() == core::BuiltinValue::kPosition) {
+                return {mem->Index()};
+            }
+        }
+        return {};
+    }
+
     /// Emit entry point attributes.
     void EmitEntryPointAttributes() {
         // Handle OpEntryPoint declarations.
@@ -1166,17 +1176,65 @@
                     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;
-                    }
+                    if (auto* str = var->Result()->Type()->UnwrapPtr()->As<core::type::Struct>()) {
+                        auto p = Position(str);
+                        if (!p.has_value()) {
+                            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());
+                        auto refs = referenced.TransitiveReferences(func);
+                        if (refs.Contains(var)) {
+                            // The `var` is referenced, but we need to determine if there is an
+                            // `access` usage which references `position` which is then used in a
+                            // `store` instruction.
+
+                            bool accessed = false;
+                            for (auto& usage : var->Result()->UsagesUnsorted()) {
+                                if (auto* access = usage->instruction->As<core::ir::Access>()) {
+                                    if (access->Indices().IsEmpty()) {
+                                        continue;
+                                    }
+
+                                    auto* cnst = access->Indices()[0]->As<core::ir::Constant>();
+                                    TINT_ASSERT(cnst);
+
+                                    if (cnst->Value()->ValueAs<uint32_t>() == p.value()) {
+                                        accessed = true;
+                                        break;
+                                    }
+                                }
+                            }
+
+                            if (accessed) {
+                                break;
+                            }
+                        }
+
+                        auto* mem_ty = str->Members()[p.value()]->Type();
+                        auto* type = ty_.ptr(
+                            var->Result()->Type()->As<core::type::Pointer>()->AddressSpace(),
+                            mem_ty);
+                        auto* access = b_.Access(type, var, u32(p.value()));
+                        access->InsertBefore(func->Block()->Front());
+
+                        auto* store = b_.Store(access, b_.Zero(mem_ty));
+                        store->InsertAfter(access);
+
+                    } else {
+                        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