Implement struct == and != on SPIR-V.

OpenGL ES2 allows structs to be compared: http://screen/6KnX4ZfkdLtqDWv
This already worked in GLSL, Metal and SkVM.

Change-Id: Iaf7029c0c1ea9d447348c8280a2788f0d36befad
Bug: skia:11846
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/393598
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
This commit is contained in:
John Stiles 2021-04-08 11:44:53 -04:00 committed by Skia Commit-Bot
parent ca1137b1d1
commit bc5c2a0ab9
6 changed files with 316 additions and 73 deletions

View File

@ -4,6 +4,8 @@ uniform half4 colorRed, colorGreen;
struct S { float x; int y; };
struct Nested { S a, b; };
S returns_a_struct() {
S s;
s.x = 1;
@ -25,6 +27,16 @@ half4 main() {
float x = accepts_a_struct(s);
modifies_a_struct(s);
bool valid = (x == 3) && (s.x == 2) && (s.y == 3);
S expected;
expected.x = 2;
expected.y = 3;
Nested n1, n2, n3;
n1.a = n1.b = returns_a_struct();
n3 = n2 = n1;
modifies_a_struct(n3.b);
bool valid = (x == 3) && (s.x == 2) && (s.y == 3) &&
(s == expected) && (s != returns_a_struct()) && (n1 == n2) && (n1 != n3);
return valid ? colorGreen : colorRed;
}

View File

@ -2394,6 +2394,9 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual,
SpvOpIEqual, SpvOpAll, SpvOpLogicalAnd, out);
}
if (operandType->isStruct()) {
return this->writeStructComparison(*operandType, lhs, op, rhs, out);
}
SkASSERT(resultType.isBoolean());
const Type* tmpType;
if (operandType->isVector()) {
@ -2413,6 +2416,9 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdNotEqual,
SpvOpINotEqual, SpvOpAny, SpvOpLogicalOr, out);
}
if (operandType->isStruct()) {
return this->writeStructComparison(*operandType, lhs, op, rhs, out);
}
[[fallthrough]];
case Token::Kind::TK_LOGICALXOR:
SkASSERT(resultType.isBoolean());
@ -2503,21 +2509,85 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
}
}
SpvId SPIRVCodeGenerator::writeStructComparison(const Type& structType, SpvId lhs, Operator op,
SpvId rhs, OutputStream& out) {
// The inputs must be structs containing fields, and the op must be == or !=.
SkASSERT(structType.isStruct());
SkASSERT(op.kind() == Token::Kind::TK_EQEQ || op.kind() == Token::Kind::TK_NEQ);
const std::vector<Type::Field>& fields = structType.fields();
SkASSERT(!fields.empty());
// Synthesize equality checks for each field in the struct.
const Type& boolType = *fContext.fTypes.fBool;
SpvId boolTypeId = this->getType(boolType);
SpvId allComparisons = (SpvId)-1;
for (int index = 0; index < (int)fields.size(); ++index) {
// Get the left and right versions of this field.
const Type& fieldType = *fields[index].fType;
SpvId fieldTypeId = this->getType(fieldType);
SpvId fieldL = this->nextId(&fieldType);
this->writeInstruction(SpvOpCompositeExtract, fieldTypeId, fieldL, lhs, index, out);
SpvId fieldR = this->nextId(&fieldType);
this->writeInstruction(SpvOpCompositeExtract, fieldTypeId, fieldR, rhs, index, out);
// Use `writeBinaryExpression` with the requested == or != operator on these fields.
SpvId comparison = this->writeBinaryExpression(fieldType, fieldL, op, fieldType, fieldR,
boolType, out);
// If this is the first field, we don't need to merge comparison results with anything.
if (allComparisons == (SpvId)-1) {
allComparisons = comparison;
continue;
}
// Use LogicalAnd or LogicalOr to combine the comparison with all the other comparisons.
SpvId logicalOp = this->nextId(&boolType);
switch (op.kind()) {
case Token::Kind::TK_EQEQ:
this->writeInstruction(SpvOpLogicalAnd, boolTypeId, logicalOp,
comparison, allComparisons, out);
break;
case Token::Kind::TK_NEQ:
this->writeInstruction(SpvOpLogicalOr, boolTypeId, logicalOp,
comparison, allComparisons, out);
break;
default:
return (SpvId)-1;
}
allComparisons = logicalOp;
}
return allComparisons;
}
static float division_by_literal_value(Operator op, const Expression& right) {
// If this is a division by a literal value, returns that literal value. Otherwise, returns 0.
if (op.kind() == Token::Kind::TK_SLASH && right.is<FloatLiteral>()) {
float rhsValue = right.as<FloatLiteral>().value();
if (std::isfinite(rhsValue)) {
return rhsValue;
}
}
return 0.0f;
}
SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) {
const Expression* left = b.left().get();
const Expression* right = b.right().get();
Operator op = b.getOperator();
// handle cases where we don't necessarily evaluate both LHS and RHS
switch (op.kind()) {
case Token::Kind::TK_EQ: {
// Handles assignment.
SpvId rhs = this->writeExpression(*right, out);
this->getLValue(*left, out)->store(rhs, out);
return rhs;
}
case Token::Kind::TK_LOGICALAND:
return this->writeLogicalAnd(b, out);
// Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
return this->writeLogicalAnd(*b.left(), *b.right(), out);
case Token::Kind::TK_LOGICALOR:
return this->writeLogicalOr(b, out);
// Handles short-circuiting; we don't necessarily evaluate both LHS and RHS.
return this->writeLogicalOr(*b.left(), *b.right(), out);
default:
break;
}
@ -2532,20 +2602,17 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, Outpu
lhs = this->writeExpression(*left, out);
}
SpvId rhs = (SpvId)-1;
if (op.kind() == Token::Kind::TK_SLASH && right->is<FloatLiteral>()) {
float rhsValue = right->as<FloatLiteral>().value();
if (std::isfinite(rhsValue) && rhsValue != 0.0f) {
// Rewrite floating-point division by a literal into multiplication by the reciprocal.
// This converts `expr / 2` into `expr * 0.5`
// This improves codegen, especially for certain types of divides (e.g. vector/scalar).
op = Operator(Token::Kind::TK_STAR);
FloatLiteral reciprocal{right->fOffset, 1.0f / rhsValue, &right->type()};
rhs = this->writeExpression(reciprocal, out);
}
}
if (rhs == (SpvId)-1) {
SpvId rhs;
float rhsValue = division_by_literal_value(op, *right);
if (rhsValue != 0.0f) {
// Rewrite floating-point division by a literal into multiplication by the reciprocal.
// This converts `expr / 2` into `expr * 0.5`
// This improves codegen, especially for certain types of divides (e.g. vector/scalar).
op = Operator(Token::Kind::TK_STAR);
FloatLiteral reciprocal{right->fOffset, 1.0f / rhsValue, &right->type()};
rhs = this->writeExpression(reciprocal, out);
} else {
// Write the right-hand side expression normally.
rhs = this->writeExpression(*right, out);
}
@ -2557,18 +2624,18 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, Outpu
return result;
}
SpvId SPIRVCodeGenerator::writeLogicalAnd(const BinaryExpression& a, OutputStream& out) {
SkASSERT(a.getOperator().kind() == Token::Kind::TK_LOGICALAND);
SpvId SPIRVCodeGenerator::writeLogicalAnd(const Expression& left, const Expression& right,
OutputStream& out) {
BoolLiteral falseLiteral(/*offset=*/-1, /*value=*/false, fContext.fTypes.fBool.get());
SpvId falseConstant = this->writeBoolLiteral(falseLiteral);
SpvId lhs = this->writeExpression(*a.left(), out);
SpvId lhs = this->writeExpression(left, out);
SpvId rhsLabel = this->nextId(nullptr);
SpvId end = this->nextId(nullptr);
SpvId lhsBlock = fCurrentBlock;
this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out);
this->writeLabel(rhsLabel, out);
SpvId rhs = this->writeExpression(*a.right(), out);
SpvId rhs = this->writeExpression(right, out);
SpvId rhsBlock = fCurrentBlock;
this->writeInstruction(SpvOpBranch, end, out);
this->writeLabel(end, out);
@ -2578,18 +2645,18 @@ SpvId SPIRVCodeGenerator::writeLogicalAnd(const BinaryExpression& a, OutputStrea
return result;
}
SpvId SPIRVCodeGenerator::writeLogicalOr(const BinaryExpression& o, OutputStream& out) {
SkASSERT(o.getOperator().kind() == Token::Kind::TK_LOGICALOR);
SpvId SPIRVCodeGenerator::writeLogicalOr(const Expression& left, const Expression& right,
OutputStream& out) {
BoolLiteral trueLiteral(/*offset=*/-1, /*value=*/true, fContext.fTypes.fBool.get());
SpvId trueConstant = this->writeBoolLiteral(trueLiteral);
SpvId lhs = this->writeExpression(*o.left(), out);
SpvId lhs = this->writeExpression(left, out);
SpvId rhsLabel = this->nextId(nullptr);
SpvId end = this->nextId(nullptr);
SpvId lhsBlock = fCurrentBlock;
this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out);
this->writeLabel(rhsLabel, out);
SpvId rhs = this->writeExpression(*o.right(), out);
SpvId rhs = this->writeExpression(right, out);
SpvId rhsBlock = fCurrentBlock;
this->writeInstruction(SpvOpBranch, end, out);
this->writeLabel(end, out);

View File

@ -324,6 +324,9 @@ private:
SpvOp_ intOperator, SpvOp_ vectorMergeOperator,
SpvOp_ mergeOperator, OutputStream& out);
SpvId writeStructComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
OutputStream& out);
SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs,
SpvOp_ floatOperator, SpvOp_ intOperator,
OutputStream& out);
@ -347,9 +350,9 @@ private:
SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out);
SpvId writeLogicalAnd(const BinaryExpression& b, OutputStream& out);
SpvId writeLogicalAnd(const Expression& left, const Expression& right, OutputStream& out);
SpvId writeLogicalOr(const BinaryExpression& o, OutputStream& out);
SpvId writeLogicalOr(const Expression& left, const Expression& right, OutputStream& out);
SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out);

View File

@ -19,6 +19,13 @@ OpName %modifies_a_struct_vS "modifies_a_struct_vS"
OpName %main "main"
OpName %s_0 "s"
OpName %x "x"
OpName %expected "expected"
OpName %Nested "Nested"
OpMemberName %Nested 0 "a"
OpMemberName %Nested 1 "b"
OpName %n1 "n1"
OpName %n2 "n2"
OpName %n3 "n3"
OpName %valid "valid"
OpDecorate %sk_FragColor RelaxedPrecision
OpDecorate %sk_FragColor Location 0
@ -35,10 +42,23 @@ OpMemberDecorate %S 0 Offset 0
OpMemberDecorate %S 1 Offset 4
OpDecorate %35 RelaxedPrecision
OpDecorate %59 RelaxedPrecision
OpDecorate %83 RelaxedPrecision
OpDecorate %91 RelaxedPrecision
OpDecorate %93 RelaxedPrecision
OpDecorate %94 RelaxedPrecision
OpMemberDecorate %Nested 0 Offset 0
OpMemberDecorate %Nested 0 RelaxedPrecision
OpMemberDecorate %Nested 1 Offset 16
OpMemberDecorate %Nested 1 RelaxedPrecision
OpDecorate %76 RelaxedPrecision
OpDecorate %78 RelaxedPrecision
OpDecorate %102 RelaxedPrecision
OpDecorate %103 RelaxedPrecision
OpDecorate %114 RelaxedPrecision
OpDecorate %126 RelaxedPrecision
OpDecorate %127 RelaxedPrecision
OpDecorate %150 RelaxedPrecision
OpDecorate %151 RelaxedPrecision
OpDecorate %172 RelaxedPrecision
OpDecorate %180 RelaxedPrecision
OpDecorate %182 RelaxedPrecision
OpDecorate %183 RelaxedPrecision
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
@ -64,11 +84,13 @@ OpDecorate %94 RelaxedPrecision
%36 = OpTypeFunction %float %_ptr_Function_S
%45 = OpTypeFunction %void %_ptr_Function_S
%54 = OpTypeFunction %v4float
%float_2 = OpConstant %float 2
%int_3 = OpConstant %int 3
%Nested = OpTypeStruct %S %S
%_ptr_Function_Nested = OpTypePointer Function %Nested
%_ptr_Function_bool = OpTypePointer Function %bool
%false = OpConstantFalse %bool
%float_3 = OpConstant %float 3
%float_2 = OpConstant %float 2
%int_3 = OpConstant %int 3
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
%_entrypoint_v = OpFunction %void None %18
@ -116,8 +138,13 @@ OpFunctionEnd
%s_0 = OpVariable %_ptr_Function_S Function
%x = OpVariable %_ptr_Function_float Function
%60 = OpVariable %_ptr_Function_S Function
%expected = OpVariable %_ptr_Function_S Function
%n1 = OpVariable %_ptr_Function_Nested Function
%n2 = OpVariable %_ptr_Function_Nested Function
%n3 = OpVariable %_ptr_Function_Nested Function
%79 = OpVariable %_ptr_Function_S Function
%valid = OpVariable %_ptr_Function_bool Function
%84 = OpVariable %_ptr_Function_v4float Function
%173 = OpVariable %_ptr_Function_v4float Function
%57 = OpFunctionCall %S %returns_a_struct_S
OpStore %s_0 %57
%59 = OpLoad %S %s_0
@ -125,41 +152,143 @@ OpStore %60 %59
%61 = OpFunctionCall %float %accepts_a_struct_fS %60
OpStore %x %61
%62 = OpFunctionCall %void %modifies_a_struct_vS %s_0
%66 = OpLoad %float %x
%68 = OpFOrdEqual %bool %66 %float_3
OpSelectionMerge %70 None
OpBranchConditional %68 %69 %70
%69 = OpLabel
%71 = OpAccessChain %_ptr_Function_float %s_0 %int_0
%72 = OpLoad %float %71
%74 = OpFOrdEqual %bool %72 %float_2
OpBranch %70
%70 = OpLabel
%75 = OpPhi %bool %false %55 %74 %69
OpSelectionMerge %77 None
OpBranchConditional %75 %76 %77
%76 = OpLabel
%78 = OpAccessChain %_ptr_Function_int %s_0 %int_1
%79 = OpLoad %int %78
%81 = OpIEqual %bool %79 %int_3
OpBranch %77
%77 = OpLabel
%82 = OpPhi %bool %false %70 %81 %76
OpStore %valid %82
%83 = OpLoad %bool %valid
OpSelectionMerge %88 None
OpBranchConditional %83 %86 %87
%86 = OpLabel
%89 = OpAccessChain %_ptr_Uniform_v4float %13 %int_1
%91 = OpLoad %v4float %89
OpStore %84 %91
OpBranch %88
%87 = OpLabel
%92 = OpAccessChain %_ptr_Uniform_v4float %13 %int_0
%93 = OpLoad %v4float %92
OpStore %84 %93
OpBranch %88
%65 = OpAccessChain %_ptr_Function_float %expected %int_0
OpStore %65 %float_2
%67 = OpAccessChain %_ptr_Function_int %expected %int_1
OpStore %67 %int_3
%73 = OpFunctionCall %S %returns_a_struct_S
%74 = OpAccessChain %_ptr_Function_S %n1 %int_1
OpStore %74 %73
%75 = OpAccessChain %_ptr_Function_S %n1 %int_0
OpStore %75 %73
%76 = OpLoad %Nested %n1
OpStore %n2 %76
OpStore %n3 %76
%77 = OpAccessChain %_ptr_Function_S %n3 %int_1
%78 = OpLoad %S %77
OpStore %79 %78
%80 = OpFunctionCall %void %modifies_a_struct_vS %79
%81 = OpLoad %S %79
OpStore %77 %81
%85 = OpLoad %float %x
%87 = OpFOrdEqual %bool %85 %float_3
OpSelectionMerge %89 None
OpBranchConditional %87 %88 %89
%88 = OpLabel
%94 = OpLoad %v4float %84
OpReturnValue %94
%90 = OpAccessChain %_ptr_Function_float %s_0 %int_0
%91 = OpLoad %float %90
%92 = OpFOrdEqual %bool %91 %float_2
OpBranch %89
%89 = OpLabel
%93 = OpPhi %bool %false %55 %92 %88
OpSelectionMerge %95 None
OpBranchConditional %93 %94 %95
%94 = OpLabel
%96 = OpAccessChain %_ptr_Function_int %s_0 %int_1
%97 = OpLoad %int %96
%98 = OpIEqual %bool %97 %int_3
OpBranch %95
%95 = OpLabel
%99 = OpPhi %bool %false %89 %98 %94
OpSelectionMerge %101 None
OpBranchConditional %99 %100 %101
%100 = OpLabel
%102 = OpLoad %S %s_0
%103 = OpLoad %S %expected
%104 = OpCompositeExtract %float %102 0
%105 = OpCompositeExtract %float %103 0
%106 = OpFOrdEqual %bool %104 %105
%107 = OpCompositeExtract %int %102 1
%108 = OpCompositeExtract %int %103 1
%109 = OpIEqual %bool %107 %108
%110 = OpLogicalAnd %bool %109 %106
OpBranch %101
%101 = OpLabel
%111 = OpPhi %bool %false %95 %110 %100
OpSelectionMerge %113 None
OpBranchConditional %111 %112 %113
%112 = OpLabel
%114 = OpLoad %S %s_0
%115 = OpFunctionCall %S %returns_a_struct_S
%116 = OpCompositeExtract %float %114 0
%117 = OpCompositeExtract %float %115 0
%118 = OpFOrdNotEqual %bool %116 %117
%119 = OpCompositeExtract %int %114 1
%120 = OpCompositeExtract %int %115 1
%121 = OpINotEqual %bool %119 %120
%122 = OpLogicalOr %bool %121 %118
OpBranch %113
%113 = OpLabel
%123 = OpPhi %bool %false %101 %122 %112
OpSelectionMerge %125 None
OpBranchConditional %123 %124 %125
%124 = OpLabel
%126 = OpLoad %Nested %n1
%127 = OpLoad %Nested %n2
%128 = OpCompositeExtract %S %126 0
%129 = OpCompositeExtract %S %127 0
%130 = OpCompositeExtract %float %128 0
%131 = OpCompositeExtract %float %129 0
%132 = OpFOrdEqual %bool %130 %131
%133 = OpCompositeExtract %int %128 1
%134 = OpCompositeExtract %int %129 1
%135 = OpIEqual %bool %133 %134
%136 = OpLogicalAnd %bool %135 %132
%137 = OpCompositeExtract %S %126 1
%138 = OpCompositeExtract %S %127 1
%139 = OpCompositeExtract %float %137 0
%140 = OpCompositeExtract %float %138 0
%141 = OpFOrdEqual %bool %139 %140
%142 = OpCompositeExtract %int %137 1
%143 = OpCompositeExtract %int %138 1
%144 = OpIEqual %bool %142 %143
%145 = OpLogicalAnd %bool %144 %141
%146 = OpLogicalAnd %bool %145 %136
OpBranch %125
%125 = OpLabel
%147 = OpPhi %bool %false %113 %146 %124
OpSelectionMerge %149 None
OpBranchConditional %147 %148 %149
%148 = OpLabel
%150 = OpLoad %Nested %n1
%151 = OpLoad %Nested %n3
%152 = OpCompositeExtract %S %150 0
%153 = OpCompositeExtract %S %151 0
%154 = OpCompositeExtract %float %152 0
%155 = OpCompositeExtract %float %153 0
%156 = OpFOrdNotEqual %bool %154 %155
%157 = OpCompositeExtract %int %152 1
%158 = OpCompositeExtract %int %153 1
%159 = OpINotEqual %bool %157 %158
%160 = OpLogicalOr %bool %159 %156
%161 = OpCompositeExtract %S %150 1
%162 = OpCompositeExtract %S %151 1
%163 = OpCompositeExtract %float %161 0
%164 = OpCompositeExtract %float %162 0
%165 = OpFOrdNotEqual %bool %163 %164
%166 = OpCompositeExtract %int %161 1
%167 = OpCompositeExtract %int %162 1
%168 = OpINotEqual %bool %166 %167
%169 = OpLogicalOr %bool %168 %165
%170 = OpLogicalOr %bool %169 %160
OpBranch %149
%149 = OpLabel
%171 = OpPhi %bool %false %125 %170 %148
OpStore %valid %171
%172 = OpLoad %bool %valid
OpSelectionMerge %177 None
OpBranchConditional %172 %175 %176
%175 = OpLabel
%178 = OpAccessChain %_ptr_Uniform_v4float %13 %int_1
%180 = OpLoad %v4float %178
OpStore %173 %180
OpBranch %177
%176 = OpLabel
%181 = OpAccessChain %_ptr_Uniform_v4float %13 %int_0
%182 = OpLoad %v4float %181
OpStore %173 %182
OpBranch %177
%177 = OpLabel
%183 = OpLoad %v4float %173
OpReturnValue %183
OpFunctionEnd

View File

@ -6,6 +6,10 @@ struct S {
float x;
int y;
};
struct Nested {
S a;
S b;
};
S returns_a_struct_S() {
S s;
s.x = 1.0;
@ -23,6 +27,15 @@ vec4 main() {
S s = returns_a_struct_S();
float x = accepts_a_struct_fS(s);
modifies_a_struct_vS(s);
bool valid = (x == 3.0 && s.x == 2.0) && s.y == 3;
S expected;
expected.x = 2.0;
expected.y = 3;
Nested n1;
Nested n2;
Nested n3;
n1.a = (n1.b = returns_a_struct_S());
n3 = (n2 = n1);
modifies_a_struct_vS(n3.b);
bool valid = (((((x == 3.0 && s.x == 2.0) && s.y == 3) && s == expected) && s != returns_a_struct_S()) && n1 == n2) && n1 != n3;
return valid ? colorGreen : colorRed;
}

View File

@ -5,6 +5,10 @@ struct S {
float x;
int y;
};
struct Nested {
S a;
S b;
};
struct Uniforms {
float4 colorRed;
float4 colorGreen;
@ -20,6 +24,12 @@ void _skOutParamHelper0_modifies_a_struct_vS(thread S& s) {
modifies_a_struct_vS(_var0);
s = _var0;
}
void modifies_a_struct_vS(thread S& s);
void _skOutParamHelper1_modifies_a_struct_vS(thread Nested& n3) {
S _var0 = n3.b;
modifies_a_struct_vS(_var0);
n3.b = _var0;
}
S returns_a_struct_S() {
S s;
@ -40,7 +50,16 @@ fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant Uniforms& _unifo
S s = returns_a_struct_S();
float x = accepts_a_struct_fS(s);
_skOutParamHelper0_modifies_a_struct_vS(s);
bool valid = (x == 3.0 && s.x == 2.0) && s.y == 3;
S expected;
expected.x = 2.0;
expected.y = 3;
Nested n1;
Nested n2;
Nested n3;
n1.a = (n1.b = returns_a_struct_S());
n3 = (n2 = n1);
_skOutParamHelper1_modifies_a_struct_vS(n3);
bool valid = (((((x == 3.0 && s.x == 2.0) && s.y == 3) && s == expected) && s != returns_a_struct_S()) && n1 == n2) && n1 != n3;
_out.sk_FragColor = valid ? _uniforms.colorGreen : _uniforms.colorRed;
return _out;
}