xref: /aosp_15_r20/external/skia/src/sksl/ir/SkSLTernaryExpression.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2021 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLTernaryExpression.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLAnalysis.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLBuiltinTypes.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLConstantFolder.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLContext.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLErrorReporter.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLOperator.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLProgramSettings.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLBinaryExpression.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLConstructorScalarCast.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLLiteral.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLPrefixExpression.h"
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker namespace SkSL {
23*c8dee2aaSAndroid Build Coastguard Worker 
Convert(const Context & context,Position pos,std::unique_ptr<Expression> test,std::unique_ptr<Expression> ifTrue,std::unique_ptr<Expression> ifFalse)24*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<Expression> TernaryExpression::Convert(const Context& context,
25*c8dee2aaSAndroid Build Coastguard Worker                                                        Position pos,
26*c8dee2aaSAndroid Build Coastguard Worker                                                        std::unique_ptr<Expression> test,
27*c8dee2aaSAndroid Build Coastguard Worker                                                        std::unique_ptr<Expression> ifTrue,
28*c8dee2aaSAndroid Build Coastguard Worker                                                        std::unique_ptr<Expression> ifFalse) {
29*c8dee2aaSAndroid Build Coastguard Worker     test = context.fTypes.fBool->coerceExpression(std::move(test), context);
30*c8dee2aaSAndroid Build Coastguard Worker     if (!test || !ifTrue || !ifFalse) {
31*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
32*c8dee2aaSAndroid Build Coastguard Worker     }
33*c8dee2aaSAndroid Build Coastguard Worker     if (ifTrue->type().componentType().isOpaque()) {
34*c8dee2aaSAndroid Build Coastguard Worker         context.fErrors->error(pos, "ternary expression of opaque type '" +
35*c8dee2aaSAndroid Build Coastguard Worker                                     ifTrue->type().displayName() + "' is not allowed");
36*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
37*c8dee2aaSAndroid Build Coastguard Worker     }
38*c8dee2aaSAndroid Build Coastguard Worker     const Type* trueType;
39*c8dee2aaSAndroid Build Coastguard Worker     const Type* falseType;
40*c8dee2aaSAndroid Build Coastguard Worker     const Type* resultType;
41*c8dee2aaSAndroid Build Coastguard Worker     Operator equalityOp(Operator::Kind::EQEQ);
42*c8dee2aaSAndroid Build Coastguard Worker     if (!equalityOp.determineBinaryType(context, ifTrue->type(), ifFalse->type(),
43*c8dee2aaSAndroid Build Coastguard Worker                                         &trueType, &falseType, &resultType) ||
44*c8dee2aaSAndroid Build Coastguard Worker         !trueType->matches(*falseType)) {
45*c8dee2aaSAndroid Build Coastguard Worker         Position errorPos = ifTrue->fPosition.rangeThrough(ifFalse->fPosition);
46*c8dee2aaSAndroid Build Coastguard Worker         if (ifTrue->type().isVoid()) {
47*c8dee2aaSAndroid Build Coastguard Worker             context.fErrors->error(errorPos, "ternary expression of type 'void' is not allowed");
48*c8dee2aaSAndroid Build Coastguard Worker         } else {
49*c8dee2aaSAndroid Build Coastguard Worker             context.fErrors->error(errorPos, "ternary operator result mismatch: '" +
50*c8dee2aaSAndroid Build Coastguard Worker                                              ifTrue->type().displayName() + "', '" +
51*c8dee2aaSAndroid Build Coastguard Worker                                              ifFalse->type().displayName() + "'");
52*c8dee2aaSAndroid Build Coastguard Worker         }
53*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
54*c8dee2aaSAndroid Build Coastguard Worker     }
55*c8dee2aaSAndroid Build Coastguard Worker     if (trueType->isOrContainsArray()) {
56*c8dee2aaSAndroid Build Coastguard Worker         context.fErrors->error(pos, "ternary operator result may not be an array (or struct "
57*c8dee2aaSAndroid Build Coastguard Worker                                     "containing an array)");
58*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
59*c8dee2aaSAndroid Build Coastguard Worker     }
60*c8dee2aaSAndroid Build Coastguard Worker     ifTrue = trueType->coerceExpression(std::move(ifTrue), context);
61*c8dee2aaSAndroid Build Coastguard Worker     if (!ifTrue) {
62*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
63*c8dee2aaSAndroid Build Coastguard Worker     }
64*c8dee2aaSAndroid Build Coastguard Worker     ifFalse = falseType->coerceExpression(std::move(ifFalse), context);
65*c8dee2aaSAndroid Build Coastguard Worker     if (!ifFalse) {
66*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
67*c8dee2aaSAndroid Build Coastguard Worker     }
68*c8dee2aaSAndroid Build Coastguard Worker     return TernaryExpression::Make(context, pos, std::move(test), std::move(ifTrue),
69*c8dee2aaSAndroid Build Coastguard Worker                                    std::move(ifFalse));
70*c8dee2aaSAndroid Build Coastguard Worker }
71*c8dee2aaSAndroid Build Coastguard Worker 
Make(const Context & context,Position pos,std::unique_ptr<Expression> test,std::unique_ptr<Expression> ifTrue,std::unique_ptr<Expression> ifFalse)72*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<Expression> TernaryExpression::Make(const Context& context,
73*c8dee2aaSAndroid Build Coastguard Worker                                                     Position pos,
74*c8dee2aaSAndroid Build Coastguard Worker                                                     std::unique_ptr<Expression> test,
75*c8dee2aaSAndroid Build Coastguard Worker                                                     std::unique_ptr<Expression> ifTrue,
76*c8dee2aaSAndroid Build Coastguard Worker                                                     std::unique_ptr<Expression> ifFalse) {
77*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(ifTrue->type().matches(ifFalse->type()));
78*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!ifTrue->type().componentType().isOpaque());
79*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(!context.fConfig->strictES2Mode() || !ifTrue->type().isOrContainsArray());
80*c8dee2aaSAndroid Build Coastguard Worker 
81*c8dee2aaSAndroid Build Coastguard Worker     const Expression* testExpr = ConstantFolder::GetConstantValueForVariable(*test);
82*c8dee2aaSAndroid Build Coastguard Worker     if (testExpr->isBoolLiteral()) {
83*c8dee2aaSAndroid Build Coastguard Worker         // static boolean test, just return one of the branches
84*c8dee2aaSAndroid Build Coastguard Worker         if (testExpr->as<Literal>().boolValue()) {
85*c8dee2aaSAndroid Build Coastguard Worker             ifTrue->fPosition = pos;
86*c8dee2aaSAndroid Build Coastguard Worker             return ifTrue;
87*c8dee2aaSAndroid Build Coastguard Worker         } else {
88*c8dee2aaSAndroid Build Coastguard Worker             ifFalse->fPosition = pos;
89*c8dee2aaSAndroid Build Coastguard Worker             return ifFalse;
90*c8dee2aaSAndroid Build Coastguard Worker         }
91*c8dee2aaSAndroid Build Coastguard Worker     }
92*c8dee2aaSAndroid Build Coastguard Worker 
93*c8dee2aaSAndroid Build Coastguard Worker     if (context.fConfig->fSettings.fOptimize) {
94*c8dee2aaSAndroid Build Coastguard Worker         const Expression* ifTrueExpr  = ConstantFolder::GetConstantValueForVariable(*ifTrue);
95*c8dee2aaSAndroid Build Coastguard Worker         const Expression* ifFalseExpr = ConstantFolder::GetConstantValueForVariable(*ifFalse);
96*c8dee2aaSAndroid Build Coastguard Worker 
97*c8dee2aaSAndroid Build Coastguard Worker         // A ternary with matching true- and false-cases does not need to branch.
98*c8dee2aaSAndroid Build Coastguard Worker         if (Analysis::IsSameExpressionTree(*ifTrueExpr, *ifFalseExpr)) {
99*c8dee2aaSAndroid Build Coastguard Worker             // If `test` has no side-effects, we can eliminate it too, and just return `ifTrue`.
100*c8dee2aaSAndroid Build Coastguard Worker             if (!Analysis::HasSideEffects(*test)) {
101*c8dee2aaSAndroid Build Coastguard Worker                 ifTrue->fPosition = pos;
102*c8dee2aaSAndroid Build Coastguard Worker                 return ifTrue;
103*c8dee2aaSAndroid Build Coastguard Worker             }
104*c8dee2aaSAndroid Build Coastguard Worker             // Return a comma-expression containing `(test, ifTrue)`.
105*c8dee2aaSAndroid Build Coastguard Worker             return BinaryExpression::Make(context, pos, std::move(test),
106*c8dee2aaSAndroid Build Coastguard Worker                                           Operator::Kind::COMMA, std::move(ifTrue));
107*c8dee2aaSAndroid Build Coastguard Worker         }
108*c8dee2aaSAndroid Build Coastguard Worker 
109*c8dee2aaSAndroid Build Coastguard Worker         // A ternary of the form `test ? expr : false` can be simplified to `test && expr`.
110*c8dee2aaSAndroid Build Coastguard Worker         if (ifFalseExpr->isBoolLiteral() && !ifFalseExpr->as<Literal>().boolValue()) {
111*c8dee2aaSAndroid Build Coastguard Worker             return BinaryExpression::Make(context, pos, std::move(test),
112*c8dee2aaSAndroid Build Coastguard Worker                                           Operator::Kind::LOGICALAND, std::move(ifTrue));
113*c8dee2aaSAndroid Build Coastguard Worker         }
114*c8dee2aaSAndroid Build Coastguard Worker 
115*c8dee2aaSAndroid Build Coastguard Worker         // A ternary of the form `test ? true : expr` can be simplified to `test || expr`.
116*c8dee2aaSAndroid Build Coastguard Worker         if (ifTrueExpr->isBoolLiteral() && ifTrueExpr->as<Literal>().boolValue()) {
117*c8dee2aaSAndroid Build Coastguard Worker             return BinaryExpression::Make(context, pos, std::move(test),
118*c8dee2aaSAndroid Build Coastguard Worker                                           Operator::Kind::LOGICALOR, std::move(ifFalse));
119*c8dee2aaSAndroid Build Coastguard Worker         }
120*c8dee2aaSAndroid Build Coastguard Worker 
121*c8dee2aaSAndroid Build Coastguard Worker         // A ternary of the form `test ? false : true` can be simplified to `!test`.
122*c8dee2aaSAndroid Build Coastguard Worker         if (ifTrueExpr->isBoolLiteral() && !ifTrueExpr->as<Literal>().boolValue() &&
123*c8dee2aaSAndroid Build Coastguard Worker             ifFalseExpr->isBoolLiteral() && ifFalseExpr->as<Literal>().boolValue()) {
124*c8dee2aaSAndroid Build Coastguard Worker             return PrefixExpression::Make(context, pos, Operator::Kind::LOGICALNOT,
125*c8dee2aaSAndroid Build Coastguard Worker                                           std::move(test));
126*c8dee2aaSAndroid Build Coastguard Worker         }
127*c8dee2aaSAndroid Build Coastguard Worker 
128*c8dee2aaSAndroid Build Coastguard Worker         // A ternary of the form `test ? 1 : 0` can be simplified to `cast(test)`.
129*c8dee2aaSAndroid Build Coastguard Worker         if (ifTrueExpr->is<Literal>() && ifTrueExpr->as<Literal>().value() == 1.0 &&
130*c8dee2aaSAndroid Build Coastguard Worker             ifFalseExpr->is<Literal>() && ifFalseExpr->as<Literal>().value() == 0.0) {
131*c8dee2aaSAndroid Build Coastguard Worker             return ConstructorScalarCast::Make(context, pos, ifTrue->type(), std::move(test));
132*c8dee2aaSAndroid Build Coastguard Worker         }
133*c8dee2aaSAndroid Build Coastguard Worker     }
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker     return std::make_unique<TernaryExpression>(pos, std::move(test), std::move(ifTrue),
136*c8dee2aaSAndroid Build Coastguard Worker                                                std::move(ifFalse));
137*c8dee2aaSAndroid Build Coastguard Worker }
138*c8dee2aaSAndroid Build Coastguard Worker 
description(OperatorPrecedence parentPrecedence) const139*c8dee2aaSAndroid Build Coastguard Worker std::string TernaryExpression::description(OperatorPrecedence parentPrecedence) const {
140*c8dee2aaSAndroid Build Coastguard Worker     bool needsParens = (OperatorPrecedence::kTernary >= parentPrecedence);
141*c8dee2aaSAndroid Build Coastguard Worker     return std::string(needsParens ? "(" : "") +
142*c8dee2aaSAndroid Build Coastguard Worker            this->test()->description(OperatorPrecedence::kTernary) + " ? " +
143*c8dee2aaSAndroid Build Coastguard Worker            this->ifTrue()->description(OperatorPrecedence::kTernary) + " : " +
144*c8dee2aaSAndroid Build Coastguard Worker            this->ifFalse()->description(OperatorPrecedence::kTernary) +
145*c8dee2aaSAndroid Build Coastguard Worker            std::string(needsParens ? ")" : "");
146*c8dee2aaSAndroid Build Coastguard Worker }
147*c8dee2aaSAndroid Build Coastguard Worker 
148*c8dee2aaSAndroid Build Coastguard Worker }  // namespace SkSL
149