Support structs in runtime effects

Uses the pipeline-stage callback mechanism. It mangles the type name
(with a test to verify that this works), and then calls defineStruct
with the entire SkSL struct definition string.

Bug: skia:10939
Change-Id: If14cf1b11faaa80ad8d4086cdacf68532bac43fc
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/368809
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: John Stiles <johnstiles@google.com>
diff --git a/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp b/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp
index f2fdd35..79897e3 100644
--- a/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp
+++ b/fuzz/oss_fuzz/FuzzSKSL2Pipeline.cpp
@@ -34,6 +34,7 @@
         }
 
         void defineFunction(const char* /*decl*/, const char* /*body*/, bool /*isMain*/) override {}
+        void defineStruct(const char* /*definition*/) override {}
 
         String sampleChild(int index, String coords) override {
             return SkSL::String::printf("sample(%d%s%s)", index, coords.empty() ? "" : ", ",
diff --git a/resources/sksl/shared/Assignment.sksl b/resources/sksl/shared/Assignment.sksl
index 16afcec..710e9a3 100644
--- a/resources/sksl/shared/Assignment.sksl
+++ b/resources/sksl/shared/Assignment.sksl
@@ -1,12 +1,11 @@
 uniform half4 colorGreen;
 
-// TODO(skia:10939): Structs not working in Runtime Effects yet
-//struct S {
-//    float f;
-//    float af[5];
-//    half4 h4;
-//    half4 ah4[5];
-//};
+struct S {
+    float f;
+    float af[5];
+    half4 h4;
+    half4 ah4[5];
+};
 
 half4 main() {
     /* assign to scalar */               int i; i = 0;
@@ -18,11 +17,10 @@
     /* assign to array of matrix */      half3x3 ah2x4[1]; ah2x4[0] = half3x3(1,2,3,4,5,6,7,8,9);
     /* assign to array swizzle */        float4 af4[1]; af4[0].x = 0; af4[0].ywxz = float4(1);
 
-// TODO(skia:10939): Structs not working in Runtime Effects yet
-//  /* assign to struct variable */      S s; s.f = 0;
-//  /* assign to struct array */         s.af[1] = 0;
-//  /* assign to struct swizzle */       s.h4.zxy = half3(9);
-//  /* assign to struct array swizzle */ s.ah4[2].yw = half2(5);
+    /* assign to struct variable */      S s; s.f = 0;
+    /* assign to struct array */         s.af[1] = 0;
+    /* assign to struct swizzle */       s.h4.zxy = half3(9);
+    /* assign to struct array swizzle */ s.ah4[2].yw = half2(5);
 
 // Not allowed in ES2
 //  /* assign to array idx by lookup */  ai[0] = 0; ai[ai[0]] = 0;
@@ -30,14 +28,14 @@
 // Not allowed natively in GLSL, but SkSL will turn these into valid GLSL expressions.
     /* assign to folded ternary */       half l, r; (true ? l : r) = 0;
     /* assign to unary plus */           +ai[0] += +ai4[0][0];
-// TODO(skia:10939): Structs not working in Runtime Effects yet
-//  /* assign to struct unary plus */    +s.f = 1; +s.af[0] = 2;
-//                                       +s.h4 = half4(1); +s.ah4[0] = half4(2);
+    /* assign to struct unary plus */    +s.f = 1; +s.af[0] = 2;
+                                         +s.h4 = half4(1); +s.ah4[0] = half4(2);
 
     // Keep these variables alive
     af4[0] *= float(ah2x4[0][0][0]);
     i4.y *= i;
     x.y *= l;
+    s.f *= l;
 
     return colorGreen;
 }
diff --git a/src/gpu/effects/GrSkSLFP.cpp b/src/gpu/effects/GrSkSLFP.cpp
index c370a5f..e2316e5 100644
--- a/src/gpu/effects/GrSkSLFP.cpp
+++ b/src/gpu/effects/GrSkSLFP.cpp
@@ -83,6 +83,10 @@
                 }
             }
 
+            void defineStruct(const char* definition) override {
+                fArgs.fFragBuilder->definitionAppend(definition);
+            }
+
             String sampleChild(int index, String coords) override {
                 return String(fSelf->invokeChild(index, fArgs, coords).c_str());
             }
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index 21c8b2f..5182795 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -92,6 +92,8 @@
        this->definitions().append(";\n");
     }
 
+    void definitionAppend(const char* str) { this->definitions().append(str); }
+
     void declareGlobal(const GrShaderVar&);
 
     // Generates a unique variable name for holding the result of a temporary expression when it's
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 3e38ea2..2b290c7 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -82,7 +82,7 @@
     return fProgram.fCaps->usesPrecisionModifiers();
 }
 
-// Returns the name of the type with array dimensions, e.g. `float[2][4]`.
+// Returns the name of the type with array dimensions, e.g. `float[2]`.
 String GLSLCodeGenerator::getTypeName(const Type& type) {
     switch (type.typeKind()) {
         case Type::TypeKind::kVector: {
@@ -165,10 +165,13 @@
     for (const auto& f : type.fields()) {
         this->writeModifiers(f.fModifiers, false);
         this->writeTypePrecision(*f.fType);
-        // sizes (which must be static in structs) are part of the type name here
-        this->writeType(*f.fType);
+        const Type& baseType = f.fType->isArray() ? f.fType->componentType() : *f.fType;
+        this->writeType(baseType);
         this->write(" ");
         this->write(f.fName);
+        if (f.fType->isArray()) {
+            this->write("[" + to_string(f.fType->columns()) + "]");
+        }
         this->writeLine(";");
     }
     fIndentation--;
@@ -1188,6 +1191,7 @@
                 return "";
             case Type::TypeKind::kVector: // fall through
             case Type::TypeKind::kMatrix:
+            case Type::TypeKind::kArray:
                 return this->getTypePrecision(type.componentType());
             default:
                 break;
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 46c1c00..4064c08 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -1117,16 +1117,7 @@
     if (returnType == nullptr) {
         return;
     }
-    auto typeIsAllowed = [&](const Type* t) {
-#if defined(SKSL_STANDALONE)
-        return true;
-#else
-        GrSLType unusedSLType;
-        return fKind != Program::kRuntimeEffect_Kind ||
-               type_to_grsltype(fContext, *t, &unusedSLType);
-#endif
-    };
-    if (returnType->isArray() || !typeIsAllowed(returnType)) {
+    if (returnType->isArray()) {
         this->errorReporter().error(
                 f.fOffset, "functions may not return type '" + returnType->displayName() + "'");
         return;
@@ -1163,8 +1154,7 @@
         // Only the (builtin) declarations of 'sample' are allowed to have FP parameters.
         // (You can pass other opaque types to functions safely; this restriction is
         // fragment-processor specific.)
-        if ((*type == *fContext.fTypes.fFragmentProcessor && !fIsBuiltinCode) ||
-            !typeIsAllowed(type)) {
+        if (*type == *fContext.fTypes.fFragmentProcessor && !fIsBuiltinCode) {
             this->errorReporter().error(
                     param.fOffset, "parameters of type '" + type->displayName() + "' not allowed");
             return;
diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp
index 7bfac72..e688ed8 100644
--- a/src/sksl/SkSLMain.cpp
+++ b/src/sksl/SkSLMain.cpp
@@ -420,6 +420,10 @@
                             fOutput += String(decl) + "{" + body + "}";
                         }
 
+                        void defineStruct(const char* definition) override {
+                            fOutput += definition;
+                        }
+
                         String sampleChild(int index, String coords) override {
                             return String::printf("sample(%s%s%s)", fChildNames[index].c_str(),
                                                   coords.empty() ? "" : ", ", coords.c_str());
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index ef76b88..804ecd3 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -25,6 +25,7 @@
 #include "src/sksl/ir/SkSLProgramElement.h"
 #include "src/sksl/ir/SkSLReturnStatement.h"
 #include "src/sksl/ir/SkSLStatement.h"
+#include "src/sksl/ir/SkSLStructDefinition.h"
 #include "src/sksl/ir/SkSLSwizzle.h"
 #include "src/sksl/ir/SkSLTernaryExpression.h"
 #include "src/sksl/ir/SkSLVarDeclarations.h"
@@ -63,8 +64,12 @@
 
     void writeModifiers(const Modifiers& modifiers);
 
+    // Handles arrays correctly, eg: `float x[2]`
+    String typedVariable(const Type& type, StringFragment name);
+
     void writeVarDeclaration(const VarDeclaration& var);
     void writeGlobalVarDeclaration(const GlobalVarDeclaration& g);
+    void writeStructDefinition(const StructDefinition& s);
 
     void writeExpression(const Expression& expr, Precedence parentPrecedence);
     void writeFunctionCall(const FunctionCall& c);
@@ -107,6 +112,7 @@
 
     std::unordered_map<const Variable*, String>            fUniformNames;
     std::unordered_map<const FunctionDeclaration*, String> fFunctionNames;
+    std::unordered_map<const Type*, String>                fStructNames;
 
     StringStream* fBuffer = nullptr;
     bool          fCastReturnsToHalf = false;
@@ -333,6 +339,18 @@
     }
 }
 
+void PipelineStageCodeGenerator::writeStructDefinition(const StructDefinition& s) {
+    const Type& type = s.type();
+    String mangledName = fCallbacks->getMangledName(String(type.name()).c_str());
+    String definition = "struct " + mangledName + " {\n";
+    for (const auto& f : type.fields()) {
+        definition += this->typedVariable(*f.fType, f.fName) + ";\n";
+    }
+    definition += "};\n";
+    fStructNames.insert({&type, std::move(mangledName)});
+    fCallbacks->defineStruct(definition.c_str());
+}
+
 void PipelineStageCodeGenerator::writeProgramElement(const ProgramElement& e) {
     switch (e.kind()) {
         case ProgramElement::Kind::kGlobalVar:
@@ -345,10 +363,12 @@
             // Runtime effects don't allow calls to undefined functions, so prototypes are never
             // necessary. If we do support them, they should emit calls to emitFunctionPrototype.
             break;
-        // Custom types (enums and structs) are ignored (so they don't yet work in runtime effects).
+        case ProgramElement::Kind::kStructDefinition:
+            this->writeStructDefinition(e.as<StructDefinition>());
+            break;
+        // Enums are ignored (so they don't yet work in runtime effects).
         // We need to emit their declarations (via callback), with name mangling support.
         case ProgramElement::Kind::kEnum:              // skbug.com/11296
-        case ProgramElement::Kind::kStructDefinition:  // skbug.com/10939
 
         case ProgramElement::Kind::kExtension:
         case ProgramElement::Kind::kInterfaceBlock:
@@ -361,7 +381,8 @@
 }
 
 String PipelineStageCodeGenerator::typeName(const Type& type) {
-    return type.name();
+    auto it = fStructNames.find(&type);
+    return it != fStructNames.end() ? it->second : type.name();
 }
 
 void PipelineStageCodeGenerator::writeType(const Type& type) {
@@ -524,16 +545,19 @@
     }
 }
 
+String PipelineStageCodeGenerator::typedVariable(const Type& type, StringFragment name) {
+    const Type& baseType = type.isArray() ? type.componentType() : type;
+
+    String decl = this->typeName(baseType) + " " + name;
+    if (type.isArray()) {
+        decl += "[" + to_string(type.columns()) + "]";
+    }
+    return decl;
+}
+
 void PipelineStageCodeGenerator::writeVarDeclaration(const VarDeclaration& var) {
     this->writeModifiers(var.var().modifiers());
-    this->writeType(var.baseType());
-    this->write(" ");
-    this->write(var.var().name());
-    if (var.arraySize() > 0) {
-        this->write("[");
-        this->write(to_string(var.arraySize()));
-        this->write("]");
-    }
+    this->write(this->typedVariable(var.var().type(), var.var().name()));
     if (var.value()) {
         this->write(" = ");
         this->writeExpression(*var.value(), Precedence::kTopLevel);
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.h b/src/sksl/SkSLPipelineStageCodeGenerator.h
index c022ada..8f5aa5d 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.h
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.h
@@ -24,6 +24,7 @@
 
         virtual String getMangledName(const char* name) { return name; }
         virtual void   defineFunction(const char* declaration, const char* body, bool isMain) = 0;
+        virtual void   defineStruct(const char* definition) = 0;
 
         virtual String declareUniform(const VarDeclaration*) = 0;
         virtual String sampleChild(int index, String coords) = 0;
diff --git a/tests/SkRuntimeEffectTest.cpp b/tests/SkRuntimeEffectTest.cpp
index a36f03f..f364c53 100644
--- a/tests/SkRuntimeEffectTest.cpp
+++ b/tests/SkRuntimeEffectTest.cpp
@@ -401,3 +401,44 @@
     REPORTER_ASSERT(r, c.fB == 0.5625f);
     REPORTER_ASSERT(r, c.fA == 1.0f);
 }
+
+static void test_RuntimeEffectStructNameReuse(skiatest::Reporter* r, GrRecordingContext* rContext) {
+    // Test that two different runtime effects can reuse struct names in a single paint operation
+    auto [childEffect, err] = SkRuntimeEffect::Make(SkString(
+        "uniform shader paint;"
+        "struct S { half4 rgba; };"
+        "void process(inout S s) { s.rgba.rgb *= 0.5; }"
+        "half4 main() { S s; s.rgba = sample(paint); process(s); return s.rgba; }"
+    ));
+    REPORTER_ASSERT(r, childEffect, "%s\n", err.c_str());
+    sk_sp<SkShader> nullChild = nullptr;
+    sk_sp<SkShader> child = childEffect->makeShader(/*uniforms=*/nullptr, &nullChild,
+                                                    /*childCount=*/1, /*localMatrix=*/nullptr,
+                                                    /*isOpaque=*/false);
+
+    SkImageInfo info = SkImageInfo::Make(2, 2, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+    sk_sp<SkSurface> surface = rContext
+                                    ? SkSurface::MakeRenderTarget(rContext, SkBudgeted::kNo, info)
+                                    : SkSurface::MakeRaster(info);
+    REPORTER_ASSERT(r, surface);
+
+    TestEffect effect(r, surface);
+    effect.build(
+            "uniform shader child;"
+            "struct S { float2 coord; };"
+            "void process(inout S s) { s.coord = s.coord.yx; }"
+            "half4 main(float2 p) { S s; s.coord = p; process(s); return sample(child, s.coord); "
+            "}");
+    effect.child("child") = child;
+    effect.test(0xFF00407F, [](SkCanvas*, SkPaint* paint) {
+        paint->setColor4f({0.99608f, 0.50196f, 0.0f, 1.0f});
+    });
+}
+
+DEF_TEST(SkRuntimeStructNameReuse, r) {
+    test_RuntimeEffectStructNameReuse(r, nullptr);
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkRuntimeStructNameReuse_GPU, r, ctxInfo) {
+    test_RuntimeEffectStructNameReuse(r, ctxInfo.directContext());
+}
diff --git a/tests/SkSLTest.cpp b/tests/SkSLTest.cpp
index 214d52e..dcce46e 100644
--- a/tests/SkSLTest.cpp
+++ b/tests/SkSLTest.cpp
@@ -130,7 +130,7 @@
 SKSL_TEST(SkSLIntFoldingES2,                   "folding/IntFoldingES2.sksl")
 SKSL_TEST(SkSLFloatFolding,                    "folding/FloatFolding.sksl")
 SKSL_TEST(SkSLMatrixFoldingES2,                "folding/MatrixFoldingES2.sksl")
-SKSL_TEST_CPU(SkSLSelfAssignment,              "folding/SelfAssignment.sksl")
+SKSL_TEST(SkSLSelfAssignment,                  "folding/SelfAssignment.sksl")
 SKSL_TEST(SkSLShortCircuitBoolFolding,         "folding/ShortCircuitBoolFolding.sksl")
 SKSL_TEST(SkSLVectorScalarFolding,             "folding/VectorScalarFolding.sksl")
 SKSL_TEST(SkSLVectorVectorFolding,             "folding/VectorVectorFolding.sksl")
@@ -173,6 +173,7 @@
 SKSL_TEST(SkSLScalarConversionConstructorsES2, "shared/ScalarConversionConstructorsES2.sksl")
 SKSL_TEST(SkSLStackingVectorCasts,             "shared/StackingVectorCasts.sksl")
 SKSL_TEST(SkSLStaticIf,                        "shared/StaticIf.sksl")
+SKSL_TEST(SkSLStructsInFunctions,              "shared/StructsInFunctions.sksl")
 SKSL_TEST(SkSLSwizzleBoolConstants,            "shared/SwizzleBoolConstants.sksl")
 SKSL_TEST(SkSLSwizzleByConstantIndex,          "shared/SwizzleByConstantIndex.sksl")
 SKSL_TEST(SkSLSwizzleConstants,                "shared/SwizzleConstants.sksl")
@@ -192,12 +193,6 @@
 */
 
 /*
-TODO(skia:10939): enable this test when Runtime Effects supports structs in function signatures
-SKSL_TEST(SkSLSelfAssignment,                  "folding/SelfAssignment.sksl")
-SKSL_TEST(SkSLStructsInFunctions,              "shared/StructsInFunctions.sksl")
-*/
-
-/*
 TODO(skia:11209): enable these tests when Runtime Effects have support for ES3
 
 SKSL_TEST(SkSLIntFoldingES3,                   "folding/IntFoldingES3.sksl")
diff --git a/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl b/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
index 96832b7..96479ba 100644
--- a/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
+++ b/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
@@ -5,8 +5,8 @@
 uniform vec4 uh4;
 uniform bool b;
 struct S {
-    vec4[1] ah4;
-    float[1] ah;
+    vec4 ah4[1];
+    float ah[1];
     vec4 h4;
     float h;
 };
diff --git a/tests/sksl/shared/Assignment.asm.frag b/tests/sksl/shared/Assignment.asm.frag
index f66558b..5c9be9d 100644
--- a/tests/sksl/shared/Assignment.asm.frag
+++ b/tests/sksl/shared/Assignment.asm.frag
@@ -15,6 +15,12 @@
 OpName %ai4 "ai4"
 OpName %ah2x4 "ah2x4"
 OpName %af4 "af4"
+OpName %S "S"
+OpMemberName %S 0 "f"
+OpMemberName %S 1 "af"
+OpMemberName %S 2 "h4"
+OpMemberName %S 3 "ah4"
+OpName %s "s"
 OpDecorate %sk_FragColor RelaxedPrecision
 OpDecorate %sk_FragColor Location 0
 OpDecorate %sk_FragColor Index 0
@@ -34,10 +40,20 @@
 OpDecorate %65 RelaxedPrecision
 OpDecorate %62 RelaxedPrecision
 OpDecorate %_arr_v4float_int_1 ArrayStride 16
-OpDecorate %87 RelaxedPrecision
-OpDecorate %94 RelaxedPrecision
-OpDecorate %95 RelaxedPrecision
-OpDecorate %98 RelaxedPrecision
+OpDecorate %_arr_float_int_5 ArrayStride 16
+OpDecorate %_arr_v4float_int_5 ArrayStride 16
+OpMemberDecorate %S 0 Offset 0
+OpMemberDecorate %S 1 Offset 16
+OpMemberDecorate %S 2 Offset 96
+OpMemberDecorate %S 2 RelaxedPrecision
+OpMemberDecorate %S 3 Offset 112
+OpMemberDecorate %S 3 RelaxedPrecision
+OpDecorate %88 RelaxedPrecision
+OpDecorate %92 RelaxedPrecision
+OpDecorate %108 RelaxedPrecision
+OpDecorate %115 RelaxedPrecision
+OpDecorate %116 RelaxedPrecision
+OpDecorate %122 RelaxedPrecision
 %float = OpTypeFloat 32
 %v4float = OpTypeVector %float 4
 %_ptr_Output_v4float = OpTypePointer Output %v4float
@@ -87,6 +103,14 @@
 %_arr_v4float_int_1 = OpTypeArray %v4float %int_1
 %_ptr_Function__arr_v4float_int_1 = OpTypePointer Function %_arr_v4float_int_1
 %73 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%int_5 = OpConstant %int 5
+%_arr_float_int_5 = OpTypeArray %float %int_5
+%_arr_v4float_int_5 = OpTypeArray %v4float %int_5
+%S = OpTypeStruct %float %_arr_float_int_5 %v4float %_arr_v4float_int_5
+%_ptr_Function_S = OpTypePointer Function %S
+%85 = OpConstantComposite %v3float %float_9 %float_9 %float_9
+%89 = OpConstantComposite %v2float %float_5 %float_5
+%102 = OpConstantComposite %v4float %float_2 %float_2 %float_2 %float_2
 %_ptr_Function_v3float = OpTypePointer Function %v3float
 %_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
 %_entrypoint = OpFunction %void None %15
@@ -103,6 +127,7 @@
 %ai4 = OpVariable %_ptr_Function__arr_v4int_int_1 Function
 %ah2x4 = OpVariable %_ptr_Function__arr_mat3v3float_int_1 Function
 %af4 = OpVariable %_ptr_Function__arr_v4float_int_1 Function
+%s = OpVariable %_ptr_Function_S Function
 OpStore %i4 %28
 %32 = OpAccessChain %_ptr_Function_float %x %int_3
 OpStore %32 %float_0
@@ -126,29 +151,53 @@
 %75 = OpLoad %v4float %74
 %76 = OpVectorShuffle %v4float %75 %73 6 4 7 5
 OpStore %74 %76
-%77 = OpAccessChain %_ptr_Function_int %ai %int_0
-%78 = OpLoad %int %77
-%79 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
-%80 = OpLoad %v4int %79
-%81 = OpCompositeExtract %int %80 0
-%82 = OpIAdd %int %78 %81
-OpStore %77 %82
-%83 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
-%84 = OpLoad %v4float %83
-%85 = OpAccessChain %_ptr_Function_v3float %ah2x4 %int_0 %int_0
-%87 = OpLoad %v3float %85
-%88 = OpCompositeExtract %float %87 0
-%89 = OpVectorTimesScalar %v4float %84 %88
-OpStore %83 %89
-%90 = OpAccessChain %_ptr_Function_int %i4 %int_1
-%91 = OpLoad %int %90
-%92 = OpIMul %int %91 %int_0
+%83 = OpAccessChain %_ptr_Function_float %s %int_0
+OpStore %83 %float_0
+%84 = OpAccessChain %_ptr_Function_float %s %int_1 %int_1
+OpStore %84 %float_0
+%86 = OpAccessChain %_ptr_Function_v4float %s %int_2
+%87 = OpLoad %v4float %86
+%88 = OpVectorShuffle %v4float %87 %85 5 6 4 3
+OpStore %86 %88
+%90 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_2
+%91 = OpLoad %v4float %90
+%92 = OpVectorShuffle %v4float %91 %89 0 4 2 5
 OpStore %90 %92
-%93 = OpAccessChain %_ptr_Function_float %x %int_1
-%94 = OpLoad %float %93
-%95 = OpFMul %float %94 %float_0
-OpStore %93 %95
-%96 = OpAccessChain %_ptr_Uniform_v4float %10 %int_0
-%98 = OpLoad %v4float %96
-OpReturnValue %98
+%93 = OpAccessChain %_ptr_Function_int %ai %int_0
+%94 = OpLoad %int %93
+%95 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
+%96 = OpLoad %v4int %95
+%97 = OpCompositeExtract %int %96 0
+%98 = OpIAdd %int %94 %97
+OpStore %93 %98
+%99 = OpAccessChain %_ptr_Function_float %s %int_0
+OpStore %99 %float_1
+%100 = OpAccessChain %_ptr_Function_float %s %int_1 %int_0
+OpStore %100 %float_2
+%101 = OpAccessChain %_ptr_Function_v4float %s %int_2
+OpStore %101 %73
+%103 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_0
+OpStore %103 %102
+%104 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
+%105 = OpLoad %v4float %104
+%106 = OpAccessChain %_ptr_Function_v3float %ah2x4 %int_0 %int_0
+%108 = OpLoad %v3float %106
+%109 = OpCompositeExtract %float %108 0
+%110 = OpVectorTimesScalar %v4float %105 %109
+OpStore %104 %110
+%111 = OpAccessChain %_ptr_Function_int %i4 %int_1
+%112 = OpLoad %int %111
+%113 = OpIMul %int %112 %int_0
+OpStore %111 %113
+%114 = OpAccessChain %_ptr_Function_float %x %int_1
+%115 = OpLoad %float %114
+%116 = OpFMul %float %115 %float_0
+OpStore %114 %116
+%117 = OpAccessChain %_ptr_Function_float %s %int_0
+%118 = OpLoad %float %117
+%119 = OpFMul %float %118 %float_0
+OpStore %117 %119
+%120 = OpAccessChain %_ptr_Uniform_v4float %10 %int_0
+%122 = OpLoad %v4float %120
+OpReturnValue %122
 OpFunctionEnd
diff --git a/tests/sksl/shared/Assignment.glsl b/tests/sksl/shared/Assignment.glsl
index fe884e6..5f2fb97 100644
--- a/tests/sksl/shared/Assignment.glsl
+++ b/tests/sksl/shared/Assignment.glsl
@@ -1,6 +1,12 @@
 
 out vec4 sk_FragColor;
 uniform vec4 colorGreen;
+struct S {
+    float f;
+    float af[5];
+    vec4 h4;
+    vec4 ah4[5];
+};
 vec4 main() {
     ivec4 i4;
     i4 = ivec4(1, 2, 3, 4);
@@ -16,9 +22,19 @@
     vec4 af4[1];
     af4[0].x = 0.0;
     af4[0].ywxz = vec4(1.0);
+    S s;
+    s.f = 0.0;
+    s.af[1] = 0.0;
+    s.h4.zxy = vec3(9.0);
+    s.ah4[2].yw = vec2(5.0);
     ai[0] += ai4[0].x;
+    s.f = 1.0;
+    s.af[0] = 2.0;
+    s.h4 = vec4(1.0);
+    s.ah4[0] = vec4(2.0);
     af4[0] *= ah2x4[0][0].x;
     i4.y *= 0;
     x.y *= 0.0;
+    s.f *= 0.0;
     return colorGreen;
 }
diff --git a/tests/sksl/shared/Assignment.metal b/tests/sksl/shared/Assignment.metal
index e28e820..7a8d59a 100644
--- a/tests/sksl/shared/Assignment.metal
+++ b/tests/sksl/shared/Assignment.metal
@@ -1,6 +1,12 @@
 #include <metal_stdlib>
 #include <simd/simd.h>
 using namespace metal;
+struct S {
+    float f;
+    array<float, 5> af;
+    float4 h4;
+    array<float4, 5> ah4;
+};
 struct Uniforms {
     float4 colorGreen;
 };
@@ -27,10 +33,20 @@
     array<float4, 1> af4;
     af4[0].x = 0.0;
     af4[0].ywxz = float4(1.0);
+    S s;
+    s.f = 0.0;
+    s.af[1] = 0.0;
+    s.h4.zxy = float3(9.0);
+    s.ah4[2].yw = float2(5.0);
     ai[0] += ai4[0].x;
+    s.f = 1.0;
+    s.af[0] = 2.0;
+    s.h4 = float4(1.0);
+    s.ah4[0] = float4(2.0);
     af4[0] *= ah2x4[0][0].x;
     i4.y = i4.y * 0;
     x.y = x.y * 0.0;
+    s.f *= 0.0;
     _out.sk_FragColor = _uniforms.colorGreen;
     return _out;
 }
diff --git a/tests/sksl/shared/StructMaxDepth.glsl b/tests/sksl/shared/StructMaxDepth.glsl
index 0b3f753..d6ab111 100644
--- a/tests/sksl/shared/StructMaxDepth.glsl
+++ b/tests/sksl/shared/StructMaxDepth.glsl
@@ -25,27 +25,27 @@
 };
 in S8 s8;
 struct SA1 {
-    int[8] x;
+    int x[8];
 };
 struct SA2 {
-    SA1[7] x;
+    SA1 x[7];
 };
 struct SA3 {
-    SA2[6] x;
+    SA2 x[6];
 };
 struct SA4 {
-    SA3[5] x;
+    SA3 x[5];
 };
 struct SA5 {
-    SA4[4] x;
+    SA4 x[4];
 };
 struct SA6 {
-    SA5[3] x;
+    SA5 x[3];
 };
 struct SA7 {
-    SA6[2] x;
+    SA6 x[2];
 };
 struct SA8 {
-    SA7[1] x;
+    SA7 x[1];
 };
 in SA8 sa8[9];
diff --git a/tests/sksl/shared/Structs.glsl b/tests/sksl/shared/Structs.glsl
index 71b7630..07d907c 100644
--- a/tests/sksl/shared/Structs.glsl
+++ b/tests/sksl/shared/Structs.glsl
@@ -7,7 +7,7 @@
 A a1;
 struct B {
     float x;
-    float[2] y;
+    float y[2];
     layout (binding = 1) A z;
 };
 B b1;