• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/sksl/SkSLConstantFolder.h"
9 
10 #include <limits>
11 
12 #include "include/sksl/SkSLErrorReporter.h"
13 #include "src/sksl/SkSLAnalysis.h"
14 #include "src/sksl/SkSLContext.h"
15 #include "src/sksl/SkSLProgramSettings.h"
16 #include "src/sksl/ir/SkSLBinaryExpression.h"
17 #include "src/sksl/ir/SkSLConstructor.h"
18 #include "src/sksl/ir/SkSLConstructorCompound.h"
19 #include "src/sksl/ir/SkSLConstructorSplat.h"
20 #include "src/sksl/ir/SkSLExpression.h"
21 #include "src/sksl/ir/SkSLLiteral.h"
22 #include "src/sksl/ir/SkSLPrefixExpression.h"
23 #include "src/sksl/ir/SkSLType.h"
24 #include "src/sksl/ir/SkSLVariable.h"
25 #include "src/sksl/ir/SkSLVariableReference.h"
26 
27 namespace SkSL {
28 
is_vec_or_mat(const Type & type)29 static bool is_vec_or_mat(const Type& type) {
30     switch (type.typeKind()) {
31         case Type::TypeKind::kMatrix:
32         case Type::TypeKind::kVector:
33             return true;
34 
35         default:
36             return false;
37     }
38 }
39 
eliminate_no_op_boolean(const Expression & left,Operator op,const Expression & right)40 static std::unique_ptr<Expression> eliminate_no_op_boolean(const Expression& left,
41                                                            Operator op,
42                                                            const Expression& right) {
43     bool rightVal = right.as<Literal>().boolValue();
44 
45     // Detect no-op Boolean expressions and optimize them away.
46     if ((op.kind() == Token::Kind::TK_LOGICALAND && rightVal)  ||  // (expr && true)  -> (expr)
47         (op.kind() == Token::Kind::TK_LOGICALOR  && !rightVal) ||  // (expr || false) -> (expr)
48         (op.kind() == Token::Kind::TK_LOGICALXOR && !rightVal) ||  // (expr ^^ false) -> (expr)
49         (op.kind() == Token::Kind::TK_EQEQ       && rightVal)  ||  // (expr == true)  -> (expr)
50         (op.kind() == Token::Kind::TK_NEQ        && !rightVal)) {  // (expr != false) -> (expr)
51 
52         return left.clone();
53     }
54 
55     return nullptr;
56 }
57 
short_circuit_boolean(const Expression & left,Operator op,const Expression & right)58 static std::unique_ptr<Expression> short_circuit_boolean(const Expression& left,
59                                                          Operator op,
60                                                          const Expression& right) {
61     bool leftVal = left.as<Literal>().boolValue();
62 
63     // When the literal is on the left, we can sometimes eliminate the other expression entirely.
64     if ((op.kind() == Token::Kind::TK_LOGICALAND && !leftVal) ||  // (false && expr) -> (false)
65         (op.kind() == Token::Kind::TK_LOGICALOR  && leftVal)) {   // (true  || expr) -> (true)
66 
67         return left.clone();
68     }
69 
70     // We can't eliminate the right-side expression via short-circuit, but we might still be able to
71     // simplify away a no-op expression.
72     return eliminate_no_op_boolean(right, op, left);
73 }
74 
simplify_constant_equality(const Context & context,const Expression & left,Operator op,const Expression & right)75 static std::unique_ptr<Expression> simplify_constant_equality(const Context& context,
76                                                               const Expression& left,
77                                                               Operator op,
78                                                               const Expression& right) {
79     if (op.kind() == Token::Kind::TK_EQEQ || op.kind() == Token::Kind::TK_NEQ) {
80         bool equality = (op.kind() == Token::Kind::TK_EQEQ);
81 
82         switch (left.compareConstant(right)) {
83             case Expression::ComparisonResult::kNotEqual:
84                 equality = !equality;
85                 [[fallthrough]];
86 
87             case Expression::ComparisonResult::kEqual:
88                 return Literal::MakeBool(context, left.fLine, equality);
89 
90             case Expression::ComparisonResult::kUnknown:
91                 break;
92         }
93     }
94     return nullptr;
95 }
96 
simplify_matrix_times_matrix(const Context & context,const Expression & left,const Expression & right)97 static std::unique_ptr<Expression> simplify_matrix_times_matrix(const Context& context,
98                                                                 const Expression& left,
99                                                                 const Expression& right) {
100     const Type& leftType = left.type();
101     const Type& rightType = right.type();
102 
103     SkASSERT(leftType.isMatrix());
104     SkASSERT(rightType.isMatrix());
105 
106     const Type& componentType = leftType.componentType();
107     SkASSERT(componentType.matches(rightType.componentType()));
108 
109     const int leftColumns  = leftType.columns(),
110               leftRows     = leftType.rows(),
111               rightColumns = rightType.columns(),
112               rightRows    = rightType.rows(),
113               outColumns   = rightColumns,
114               outRows      = leftRows;
115     SkASSERT(leftColumns == rightRows);
116     const Type& resultType = componentType.toCompound(context, outColumns, outRows);
117 
118     // Fetch the left matrix.
119     double leftVals[4][4];
120     for (int c = 0; c < leftColumns; ++c) {
121         for (int r = 0; r < leftRows; ++r) {
122             leftVals[c][r] = *left.getConstantValue((c * leftRows) + r);
123         }
124     }
125     // Fetch the right matrix.
126     double rightVals[4][4];
127     for (int c = 0; c < rightColumns; ++c) {
128         for (int r = 0; r < rightRows; ++r) {
129             rightVals[c][r] = *right.getConstantValue((c * rightRows) + r);
130         }
131     }
132 
133     ExpressionArray args;
134     args.reserve_back(outColumns * outRows);
135     for (int c = 0; c < outColumns; ++c) {
136         for (int r = 0; r < outRows; ++r) {
137             // Compute a dot product for this position.
138             double val = 0;
139             for (int dotIdx = 0; dotIdx < leftColumns; ++dotIdx) {
140                 val += leftVals[dotIdx][r] * rightVals[c][dotIdx];
141             }
142             args.push_back(Literal::Make(left.fLine, val, &componentType));
143         }
144     }
145 
146     return ConstructorCompound::Make(context, left.fLine, resultType, std::move(args));
147 }
148 
simplify_componentwise(const Context & context,const Expression & left,Operator op,const Expression & right)149 static std::unique_ptr<Expression> simplify_componentwise(const Context& context,
150                                                           const Expression& left,
151                                                           Operator op,
152                                                           const Expression& right) {
153     SkASSERT(is_vec_or_mat(left.type()));
154     SkASSERT(left.type().matches(right.type()));
155     const Type& type = left.type();
156 
157     // Handle equality operations: == !=
158     if (std::unique_ptr<Expression> result = simplify_constant_equality(context, left, op, right)) {
159         return result;
160     }
161 
162     // Handle floating-point arithmetic: + - * /
163     using FoldFn = double (*)(double, double);
164     FoldFn foldFn;
165     switch (op.kind()) {
166         case Token::Kind::TK_PLUS:  foldFn = +[](double a, double b) { return a + b; }; break;
167         case Token::Kind::TK_MINUS: foldFn = +[](double a, double b) { return a - b; }; break;
168         case Token::Kind::TK_STAR:  foldFn = +[](double a, double b) { return a * b; }; break;
169         case Token::Kind::TK_SLASH: foldFn = +[](double a, double b) { return a / b; }; break;
170         default:
171             return nullptr;
172     }
173 
174     const Type& componentType = type.componentType();
175     SkASSERT(componentType.isNumber());
176 
177     double minimumValue = -INFINITY, maximumValue = INFINITY;
178     if (componentType.isInteger()) {
179         minimumValue = componentType.minimumValue();
180         maximumValue = componentType.maximumValue();
181     }
182 
183     ExpressionArray args;
184     int numSlots = type.slotCount();
185     args.reserve_back(numSlots);
186     for (int i = 0; i < numSlots; i++) {
187         double value = foldFn(*left.getConstantValue(i), *right.getConstantValue(i));
188         if (value < minimumValue || value > maximumValue) {
189             return nullptr;
190         }
191 
192         args.push_back(Literal::Make(left.fLine, value, &componentType));
193     }
194     return ConstructorCompound::Make(context, left.fLine, type, std::move(args));
195 }
196 
splat_scalar(const Context & context,const Expression & scalar,const Type & type)197 static std::unique_ptr<Expression> splat_scalar(const Context& context,
198                                                 const Expression& scalar,
199                                                 const Type& type) {
200     if (type.isVector()) {
201         return ConstructorSplat::Make(context, scalar.fLine, type, scalar.clone());
202     }
203     if (type.isMatrix()) {
204         int numSlots = type.slotCount();
205         ExpressionArray splatMatrix;
206         splatMatrix.reserve_back(numSlots);
207         for (int index = 0; index < numSlots; ++index) {
208             splatMatrix.push_back(scalar.clone());
209         }
210         return ConstructorCompound::Make(context, scalar.fLine, type, std::move(splatMatrix));
211     }
212     SkDEBUGFAILF("unsupported type %s", type.description().c_str());
213     return nullptr;
214 }
215 
cast_expression(const Context & context,const Expression & expr,const Type & type)216 static std::unique_ptr<Expression> cast_expression(const Context& context,
217                                                    const Expression& expr,
218                                                    const Type& type) {
219     ExpressionArray ctorArgs;
220     ctorArgs.push_back(expr.clone());
221     return Constructor::Convert(context, expr.fLine, type, std::move(ctorArgs));
222 }
223 
GetConstantInt(const Expression & value,SKSL_INT * out)224 bool ConstantFolder::GetConstantInt(const Expression& value, SKSL_INT* out) {
225     const Expression* expr = GetConstantValueForVariable(value);
226     if (!expr->isIntLiteral()) {
227         return false;
228     }
229     *out = expr->as<Literal>().intValue();
230     return true;
231 }
232 
GetConstantValue(const Expression & value,double * out)233 bool ConstantFolder::GetConstantValue(const Expression& value, double* out) {
234     const Expression* expr = GetConstantValueForVariable(value);
235     if (!expr->is<Literal>()) {
236         return false;
237     }
238     *out = expr->as<Literal>().value();
239     return true;
240 }
241 
contains_constant_zero(const Expression & expr)242 static bool contains_constant_zero(const Expression& expr) {
243     int numSlots = expr.type().slotCount();
244     for (int index = 0; index < numSlots; ++index) {
245         std::optional<double> slotVal = expr.getConstantValue(index);
246         if (slotVal.has_value() && *slotVal == 0.0) {
247             return true;
248         }
249     }
250     return false;
251 }
252 
is_constant_value(const Expression & expr,double value)253 static bool is_constant_value(const Expression& expr, double value) {
254     int numSlots = expr.type().slotCount();
255     for (int index = 0; index < numSlots; ++index) {
256         std::optional<double> slotVal = expr.getConstantValue(index);
257         if (!slotVal.has_value() || *slotVal != value) {
258             return false;
259         }
260     }
261     return true;
262 }
263 
error_on_divide_by_zero(const Context & context,int line,Operator op,const Expression & right)264 static bool error_on_divide_by_zero(const Context& context, int line, Operator op,
265                                     const Expression& right) {
266     switch (op.kind()) {
267         case Token::Kind::TK_SLASH:
268         case Token::Kind::TK_SLASHEQ:
269         case Token::Kind::TK_PERCENT:
270         case Token::Kind::TK_PERCENTEQ:
271             if (contains_constant_zero(right)) {
272                 context.fErrors->error(line, "division by zero");
273                 return true;
274             }
275             return false;
276         default:
277             return false;
278     }
279 }
280 
GetConstantValueForVariable(const Expression & inExpr)281 const Expression* ConstantFolder::GetConstantValueForVariable(const Expression& inExpr) {
282     for (const Expression* expr = &inExpr;;) {
283         if (!expr->is<VariableReference>()) {
284             break;
285         }
286         const VariableReference& varRef = expr->as<VariableReference>();
287         if (varRef.refKind() != VariableRefKind::kRead) {
288             break;
289         }
290         const Variable& var = *varRef.variable();
291         if (!(var.modifiers().fFlags & Modifiers::kConst_Flag)) {
292             break;
293         }
294         expr = var.initialValue();
295         if (!expr) {
296             // Function parameters can be const but won't have an initial value.
297             break;
298         }
299         if (expr->isCompileTimeConstant()) {
300             return expr;
301         }
302     }
303     // We didn't find a compile-time constant at the end. Return the expression as-is.
304     return &inExpr;
305 }
306 
MakeConstantValueForVariable(std::unique_ptr<Expression> expr)307 std::unique_ptr<Expression> ConstantFolder::MakeConstantValueForVariable(
308         std::unique_ptr<Expression> expr) {
309     const Expression* constantExpr = GetConstantValueForVariable(*expr);
310     if (constantExpr != expr.get()) {
311         expr = constantExpr->clone();
312     }
313     return expr;
314 }
315 
simplify_no_op_arithmetic(const Context & context,const Expression & left,Operator op,const Expression & right,const Type & resultType)316 static std::unique_ptr<Expression> simplify_no_op_arithmetic(const Context& context,
317                                                              const Expression& left,
318                                                              Operator op,
319                                                              const Expression& right,
320                                                              const Type& resultType) {
321     switch (op.kind()) {
322         case Token::Kind::TK_PLUS:
323             if (is_constant_value(right, 0.0)) {  // x + 0
324                 return cast_expression(context, left, resultType);
325             }
326             if (is_constant_value(left, 0.0)) {   // 0 + x
327                 return cast_expression(context, right, resultType);
328             }
329             break;
330 
331         case Token::Kind::TK_STAR:
332             if (is_constant_value(right, 1.0)) {  // x * 1
333                 return cast_expression(context, left, resultType);
334             }
335             if (is_constant_value(left, 1.0)) {   // 1 * x
336                 return cast_expression(context, right, resultType);
337             }
338             if (is_constant_value(right, 0.0) && !left.hasSideEffects()) {  // x * 0
339                 return cast_expression(context, right, resultType);
340             }
341             if (is_constant_value(left, 0.0) && !right.hasSideEffects()) {  // 0 * x
342                 return cast_expression(context, left, resultType);
343             }
344             break;
345 
346         case Token::Kind::TK_MINUS:
347             if (is_constant_value(right, 0.0)) {  // x - 0
348                 return cast_expression(context, left, resultType);
349             }
350             if (is_constant_value(left, 0.0)) {   // 0 - x (to `-x`)
351                 if (std::unique_ptr<Expression> val = cast_expression(context, right, resultType)) {
352                     return PrefixExpression::Make(context, Token::Kind::TK_MINUS, std::move(val));
353                 }
354             }
355             break;
356 
357         case Token::Kind::TK_SLASH:
358             if (is_constant_value(right, 1.0)) {  // x / 1
359                 return cast_expression(context, left, resultType);
360             }
361             break;
362 
363         case Token::Kind::TK_PLUSEQ:
364         case Token::Kind::TK_MINUSEQ:
365             if (is_constant_value(right, 0.0)) {  // x += 0, x -= 0
366                 if (std::unique_ptr<Expression> var = cast_expression(context, left, resultType)) {
367                     Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead);
368                     return var;
369                 }
370             }
371             break;
372 
373         case Token::Kind::TK_STAREQ:
374         case Token::Kind::TK_SLASHEQ:
375             if (is_constant_value(right, 1.0)) {  // x *= 1, x /= 1
376                 if (std::unique_ptr<Expression> var = cast_expression(context, left, resultType)) {
377                     Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead);
378                     return var;
379                 }
380             }
381             break;
382 
383         default:
384             break;
385     }
386 
387     return nullptr;
388 }
389 
390 template <typename T>
fold_float_expression(int line,T result,const Type * resultType)391 static std::unique_ptr<Expression> fold_float_expression(int line,
392                                                          T result,
393                                                          const Type* resultType) {
394     // If constant-folding this expression would generate a NaN/infinite result, leave it as-is.
395     if constexpr (!std::is_same<T, bool>::value) {
396         if (!std::isfinite(result)) {
397             return nullptr;
398         }
399     }
400 
401     return Literal::Make(line, result, resultType);
402 }
403 
404 template <typename T>
fold_int_expression(int line,T result,const Type * resultType)405 static std::unique_ptr<Expression> fold_int_expression(int line,
406                                                        T result,
407                                                        const Type* resultType) {
408     // If constant-folding this expression would overflow the result type, leave it as-is.
409     if constexpr (!std::is_same<T, bool>::value) {
410         if (result < resultType->minimumValue() || result > resultType->maximumValue()) {
411             return nullptr;
412         }
413     }
414 
415     return Literal::Make(line, result, resultType);
416 }
417 
Simplify(const Context & context,int line,const Expression & leftExpr,Operator op,const Expression & rightExpr,const Type & resultType)418 std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
419                                                      int line,
420                                                      const Expression& leftExpr,
421                                                      Operator op,
422                                                      const Expression& rightExpr,
423                                                      const Type& resultType) {
424     // Replace constant variables with their literal values.
425     const Expression* left = GetConstantValueForVariable(leftExpr);
426     const Expression* right = GetConstantValueForVariable(rightExpr);
427 
428     // If this is the comma operator, the left side is evaluated but not otherwise used in any way.
429     // So if the left side has no side effects, it can just be eliminated entirely.
430     if (op.kind() == Token::Kind::TK_COMMA && !left->hasSideEffects()) {
431         return right->clone();
432     }
433 
434     // If this is the assignment operator, and both sides are the same trivial expression, this is
435     // self-assignment (i.e., `var = var`) and can be reduced to just a variable reference (`var`).
436     // This can happen when other parts of the assignment are optimized away.
437     if (op.kind() == Token::Kind::TK_EQ && Analysis::IsSameExpressionTree(*left, *right)) {
438         return right->clone();
439     }
440 
441     // Simplify the expression when both sides are constant Boolean literals.
442     if (left->isBoolLiteral() && right->isBoolLiteral()) {
443         bool leftVal  = left->as<Literal>().boolValue();
444         bool rightVal = right->as<Literal>().boolValue();
445         bool result;
446         switch (op.kind()) {
447             case Token::Kind::TK_LOGICALAND: result = leftVal && rightVal; break;
448             case Token::Kind::TK_LOGICALOR:  result = leftVal || rightVal; break;
449             case Token::Kind::TK_LOGICALXOR: result = leftVal ^  rightVal; break;
450             case Token::Kind::TK_EQEQ:       result = leftVal == rightVal; break;
451             case Token::Kind::TK_NEQ:        result = leftVal != rightVal; break;
452             default: return nullptr;
453         }
454         return Literal::MakeBool(context, line, result);
455     }
456 
457     // If the left side is a Boolean literal, apply short-circuit optimizations.
458     if (left->isBoolLiteral()) {
459         return short_circuit_boolean(*left, op, *right);
460     }
461 
462     // If the right side is a Boolean literal...
463     if (right->isBoolLiteral()) {
464         // ... and the left side has no side effects...
465         if (!left->hasSideEffects()) {
466             // We can reverse the expressions and short-circuit optimizations are still valid.
467             return short_circuit_boolean(*right, op, *left);
468         }
469 
470         // We can't use short-circuiting, but we can still optimize away no-op Boolean expressions.
471         return eliminate_no_op_boolean(*left, op, *right);
472     }
473 
474     if (op.kind() == Token::Kind::TK_EQEQ && Analysis::IsSameExpressionTree(*left, *right)) {
475         // With == comparison, if both sides are the same trivial expression, this is self-
476         // comparison and is always true. (We are not concerned with NaN.)
477         return Literal::MakeBool(context, leftExpr.fLine, /*value=*/true);
478     }
479 
480     if (op.kind() == Token::Kind::TK_NEQ && Analysis::IsSameExpressionTree(*left, *right)) {
481         // With != comparison, if both sides are the same trivial expression, this is self-
482         // comparison and is always false. (We are not concerned with NaN.)
483         return Literal::MakeBool(context, leftExpr.fLine, /*value=*/false);
484     }
485 
486     if (error_on_divide_by_zero(context, line, op, *right)) {
487         return nullptr;
488     }
489 
490     // Optimize away no-op arithmetic like `x * 1`, `x *= 1`, `x + 0`, `x * 0`, `0 / x`, etc.
491     const Type& leftType = left->type();
492     const Type& rightType = right->type();
493     if ((leftType.isScalar() || leftType.isVector()) &&
494         (rightType.isScalar() || rightType.isVector())) {
495         std::unique_ptr<Expression> expr = simplify_no_op_arithmetic(context, *left, op, *right,
496                                                                      resultType);
497         if (expr) {
498             return expr;
499         }
500     }
501 
502     // Other than the cases above, constant folding requires both sides to be constant.
503     if (!left->isCompileTimeConstant() || !right->isCompileTimeConstant()) {
504         return nullptr;
505     }
506 
507     // Note that fold_int_expression returns null if the result would overflow its type.
508     using SKSL_UINT = uint64_t;
509     if (left->isIntLiteral() && right->isIntLiteral()) {
510         SKSL_INT leftVal  = left->as<Literal>().intValue();
511         SKSL_INT rightVal = right->as<Literal>().intValue();
512 
513         #define RESULT(Op)   fold_int_expression(line, \
514                                         (SKSL_INT)(leftVal) Op (SKSL_INT)(rightVal), &resultType)
515         #define URESULT(Op)  fold_int_expression(line, \
516                              (SKSL_INT)((SKSL_UINT)(leftVal) Op (SKSL_UINT)(rightVal)), &resultType)
517         switch (op.kind()) {
518             case Token::Kind::TK_PLUS:       return URESULT(+);
519             case Token::Kind::TK_MINUS:      return URESULT(-);
520             case Token::Kind::TK_STAR:       return URESULT(*);
521             case Token::Kind::TK_SLASH:
522                 if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
523                     context.fErrors->error(line, "arithmetic overflow");
524                     return nullptr;
525                 }
526                 return RESULT(/);
527             case Token::Kind::TK_PERCENT:
528                 if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
529                     context.fErrors->error(line, "arithmetic overflow");
530                     return nullptr;
531                 }
532                 return RESULT(%);
533             case Token::Kind::TK_BITWISEAND: return RESULT(&);
534             case Token::Kind::TK_BITWISEOR:  return RESULT(|);
535             case Token::Kind::TK_BITWISEXOR: return RESULT(^);
536             case Token::Kind::TK_EQEQ:       return RESULT(==);
537             case Token::Kind::TK_NEQ:        return RESULT(!=);
538             case Token::Kind::TK_GT:         return RESULT(>);
539             case Token::Kind::TK_GTEQ:       return RESULT(>=);
540             case Token::Kind::TK_LT:         return RESULT(<);
541             case Token::Kind::TK_LTEQ:       return RESULT(<=);
542             case Token::Kind::TK_SHL:
543                 if (rightVal >= 0 && rightVal <= 31) {
544                     // Left-shifting a negative (or really, any signed) value is undefined behavior
545                     // in C++, but not GLSL. Do the shift on unsigned values, to avoid UBSAN.
546                     return URESULT(<<);
547                 }
548                 context.fErrors->error(line, "shift value out of range");
549                 return nullptr;
550             case Token::Kind::TK_SHR:
551                 if (rightVal >= 0 && rightVal <= 31) {
552                     return RESULT(>>);
553                 }
554                 context.fErrors->error(line, "shift value out of range");
555                 return nullptr;
556 
557             default:
558                 return nullptr;
559         }
560         #undef RESULT
561         #undef URESULT
562     }
563 
564     // Perform constant folding on pairs of floating-point literals.
565     if (left->isFloatLiteral() && right->isFloatLiteral()) {
566         SKSL_FLOAT leftVal  = left->as<Literal>().floatValue();
567         SKSL_FLOAT rightVal = right->as<Literal>().floatValue();
568 
569         #define RESULT(Op) fold_float_expression(line, leftVal Op rightVal, &resultType)
570         switch (op.kind()) {
571             case Token::Kind::TK_PLUS:  return RESULT(+);
572             case Token::Kind::TK_MINUS: return RESULT(-);
573             case Token::Kind::TK_STAR:  return RESULT(*);
574             case Token::Kind::TK_SLASH: return RESULT(/);
575             case Token::Kind::TK_EQEQ:  return RESULT(==);
576             case Token::Kind::TK_NEQ:   return RESULT(!=);
577             case Token::Kind::TK_GT:    return RESULT(>);
578             case Token::Kind::TK_GTEQ:  return RESULT(>=);
579             case Token::Kind::TK_LT:    return RESULT(<);
580             case Token::Kind::TK_LTEQ:  return RESULT(<=);
581             default:                    return nullptr;
582         }
583         #undef RESULT
584     }
585 
586     // Perform matrix * matrix multiplication.
587     if (op.kind() == Token::Kind::TK_STAR && leftType.isMatrix() && rightType.isMatrix()) {
588         return simplify_matrix_times_matrix(context, *left, *right);
589     }
590 
591     // Perform constant folding on pairs of vectors/matrices.
592     if (is_vec_or_mat(leftType) && leftType.matches(rightType)) {
593         return simplify_componentwise(context, *left, op, *right);
594     }
595 
596     // Perform constant folding on vectors/matrices against scalars, e.g.: half4(2) + 2
597     if (rightType.isScalar() && is_vec_or_mat(leftType) &&
598         leftType.componentType().matches(rightType)) {
599         return simplify_componentwise(context, *left, op,
600                                       *splat_scalar(context, *right, left->type()));
601     }
602 
603     // Perform constant folding on scalars against vectors/matrices, e.g.: 2 + half4(2)
604     if (leftType.isScalar() && is_vec_or_mat(rightType) &&
605         rightType.componentType().matches(leftType)) {
606         return simplify_componentwise(context, *splat_scalar(context, *left, right->type()),
607                                       op, *right);
608     }
609 
610     // Perform constant folding on pairs of matrices or arrays.
611     if ((leftType.isMatrix() && rightType.isMatrix()) ||
612         (leftType.isArray() && rightType.isArray())) {
613         return simplify_constant_equality(context, *left, op, *right);
614     }
615 
616     // We aren't able to constant-fold.
617     return nullptr;
618 }
619 
620 }  // namespace SkSL
621