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