blob: 6c9ddc92b47c9f2d46301873c6688ea18d5a8e04 [file] [log] [blame]
/*
* 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 "src/base/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>
namespace SkSL {
OperatorPrecedence Operator::getBinaryPrecedence() const {
switch (this->kind()) {
case Kind::STAR: // fall through
case Kind::SLASH: // fall through
case Kind::PERCENT: return OperatorPrecedence::kMultiplicative;
case Kind::PLUS: // fall through
case Kind::MINUS: return OperatorPrecedence::kAdditive;
case Kind::SHL: // fall through
case Kind::SHR: return OperatorPrecedence::kShift;
case Kind::LT: // fall through
case Kind::GT: // fall through
case Kind::LTEQ: // fall through
case Kind::GTEQ: return OperatorPrecedence::kRelational;
case Kind::EQEQ: // fall through
case Kind::NEQ: return OperatorPrecedence::kEquality;
case Kind::BITWISEAND: return OperatorPrecedence::kBitwiseAnd;
case Kind::BITWISEXOR: return OperatorPrecedence::kBitwiseXor;
case Kind::BITWISEOR: return OperatorPrecedence::kBitwiseOr;
case Kind::LOGICALAND: return OperatorPrecedence::kLogicalAnd;
case Kind::LOGICALXOR: return OperatorPrecedence::kLogicalXor;
case Kind::LOGICALOR: return OperatorPrecedence::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 OperatorPrecedence::kAssignment;
case Kind::COMMA: return OperatorPrecedence::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