1 /*
2 * Copyright 2021 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 "include/core/SkTypes.h"
9 #include "include/private/SkStringView.h"
10 #include "src/sksl/SkSLContext.h"
11 #include "src/sksl/SkSLOperators.h"
12 #include "src/sksl/SkSLProgramSettings.h"
13 #include "src/sksl/ir/SkSLType.h"
14
15 namespace SkSL {
16
getBinaryPrecedence() const17 Operator::Precedence Operator::getBinaryPrecedence() const {
18 switch (this->kind()) {
19 case Kind::TK_STAR: // fall through
20 case Kind::TK_SLASH: // fall through
21 case Kind::TK_PERCENT: return Precedence::kMultiplicative;
22 case Kind::TK_PLUS: // fall through
23 case Kind::TK_MINUS: return Precedence::kAdditive;
24 case Kind::TK_SHL: // fall through
25 case Kind::TK_SHR: return Precedence::kShift;
26 case Kind::TK_LT: // fall through
27 case Kind::TK_GT: // fall through
28 case Kind::TK_LTEQ: // fall through
29 case Kind::TK_GTEQ: return Precedence::kRelational;
30 case Kind::TK_EQEQ: // fall through
31 case Kind::TK_NEQ: return Precedence::kEquality;
32 case Kind::TK_BITWISEAND: return Precedence::kBitwiseAnd;
33 case Kind::TK_BITWISEXOR: return Precedence::kBitwiseXor;
34 case Kind::TK_BITWISEOR: return Precedence::kBitwiseOr;
35 case Kind::TK_LOGICALAND: return Precedence::kLogicalAnd;
36 case Kind::TK_LOGICALXOR: return Precedence::kLogicalXor;
37 case Kind::TK_LOGICALOR: return Precedence::kLogicalOr;
38 case Kind::TK_EQ: // fall through
39 case Kind::TK_PLUSEQ: // fall through
40 case Kind::TK_MINUSEQ: // fall through
41 case Kind::TK_STAREQ: // fall through
42 case Kind::TK_SLASHEQ: // fall through
43 case Kind::TK_PERCENTEQ: // fall through
44 case Kind::TK_SHLEQ: // fall through
45 case Kind::TK_SHREQ: // fall through
46 case Kind::TK_BITWISEANDEQ: // fall through
47 case Kind::TK_BITWISEXOREQ: // fall through
48 case Kind::TK_BITWISEOREQ: return Precedence::kAssignment;
49 case Kind::TK_COMMA: return Precedence::kSequence;
50 default: SK_ABORT("unsupported binary operator");
51 }
52 }
53
isOperator() const54 bool Operator::isOperator() const {
55 switch (this->kind()) {
56 case Kind::TK_PLUS:
57 case Kind::TK_MINUS:
58 case Kind::TK_STAR:
59 case Kind::TK_SLASH:
60 case Kind::TK_PERCENT:
61 case Kind::TK_SHL:
62 case Kind::TK_SHR:
63 case Kind::TK_LOGICALNOT:
64 case Kind::TK_LOGICALAND:
65 case Kind::TK_LOGICALOR:
66 case Kind::TK_LOGICALXOR:
67 case Kind::TK_BITWISENOT:
68 case Kind::TK_BITWISEAND:
69 case Kind::TK_BITWISEOR:
70 case Kind::TK_BITWISEXOR:
71 case Kind::TK_EQ:
72 case Kind::TK_EQEQ:
73 case Kind::TK_NEQ:
74 case Kind::TK_LT:
75 case Kind::TK_GT:
76 case Kind::TK_LTEQ:
77 case Kind::TK_GTEQ:
78 case Kind::TK_PLUSEQ:
79 case Kind::TK_MINUSEQ:
80 case Kind::TK_STAREQ:
81 case Kind::TK_SLASHEQ:
82 case Kind::TK_PERCENTEQ:
83 case Kind::TK_SHLEQ:
84 case Kind::TK_SHREQ:
85 case Kind::TK_BITWISEANDEQ:
86 case Kind::TK_BITWISEOREQ:
87 case Kind::TK_BITWISEXOREQ:
88 case Kind::TK_PLUSPLUS:
89 case Kind::TK_MINUSMINUS:
90 case Kind::TK_COMMA:
91 return true;
92 default:
93 return false;
94 }
95 }
96
operatorName() const97 const char* Operator::operatorName() const {
98 switch (this->kind()) {
99 case Kind::TK_PLUS: return " + ";
100 case Kind::TK_MINUS: return " - ";
101 case Kind::TK_STAR: return " * ";
102 case Kind::TK_SLASH: return " / ";
103 case Kind::TK_PERCENT: return " % ";
104 case Kind::TK_SHL: return " << ";
105 case Kind::TK_SHR: return " >> ";
106 case Kind::TK_LOGICALNOT: return "!";
107 case Kind::TK_LOGICALAND: return " && ";
108 case Kind::TK_LOGICALOR: return " || ";
109 case Kind::TK_LOGICALXOR: return " ^^ ";
110 case Kind::TK_BITWISENOT: return "~";
111 case Kind::TK_BITWISEAND: return " & ";
112 case Kind::TK_BITWISEOR: return " | ";
113 case Kind::TK_BITWISEXOR: return " ^ ";
114 case Kind::TK_EQ: return " = ";
115 case Kind::TK_EQEQ: return " == ";
116 case Kind::TK_NEQ: return " != ";
117 case Kind::TK_LT: return " < ";
118 case Kind::TK_GT: return " > ";
119 case Kind::TK_LTEQ: return " <= ";
120 case Kind::TK_GTEQ: return " >= ";
121 case Kind::TK_PLUSEQ: return " += ";
122 case Kind::TK_MINUSEQ: return " -= ";
123 case Kind::TK_STAREQ: return " *= ";
124 case Kind::TK_SLASHEQ: return " /= ";
125 case Kind::TK_PERCENTEQ: return " %= ";
126 case Kind::TK_SHLEQ: return " <<= ";
127 case Kind::TK_SHREQ: return " >>= ";
128 case Kind::TK_BITWISEANDEQ: return " &= ";
129 case Kind::TK_BITWISEOREQ: return " |= ";
130 case Kind::TK_BITWISEXOREQ: return " ^= ";
131 case Kind::TK_PLUSPLUS: return "++";
132 case Kind::TK_MINUSMINUS: return "--";
133 case Kind::TK_COMMA: return ", ";
134 default:
135 SK_ABORT("unsupported operator: %d\n", (int) fKind);
136 }
137 }
138
tightOperatorName() const139 std::string_view Operator::tightOperatorName() const {
140 std::string_view name = this->operatorName();
141 if (skstd::starts_with(name, ' ')) {
142 name.remove_prefix(1);
143 }
144 if (skstd::ends_with(name, ' ')) {
145 name.remove_suffix(1);
146 }
147 return name;
148 }
149
isAssignment() const150 bool Operator::isAssignment() const {
151 switch (this->kind()) {
152 case Kind::TK_EQ: // fall through
153 case Kind::TK_PLUSEQ: // fall through
154 case Kind::TK_MINUSEQ: // fall through
155 case Kind::TK_STAREQ: // fall through
156 case Kind::TK_SLASHEQ: // fall through
157 case Kind::TK_PERCENTEQ: // fall through
158 case Kind::TK_SHLEQ: // fall through
159 case Kind::TK_SHREQ: // fall through
160 case Kind::TK_BITWISEOREQ: // fall through
161 case Kind::TK_BITWISEXOREQ: // fall through
162 case Kind::TK_BITWISEANDEQ:
163 return true;
164 default:
165 return false;
166 }
167 }
168
removeAssignment() const169 Operator Operator::removeAssignment() const {
170 switch (this->kind()) {
171 case Kind::TK_PLUSEQ: return Operator{Kind::TK_PLUS};
172 case Kind::TK_MINUSEQ: return Operator{Kind::TK_MINUS};
173 case Kind::TK_STAREQ: return Operator{Kind::TK_STAR};
174 case Kind::TK_SLASHEQ: return Operator{Kind::TK_SLASH};
175 case Kind::TK_PERCENTEQ: return Operator{Kind::TK_PERCENT};
176 case Kind::TK_SHLEQ: return Operator{Kind::TK_SHL};
177 case Kind::TK_SHREQ: return Operator{Kind::TK_SHR};
178 case Kind::TK_BITWISEOREQ: return Operator{Kind::TK_BITWISEOR};
179 case Kind::TK_BITWISEXOREQ: return Operator{Kind::TK_BITWISEXOR};
180 case Kind::TK_BITWISEANDEQ: return Operator{Kind::TK_BITWISEAND};
181 default: return *this;
182 }
183 }
184
isLogical() const185 bool Operator::isLogical() const {
186 switch (this->kind()) {
187 case Token::Kind::TK_LT:
188 case Token::Kind::TK_GT:
189 case Token::Kind::TK_LTEQ:
190 case Token::Kind::TK_GTEQ:
191 return true;
192 default:
193 return false;
194 }
195 }
196
isOnlyValidForIntegralTypes() const197 bool Operator::isOnlyValidForIntegralTypes() const {
198 switch (this->kind()) {
199 case Token::Kind::TK_SHL:
200 case Token::Kind::TK_SHR:
201 case Token::Kind::TK_BITWISEAND:
202 case Token::Kind::TK_BITWISEOR:
203 case Token::Kind::TK_BITWISEXOR:
204 case Token::Kind::TK_PERCENT:
205 case Token::Kind::TK_SHLEQ:
206 case Token::Kind::TK_SHREQ:
207 case Token::Kind::TK_BITWISEANDEQ:
208 case Token::Kind::TK_BITWISEOREQ:
209 case Token::Kind::TK_BITWISEXOREQ:
210 case Token::Kind::TK_PERCENTEQ:
211 return true;
212 default:
213 return false;
214 }
215 }
216
isValidForMatrixOrVector() const217 bool Operator::isValidForMatrixOrVector() const {
218 switch (this->kind()) {
219 case Token::Kind::TK_PLUS:
220 case Token::Kind::TK_MINUS:
221 case Token::Kind::TK_STAR:
222 case Token::Kind::TK_SLASH:
223 case Token::Kind::TK_PERCENT:
224 case Token::Kind::TK_SHL:
225 case Token::Kind::TK_SHR:
226 case Token::Kind::TK_BITWISEAND:
227 case Token::Kind::TK_BITWISEOR:
228 case Token::Kind::TK_BITWISEXOR:
229 case Token::Kind::TK_PLUSEQ:
230 case Token::Kind::TK_MINUSEQ:
231 case Token::Kind::TK_STAREQ:
232 case Token::Kind::TK_SLASHEQ:
233 case Token::Kind::TK_PERCENTEQ:
234 case Token::Kind::TK_SHLEQ:
235 case Token::Kind::TK_SHREQ:
236 case Token::Kind::TK_BITWISEANDEQ:
237 case Token::Kind::TK_BITWISEOREQ:
238 case Token::Kind::TK_BITWISEXOREQ:
239 return true;
240 default:
241 return false;
242 }
243 }
244
isMatrixMultiply(const Type & left,const Type & right) const245 bool Operator::isMatrixMultiply(const Type& left, const Type& right) const {
246 if (this->kind() != Token::Kind::TK_STAR && this->kind() != Token::Kind::TK_STAREQ) {
247 return false;
248 }
249 if (left.isMatrix()) {
250 return right.isMatrix() || right.isVector();
251 }
252 return left.isVector() && right.isMatrix();
253 }
254
255 /**
256 * Determines the operand and result types of a binary expression. Returns true if the expression is
257 * legal, false otherwise. If false, the values of the out parameters are undefined.
258 */
determineBinaryType(const Context & context,const Type & left,const Type & right,const Type ** outLeftType,const Type ** outRightType,const Type ** outResultType) const259 bool Operator::determineBinaryType(const Context& context,
260 const Type& left,
261 const Type& right,
262 const Type** outLeftType,
263 const Type** outRightType,
264 const Type** outResultType) const {
265 const bool allowNarrowing = context.fConfig->fSettings.fAllowNarrowingConversions;
266 switch (this->kind()) {
267 case Token::Kind::TK_EQ: // left = right
268 *outLeftType = &left;
269 *outRightType = &left;
270 *outResultType = &left;
271 return right.canCoerceTo(left, allowNarrowing);
272
273 case Token::Kind::TK_EQEQ: // left == right
274 case Token::Kind::TK_NEQ: { // left != right
275 CoercionCost rightToLeft = right.coercionCost(left),
276 leftToRight = left.coercionCost(right);
277 if (rightToLeft < leftToRight) {
278 if (rightToLeft.isPossible(allowNarrowing)) {
279 *outLeftType = &left;
280 *outRightType = &left;
281 *outResultType = context.fTypes.fBool.get();
282 return true;
283 }
284 } else {
285 if (leftToRight.isPossible(allowNarrowing)) {
286 *outLeftType = &right;
287 *outRightType = &right;
288 *outResultType = context.fTypes.fBool.get();
289 return true;
290 }
291 }
292 return false;
293 }
294 case Token::Kind::TK_LOGICALOR: // left || right
295 case Token::Kind::TK_LOGICALAND: // left && right
296 case Token::Kind::TK_LOGICALXOR: // left ^^ right
297 *outLeftType = context.fTypes.fBool.get();
298 *outRightType = context.fTypes.fBool.get();
299 *outResultType = context.fTypes.fBool.get();
300 return left.canCoerceTo(*context.fTypes.fBool, allowNarrowing) &&
301 right.canCoerceTo(*context.fTypes.fBool, allowNarrowing);
302
303 case Token::Kind::TK_COMMA: // left, right
304 if (left.isOpaque() || right.isOpaque()) {
305 return false;
306 }
307 *outLeftType = &left;
308 *outRightType = &right;
309 *outResultType = &right;
310 return true;
311
312 default:
313 break;
314 }
315
316 // Boolean types only support the operators listed above (, = == != || && ^^).
317 // If we've gotten this far with a boolean, we have an unsupported operator.
318 const Type& leftComponentType = left.componentType();
319 const Type& rightComponentType = right.componentType();
320 if (leftComponentType.isBoolean() || rightComponentType.isBoolean()) {
321 return false;
322 }
323
324 bool isAssignment = this->isAssignment();
325 if (this->isMatrixMultiply(left, right)) { // left * right
326 // Determine final component type.
327 if (!this->determineBinaryType(context, left.componentType(), right.componentType(),
328 outLeftType, outRightType, outResultType)) {
329 return false;
330 }
331 // Convert component type to compound.
332 *outLeftType = &(*outResultType)->toCompound(context, left.columns(), left.rows());
333 *outRightType = &(*outResultType)->toCompound(context, right.columns(), right.rows());
334 int leftColumns = left.columns(), leftRows = left.rows();
335 int rightColumns = right.columns(), rightRows = right.rows();
336 if (right.isVector()) {
337 // `matrix * vector` treats the vector as a column vector; we need to transpose it.
338 std::swap(rightColumns, rightRows);
339 SkASSERT(rightColumns == 1);
340 }
341 if (rightColumns > 1) {
342 *outResultType = &(*outResultType)->toCompound(context, rightColumns, leftRows);
343 } else {
344 // The result was a column vector. Transpose it back to a row.
345 *outResultType = &(*outResultType)->toCompound(context, leftRows, rightColumns);
346 }
347 if (isAssignment && ((*outResultType)->columns() != leftColumns ||
348 (*outResultType)->rows() != leftRows)) {
349 return false;
350 }
351 return leftColumns == rightRows;
352 }
353
354 bool leftIsVectorOrMatrix = left.isVector() || left.isMatrix();
355 bool validMatrixOrVectorOp = this->isValidForMatrixOrVector();
356
357 if (leftIsVectorOrMatrix && validMatrixOrVectorOp && right.isScalar()) {
358 // Determine final component type.
359 if (!this->determineBinaryType(context, left.componentType(), right,
360 outLeftType, outRightType, outResultType)) {
361 return false;
362 }
363 // Convert component type to compound.
364 *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows());
365 if (!this->isLogical()) {
366 *outResultType = &(*outResultType)->toCompound(context, left.columns(), left.rows());
367 }
368 return true;
369 }
370
371 bool rightIsVectorOrMatrix = right.isVector() || right.isMatrix();
372
373 if (!isAssignment && rightIsVectorOrMatrix && validMatrixOrVectorOp && left.isScalar()) {
374 // Determine final component type.
375 if (!this->determineBinaryType(context, left, right.componentType(),
376 outLeftType, outRightType, outResultType)) {
377 return false;
378 }
379 // Convert component type to compound.
380 *outRightType = &(*outRightType)->toCompound(context, right.columns(), right.rows());
381 if (!this->isLogical()) {
382 *outResultType = &(*outResultType)->toCompound(context, right.columns(), right.rows());
383 }
384 return true;
385 }
386
387 CoercionCost rightToLeftCost = right.coercionCost(left);
388 CoercionCost leftToRightCost = isAssignment ? CoercionCost::Impossible()
389 : left.coercionCost(right);
390
391 if ((left.isScalar() && right.isScalar()) || (leftIsVectorOrMatrix && validMatrixOrVectorOp)) {
392 if (this->isOnlyValidForIntegralTypes()) {
393 if (!leftComponentType.isInteger() || !rightComponentType.isInteger()) {
394 return false;
395 }
396 }
397 if (rightToLeftCost.isPossible(allowNarrowing) && rightToLeftCost < leftToRightCost) {
398 // Right-to-Left conversion is possible and cheaper
399 *outLeftType = &left;
400 *outRightType = &left;
401 *outResultType = &left;
402 } else if (leftToRightCost.isPossible(allowNarrowing)) {
403 // Left-to-Right conversion is possible (and at least as cheap as Right-to-Left)
404 *outLeftType = &right;
405 *outRightType = &right;
406 *outResultType = &right;
407 } else {
408 return false;
409 }
410 if (this->isLogical()) {
411 *outResultType = context.fTypes.fBool.get();
412 }
413 return true;
414 }
415 return false;
416 }
417
418 } // namespace SkSL
419