| /* |
| * 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/sksl/SkSLOperator.h" |
| |
| #include "include/core/SkTypes.h" |
| #include "include/private/SkStringView.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/ir/SkSLType.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| namespace SkSL { |
| |
| Operator::Precedence Operator::getBinaryPrecedence() const { |
| switch (this->kind()) { |
| case Kind::STAR: // fall through |
| case Kind::SLASH: // fall through |
| case Kind::PERCENT: return Precedence::kMultiplicative; |
| case Kind::PLUS: // fall through |
| case Kind::MINUS: return Precedence::kAdditive; |
| case Kind::SHL: // fall through |
| case Kind::SHR: return Precedence::kShift; |
| case Kind::LT: // fall through |
| case Kind::GT: // fall through |
| case Kind::LTEQ: // fall through |
| case Kind::GTEQ: return Precedence::kRelational; |
| case Kind::EQEQ: // fall through |
| case Kind::NEQ: return Precedence::kEquality; |
| case Kind::BITWISEAND: return Precedence::kBitwiseAnd; |
| case Kind::BITWISEXOR: return Precedence::kBitwiseXor; |
| case Kind::BITWISEOR: return Precedence::kBitwiseOr; |
| case Kind::LOGICALAND: return Precedence::kLogicalAnd; |
| case Kind::LOGICALXOR: return Precedence::kLogicalXor; |
| case Kind::LOGICALOR: return Precedence::kLogicalOr; |
| case Kind::EQ: // fall through |
| case Kind::PLUSEQ: // fall through |
| case Kind::MINUSEQ: // fall through |
| case Kind::STAREQ: // fall through |
| case Kind::SLASHEQ: // fall through |
| case Kind::PERCENTEQ: // fall through |
| case Kind::SHLEQ: // fall through |
| case Kind::SHREQ: // fall through |
| case Kind::BITWISEANDEQ: // fall through |
| case Kind::BITWISEXOREQ: // fall through |
| case Kind::BITWISEOREQ: return Precedence::kAssignment; |
| case Kind::COMMA: return Precedence::kSequence; |
| default: SK_ABORT("unsupported binary operator"); |
| } |
| } |
| |
| const char* Operator::operatorName() const { |
| switch (this->kind()) { |
| case Kind::PLUS: return " + "; |
| case Kind::MINUS: return " - "; |
| case Kind::STAR: return " * "; |
| case Kind::SLASH: return " / "; |
| case Kind::PERCENT: return " % "; |
| case Kind::SHL: return " << "; |
| case Kind::SHR: return " >> "; |
| case Kind::LOGICALNOT: return "!"; |
| case Kind::LOGICALAND: return " && "; |
| case Kind::LOGICALOR: return " || "; |
| case Kind::LOGICALXOR: return " ^^ "; |
| case Kind::BITWISENOT: return "~"; |
| case Kind::BITWISEAND: return " & "; |
| case Kind::BITWISEOR: return " | "; |
| case Kind::BITWISEXOR: return " ^ "; |
| case Kind::EQ: return " = "; |
| case Kind::EQEQ: return " == "; |
| case Kind::NEQ: return " != "; |
| case Kind::LT: return " < "; |
| case Kind::GT: return " > "; |
| case Kind::LTEQ: return " <= "; |
| case Kind::GTEQ: return " >= "; |
| case Kind::PLUSEQ: return " += "; |
| case Kind::MINUSEQ: return " -= "; |
| case Kind::STAREQ: return " *= "; |
| case Kind::SLASHEQ: return " /= "; |
| case Kind::PERCENTEQ: return " %= "; |
| case Kind::SHLEQ: return " <<= "; |
| case Kind::SHREQ: return " >>= "; |
| case Kind::BITWISEANDEQ: return " &= "; |
| case Kind::BITWISEOREQ: return " |= "; |
| case Kind::BITWISEXOREQ: return " ^= "; |
| case Kind::PLUSPLUS: return "++"; |
| case Kind::MINUSMINUS: return "--"; |
| case Kind::COMMA: return ", "; |
| default: SkUNREACHABLE; |
| } |
| } |
| |
| std::string_view Operator::tightOperatorName() const { |
| std::string_view name = this->operatorName(); |
| if (skstd::starts_with(name, ' ')) { |
| name.remove_prefix(1); |
| } |
| if (skstd::ends_with(name, ' ')) { |
| name.remove_suffix(1); |
| } |
| return name; |
| } |
| |
| bool Operator::isAssignment() const { |
| switch (this->kind()) { |
| case Kind::EQ: // fall through |
| case Kind::PLUSEQ: // fall through |
| case Kind::MINUSEQ: // fall through |
| case Kind::STAREQ: // fall through |
| case Kind::SLASHEQ: // fall through |
| case Kind::PERCENTEQ: // fall through |
| case Kind::SHLEQ: // fall through |
| case Kind::SHREQ: // fall through |
| case Kind::BITWISEOREQ: // fall through |
| case Kind::BITWISEXOREQ: // fall through |
| case Kind::BITWISEANDEQ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| Operator Operator::removeAssignment() const { |
| switch (this->kind()) { |
| case Kind::PLUSEQ: return Kind::PLUS; |
| case Kind::MINUSEQ: return Kind::MINUS; |
| case Kind::STAREQ: return Kind::STAR; |
| case Kind::SLASHEQ: return Kind::SLASH; |
| case Kind::PERCENTEQ: return Kind::PERCENT; |
| case Kind::SHLEQ: return Kind::SHL; |
| case Kind::SHREQ: return Kind::SHR; |
| case Kind::BITWISEOREQ: return Kind::BITWISEOR; |
| case Kind::BITWISEXOREQ: return Kind::BITWISEXOR; |
| case Kind::BITWISEANDEQ: return Kind::BITWISEAND; |
| default: return *this; |
| } |
| } |
| |
| bool Operator::isRelational() const { |
| switch (this->kind()) { |
| case Kind::LT: |
| case Kind::GT: |
| case Kind::LTEQ: |
| case Kind::GTEQ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool Operator::isOnlyValidForIntegralTypes() const { |
| switch (this->kind()) { |
| case Kind::SHL: |
| case Kind::SHR: |
| case Kind::BITWISEAND: |
| case Kind::BITWISEOR: |
| case Kind::BITWISEXOR: |
| case Kind::PERCENT: |
| case Kind::SHLEQ: |
| case Kind::SHREQ: |
| case Kind::BITWISEANDEQ: |
| case Kind::BITWISEOREQ: |
| case Kind::BITWISEXOREQ: |
| case Kind::PERCENTEQ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool Operator::isValidForMatrixOrVector() const { |
| switch (this->kind()) { |
| case Kind::PLUS: |
| case Kind::MINUS: |
| case Kind::STAR: |
| case Kind::SLASH: |
| case Kind::PERCENT: |
| case Kind::SHL: |
| case Kind::SHR: |
| case Kind::BITWISEAND: |
| case Kind::BITWISEOR: |
| case Kind::BITWISEXOR: |
| case Kind::PLUSEQ: |
| case Kind::MINUSEQ: |
| case Kind::STAREQ: |
| case Kind::SLASHEQ: |
| case Kind::PERCENTEQ: |
| case Kind::SHLEQ: |
| case Kind::SHREQ: |
| case Kind::BITWISEANDEQ: |
| case Kind::BITWISEOREQ: |
| case Kind::BITWISEXOREQ: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool Operator::isMatrixMultiply(const Type& left, const Type& right) const { |
| if (this->kind() != Kind::STAR && this->kind() != Kind::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 { |
| const bool allowNarrowing = context.fConfig->fSettings.fAllowNarrowingConversions; |
| switch (this->kind()) { |
| case Kind::EQ: // left = right |
| if (left.isVoid()) { |
| return false; |
| } |
| *outLeftType = &left; |
| *outRightType = &left; |
| *outResultType = &left; |
| return right.canCoerceTo(left, allowNarrowing); |
| |
| case Kind::EQEQ: // left == right |
| case Kind::NEQ: { // left != right |
| if (left.isVoid() || left.isOpaque()) { |
| return false; |
| } |
| 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 Kind::LOGICALOR: // left || right |
| case Kind::LOGICALAND: // left && right |
| case Kind::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 Operator::Kind::COMMA: // left, right |
| if (left.isOpaque() || right.isOpaque()) { |
| return false; |
| } |
| *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->isRelational()) { |
| *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->isRelational()) { |
| *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->isRelational()) { |
| *outResultType = context.fTypes.fBool.get(); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace SkSL |