/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkTypes.h" #include "src/sksl/SkSLContext.h" #include "src/sksl/SkSLOperators.h" #include "src/sksl/SkSLProgramSettings.h" #include "src/sksl/ir/SkSLType.h" namespace SkSL { Operator::Precedence Operator::getBinaryPrecedence() const { switch (this->kind()) { case Kind::TK_STAR: // fall through case Kind::TK_SLASH: // fall through case Kind::TK_PERCENT: return Precedence::kMultiplicative; case Kind::TK_PLUS: // fall through case Kind::TK_MINUS: return Precedence::kAdditive; case Kind::TK_SHL: // fall through case Kind::TK_SHR: return Precedence::kShift; case Kind::TK_LT: // fall through case Kind::TK_GT: // fall through case Kind::TK_LTEQ: // fall through case Kind::TK_GTEQ: return Precedence::kRelational; case Kind::TK_EQEQ: // fall through case Kind::TK_NEQ: return Precedence::kEquality; case Kind::TK_BITWISEAND: return Precedence::kBitwiseAnd; case Kind::TK_BITWISEXOR: return Precedence::kBitwiseXor; case Kind::TK_BITWISEOR: return Precedence::kBitwiseOr; case Kind::TK_LOGICALAND: return Precedence::kLogicalAnd; case Kind::TK_LOGICALXOR: return Precedence::kLogicalXor; case Kind::TK_LOGICALOR: return Precedence::kLogicalOr; case Kind::TK_EQ: // fall through case Kind::TK_PLUSEQ: // fall through case Kind::TK_MINUSEQ: // fall through case Kind::TK_STAREQ: // fall through case Kind::TK_SLASHEQ: // fall through case Kind::TK_PERCENTEQ: // fall through case Kind::TK_SHLEQ: // fall through case Kind::TK_SHREQ: // fall through case Kind::TK_BITWISEANDEQ: // fall through case Kind::TK_BITWISEXOREQ: // fall through case Kind::TK_BITWISEOREQ: return Precedence::kAssignment; case Kind::TK_COMMA: return Precedence::kSequence; default: SK_ABORT("unsupported binary operator"); } } bool Operator::isOperator() const { switch (this->kind()) { case Kind::TK_PLUS: case Kind::TK_MINUS: case Kind::TK_STAR: case Kind::TK_SLASH: case Kind::TK_PERCENT: case Kind::TK_SHL: case Kind::TK_SHR: case Kind::TK_LOGICALNOT: case Kind::TK_LOGICALAND: case Kind::TK_LOGICALOR: case Kind::TK_LOGICALXOR: case Kind::TK_BITWISENOT: case Kind::TK_BITWISEAND: case Kind::TK_BITWISEOR: case Kind::TK_BITWISEXOR: case Kind::TK_EQ: case Kind::TK_EQEQ: case Kind::TK_NEQ: case Kind::TK_LT: case Kind::TK_GT: case Kind::TK_LTEQ: case Kind::TK_GTEQ: case Kind::TK_PLUSEQ: case Kind::TK_MINUSEQ: case Kind::TK_STAREQ: case Kind::TK_SLASHEQ: case Kind::TK_PERCENTEQ: case Kind::TK_SHLEQ: case Kind::TK_SHREQ: case Kind::TK_BITWISEANDEQ: case Kind::TK_BITWISEOREQ: case Kind::TK_BITWISEXOREQ: case Kind::TK_PLUSPLUS: case Kind::TK_MINUSMINUS: case Kind::TK_COMMA: return true; default: return false; } } const char* Operator::operatorName() const { switch (this->kind()) { case Kind::TK_PLUS: return "+"; case Kind::TK_MINUS: return "-"; case Kind::TK_STAR: return "*"; case Kind::TK_SLASH: return "/"; case Kind::TK_PERCENT: return "%"; case Kind::TK_SHL: return "<<"; case Kind::TK_SHR: return ">>"; case Kind::TK_LOGICALNOT: return "!"; case Kind::TK_LOGICALAND: return "&&"; case Kind::TK_LOGICALOR: return "||"; case Kind::TK_LOGICALXOR: return "^^"; case Kind::TK_BITWISENOT: return "~"; case Kind::TK_BITWISEAND: return "&"; case Kind::TK_BITWISEOR: return "|"; case Kind::TK_BITWISEXOR: return "^"; case Kind::TK_EQ: return "="; case Kind::TK_EQEQ: return "=="; case Kind::TK_NEQ: return "!="; case Kind::TK_LT: return "<"; case Kind::TK_GT: return ">"; case Kind::TK_LTEQ: return "<="; case Kind::TK_GTEQ: return ">="; case Kind::TK_PLUSEQ: return "+="; case Kind::TK_MINUSEQ: return "-="; case Kind::TK_STAREQ: return "*="; case Kind::TK_SLASHEQ: return "/="; case Kind::TK_PERCENTEQ: return "%="; case Kind::TK_SHLEQ: return "<<="; case Kind::TK_SHREQ: return ">>="; case Kind::TK_BITWISEANDEQ: return "&="; case Kind::TK_BITWISEOREQ: return "|="; case Kind::TK_BITWISEXOREQ: return "^="; case Kind::TK_PLUSPLUS: return "++"; case Kind::TK_MINUSMINUS: return "--"; case Kind::TK_COMMA: return ","; default: SK_ABORT("unsupported operator: %d\n", (int) fKind); } } bool Operator::isAssignment() const { switch (this->kind()) { case Kind::TK_EQ: // fall through case Kind::TK_PLUSEQ: // fall through case Kind::TK_MINUSEQ: // fall through case Kind::TK_STAREQ: // fall through case Kind::TK_SLASHEQ: // fall through case Kind::TK_PERCENTEQ: // fall through case Kind::TK_SHLEQ: // fall through case Kind::TK_SHREQ: // fall through case Kind::TK_BITWISEOREQ: // fall through case Kind::TK_BITWISEXOREQ: // fall through case Kind::TK_BITWISEANDEQ: return true; default: return false; } } Operator Operator::removeAssignment() const { switch (this->kind()) { case Kind::TK_PLUSEQ: return Operator{Kind::TK_PLUS}; case Kind::TK_MINUSEQ: return Operator{Kind::TK_MINUS}; case Kind::TK_STAREQ: return Operator{Kind::TK_STAR}; case Kind::TK_SLASHEQ: return Operator{Kind::TK_SLASH}; case Kind::TK_PERCENTEQ: return Operator{Kind::TK_PERCENT}; case Kind::TK_SHLEQ: return Operator{Kind::TK_SHL}; case Kind::TK_SHREQ: return Operator{Kind::TK_SHR}; case Kind::TK_BITWISEOREQ: return Operator{Kind::TK_BITWISEOR}; case Kind::TK_BITWISEXOREQ: return Operator{Kind::TK_BITWISEXOR}; case Kind::TK_BITWISEANDEQ: return Operator{Kind::TK_BITWISEAND}; default: return *this; } } bool Operator::isLogical() const { switch (this->kind()) { case Token::Kind::TK_LT: case Token::Kind::TK_GT: case Token::Kind::TK_LTEQ: case Token::Kind::TK_GTEQ: return true; default: return false; } } bool Operator::isOnlyValidForIntegralTypes() const { switch (this->kind()) { case Token::Kind::TK_SHL: case Token::Kind::TK_SHR: case Token::Kind::TK_BITWISEAND: case Token::Kind::TK_BITWISEOR: case Token::Kind::TK_BITWISEXOR: case Token::Kind::TK_PERCENT: case Token::Kind::TK_SHLEQ: case Token::Kind::TK_SHREQ: case Token::Kind::TK_BITWISEANDEQ: case Token::Kind::TK_BITWISEOREQ: case Token::Kind::TK_BITWISEXOREQ: case Token::Kind::TK_PERCENTEQ: return true; default: return false; } } bool Operator::isValidForMatrixOrVector() const { switch (this->kind()) { case Token::Kind::TK_PLUS: case Token::Kind::TK_MINUS: case Token::Kind::TK_STAR: case Token::Kind::TK_SLASH: case Token::Kind::TK_PERCENT: case Token::Kind::TK_SHL: case Token::Kind::TK_SHR: case Token::Kind::TK_BITWISEAND: case Token::Kind::TK_BITWISEOR: case Token::Kind::TK_BITWISEXOR: case Token::Kind::TK_PLUSEQ: case Token::Kind::TK_MINUSEQ: case Token::Kind::TK_STAREQ: case Token::Kind::TK_SLASHEQ: case Token::Kind::TK_PERCENTEQ: case Token::Kind::TK_SHLEQ: case Token::Kind::TK_SHREQ: case Token::Kind::TK_BITWISEANDEQ: case Token::Kind::TK_BITWISEOREQ: case Token::Kind::TK_BITWISEXOREQ: return true; default: return false; } } bool Operator::isMatrixMultiply(const Type& left, const Type& right) { if (this->kind() != Token::Kind::TK_STAR && this->kind() != Token::Kind::TK_STAREQ) { return false; } if (left.isMatrix()) { return right.isMatrix() || right.isVector(); } return left.isVector() && right.isMatrix(); } /** * Determines the operand and result types of a binary expression. Returns true if the expression is * legal, false otherwise. If false, the values of the out parameters are undefined. */ bool Operator::determineBinaryType(const Context& context, const Type& left, const Type& right, const Type** outLeftType, const Type** outRightType, const Type** outResultType) { const bool allowNarrowing = context.fConfig->fSettings.fAllowNarrowingConversions; switch (this->kind()) { case Token::Kind::TK_EQ: // left = right *outLeftType = &left; *outRightType = &left; *outResultType = &left; return right.canCoerceTo(left, allowNarrowing); case Token::Kind::TK_EQEQ: // left == right case Token::Kind::TK_NEQ: { // left != right CoercionCost rightToLeft = right.coercionCost(left), leftToRight = left.coercionCost(right); if (rightToLeft < leftToRight) { if (rightToLeft.isPossible(allowNarrowing)) { *outLeftType = &left; *outRightType = &left; *outResultType = context.fTypes.fBool.get(); return true; } } else { if (leftToRight.isPossible(allowNarrowing)) { *outLeftType = &right; *outRightType = &right; *outResultType = context.fTypes.fBool.get(); return true; } } return false; } case Token::Kind::TK_LOGICALOR: // left || right case Token::Kind::TK_LOGICALAND: // left && right case Token::Kind::TK_LOGICALXOR: // left ^^ right *outLeftType = context.fTypes.fBool.get(); *outRightType = context.fTypes.fBool.get(); *outResultType = context.fTypes.fBool.get(); return left.canCoerceTo(*context.fTypes.fBool, allowNarrowing) && right.canCoerceTo(*context.fTypes.fBool, allowNarrowing); case Token::Kind::TK_COMMA: // left, right *outLeftType = &left; *outRightType = &right; *outResultType = &right; return true; default: break; } // Boolean types only support the operators listed above (, = == != || && ^^). // If we've gotten this far with a boolean, we have an unsupported operator. const Type& leftComponentType = left.componentType(); const Type& rightComponentType = right.componentType(); if (leftComponentType.isBoolean() || rightComponentType.isBoolean()) { return false; } bool isAssignment = this->isAssignment(); if (this->isMatrixMultiply(left, right)) { // left * right // Determine final component type. if (!this->determineBinaryType(context, left.componentType(), right.componentType(), outLeftType, outRightType, outResultType)) { return false; } // Convert component type to compound. *outLeftType = &(*outResultType)->toCompound(context, left.columns(), left.rows()); *outRightType = &(*outResultType)->toCompound(context, right.columns(), right.rows()); int leftColumns = left.columns(), leftRows = left.rows(); int rightColumns = right.columns(), rightRows = right.rows(); if (right.isVector()) { // `matrix * vector` treats the vector as a column vector; we need to transpose it. std::swap(rightColumns, rightRows); SkASSERT(rightColumns == 1); } if (rightColumns > 1) { *outResultType = &(*outResultType)->toCompound(context, rightColumns, leftRows); } else { // The result was a column vector. Transpose it back to a row. *outResultType = &(*outResultType)->toCompound(context, leftRows, rightColumns); } if (isAssignment && ((*outResultType)->columns() != leftColumns || (*outResultType)->rows() != leftRows)) { return false; } return leftColumns == rightRows; } bool leftIsVectorOrMatrix = left.isVector() || left.isMatrix(); bool validMatrixOrVectorOp = this->isValidForMatrixOrVector(); if (leftIsVectorOrMatrix && validMatrixOrVectorOp && right.isScalar()) { // Determine final component type. if (!this->determineBinaryType(context, left.componentType(), right, outLeftType, outRightType, outResultType)) { return false; } // Convert component type to compound. *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows()); if (!this->isLogical()) { *outResultType = &(*outResultType)->toCompound(context, left.columns(), left.rows()); } return true; } bool rightIsVectorOrMatrix = right.isVector() || right.isMatrix(); if (!isAssignment && rightIsVectorOrMatrix && validMatrixOrVectorOp && left.isScalar()) { // Determine final component type. if (!this->determineBinaryType(context, left, right.componentType(), outLeftType, outRightType, outResultType)) { return false; } // Convert component type to compound. *outRightType = &(*outRightType)->toCompound(context, right.columns(), right.rows()); if (!this->isLogical()) { *outResultType = &(*outResultType)->toCompound(context, right.columns(), right.rows()); } return true; } CoercionCost rightToLeftCost = right.coercionCost(left); CoercionCost leftToRightCost = isAssignment ? CoercionCost::Impossible() : left.coercionCost(right); if ((left.isScalar() && right.isScalar()) || (leftIsVectorOrMatrix && validMatrixOrVectorOp)) { if (this->isOnlyValidForIntegralTypes()) { if (!leftComponentType.isInteger() || !rightComponentType.isInteger()) { return false; } } if (rightToLeftCost.isPossible(allowNarrowing) && rightToLeftCost < leftToRightCost) { // Right-to-Left conversion is possible and cheaper *outLeftType = &left; *outRightType = &left; *outResultType = &left; } else if (leftToRightCost.isPossible(allowNarrowing)) { // Left-to-Right conversion is possible (and at least as cheap as Right-to-Left) *outLeftType = &right; *outRightType = &right; *outResultType = &right; } else { return false; } if (this->isLogical()) { *outResultType = context.fTypes.fBool.get(); } return true; } return false; } } // namespace SkSL