Allow op-assigned local variables to be eliminated.

I realized that we were over-protective of expressions like `a += b`
because the `a` was being marked as read-write, but in practice, the
expression's result was being discarded. That is, we were writing,
but we weren't actually reading. An ExpressionStatement will now
look for assignment expressions like this one, and demote the
assignment variable from "read-write" to "write." This gives us
exciting new opportunities for dead local-variable elimination.

Change-Id: I841d001128f0a18e6670a81ff9915aa5a2e44ee0
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/582822
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
diff --git a/resources/sksl/inliner/TrivialArgumentsInlineDirectly.sksl b/resources/sksl/inliner/TrivialArgumentsInlineDirectly.sksl
index 0acb174..46df666 100644
--- a/resources/sksl/inliner/TrivialArgumentsInlineDirectly.sksl
+++ b/resources/sksl/inliner/TrivialArgumentsInlineDirectly.sksl
@@ -51,6 +51,8 @@
     return half4(s.a, s.b, s.c, s.d) * s.e;
 }
 
+noinline void keepAlive(inout half f) {}
+
 half4 main(float2 coords) {
     S s;
     s.ah4[0] = half4(unknownInput);
@@ -98,7 +100,8 @@
     var = func3(s.h4.yyy + s.h4.zzz);              // binary expression
     var = func4(s.h4.y001);                        // compound ctor, not compile-time constant
 
-    var[0] += mat[0][0];
+    keepAlive(var[0]);
+    keepAlive(mat[0][0]);
 
     return colorGreen;
 }
diff --git a/resources/sksl/shared/Assignment.sksl b/resources/sksl/shared/Assignment.sksl
index 8aa7dc5..21ab4bd 100644
--- a/resources/sksl/shared/Assignment.sksl
+++ b/resources/sksl/shared/Assignment.sksl
@@ -10,6 +10,10 @@
 half4 globalVar;
 S globalStruct;
 
+noinline void keepAlive(inout half h) {}
+noinline void keepAlive(inout float f) {}
+noinline void keepAlive(inout int i) {}
+
 half4 main(float2 coords) {
     /* assign to scalar */               int i; i = 0;
     /* assign to vector */               int4 i4; i4 = int4(1,2,3,4);
@@ -37,12 +41,16 @@
     /* 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(ah3x3[0][0][0]);
-    i4.y *= i;
-    x.y *= l;
-    s.f *= l;
-    f3x3[0][0] *= s.f;
+    keepAlive(af4[0][0]);
+    keepAlive(ah3x3[0][0][0]);
+    keepAlive(i);
+    keepAlive(i4.y);
+    keepAlive(ai[0]);
+    keepAlive(ai4[0][0]);
+    keepAlive(x.y);
+    keepAlive(s.f);
+    keepAlive(l);
+    keepAlive(f3x3[0][0]);
 
     return colorGreen;
 }
diff --git a/src/sksl/generated/sksl_gpu.dehydrated.sksl b/src/sksl/generated/sksl_gpu.dehydrated.sksl
index 1ca32ab..edf672c 100644
--- a/src/sksl/generated/sksl_gpu.dehydrated.sksl
+++ b/src/sksl/generated/sksl_gpu.dehydrated.sksl
@@ -1617,7 +1617,7 @@
 22,
 1,
 50,
-56,28,1,2,3,0,1,2,22,
+56,28,1,1,3,0,1,2,22,
 1,
 1,
 50,
diff --git a/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl b/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl
index 4c0255a..398c341 100644
--- a/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl
+++ b/src/sksl/generated/sksl_graphite_frag.dehydrated.sksl
@@ -1199,7 +1199,7 @@
 52,1,0,0,0,0,1,
 22,
 1,
-56,167,0,2,24,
+56,167,0,1,24,
 27,
 51,255,255,28,4,255,255,141,4,2,
 1,
@@ -1224,7 +1224,7 @@
 52,1,0,0,0,0,1,
 22,
 1,
-56,167,0,2,24,
+56,167,0,1,24,
 27,
 51,255,255,28,4,255,255,141,4,2,
 1,
@@ -1864,7 +1864,7 @@
 1,0,4,
 22,
 1,
-56,48,0,2,23,
+56,48,0,1,23,
 56,46,0,0,
 55,172,0,
 51,255,255,142,1,0,
@@ -1922,7 +1922,7 @@
 1,0,4,
 22,
 1,
-56,57,0,2,23,
+56,57,0,1,23,
 56,54,0,0,
 55,175,0,
 51,255,255,18,1,0,
@@ -2396,19 +2396,19 @@
 52,1,0,0,0,0,2,
 22,
 1,
-56,196,0,2,24,
+56,196,0,1,24,
 25,
 51,255,255,18,1,0,0,0,63,
 22,
 1,
-56,197,0,2,24,
+56,197,0,1,24,
 25,
 51,255,255,18,1,0,0,0,63,1,
 2,
 52,1,0,0,0,0,2,
 22,
 1,
-56,196,0,2,24,
+56,196,0,1,24,
 1,
 56,198,0,0,3,
 1,
@@ -2419,7 +2419,7 @@
 51,255,255,18,1,0,0,128,63,
 22,
 1,
-56,197,0,2,25,
+56,197,0,1,25,
 27,
 51,255,255,18,1,255,255,115,5,1,
 27,
@@ -2992,7 +2992,7 @@
 22,
 1,
 50,
-56,224,0,2,3,0,1,2,24,
+56,224,0,1,3,0,1,2,24,
 50,
 56,224,0,0,1,3,1,
 44,
diff --git a/src/sksl/generated/sksl_graphite_vert.dehydrated.sksl b/src/sksl/generated/sksl_graphite_vert.dehydrated.sksl
index 1353119..326e86a 100644
--- a/src/sksl/generated/sksl_graphite_vert.dehydrated.sksl
+++ b/src/sksl/generated/sksl_graphite_vert.dehydrated.sksl
@@ -666,15 +666,15 @@
 51,255,255,6,0,0,0,0,63,
 22,
 1,
-56,24,0,2,23,
+56,24,0,1,23,
 56,82,0,0,
 22,
 1,
-56,25,0,2,23,
+56,25,0,1,23,
 56,82,0,0,
 22,
 1,
-56,26,0,2,23,
+56,26,0,1,23,
 56,82,0,0,
 55,83,0,
 51,255,255,6,0,0,
@@ -1085,7 +1085,7 @@
 56,99,0,0,
 22,
 1,
-56,96,0,2,24,
+56,96,0,1,24,
 56,99,0,0,
 22,
 1,
@@ -1825,7 +1825,7 @@
 56,112,0,0,
 22,
 1,
-56,126,0,2,22,
+56,126,0,1,22,
 1,
 56,129,0,0,0,
 25,
@@ -1994,7 +1994,7 @@
 52,1,0,0,0,0,1,
 22,
 1,
-56,130,0,2,24,
+56,130,0,1,24,
 27,
 51,255,255,6,0,47,0,2,
 56,127,0,0,
@@ -2177,7 +2177,7 @@
 52,1,0,0,0,0,4,
 22,
 1,
-56,145,0,2,24,
+56,145,0,1,24,
 56,116,0,0,
 22,
 1,
@@ -2199,7 +2199,7 @@
 56,146,0,0,
 22,
 1,
-56,113,0,2,24,
+56,113,0,1,24,
 56,116,0,0,1,
 2,
 52,1,1,0,
@@ -2491,7 +2491,7 @@
 57,
 22,
 1,
-56,162,0,2,23,
+56,162,0,1,23,
 56,159,0,0,
 55,163,0,
 51,255,255,6,0,0,
@@ -2708,7 +2708,7 @@
 56,134,0,0,1,0,
 22,
 1,
-56,135,0,2,22,
+56,135,0,1,22,
 1,
 56,136,0,0,2,
 1,
diff --git a/src/sksl/ir/SkSLExpressionStatement.cpp b/src/sksl/ir/SkSLExpressionStatement.cpp
index e5dbd5c..7f28316 100644
--- a/src/sksl/ir/SkSLExpressionStatement.cpp
+++ b/src/sksl/ir/SkSLExpressionStatement.cpp
@@ -11,7 +11,9 @@
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLContext.h"
 #include "src/sksl/SkSLProgramSettings.h"
+#include "src/sksl/ir/SkSLBinaryExpression.h"
 #include "src/sksl/ir/SkSLNop.h"
+#include "src/sksl/ir/SkSLVariableReference.h"
 
 namespace SkSL {
 
@@ -34,6 +36,19 @@
         if (!Analysis::HasSideEffects(*expr)) {
             return Nop::Make();
         }
+
+        // If this is an assignment statement like `a += b;`, the ref-kind of `a` will be set as
+        // read-write; `a` is written-to by the +=, and read-from by the consumer of the expression.
+        // We can demote the ref-kind to "write" safely, because the result of the expression is
+        // discarded; that is, `a` is never actually read-from.
+        if (expr->is<BinaryExpression>()) {
+            BinaryExpression& binary = expr->as<BinaryExpression>();
+            if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) {
+                if (assignedVar->refKind() == VariableRefKind::kReadWrite) {
+                    assignedVar->setRefKind(VariableRefKind::kWrite);
+                }
+            }
+        }
     }
 
     return std::make_unique<ExpressionStatement>(std::move(expr));
diff --git a/tests/SkSLTest.cpp b/tests/SkSLTest.cpp
index 3bf2b36..2f4b65e 100644
--- a/tests/SkSLTest.cpp
+++ b/tests/SkSLTest.cpp
@@ -314,7 +314,7 @@
 }
 
 static void test_rehydrate(skiatest::Reporter* r, const char* testFile, int flags) {
-    SkString shaderString = load_source(r, testFile, "");
+    SkString shaderString = load_source(r, testFile, /*permutationSuffix=*/"");
     if (shaderString.isEmpty()) {
         return;
     }
@@ -343,9 +343,12 @@
                                 stream.str().length());
     std::unique_ptr<SkSL::Program> rehydrated = rehydrator.program();
     REPORTER_ASSERT(r, rehydrated->description() == program->description(),
-            "Mismatch between original and dehydrated/rehydrated:\n-- Original:\n%s\n"
-            "-- Rehydrated:\n%s", program->description().c_str(),
-            rehydrated->description().c_str());
+                    "%s: Mismatch between original and dehydrated/rehydrated:\n"
+                    "-- Original:\n%s\n"
+                    "-- Rehydrated:\n%s",
+                    testFile,
+                    program->description().c_str(),
+                    rehydrated->description().c_str());
 }
 
 #define SKSL_TEST(flags, ctsEnforcement, name, path)                                       \
diff --git a/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl b/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
index 8777f07..cd277f2 100644
--- a/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
+++ b/tests/sksl/inliner/TrivialArgumentsInlineDirectly.glsl
@@ -22,6 +22,8 @@
     float d;
     float e;
 };
+void keepAlive_vh(inout float f) {
+}
 vec4 main() {
     S s;
     s.ah4[0] = vec4(unknownInput);
@@ -68,6 +70,7 @@
     var = _7_h3.xyzx * _7_h3.xyzx;
     vec4 _8_h4 = vec4(s.h4.y, 0.0, 0.0, 1.0);
     var = _8_h4 * _8_h4;
-    var.x += mat[0].x;
+    keepAlive_vh(var.x);
+    keepAlive_vh(mat[0].x);
     return colorGreen;
 }
diff --git a/tests/sksl/shared/Assignment.asm.frag b/tests/sksl/shared/Assignment.asm.frag
index 6ccbb63..4afc9c7 100644
--- a/tests/sksl/shared/Assignment.asm.frag
+++ b/tests/sksl/shared/Assignment.asm.frag
@@ -15,6 +15,9 @@
 OpName %_UniformBuffer "_UniformBuffer"
 OpMemberName %_UniformBuffer 0 "colorGreen"
 OpName %_entrypoint_v "_entrypoint_v"
+OpName %keepAlive_vh "keepAlive_vh"
+OpName %keepAlive_vf "keepAlive_vf"
+OpName %keepAlive_vi "keepAlive_vi"
 OpName %main "main"
 OpName %i "i"
 OpName %i4 "i4"
@@ -42,27 +45,30 @@
 OpMemberDecorate %_UniformBuffer 0 Offset 0
 OpMemberDecorate %_UniformBuffer 0 RelaxedPrecision
 OpDecorate %_UniformBuffer Block
-OpDecorate %19 Binding 0
-OpDecorate %19 DescriptorSet 0
+OpDecorate %22 Binding 0
+OpDecorate %22 DescriptorSet 0
 OpDecorate %x RelaxedPrecision
-OpDecorate %67 RelaxedPrecision
-OpDecorate %68 RelaxedPrecision
+OpDecorate %78 RelaxedPrecision
+OpDecorate %79 RelaxedPrecision
 OpDecorate %_arr_int_int_1 ArrayStride 16
 OpDecorate %_arr_v4int_int_1 ArrayStride 16
 OpDecorate %_arr_mat3v3float_int_1 ArrayStride 48
 OpDecorate %_arr_v4float_int_1 ArrayStride 16
-OpDecorate %96 RelaxedPrecision
-OpDecorate %97 RelaxedPrecision
-OpDecorate %100 RelaxedPrecision
-OpDecorate %101 RelaxedPrecision
+OpDecorate %107 RelaxedPrecision
+OpDecorate %108 RelaxedPrecision
+OpDecorate %111 RelaxedPrecision
+OpDecorate %112 RelaxedPrecision
 OpDecorate %l RelaxedPrecision
-OpDecorate %121 RelaxedPrecision
-OpDecorate %122 RelaxedPrecision
-OpDecorate %129 RelaxedPrecision
-OpDecorate %130 RelaxedPrecision
-OpDecorate %131 RelaxedPrecision
-OpDecorate %134 RelaxedPrecision
-OpDecorate %144 RelaxedPrecision
+OpDecorate %137 RelaxedPrecision
+OpDecorate %138 RelaxedPrecision
+OpDecorate %140 RelaxedPrecision
+OpDecorate %162 RelaxedPrecision
+OpDecorate %163 RelaxedPrecision
+OpDecorate %165 RelaxedPrecision
+OpDecorate %171 RelaxedPrecision
+OpDecorate %172 RelaxedPrecision
+OpDecorate %174 RelaxedPrecision
+OpDecorate %183 RelaxedPrecision
 %bool = OpTypeBool
 %_ptr_Input_bool = OpTypePointer Input %bool
 %sk_Clockwise = OpVariable %_ptr_Input_bool Input
@@ -81,15 +87,18 @@
 %globalStruct = OpVariable %_ptr_Private_S Private
 %_UniformBuffer = OpTypeStruct %v4float
 %_ptr_Uniform__UniformBuffer = OpTypePointer Uniform %_UniformBuffer
-%19 = OpVariable %_ptr_Uniform__UniformBuffer Uniform
+%22 = OpVariable %_ptr_Uniform__UniformBuffer Uniform
 %void = OpTypeVoid
-%24 = OpTypeFunction %void
+%27 = OpTypeFunction %void
 %float_0 = OpConstant %float 0
 %v2float = OpTypeVector %float 2
-%28 = OpConstantComposite %v2float %float_0 %float_0
+%31 = OpConstantComposite %v2float %float_0 %float_0
 %_ptr_Function_v2float = OpTypePointer Function %v2float
-%32 = OpTypeFunction %v4float %_ptr_Function_v2float
+%_ptr_Function_float = OpTypePointer Function %float
+%36 = OpTypeFunction %void %_ptr_Function_float
 %_ptr_Function_int = OpTypePointer Function %int
+%42 = OpTypeFunction %void %_ptr_Function_int
+%45 = OpTypeFunction %v4float %_ptr_Function_v2float
 %int_0 = OpConstant %int 0
 %v4int = OpTypeVector %int 4
 %_ptr_Function_v4int = OpTypePointer Function %v4int
@@ -97,7 +106,7 @@
 %int_2 = OpConstant %int 2
 %int_3 = OpConstant %int 3
 %int_4 = OpConstant %int 4
-%45 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4
+%57 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4
 %v3float = OpTypeVector %float 3
 %mat3v3float = OpTypeMatrix %v3float 3
 %_ptr_Function_mat3v3float = OpTypePointer Function %mat3v3float
@@ -110,12 +119,11 @@
 %float_7 = OpConstant %float 7
 %float_8 = OpConstant %float 8
 %float_9 = OpConstant %float 9
-%59 = OpConstantComposite %v3float %float_1 %float_2 %float_3
-%60 = OpConstantComposite %v3float %float_4 %float_5 %float_6
-%61 = OpConstantComposite %v3float %float_7 %float_8 %float_9
-%62 = OpConstantComposite %mat3v3float %59 %60 %61
+%71 = OpConstantComposite %v3float %float_1 %float_2 %float_3
+%72 = OpConstantComposite %v3float %float_4 %float_5 %float_6
+%73 = OpConstantComposite %v3float %float_7 %float_8 %float_9
+%74 = OpConstantComposite %mat3v3float %71 %72 %73
 %_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Function_float = OpTypePointer Function %float
 %_arr_int_int_1 = OpTypeArray %int %int_1
 %_ptr_Function__arr_int_int_1 = OpTypePointer Function %_arr_int_int_1
 %_arr_v4int_int_1 = OpTypeArray %v4int %int_1
@@ -124,26 +132,41 @@
 %_ptr_Function__arr_mat3v3float_int_1 = OpTypePointer Function %_arr_mat3v3float_int_1
 %_arr_v4float_int_1 = OpTypeArray %v4float %int_1
 %_ptr_Function__arr_v4float_int_1 = OpTypePointer Function %_arr_v4float_int_1
-%86 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%97 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
 %_ptr_Function_S = OpTypePointer Function %S
-%94 = OpConstantComposite %v3float %float_9 %float_9 %float_9
-%98 = OpConstantComposite %v2float %float_5 %float_5
-%102 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%105 = OpConstantComposite %v3float %float_9 %float_9 %float_9
+%109 = OpConstantComposite %v2float %float_5 %float_5
+%113 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %_ptr_Private_float = OpTypePointer Private %float
-%115 = OpConstantComposite %v4float %float_2 %float_2 %float_2 %float_2
+%126 = OpConstantComposite %v4float %float_2 %float_2 %float_2 %float_2
 %_ptr_Function_v3float = OpTypePointer Function %v3float
 %_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
-%_entrypoint_v = OpFunction %void None %24
-%25 = OpLabel
-%29 = OpVariable %_ptr_Function_v2float Function
-OpStore %29 %28
-%31 = OpFunctionCall %v4float %main %29
-OpStore %sk_FragColor %31
+%_entrypoint_v = OpFunction %void None %27
+%28 = OpLabel
+%32 = OpVariable %_ptr_Function_v2float Function
+OpStore %32 %31
+%34 = OpFunctionCall %v4float %main %32
+OpStore %sk_FragColor %34
 OpReturn
 OpFunctionEnd
-%main = OpFunction %v4float None %32
-%33 = OpFunctionParameter %_ptr_Function_v2float
-%34 = OpLabel
+%keepAlive_vh = OpFunction %void None %36
+%37 = OpFunctionParameter %_ptr_Function_float
+%38 = OpLabel
+OpReturn
+OpFunctionEnd
+%keepAlive_vf = OpFunction %void None %36
+%39 = OpFunctionParameter %_ptr_Function_float
+%40 = OpLabel
+OpReturn
+OpFunctionEnd
+%keepAlive_vi = OpFunction %void None %42
+%43 = OpFunctionParameter %_ptr_Function_int
+%44 = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %v4float None %45
+%46 = OpFunctionParameter %_ptr_Function_v2float
+%47 = OpLabel
 %i = OpVariable %_ptr_Function_int Function
 %i4 = OpVariable %_ptr_Function_v4int Function
 %f3x3 = OpVariable %_ptr_Function_mat3v3float Function
@@ -154,88 +177,131 @@
 %af4 = OpVariable %_ptr_Function__arr_v4float_int_1 Function
 %s = OpVariable %_ptr_Function_S Function
 %l = OpVariable %_ptr_Function_float Function
+%131 = OpVariable %_ptr_Function_float Function
+%138 = OpVariable %_ptr_Function_float Function
+%142 = OpVariable %_ptr_Function_int Function
+%147 = OpVariable %_ptr_Function_int Function
+%152 = OpVariable %_ptr_Function_int Function
+%158 = OpVariable %_ptr_Function_int Function
+%163 = OpVariable %_ptr_Function_float Function
+%168 = OpVariable %_ptr_Function_float Function
+%172 = OpVariable %_ptr_Function_float Function
+%178 = OpVariable %_ptr_Function_float Function
 OpStore %i %int_0
-OpStore %i4 %45
-OpStore %f3x3 %62
-%65 = OpAccessChain %_ptr_Function_float %x %int_3
-OpStore %65 %float_0
-%67 = OpLoad %v4float %x
-%68 = OpVectorShuffle %v4float %67 %28 5 4 2 3
-OpStore %x %68
-%72 = OpAccessChain %_ptr_Function_int %ai %int_0
-OpStore %72 %int_0
-%76 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
-OpStore %76 %45
-%80 = OpAccessChain %_ptr_Function_mat3v3float %ah3x3 %int_0
-OpStore %80 %62
-%84 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
-%85 = OpAccessChain %_ptr_Function_float %84 %int_0
-OpStore %85 %float_0
-%87 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
-%88 = OpLoad %v4float %87
-%89 = OpVectorShuffle %v4float %88 %86 6 4 7 5
-OpStore %87 %89
-%92 = OpAccessChain %_ptr_Function_float %s %int_0
-OpStore %92 %float_0
-%93 = OpAccessChain %_ptr_Function_float %s %int_1 %int_1
-OpStore %93 %float_0
-%95 = OpAccessChain %_ptr_Function_v4float %s %int_2
-%96 = OpLoad %v4float %95
-%97 = OpVectorShuffle %v4float %96 %94 5 6 4 3
-OpStore %95 %97
-%99 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_2
-%100 = OpLoad %v4float %99
-%101 = OpVectorShuffle %v4float %100 %98 0 4 2 5
-OpStore %99 %101
-OpStore %globalVar %102
-%103 = OpAccessChain %_ptr_Private_float %globalStruct %int_0
+OpStore %i4 %57
+OpStore %f3x3 %74
+%77 = OpAccessChain %_ptr_Function_float %x %int_3
+OpStore %77 %float_0
+%78 = OpLoad %v4float %x
+%79 = OpVectorShuffle %v4float %78 %31 5 4 2 3
+OpStore %x %79
+%83 = OpAccessChain %_ptr_Function_int %ai %int_0
+OpStore %83 %int_0
+%87 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
+OpStore %87 %57
+%91 = OpAccessChain %_ptr_Function_mat3v3float %ah3x3 %int_0
+OpStore %91 %74
+%95 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
+%96 = OpAccessChain %_ptr_Function_float %95 %int_0
+OpStore %96 %float_0
+%98 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
+%99 = OpLoad %v4float %98
+%100 = OpVectorShuffle %v4float %99 %97 6 4 7 5
+OpStore %98 %100
+%103 = OpAccessChain %_ptr_Function_float %s %int_0
 OpStore %103 %float_0
+%104 = OpAccessChain %_ptr_Function_float %s %int_1 %int_1
+OpStore %104 %float_0
+%106 = OpAccessChain %_ptr_Function_v4float %s %int_2
+%107 = OpLoad %v4float %106
+%108 = OpVectorShuffle %v4float %107 %105 5 6 4 3
+OpStore %106 %108
+%110 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_2
+%111 = OpLoad %v4float %110
+%112 = OpVectorShuffle %v4float %111 %109 0 4 2 5
+OpStore %110 %112
+OpStore %globalVar %113
+%114 = OpAccessChain %_ptr_Private_float %globalStruct %int_0
+OpStore %114 %float_0
 OpStore %l %float_0
-%106 = OpAccessChain %_ptr_Function_int %ai %int_0
-%107 = OpLoad %int %106
-%108 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
-%109 = OpLoad %v4int %108
-%110 = OpCompositeExtract %int %109 0
-%111 = OpIAdd %int %107 %110
-OpStore %106 %111
-%112 = OpAccessChain %_ptr_Function_float %s %int_0
-OpStore %112 %float_1
-%113 = OpAccessChain %_ptr_Function_float %s %int_1 %int_0
-OpStore %113 %float_2
-%114 = OpAccessChain %_ptr_Function_v4float %s %int_2
-OpStore %114 %86
-%116 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_0
-OpStore %116 %115
-%117 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
-%118 = OpLoad %v4float %117
-%119 = OpAccessChain %_ptr_Function_v3float %ah3x3 %int_0 %int_0
-%121 = OpLoad %v3float %119
-%122 = OpCompositeExtract %float %121 0
-%123 = OpVectorTimesScalar %v4float %118 %122
-OpStore %117 %123
-%124 = OpAccessChain %_ptr_Function_int %i4 %int_1
-%125 = OpLoad %int %124
-%126 = OpLoad %int %i
-%127 = OpIMul %int %125 %126
-OpStore %124 %127
-%128 = OpAccessChain %_ptr_Function_float %x %int_1
-%129 = OpLoad %float %128
-%130 = OpLoad %float %l
-%131 = OpFMul %float %129 %130
-OpStore %128 %131
-%132 = OpAccessChain %_ptr_Function_float %s %int_0
-%133 = OpLoad %float %132
-%134 = OpLoad %float %l
-%135 = OpFMul %float %133 %134
-OpStore %132 %135
-%136 = OpAccessChain %_ptr_Function_v3float %f3x3 %int_0
-%137 = OpAccessChain %_ptr_Function_float %136 %int_0
-%138 = OpLoad %float %137
-%139 = OpAccessChain %_ptr_Function_float %s %int_0
-%140 = OpLoad %float %139
-%141 = OpFMul %float %138 %140
-OpStore %137 %141
-%142 = OpAccessChain %_ptr_Uniform_v4float %19 %int_0
-%144 = OpLoad %v4float %142
-OpReturnValue %144
+%117 = OpAccessChain %_ptr_Function_int %ai %int_0
+%118 = OpLoad %int %117
+%119 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
+%120 = OpLoad %v4int %119
+%121 = OpCompositeExtract %int %120 0
+%122 = OpIAdd %int %118 %121
+OpStore %117 %122
+%123 = OpAccessChain %_ptr_Function_float %s %int_0
+OpStore %123 %float_1
+%124 = OpAccessChain %_ptr_Function_float %s %int_1 %int_0
+OpStore %124 %float_2
+%125 = OpAccessChain %_ptr_Function_v4float %s %int_2
+OpStore %125 %97
+%127 = OpAccessChain %_ptr_Function_v4float %s %int_3 %int_0
+OpStore %127 %126
+%128 = OpAccessChain %_ptr_Function_v4float %af4 %int_0
+%129 = OpAccessChain %_ptr_Function_float %128 %int_0
+%130 = OpLoad %float %129
+OpStore %131 %130
+%132 = OpFunctionCall %void %keepAlive_vf %131
+%133 = OpLoad %float %131
+OpStore %129 %133
+%134 = OpAccessChain %_ptr_Function_v3float %ah3x3 %int_0 %int_0
+%136 = OpAccessChain %_ptr_Function_float %134 %int_0
+%137 = OpLoad %float %136
+OpStore %138 %137
+%139 = OpFunctionCall %void %keepAlive_vh %138
+%140 = OpLoad %float %138
+OpStore %136 %140
+%141 = OpLoad %int %i
+OpStore %142 %141
+%143 = OpFunctionCall %void %keepAlive_vi %142
+%144 = OpLoad %int %142
+OpStore %i %144
+%145 = OpAccessChain %_ptr_Function_int %i4 %int_1
+%146 = OpLoad %int %145
+OpStore %147 %146
+%148 = OpFunctionCall %void %keepAlive_vi %147
+%149 = OpLoad %int %147
+OpStore %145 %149
+%150 = OpAccessChain %_ptr_Function_int %ai %int_0
+%151 = OpLoad %int %150
+OpStore %152 %151
+%153 = OpFunctionCall %void %keepAlive_vi %152
+%154 = OpLoad %int %152
+OpStore %150 %154
+%155 = OpAccessChain %_ptr_Function_v4int %ai4 %int_0
+%156 = OpAccessChain %_ptr_Function_int %155 %int_0
+%157 = OpLoad %int %156
+OpStore %158 %157
+%159 = OpFunctionCall %void %keepAlive_vi %158
+%160 = OpLoad %int %158
+OpStore %156 %160
+%161 = OpAccessChain %_ptr_Function_float %x %int_1
+%162 = OpLoad %float %161
+OpStore %163 %162
+%164 = OpFunctionCall %void %keepAlive_vh %163
+%165 = OpLoad %float %163
+OpStore %161 %165
+%166 = OpAccessChain %_ptr_Function_float %s %int_0
+%167 = OpLoad %float %166
+OpStore %168 %167
+%169 = OpFunctionCall %void %keepAlive_vf %168
+%170 = OpLoad %float %168
+OpStore %166 %170
+%171 = OpLoad %float %l
+OpStore %172 %171
+%173 = OpFunctionCall %void %keepAlive_vh %172
+%174 = OpLoad %float %172
+OpStore %l %174
+%175 = OpAccessChain %_ptr_Function_v3float %f3x3 %int_0
+%176 = OpAccessChain %_ptr_Function_float %175 %int_0
+%177 = OpLoad %float %176
+OpStore %178 %177
+%179 = OpFunctionCall %void %keepAlive_vf %178
+%180 = OpLoad %float %178
+OpStore %176 %180
+%181 = OpAccessChain %_ptr_Uniform_v4float %22 %int_0
+%183 = OpLoad %v4float %181
+OpReturnValue %183
 OpFunctionEnd
diff --git a/tests/sksl/shared/Assignment.glsl b/tests/sksl/shared/Assignment.glsl
index d9cbe52..6b59aae 100644
--- a/tests/sksl/shared/Assignment.glsl
+++ b/tests/sksl/shared/Assignment.glsl
@@ -9,6 +9,12 @@
 };
 vec4 globalVar;
 S globalStruct;
+void keepAlive_vh(inout float h) {
+}
+void keepAlive_vf(inout float f) {
+}
+void keepAlive_vi(inout int i) {
+}
 vec4 main() {
     int i;
     i = 0;
@@ -42,10 +48,15 @@
     s.af[0] = 2.0;
     s.h4 = vec4(1.0);
     s.ah4[0] = vec4(2.0);
-    af4[0] *= ah3x3[0][0].x;
-    i4.y *= i;
-    x.y *= l;
-    s.f *= l;
-    f3x3[0].x *= s.f;
+    keepAlive_vf(af4[0].x);
+    keepAlive_vh(ah3x3[0][0].x);
+    keepAlive_vi(i);
+    keepAlive_vi(i4.y);
+    keepAlive_vi(ai[0]);
+    keepAlive_vi(ai4[0].x);
+    keepAlive_vh(x.y);
+    keepAlive_vf(s.f);
+    keepAlive_vh(l);
+    keepAlive_vf(f3x3[0].x);
     return colorGreen;
 }
diff --git a/tests/sksl/shared/Assignment.metal b/tests/sksl/shared/Assignment.metal
index 32e19e1..736cb86 100644
--- a/tests/sksl/shared/Assignment.metal
+++ b/tests/sksl/shared/Assignment.metal
@@ -19,6 +19,72 @@
     half4 globalVar;
     S globalStruct;
 };
+void keepAlive_vf(thread float& f);
+void _skOutParamHelper0_keepAlive_vf(thread array<float4, 1>& af4) {
+    float _var0 = af4[0].x;
+    keepAlive_vf(_var0);
+    af4[0].x = _var0;
+}
+void keepAlive_vh(thread half& h);
+void _skOutParamHelper1_keepAlive_vh(thread array<half3x3, 1>& ah3x3) {
+    half _var0 = ah3x3[0][0].x;
+    keepAlive_vh(_var0);
+    ah3x3[0][0].x = _var0;
+}
+void keepAlive_vi(thread int& i);
+void _skOutParamHelper2_keepAlive_vi(thread int& i) {
+    int _var0 = i;
+    keepAlive_vi(_var0);
+    i = _var0;
+}
+void keepAlive_vi(thread int& i);
+void _skOutParamHelper3_keepAlive_vi(thread int4& i4) {
+    int _var0 = i4.y;
+    keepAlive_vi(_var0);
+    i4.y = _var0;
+}
+void keepAlive_vi(thread int& i);
+void _skOutParamHelper4_keepAlive_vi(thread array<int, 1>& ai) {
+    int _var0 = ai[0];
+    keepAlive_vi(_var0);
+    ai[0] = _var0;
+}
+void keepAlive_vi(thread int& i);
+void _skOutParamHelper5_keepAlive_vi(thread array<int4, 1>& ai4) {
+    int _var0 = ai4[0].x;
+    keepAlive_vi(_var0);
+    ai4[0].x = _var0;
+}
+void keepAlive_vh(thread half& h);
+void _skOutParamHelper6_keepAlive_vh(thread half4& x) {
+    half _var0 = x.y;
+    keepAlive_vh(_var0);
+    x.y = _var0;
+}
+void keepAlive_vf(thread float& f);
+void _skOutParamHelper7_keepAlive_vf(thread S& s) {
+    float _var0 = s.f;
+    keepAlive_vf(_var0);
+    s.f = _var0;
+}
+void keepAlive_vh(thread half& h);
+void _skOutParamHelper8_keepAlive_vh(thread half& l) {
+    half _var0 = l;
+    keepAlive_vh(_var0);
+    l = _var0;
+}
+void keepAlive_vf(thread float& f);
+void _skOutParamHelper9_keepAlive_vf(thread float3x3& f3x3) {
+    float _var0 = f3x3[0].x;
+    keepAlive_vf(_var0);
+    f3x3[0].x = _var0;
+}
+void keepAlive_vh(thread half& h) {
+}
+void keepAlive_vf(thread float& f) {
+}
+void keepAlive_vi(thread int& i) {
+}
 fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant Uniforms& _uniforms [[buffer(0)]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {
     Globals _globals{{}, {}};
     (void)_globals;
@@ -56,11 +122,16 @@
     s.af[0] = 2.0;
     s.h4 = half4(1.0h);
     s.ah4[0] = half4(2.0h);
-    af4[0] *= float(ah3x3[0][0].x);
-    i4.y = i4.y * i;
-    x.y = x.y * l;
-    s.f *= float(l);
-    f3x3[0].x = f3x3[0].x * s.f;
+    _skOutParamHelper0_keepAlive_vf(af4);
+    _skOutParamHelper1_keepAlive_vh(ah3x3);
+    _skOutParamHelper2_keepAlive_vi(i);
+    _skOutParamHelper3_keepAlive_vi(i4);
+    _skOutParamHelper4_keepAlive_vi(ai);
+    _skOutParamHelper5_keepAlive_vi(ai4);
+    _skOutParamHelper6_keepAlive_vh(x);
+    _skOutParamHelper7_keepAlive_vf(s);
+    _skOutParamHelper8_keepAlive_vh(l);
+    _skOutParamHelper9_keepAlive_vf(f3x3);
     _out.sk_FragColor = _uniforms.colorGreen;
     return _out;
 }