in src/sksl/codegen/SkSLRasterPipelineCodeGenerator.cpp [2439:2674]
bool Generator::pushBinaryExpression(const Expression& left, Operator op, const Expression& right) {
switch (op.kind()) {
// Rewrite greater-than ops as their less-than equivalents.
case OperatorKind::GT:
return this->pushBinaryExpression(right, OperatorKind::LT, left);
case OperatorKind::GTEQ:
return this->pushBinaryExpression(right, OperatorKind::LTEQ, left);
// Handle struct and array comparisons.
case OperatorKind::EQEQ:
case OperatorKind::NEQ:
if (left.type().isStruct() || left.type().isArray()) {
SkASSERT(left.type().matches(right.type()));
std::unique_ptr<LValue> lvLeft = this->makeLValue(left, /*allowScratch=*/true);
std::unique_ptr<LValue> lvRight = this->makeLValue(right, /*allowScratch=*/true);
return this->pushStructuredComparison(lvLeft.get(), op, lvRight.get(), left.type());
}
[[fallthrough]];
// Rewrite commutative ops so that the literal is on the right-hand side. This gives the
// Builder more opportunities to use immediate-mode ops.
case OperatorKind::PLUS:
case OperatorKind::STAR:
case OperatorKind::BITWISEAND:
case OperatorKind::BITWISEXOR:
case OperatorKind::LOGICALXOR: {
double unused;
if (ConstantFolder::GetConstantValue(left, &unused) &&
!ConstantFolder::GetConstantValue(right, &unused)) {
return this->pushBinaryExpression(right, op, left);
}
break;
}
// Emit comma expressions.
case OperatorKind::COMMA:
if (Analysis::HasSideEffects(left)) {
if (!this->pushExpression(left, /*usesResult=*/false)) {
return unsupported();
}
this->discardExpression(left.type().slotCount());
}
return this->pushExpression(right);
default:
break;
}
// Handle binary expressions with mismatched types.
bool vectorizeLeft = false, vectorizeRight = false;
if (!left.type().matches(right.type())) {
if (left.type().componentType().numberKind() != right.type().componentType().numberKind()) {
return unsupported();
}
if (left.type().isScalar() && (right.type().isVector() || right.type().isMatrix())) {
vectorizeLeft = true;
} else if ((left.type().isVector() || left.type().isMatrix()) && right.type().isScalar()) {
vectorizeRight = true;
}
}
const Type& type = vectorizeLeft ? right.type() : left.type();
// If this is an assignment...
std::unique_ptr<LValue> lvalue;
if (op.isAssignment()) {
// ... turn the left side into an lvalue.
lvalue = this->makeLValue(left);
if (!lvalue) {
return unsupported();
}
// Handle simple assignment (`var = expr`).
if (op.kind() == OperatorKind::EQ) {
return this->pushExpression(right) &&
this->store(*lvalue);
}
// Strip off the assignment from the op (turning += into +).
op = op.removeAssignment();
}
// Handle matrix multiplication (MxM/MxV/VxM).
if (op.kind() == OperatorKind::STAR) {
// Matrix * matrix:
if (left.type().isMatrix() && right.type().isMatrix()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), left.type().rows(),
right.type().columns(), right.type().rows());
}
// Vector * matrix:
if (left.type().isVector() && right.type().isMatrix()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), 1,
right.type().columns(), right.type().rows());
}
// Matrix * vector:
if (left.type().isMatrix() && right.type().isVector()) {
return this->pushMatrixMultiply(lvalue.get(), left, right,
left.type().columns(), left.type().rows(),
1, right.type().columns());
}
}
if (!vectorizeLeft && !vectorizeRight && !type.matches(right.type())) {
// We have mismatched types but don't know how to handle them.
return unsupported();
}
// Handle binary ops which require short-circuiting.
switch (op.kind()) {
case OperatorKind::LOGICALAND:
if (Analysis::HasSideEffects(right)) {
// If the RHS has side effects, we rewrite `a && b` as `a ? b : false`. This
// generates pretty solid code and gives us the required short-circuit behavior.
SkASSERT(!op.isAssignment());
SkASSERT(type.componentType().isBoolean());
SkASSERT(type.slotCount() == 1); // operator&& only works with scalar types
Literal falseLiteral{Position{}, 0.0, &right.type()};
return this->pushTernaryExpression(left, right, falseLiteral);
}
break;
case OperatorKind::LOGICALOR:
if (Analysis::HasSideEffects(right)) {
// If the RHS has side effects, we rewrite `a || b` as `a ? true : b`.
SkASSERT(!op.isAssignment());
SkASSERT(type.componentType().isBoolean());
SkASSERT(type.slotCount() == 1); // operator|| only works with scalar types
Literal trueLiteral{Position{}, 1.0, &right.type()};
return this->pushTernaryExpression(left, trueLiteral, right);
}
break;
default:
break;
}
// Push the left- and right-expressions onto the stack.
if (!this->pushLValueOrExpression(lvalue.get(), left)) {
return unsupported();
}
if (vectorizeLeft) {
fBuilder.push_duplicates(right.type().slotCount() - 1);
}
if (!this->pushExpression(right)) {
return unsupported();
}
if (vectorizeRight) {
fBuilder.push_duplicates(left.type().slotCount() - 1);
}
switch (op.kind()) {
case OperatorKind::PLUS:
if (!this->binaryOp(type, kAddOps)) {
return unsupported();
}
break;
case OperatorKind::MINUS:
if (!this->binaryOp(type, kSubtractOps)) {
return unsupported();
}
break;
case OperatorKind::STAR:
if (!this->binaryOp(type, kMultiplyOps)) {
return unsupported();
}
break;
case OperatorKind::SLASH:
if (!this->binaryOp(type, kDivideOps)) {
return unsupported();
}
break;
case OperatorKind::LT:
case OperatorKind::GT:
if (!this->binaryOp(type, kLessThanOps)) {
return unsupported();
}
SkASSERT(type.slotCount() == 1); // operator< only works with scalar types
break;
case OperatorKind::LTEQ:
case OperatorKind::GTEQ:
if (!this->binaryOp(type, kLessThanEqualOps)) {
return unsupported();
}
SkASSERT(type.slotCount() == 1); // operator<= only works with scalar types
break;
case OperatorKind::EQEQ:
if (!this->binaryOp(type, kEqualOps)) {
return unsupported();
}
this->foldComparisonOp(op, type.slotCount());
break;
case OperatorKind::NEQ:
if (!this->binaryOp(type, kNotEqualOps)) {
return unsupported();
}
this->foldComparisonOp(op, type.slotCount());
break;
case OperatorKind::LOGICALAND:
case OperatorKind::BITWISEAND:
// For logical-and, we verified above that the RHS does not have side effects, so we
// don't need to worry about short-circuiting side effects.
fBuilder.binary_op(BuilderOp::bitwise_and_n_ints, type.slotCount());
break;
case OperatorKind::LOGICALOR:
case OperatorKind::BITWISEOR:
// For logical-or, we verified above that the RHS does not have side effects.
fBuilder.binary_op(BuilderOp::bitwise_or_n_ints, type.slotCount());
break;
case OperatorKind::LOGICALXOR:
case OperatorKind::BITWISEXOR:
// Logical-xor does not short circuit.
fBuilder.binary_op(BuilderOp::bitwise_xor_n_ints, type.slotCount());
break;
default:
return unsupported();
}
// If we have an lvalue, we need to write the result back into it.
return lvalue ? this->store(*lvalue)
: true;
}