Use references instead of pointers for Metal out params.

Pointers require decorating the variable with a * to read back the
value, which the code generator did not properly handle. There was a
special case to add the * but it only supported assignment into the
variable, not reading back. References require no special decoration.

This change fixes compile errors in Functions.sksl with the "bar"
function. (This test marks `x` as an inout but never actually mutates
it.) It also allows us to remove a special-case workaround for `frexp`,
an intrinsic function which uses a reference for its out-parameter.

Additionally, this CL adds a non-inlining copy of "OutParams.sksl" to
the Metal test directory, as most of our tests which use out-parameters
end up inlining all the code, which hides these sorts of bugs.

Change-Id: I31c4db04f6b512b4cd4fe65b3347b82bdbf039cd
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/341000
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index 93e373f..f8d730a 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -184,6 +184,7 @@
   "$_tests/sksl/metal/NumericGlobals.sksl",
   "$_tests/sksl/metal/OpaqueTypeInInterfaceBlock.sksl",
   "$_tests/sksl/metal/OpaqueTypeInStruct.sksl",
+  "$_tests/sksl/metal/OutParams.sksl",
   "$_tests/sksl/metal/OutVarsRequireLocation.sksl",
   "$_tests/sksl/metal/SamplerGlobals.sksl",
 ]
diff --git a/src/sksl/SkSLMetalCodeGenerator.cpp b/src/sksl/SkSLMetalCodeGenerator.cpp
index 1ba9b3e..f299497 100644
--- a/src/sksl/SkSLMetalCodeGenerator.cpp
+++ b/src/sksl/SkSLMetalCodeGenerator.cpp
@@ -279,17 +279,6 @@
     } else if (builtin && name == "inverse") {
         SkASSERT(arguments.size() == 1);
         this->writeInverseHack(*arguments[0]);
-    } else if (builtin && name == "frexp") {
-        // Our Metal codegen assumes that out params are pointers, but Metal's built-in frexp
-        // actually takes a reference for the exponent, not a pointer. We add in a helper function
-        // here to translate.
-        SkASSERT(arguments.size() == 2);
-        auto [iter, newlyCreated] = fHelpers.insert("frexp");
-        if (newlyCreated) {
-            fExtraFunctions.printf(
-                    "float frexp(float arg, thread int* exp) { return frexp(arg, *exp); }\n");
-        }
-        this->write("frexp");
     } else if (builtin && name == "dFdx") {
         this->write("dfdx");
     } else if (builtin && name == "dFdy") {
@@ -324,14 +313,10 @@
         this->write("_fragCoord");
         separator = ", ";
     }
-    const std::vector<const Variable*>& parameters = function.parameters();
     for (size_t i = 0; i < arguments.size(); ++i) {
         const Expression& arg = *arguments[i];
         this->write(separator);
         separator = ", ";
-        if (parameters[i]->modifiers().fFlags & Modifiers::kOut_Flag) {
-            this->write("&");
-        }
         this->writeExpression(arg, kSequence_Precedence);
     }
     this->write(")");
@@ -943,13 +928,6 @@
     if (needParens) {
         this->write("(");
     }
-    if (Compiler::IsAssignment(op) && left.is<VariableReference>() &&
-        left.as<VariableReference>().variable()->storage() == Variable::Storage::kParameter &&
-        left.as<VariableReference>().variable()->modifiers().fFlags & Modifiers::kOut_Flag) {
-        // writing to an out parameter. Since we have to turn those into pointers, we have to
-        // dereference it here.
-        this->write("*");
-    }
     if (op == Token::Kind::TK_STAREQ && leftType.isMatrix() && rightType.isMatrix()) {
         this->writeMatrixTimesEqualHelper(leftType, rightType, b.type());
     }
@@ -1153,7 +1131,7 @@
         const Type* type = &param->type();
         this->writeBaseType(*type);
         if (param->modifiers().fFlags & Modifiers::kOut_Flag) {
-            this->write("*");
+            this->write("&");
         }
         this->write(" ");
         this->writeName(param->name());
diff --git a/tests/sksl/metal/OutParams.sksl b/tests/sksl/metal/OutParams.sksl
new file mode 100644
index 0000000..b5c8d2d
--- /dev/null
+++ b/tests/sksl/metal/OutParams.sksl
@@ -0,0 +1,73 @@
+/*#pragma settings NoInline*/
+
+void out_half (out half  v) { v = 1; }
+void out_half2(out half2 v) { v = half2(2); }
+void out_half3(out half3 v) { v = half3(3); }
+void out_half4(out half4 v) { v = half4(4); }
+
+void out_half2x2(out half2x2 v) { v = half2x2(2); }
+void out_half3x3(out half3x3 v) { v = half3x3(3); }
+void out_half4x4(out half4x4 v) { v = half4x4(4); }
+
+void out_int (out int  v) { v = 1; }
+void out_int2(out int2 v) { v = int2(2); }
+void out_int3(out int3 v) { v = int3(3); }
+void out_int4(out int4 v) { v = int4(4); }
+
+void out_float (out float  v) { v = 1; }
+void out_float2(out float2 v) { v = float2(2); }
+void out_float3(out float3 v) { v = float3(3); }
+void out_float4(out float4 v) { v = float4(4); }
+
+void out_float2x2(out float2x2 v) { v = float2x2(2); }
+void out_float3x3(out float3x3 v) { v = float3x3(3); }
+void out_float4x4(out float4x4 v) { v = float4x4(4); }
+
+void out_bool (out bool  v) { v = true; }
+void out_bool2(out bool2 v) { v = bool2(false); }
+void out_bool3(out bool3 v) { v = bool3(true);  }
+void out_bool4(out bool4 v) { v = bool4(false); }
+
+void main() {
+    half     h;    out_half (h);
+    half2    h2;   out_half2(h2);
+    half3    h3;   out_half3(h3);
+    half4    h4;   out_half4(h4);
+                   out_half2(h3.xz);
+                   out_half4(h4.zwxy);
+    sk_FragColor = half4(h, h2.x, h3.x, h4.x);
+
+    half2x2  h2x2; out_half2x2(h2x2);
+    half3x3  h3x3; out_half3x3(h3x3);
+    half4x4  h4x4; out_half4x4(h4x4);
+                   out_half3(h3x3[1]);
+                   out_half(h4x4[3].w);
+    sk_FragColor = half4(h2x2[0][0], h3x3[0][0], h4x4[0][0], 1);
+
+    int      i;    out_int (i);
+    int2     i2;   out_int2(i2);
+    int3     i3;   out_int3(i3);
+    int4     i4;   out_int4(i4);
+                   out_int3(i4.xyz);
+    sk_FragColor = half4(i, i2.x, i3.x, i4.x);
+
+    float    f;    out_float (f);
+    float2   f2;   out_float2(f2);
+    float3   f3;   out_float3(f3);
+    float4   f4;   out_float4(f4);
+                   out_float2(f3.xy);
+    sk_FragColor = half4(half(f), half(f2.x), half(f3.x), half(f4.x));
+
+    float2x2 f2x2; out_float2x2(f2x2);
+    float3x3 f3x3; out_float3x3(f3x3);
+    float4x4 f4x4; out_float4x4(f4x4);
+                   out_float(f2x2[0][0]);
+    sk_FragColor = half4(half(f2x2[0][0]), half(f3x3[0][0]), half(f4x4[0][0]), 1);
+
+    bool     b;    out_bool (b);
+    bool2    b2;   out_bool2(b2);
+    bool3    b3;   out_bool3(b3);
+    bool4    b4;   out_bool4(b4);
+                   out_bool2(b4.xw);
+    sk_FragColor = half4(half(b), half(b2.x), half(b3.x), half(b4.x));
+}
diff --git a/tests/sksl/metal/golden/OutParams.metal b/tests/sksl/metal/golden/OutParams.metal
new file mode 100644
index 0000000..b2605a5
--- /dev/null
+++ b/tests/sksl/metal/golden/OutParams.metal
@@ -0,0 +1,137 @@
+#include <metal_stdlib>
+#include <simd/simd.h>
+using namespace metal;
+struct Inputs {
+};
+struct Outputs {
+    float4 sk_FragColor [[color(0)]];
+};
+void out_half(thread float& v) {
+    v = 1.0;
+}
+void out_half2(thread float2& v) {
+    v = float2(2.0);
+}
+void out_half3(thread float3& v) {
+    v = float3(3.0);
+}
+void out_half4(thread float4& v) {
+    v = float4(4.0);
+}
+void out_half2x2(thread float2x2& v) {
+    v = float2x2(2.0);
+}
+void out_half3x3(thread float3x3& v) {
+    v = float3x3(3.0);
+}
+void out_half4x4(thread float4x4& v) {
+    v = float4x4(4.0);
+}
+void out_int(thread int& v) {
+    v = 1;
+}
+void out_int2(thread int2& v) {
+    v = int2(2);
+}
+void out_int3(thread int3& v) {
+    v = int3(3);
+}
+void out_int4(thread int4& v) {
+    v = int4(4);
+}
+void out_float(thread float& v) {
+    v = 1.0;
+}
+void out_float2(thread float2& v) {
+    v = float2(2.0);
+}
+void out_float3(thread float3& v) {
+    v = float3(3.0);
+}
+void out_float4(thread float4& v) {
+    v = float4(4.0);
+}
+void out_float2x2(thread float2x2& v) {
+    v = float2x2(2.0);
+}
+void out_float3x3(thread float3x3& v) {
+    v = float3x3(3.0);
+}
+void out_float4x4(thread float4x4& v) {
+    v = float4x4(4.0);
+}
+void out_bool(thread bool& v) {
+    v = true;
+}
+void out_bool2(thread bool2& v) {
+    v = bool2(false);
+}
+void out_bool3(thread bool3& v) {
+    v = bool3(true);
+}
+void out_bool4(thread bool4& v) {
+    v = bool4(false);
+}
+fragment Outputs fragmentMain(Inputs _in [[stage_in]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {
+    Outputs _outputStruct;
+    thread Outputs* _out = &_outputStruct;
+    float h;
+    out_half(h);
+    float2 h2;
+    out_half2(h2);
+    float3 h3;
+    out_half3(h3);
+    float4 h4;
+    out_half4(h4);
+    out_half2(h3.xz);
+    out_half4(h4.zwxy);
+    _out->sk_FragColor = float4(h, h2.x, h3.x, h4.x);
+    float2x2 h2x2;
+    out_half2x2(h2x2);
+    float3x3 h3x3;
+    out_half3x3(h3x3);
+    float4x4 h4x4;
+    out_half4x4(h4x4);
+    out_half3(h3x3[1]);
+    out_half(h4x4[3].w);
+    _out->sk_FragColor = float4(h2x2[0][0], h3x3[0][0], h4x4[0][0], 1.0);
+    int i;
+    out_int(i);
+    int2 i2;
+    out_int2(i2);
+    int3 i3;
+    out_int3(i3);
+    int4 i4;
+    out_int4(i4);
+    out_int3(i4.xyz);
+    _out->sk_FragColor = float4(float(i), float(i2.x), float(i3.x), float(i4.x));
+    float f;
+    out_float(f);
+    float2 f2;
+    out_float2(f2);
+    float3 f3;
+    out_float3(f3);
+    float4 f4;
+    out_float4(f4);
+    out_float2(f3.xy);
+    _out->sk_FragColor = float4(f, f2.x, f3.x, f4.x);
+    float2x2 f2x2;
+    out_float2x2(f2x2);
+    float3x3 f3x3;
+    out_float3x3(f3x3);
+    float4x4 f4x4;
+    out_float4x4(f4x4);
+    out_float(f2x2[0][0]);
+    _out->sk_FragColor = float4(f2x2[0][0], f3x3[0][0], f4x4[0][0], 1.0);
+    bool b;
+    out_bool(b);
+    bool2 b2;
+    out_bool2(b2);
+    bool3 b3;
+    out_bool3(b3);
+    bool4 b4;
+    out_bool4(b4);
+    out_bool2(b4.xw);
+    _out->sk_FragColor = float4(b ? 1.0 : 0.0, b2.x ? 1.0 : 0.0, b3.x ? 1.0 : 0.0, b4.x ? 1.0 : 0.0);
+    return *_out;
+}
diff --git a/tests/sksl/shared/golden/FrExp.metal b/tests/sksl/shared/golden/FrExp.metal
index 6431593..d5277f7 100644
--- a/tests/sksl/shared/golden/FrExp.metal
+++ b/tests/sksl/shared/golden/FrExp.metal
@@ -6,12 +6,11 @@
 struct Outputs {
     float4 sk_FragColor [[color(0)]];
 };
-float frexp(float arg, thread int* exp) { return frexp(arg, *exp); }
 fragment Outputs fragmentMain(Inputs _in [[stage_in]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {
     Outputs _outputStruct;
     thread Outputs* _out = &_outputStruct;
     int exp;
-    float foo = frexp(0.5, &exp);
+    float foo = frexp(0.5, exp);
     _out->sk_FragColor = float4(float(exp));
     return *_out;
 }
diff --git a/tests/sksl/shared/golden/Functions.metal b/tests/sksl/shared/golden/Functions.metal
index 042ac36..b6d639a 100644
--- a/tests/sksl/shared/golden/Functions.metal
+++ b/tests/sksl/shared/golden/Functions.metal
@@ -9,7 +9,7 @@
 float foo(float v[2]) {
     return v[0] * v[1];
 }
-void bar(thread float* x) {
+void bar(thread float& x) {
     float y[2];
 
     y[0] = x;
@@ -20,7 +20,7 @@
     Outputs _outputStruct;
     thread Outputs* _out = &_outputStruct;
     float x = 10.0;
-    bar(&x);
+    bar(x);
     _out->sk_FragColor = float4(x);
     return *_out;
 }