blob: 355b53caf7ac47b3416d83477fcf547865d35556 [file] [log] [blame]
John Stilesdc8ec312021-01-11 11:05:21 -05001/*
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 "src/sksl/SkSLContext.h"
13#include "src/sksl/SkSLErrorReporter.h"
14#include "src/sksl/ir/SkSLBinaryExpression.h"
15#include "src/sksl/ir/SkSLBoolLiteral.h"
16#include "src/sksl/ir/SkSLConstructor.h"
17#include "src/sksl/ir/SkSLExpression.h"
18#include "src/sksl/ir/SkSLFloatLiteral.h"
19#include "src/sksl/ir/SkSLIntLiteral.h"
20#include "src/sksl/ir/SkSLType.h"
21#include "src/sksl/ir/SkSLVariable.h"
22#include "src/sksl/ir/SkSLVariableReference.h"
23
24namespace SkSL {
25
John Stiles8a9da732021-01-20 14:32:33 -050026static std::unique_ptr<Expression> eliminate_no_op_boolean(const Expression& left,
John Stiles45990502021-02-16 10:55:27 -050027 Operator op,
John Stiles8a9da732021-01-20 14:32:33 -050028 const Expression& right) {
29 SkASSERT(right.is<BoolLiteral>());
30 bool rightVal = right.as<BoolLiteral>().value();
31
32 // Detect no-op Boolean expressions and optimize them away.
John Stiles45990502021-02-16 10:55:27 -050033 if ((op.kind() == Token::Kind::TK_LOGICALAND && rightVal) || // (expr && true) -> (expr)
34 (op.kind() == Token::Kind::TK_LOGICALOR && !rightVal) || // (expr || false) -> (expr)
35 (op.kind() == Token::Kind::TK_LOGICALXOR && !rightVal) || // (expr ^^ false) -> (expr)
36 (op.kind() == Token::Kind::TK_EQEQ && rightVal) || // (expr == true) -> (expr)
37 (op.kind() == Token::Kind::TK_NEQ && !rightVal)) { // (expr != false) -> (expr)
John Stiles8a9da732021-01-20 14:32:33 -050038
39 return left.clone();
40 }
41
42 return nullptr;
43}
44
John Stilesdc8ec312021-01-11 11:05:21 -050045static std::unique_ptr<Expression> short_circuit_boolean(const Expression& left,
John Stiles45990502021-02-16 10:55:27 -050046 Operator op,
John Stilesdc8ec312021-01-11 11:05:21 -050047 const Expression& right) {
48 SkASSERT(left.is<BoolLiteral>());
49 bool leftVal = left.as<BoolLiteral>().value();
50
John Stiles8a9da732021-01-20 14:32:33 -050051 // When the literal is on the left, we can sometimes eliminate the other expression entirely.
John Stiles45990502021-02-16 10:55:27 -050052 if ((op.kind() == Token::Kind::TK_LOGICALAND && !leftVal) || // (false && expr) -> (false)
53 (op.kind() == Token::Kind::TK_LOGICALOR && leftVal)) { // (true || expr) -> (true)
John Stiles8a9da732021-01-20 14:32:33 -050054
55 return left.clone();
John Stilesdc8ec312021-01-11 11:05:21 -050056 }
57
John Stiles8a9da732021-01-20 14:32:33 -050058 // We can't eliminate the right-side expression via short-circuit, but we might still be able to
59 // simplify away a no-op expression.
60 return eliminate_no_op_boolean(right, op, left);
John Stilesdc8ec312021-01-11 11:05:21 -050061}
62
Brian Osman21d2b6a2021-02-01 13:48:50 -050063// 'T' is the actual stored type of the literal data (SKSL_FLOAT or SKSL_INT).
64// 'U' is an unsigned version of that, used to perform addition, subtraction, and multiplication,
65// to avoid signed-integer overflow errors. This mimics the use of URESULT vs. RESULT when doing
66// scalar folding in Simplify, later in this file.
67template <typename T, typename U = T>
John Stilesdc8ec312021-01-11 11:05:21 -050068static std::unique_ptr<Expression> simplify_vector(const Context& context,
John Stilesdc8ec312021-01-11 11:05:21 -050069 const Expression& left,
John Stiles45990502021-02-16 10:55:27 -050070 Operator op,
John Stilesdc8ec312021-01-11 11:05:21 -050071 const Expression& right) {
John Stiles508eba72021-01-11 13:07:47 -050072 SkASSERT(left.type().isVector());
John Stilesdc8ec312021-01-11 11:05:21 -050073 SkASSERT(left.type() == right.type());
74 const Type& type = left.type();
75
76 // Handle boolean operations: == !=
John Stiles45990502021-02-16 10:55:27 -050077 if (op.kind() == Token::Kind::TK_EQEQ || op.kind() == Token::Kind::TK_NEQ) {
78 bool equality = (op.kind() == Token::Kind::TK_EQEQ);
John Stilesdc8ec312021-01-11 11:05:21 -050079
80 switch (left.compareConstant(right)) {
81 case Expression::ComparisonResult::kNotEqual:
82 equality = !equality;
83 [[fallthrough]];
84
85 case Expression::ComparisonResult::kEqual:
86 return std::make_unique<BoolLiteral>(context, left.fOffset, equality);
87
88 case Expression::ComparisonResult::kUnknown:
89 return nullptr;
90 }
91 }
92
93 // Handle floating-point arithmetic: + - * /
John Stiles54f00492021-02-19 11:46:10 -050094 const auto vectorComponentwiseFold = [&](auto foldFn) -> std::unique_ptr<Expression> {
John Stilesdc8ec312021-01-11 11:05:21 -050095 const Type& componentType = type.componentType();
96 ExpressionArray args;
97 args.reserve_back(type.columns());
98 for (int i = 0; i < type.columns(); i++) {
Brian Osman21d2b6a2021-02-01 13:48:50 -050099 U value = foldFn(left.getVecComponent<T>(i), right.getVecComponent<T>(i));
John Stilesdc8ec312021-01-11 11:05:21 -0500100 args.push_back(std::make_unique<Literal<T>>(left.fOffset, value, &componentType));
101 }
John Stiles54f00492021-02-19 11:46:10 -0500102 return Constructor::Make(context, left.fOffset, type, std::move(args));
John Stilesdc8ec312021-01-11 11:05:21 -0500103 };
104
John Stiles45990502021-02-16 10:55:27 -0500105 switch (op.kind()) {
Brian Osman21d2b6a2021-02-01 13:48:50 -0500106 case Token::Kind::TK_PLUS: return vectorComponentwiseFold([](U a, U b) { return a + b; });
107 case Token::Kind::TK_MINUS: return vectorComponentwiseFold([](U a, U b) { return a - b; });
108 case Token::Kind::TK_STAR: return vectorComponentwiseFold([](U a, U b) { return a * b; });
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500109 case Token::Kind::TK_SLASH: return vectorComponentwiseFold([](T a, T b) { return a / b; });
John Stilesdc8ec312021-01-11 11:05:21 -0500110 default:
111 return nullptr;
112 }
113}
114
John Stiles508eba72021-01-11 13:07:47 -0500115static Constructor splat_scalar(const Expression& scalar, const Type& type) {
116 SkASSERT(type.isVector());
117 SkASSERT(type.componentType() == scalar.type());
118
119 // Use a Constructor to splat the scalar expression across a vector.
120 ExpressionArray arg;
121 arg.push_back(scalar.clone());
John Stiles54f00492021-02-19 11:46:10 -0500122 return Constructor{scalar.fOffset, type, std::move(arg)};
John Stiles508eba72021-01-11 13:07:47 -0500123}
124
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500125bool ConstantFolder::GetConstantInt(const Expression& value, SKSL_INT* out) {
126 switch (value.kind()) {
127 case Expression::Kind::kIntLiteral:
128 *out = value.as<IntLiteral>().value();
129 return true;
130 case Expression::Kind::kVariableReference: {
131 const Variable& var = *value.as<VariableReference>().variable();
132 return (var.modifiers().fFlags & Modifiers::kConst_Flag) &&
133 var.initialValue() && GetConstantInt(*var.initialValue(), out);
134 }
135 default:
136 return false;
137 }
138}
139
140bool ConstantFolder::GetConstantFloat(const Expression& value, SKSL_FLOAT* out) {
141 switch (value.kind()) {
142 case Expression::Kind::kFloatLiteral:
143 *out = value.as<FloatLiteral>().value();
144 return true;
145 case Expression::Kind::kVariableReference: {
146 const Variable& var = *value.as<VariableReference>().variable();
147 return (var.modifiers().fFlags & Modifiers::kConst_Flag) &&
148 var.initialValue() && GetConstantFloat(*var.initialValue(), out);
149 }
150 default:
151 return false;
152 }
153}
154
155static bool contains_constant_zero(const Expression& expr) {
156 if (expr.is<Constructor>()) {
157 for (const auto& arg : expr.as<Constructor>().arguments()) {
158 if (contains_constant_zero(*arg)) {
159 return true;
160 }
161 }
162 return false;
163 }
164 SKSL_INT intValue;
165 SKSL_FLOAT floatValue;
166 return (ConstantFolder::GetConstantInt(expr, &intValue) && intValue == 0) ||
167 (ConstantFolder::GetConstantFloat(expr, &floatValue) && floatValue == 0.0f);
168}
169
John Stiles45990502021-02-16 10:55:27 -0500170bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Operator op,
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500171 const Expression& right) {
John Stiles45990502021-02-16 10:55:27 -0500172 switch (op.kind()) {
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500173 case Token::Kind::TK_SLASH:
174 case Token::Kind::TK_SLASHEQ:
175 case Token::Kind::TK_PERCENT:
176 case Token::Kind::TK_PERCENTEQ:
177 if (contains_constant_zero(right)) {
178 context.fErrors.error(offset, "division by zero");
179 return true;
180 }
181 return false;
182 default:
183 return false;
184 }
185}
186
John Stilesdc8ec312021-01-11 11:05:21 -0500187std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500188 int offset,
John Stilesdc8ec312021-01-11 11:05:21 -0500189 const Expression& left,
John Stiles45990502021-02-16 10:55:27 -0500190 Operator op,
John Stilesdc8ec312021-01-11 11:05:21 -0500191 const Expression& right) {
John Stilesabc3b782021-02-05 10:07:19 -0500192 // If this is the comma operator, the left side is evaluated but not otherwise used in any way.
193 // So if the left side has no side effects, it can just be eliminated entirely.
John Stiles45990502021-02-16 10:55:27 -0500194 if (op.kind() == Token::Kind::TK_COMMA && !left.hasSideEffects()) {
John Stilesabc3b782021-02-05 10:07:19 -0500195 return right.clone();
196 }
197
John Stiles95d0bad2021-03-01 17:02:28 -0500198 // If this is the assignment operator, and both sides are the same trivial expression, this is
199 // self-assignment (i.e., `var = var`) and can be reduced to just a variable reference (`var`).
200 // This can happen when other parts of the assignment are optimized away.
201 if (op.kind() == Token::Kind::TK_EQ && Analysis::IsSelfAssignment(left, right)) {
202 return right.clone();
203 }
204
John Stiles8a9da732021-01-20 14:32:33 -0500205 // Simplify the expression when both sides are constant Boolean literals.
John Stilesdc8ec312021-01-11 11:05:21 -0500206 if (left.is<BoolLiteral>() && right.is<BoolLiteral>()) {
207 bool leftVal = left.as<BoolLiteral>().value();
208 bool rightVal = right.as<BoolLiteral>().value();
209 bool result;
John Stiles45990502021-02-16 10:55:27 -0500210 switch (op.kind()) {
John Stilesdc8ec312021-01-11 11:05:21 -0500211 case Token::Kind::TK_LOGICALAND: result = leftVal && rightVal; break;
212 case Token::Kind::TK_LOGICALOR: result = leftVal || rightVal; break;
213 case Token::Kind::TK_LOGICALXOR: result = leftVal ^ rightVal; break;
John Stiles26fdcbb2021-01-19 19:00:31 -0500214 case Token::Kind::TK_EQEQ: result = leftVal == rightVal; break;
215 case Token::Kind::TK_NEQ: result = leftVal != rightVal; break;
John Stilesdc8ec312021-01-11 11:05:21 -0500216 default: return nullptr;
217 }
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500218 return std::make_unique<BoolLiteral>(context, offset, result);
John Stilesdc8ec312021-01-11 11:05:21 -0500219 }
220
John Stiles8a9da732021-01-20 14:32:33 -0500221 // If the left side is a Boolean literal, apply short-circuit optimizations.
222 if (left.is<BoolLiteral>()) {
223 return short_circuit_boolean(left, op, right);
224 }
225
226 // If the right side is a Boolean literal...
227 if (right.is<BoolLiteral>()) {
228 // ... and the left side has no side effects...
229 if (!left.hasSideEffects()) {
230 // We can reverse the expressions and short-circuit optimizations are still valid.
231 return short_circuit_boolean(right, op, left);
232 }
233
234 // We can't use short-circuiting, but we can still optimize away no-op Boolean expressions.
235 return eliminate_no_op_boolean(left, op, right);
236 }
237
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500238 if (ErrorOnDivideByZero(context, offset, op, right)) {
239 return nullptr;
240 }
241
John Stiles8a9da732021-01-20 14:32:33 -0500242 // Other than the short-circuit cases above, constant folding requires both sides to be constant
243 if (!left.isCompileTimeConstant() || !right.isCompileTimeConstant()) {
244 return nullptr;
245 }
246
John Stilesdc8ec312021-01-11 11:05:21 -0500247 // Note that we expressly do not worry about precision and overflow here -- we use the maximum
248 // precision to calculate the results and hope the result makes sense.
249 // TODO: detect and handle integer overflow properly.
Brian Osman21d2b6a2021-02-01 13:48:50 -0500250 using SKSL_UINT = uint64_t;
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500251 #define RESULT(t, op) std::make_unique<t ## Literal>(context, offset, \
John Stilesdc8ec312021-01-11 11:05:21 -0500252 leftVal op rightVal)
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500253 #define URESULT(t, op) std::make_unique<t ## Literal>(context, offset, \
Brian Osman21d2b6a2021-02-01 13:48:50 -0500254 (SKSL_UINT) leftVal op \
255 (SKSL_UINT) rightVal)
John Stilesdc8ec312021-01-11 11:05:21 -0500256 if (left.is<IntLiteral>() && right.is<IntLiteral>()) {
257 SKSL_INT leftVal = left.as<IntLiteral>().value();
258 SKSL_INT rightVal = right.as<IntLiteral>().value();
John Stiles45990502021-02-16 10:55:27 -0500259 switch (op.kind()) {
John Stilesdc8ec312021-01-11 11:05:21 -0500260 case Token::Kind::TK_PLUS: return URESULT(Int, +);
261 case Token::Kind::TK_MINUS: return URESULT(Int, -);
262 case Token::Kind::TK_STAR: return URESULT(Int, *);
263 case Token::Kind::TK_SLASH:
264 if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500265 context.fErrors.error(offset, "arithmetic overflow");
John Stilesdc8ec312021-01-11 11:05:21 -0500266 return nullptr;
267 }
268 return RESULT(Int, /);
269 case Token::Kind::TK_PERCENT:
270 if (leftVal == std::numeric_limits<SKSL_INT>::min() && rightVal == -1) {
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500271 context.fErrors.error(offset, "arithmetic overflow");
John Stilesdc8ec312021-01-11 11:05:21 -0500272 return nullptr;
273 }
274 return RESULT(Int, %);
275 case Token::Kind::TK_BITWISEAND: return RESULT(Int, &);
276 case Token::Kind::TK_BITWISEOR: return RESULT(Int, |);
277 case Token::Kind::TK_BITWISEXOR: return RESULT(Int, ^);
278 case Token::Kind::TK_EQEQ: return RESULT(Bool, ==);
279 case Token::Kind::TK_NEQ: return RESULT(Bool, !=);
280 case Token::Kind::TK_GT: return RESULT(Bool, >);
281 case Token::Kind::TK_GTEQ: return RESULT(Bool, >=);
282 case Token::Kind::TK_LT: return RESULT(Bool, <);
283 case Token::Kind::TK_LTEQ: return RESULT(Bool, <=);
284 case Token::Kind::TK_SHL:
285 if (rightVal >= 0 && rightVal <= 31) {
Brian Osmana7eb6812021-02-01 11:43:05 -0500286 // Left-shifting a negative (or really, any signed) value is undefined behavior
287 // in C++, but not GLSL. Do the shift on unsigned values, to avoid UBSAN.
288 return URESULT(Int, <<);
John Stilesdc8ec312021-01-11 11:05:21 -0500289 }
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500290 context.fErrors.error(offset, "shift value out of range");
John Stilesdc8ec312021-01-11 11:05:21 -0500291 return nullptr;
292 case Token::Kind::TK_SHR:
293 if (rightVal >= 0 && rightVal <= 31) {
294 return RESULT(Int, >>);
295 }
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500296 context.fErrors.error(offset, "shift value out of range");
John Stilesdc8ec312021-01-11 11:05:21 -0500297 return nullptr;
298
299 default:
300 return nullptr;
301 }
302 }
303
304 // Perform constant folding on pairs of floating-point literals.
305 if (left.is<FloatLiteral>() && right.is<FloatLiteral>()) {
306 SKSL_FLOAT leftVal = left.as<FloatLiteral>().value();
307 SKSL_FLOAT rightVal = right.as<FloatLiteral>().value();
John Stiles45990502021-02-16 10:55:27 -0500308 switch (op.kind()) {
John Stilesdc8ec312021-01-11 11:05:21 -0500309 case Token::Kind::TK_PLUS: return RESULT(Float, +);
310 case Token::Kind::TK_MINUS: return RESULT(Float, -);
311 case Token::Kind::TK_STAR: return RESULT(Float, *);
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500312 case Token::Kind::TK_SLASH: return RESULT(Float, /);
John Stilesdc8ec312021-01-11 11:05:21 -0500313 case Token::Kind::TK_EQEQ: return RESULT(Bool, ==);
314 case Token::Kind::TK_NEQ: return RESULT(Bool, !=);
315 case Token::Kind::TK_GT: return RESULT(Bool, >);
316 case Token::Kind::TK_GTEQ: return RESULT(Bool, >=);
317 case Token::Kind::TK_LT: return RESULT(Bool, <);
318 case Token::Kind::TK_LTEQ: return RESULT(Bool, <=);
319 default: return nullptr;
320 }
321 }
322
323 // Perform constant folding on pairs of vectors.
324 const Type& leftType = left.type();
325 const Type& rightType = right.type();
326 if (leftType.isVector() && leftType == rightType) {
327 if (leftType.componentType().isFloat()) {
John Stilesb30151e2021-01-11 16:13:08 -0500328 return simplify_vector<SKSL_FLOAT>(context, left, op, right);
John Stilesdc8ec312021-01-11 11:05:21 -0500329 }
330 if (leftType.componentType().isInteger()) {
Brian Osman21d2b6a2021-02-01 13:48:50 -0500331 return simplify_vector<SKSL_INT, SKSL_UINT>(context, left, op, right);
John Stilesdc8ec312021-01-11 11:05:21 -0500332 }
333 return nullptr;
334 }
335
John Stiles508eba72021-01-11 13:07:47 -0500336 // Perform constant folding on vectors against scalars, e.g.: half4(2) + 2
337 if (leftType.isVector() && leftType.componentType() == rightType) {
338 if (rightType.isFloat()) {
John Stilesb30151e2021-01-11 16:13:08 -0500339 return simplify_vector<SKSL_FLOAT>(context, left, op, splat_scalar(right, left.type()));
John Stiles508eba72021-01-11 13:07:47 -0500340 }
341 if (rightType.isInteger()) {
Brian Osman21d2b6a2021-02-01 13:48:50 -0500342 return simplify_vector<SKSL_INT, SKSL_UINT>(context, left, op,
343 splat_scalar(right, left.type()));
John Stiles508eba72021-01-11 13:07:47 -0500344 }
345 return nullptr;
346 }
347
348 // Perform constant folding on scalars against vectors, e.g.: 2 + half4(2)
349 if (rightType.isVector() && rightType.componentType() == leftType) {
350 if (leftType.isFloat()) {
John Stilesb30151e2021-01-11 16:13:08 -0500351 return simplify_vector<SKSL_FLOAT>(context, splat_scalar(left, right.type()), op,
352 right);
John Stiles508eba72021-01-11 13:07:47 -0500353 }
354 if (leftType.isInteger()) {
Brian Osman21d2b6a2021-02-01 13:48:50 -0500355 return simplify_vector<SKSL_INT, SKSL_UINT>(context, splat_scalar(left, right.type()),
356 op, right);
John Stiles508eba72021-01-11 13:07:47 -0500357 }
358 return nullptr;
359 }
360
John Stilesdc8ec312021-01-11 11:05:21 -0500361 // Perform constant folding on pairs of matrices.
362 if (leftType.isMatrix() && rightType.isMatrix()) {
363 bool equality;
John Stiles45990502021-02-16 10:55:27 -0500364 switch (op.kind()) {
John Stilesdc8ec312021-01-11 11:05:21 -0500365 case Token::Kind::TK_EQEQ:
366 equality = true;
367 break;
368 case Token::Kind::TK_NEQ:
369 equality = false;
370 break;
371 default:
372 return nullptr;
373 }
374
375 switch (left.compareConstant(right)) {
376 case Expression::ComparisonResult::kNotEqual:
377 equality = !equality;
378 [[fallthrough]];
379
380 case Expression::ComparisonResult::kEqual:
Ethan Nicholasc0f98152021-02-05 16:21:10 -0500381 return std::make_unique<BoolLiteral>(context, offset, equality);
John Stilesdc8ec312021-01-11 11:05:21 -0500382
383 case Expression::ComparisonResult::kUnknown:
384 return nullptr;
385 }
386 }
387
388 // We aren't able to constant-fold.
389 #undef RESULT
390 #undef URESULT
391 return nullptr;
392}
393
394} // namespace SkSL