// When the literal is on the left, we can sometimes eliminate the other expression entirely. if ((op.kind() == Operator::Kind::LOGICALAND && !leftVal) || // (false && expr) -> (false)
(op.kind() == Operator::Kind::LOGICALOR && leftVal)) { // (true || expr) -> (true)
return left.clone(pos);
}
// We can't eliminate the right-side expression via short-circuit, but we might still be able to // simplify away a no-op expression. return eliminate_no_op_boolean(pos, right, op, left);
}
switch (left.compareConstant(right)) { case Expression::ComparisonResult::kNotEqual:
equality = !equality;
[[fallthrough]];
case Expression::ComparisonResult::kEqual: return Literal::MakeBool(context, pos, equality);
case Expression::ComparisonResult::kUnknown: break;
}
} return nullptr;
}
static std::unique_ptr<Expression> simplify_matrix_multiplication(const Context& context,
Position pos, const Expression& left, const Expression& right, int leftColumns, int leftRows, int rightColumns, int rightRows) { const Type& componentType = left.type().componentType();
SkASSERT(componentType.matches(right.type().componentType()));
// Fetch the left matrix. double leftVals[4][4]; for (int c = 0; c < leftColumns; ++c) { for (int r = 0; r < leftRows; ++r) {
leftVals[c][r] = *left.getConstantValue((c * leftRows) + r);
}
} // Fetch the right matrix. double rightVals[4][4]; for (int c = 0; c < rightColumns; ++c) { for (int r = 0; r < rightRows; ++r) {
rightVals[c][r] = *right.getConstantValue((c * rightRows) + r);
}
}
SkASSERT(leftColumns == rightRows); int outColumns = rightColumns,
outRows = leftRows;
double args[16]; int argIndex = 0; for (int c = 0; c < outColumns; ++c) { for (int r = 0; r < outRows; ++r) { // Compute a dot product for this position. double val = 0; for (int dotIdx = 0; dotIdx < leftColumns; ++dotIdx) {
val += leftVals[dotIdx][r] * rightVals[c][dotIdx];
}
if (val >= -FLT_MAX && val <= FLT_MAX) {
args[argIndex++] = val;
} else { // The value is outside the 32-bit float range, or is NaN; do not optimize. return nullptr;
}
}
}
if (outColumns == 1) { // Matrix-times-vector conceptually makes a 1-column N-row matrix, but we return vecN.
std::swap(outColumns, outRows);
}
staticbool contains_constant_zero(const Expression& expr) { int numSlots = expr.type().slotCount(); for (int index = 0; index < numSlots; ++index) {
std::optional<double> slotVal = expr.getConstantValue(index); if (slotVal.has_value() && *slotVal == 0.0) { returntrue;
}
} returnfalse;
}
bool ConstantFolder::IsConstantSplat(const Expression& expr, double value) { int numSlots = expr.type().slotCount(); for (int index = 0; index < numSlots; ++index) {
std::optional<double> slotVal = expr.getConstantValue(index); if (!slotVal.has_value() || *slotVal != value) { returnfalse;
}
} returntrue;
}
// Returns true if the expression is a square diagonal matrix containing `value`. staticbool is_constant_diagonal(const Expression& expr, double value) {
SkASSERT(expr.type().isMatrix()); int columns = expr.type().columns(); int rows = expr.type().rows(); if (columns != rows) { returnfalse;
} int slotIdx = 0; for (int c = 0; c < columns; ++c) { for (int r = 0; r < rows; ++r) { double expectation = (c == r) ? value : 0;
std::optional<double> slotVal = expr.getConstantValue(slotIdx++); if (!slotVal.has_value() || *slotVal != expectation) { returnfalse;
}
}
} returntrue;
}
// Returns true if the expression is a scalar, vector, or diagonal matrix containing `value`. staticbool is_constant_value(const Expression& expr, double value) { return expr.type().isMatrix() ? is_constant_diagonal(expr, value)
: ConstantFolder::IsConstantSplat(expr, value);
}
// The expression represents the right-hand side of a division op. If the division can be // strength-reduced into multiplication by a reciprocal, returns that reciprocal as an expression. // Note that this only supports literal values with safe-to-use reciprocals, and returns null if // Expression contains anything else. static std::unique_ptr<Expression> make_reciprocal_expression(const Context& context, const Expression& right) { if (right.type().isMatrix() || !right.type().componentType().isFloat()) { return nullptr;
} // Verify that each slot contains a finite, non-zero literal, take its reciprocal. double values[4]; int nslots = right.type().slotCount(); for (int index = 0; index < nslots; ++index) {
std::optional<double> value = right.getConstantValue(index); if (!value) { return nullptr;
}
*value = sk_ieee_double_divide(1.0, *value); if (*value >= -FLT_MAX && *value <= FLT_MAX && *value != 0.0) { // The reciprocal can be represented safely as a finite 32-bit float.
values[index] = *value;
} else { // The value is outside the 32-bit float range, or is NaN; do not optimize. return nullptr;
}
} // Turn the expression array into a compound constructor. (If this is a single-slot expression, // this will return the literal as-is.) return ConstructorCompound::MakeFromConstants(context, right.fPosition, right.type(), values);
}
const Expression* ConstantFolder::GetConstantValueOrNull(const Expression& inExpr) { const Expression* expr = &inExpr; while (expr->is<VariableReference>()) { const VariableReference& varRef = expr->as<VariableReference>(); if (varRef.refKind() != VariableRefKind::kRead) { return nullptr;
} const Variable& var = *varRef.variable(); if (!var.modifierFlags().isConst()) { return nullptr;
}
expr = var.initialValue(); if (!expr) { // Generally, const variables must have initial values. However, function parameters are // an exception; they can be const but won't have an initial value. return nullptr;
}
} return Analysis::IsCompileTimeConstant(*expr) ? expr : nullptr;
}
caseOperator::Kind::STAR: if (is_constant_value(right, 1.0)) { // x * 1 if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
resultType)) { return expr;
}
} if (is_constant_value(left, 1.0)) { // 1 * x if (std::unique_ptr<Expression> expr = cast_expression(context, pos, right,
resultType)) { return expr;
}
} if (is_constant_value(right, 0.0) && !Analysis::HasSideEffects(left)) { // x * 0 return zero_expression(context, pos, resultType);
} if (is_constant_value(left, 0.0) && !Analysis::HasSideEffects(right)) { // 0 * x return zero_expression(context, pos, resultType);
} if (is_constant_value(right, -1.0)) { // x * -1 (to `-x`) if (std::unique_ptr<Expression> expr = negate_expression(context, pos, left,
resultType)) { return expr;
}
} if (is_constant_value(left, -1.0)) { // -1 * x (to `-x`) if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right,
resultType)) { return expr;
}
} break;
caseOperator::Kind::MINUS: if (!is_scalar_op_matrix(left, right) &&
ConstantFolder::IsConstantSplat(right, 0.0)) { // x - 0 if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
resultType)) { return expr;
}
} if (!is_matrix_op_scalar(left, right) &&
ConstantFolder::IsConstantSplat(left, 0.0)) { // 0 - x if (std::unique_ptr<Expression> expr = negate_expression(context, pos, right,
resultType)) { return expr;
}
} break;
caseOperator::Kind::SLASH: if (!is_scalar_op_matrix(left, right) &&
ConstantFolder::IsConstantSplat(right, 1.0)) { // x / 1 if (std::unique_ptr<Expression> expr = cast_expression(context, pos, left,
resultType)) { return expr;
}
} if (!left.type().isMatrix()) { // convert `x / 2` into `x * 0.5` if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) { return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAR,
std::move(expr));
}
} break;
caseOperator::Kind::PLUSEQ: caseOperator::Kind::MINUSEQ: if (ConstantFolder::IsConstantSplat(right, 0.0)) { // x += 0, x -= 0 if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
resultType)) {
Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); return var;
}
} break;
caseOperator::Kind::STAREQ: if (is_constant_value(right, 1.0)) { // x *= 1 if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
resultType)) {
Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); return var;
}
} break;
caseOperator::Kind::SLASHEQ: if (ConstantFolder::IsConstantSplat(right, 1.0)) { // x /= 1 if (std::unique_ptr<Expression> var = cast_expression(context, pos, left,
resultType)) {
Analysis::UpdateVariableRefKind(var.get(), VariableRefKind::kRead); return var;
}
} if (std::unique_ptr<Expression> expr = make_reciprocal_expression(context, right)) { return BinaryExpression::Make(context, pos, left.clone(), Operator::Kind::STAREQ,
std::move(expr));
} break;
default: break;
}
return nullptr;
}
// The expression must be scalar, and represents the right-hand side of a division op. It can // contain anything, not just literal values. This returns the binary expression `1.0 / expr`. The // expression might be further simplified by the constant folding, if possible. static std::unique_ptr<Expression> one_over_scalar(const Context& context, const Expression& right) {
SkASSERT(right.type().isScalar());
Position pos = right.fPosition; return BinaryExpression::Make(context, pos,
Literal::Make(pos, 1.0, &right.type()), Operator::Kind::SLASH,
right.clone());
}
static std::unique_ptr<Expression> simplify_matrix_division(const Context& context,
Position pos, const Expression& left, Operator op, const Expression& right, const Type& resultType) { // Convert matrix-over-scalar `x /= y` into `x *= (1.0 / y)`. This generates better // code in SPIR-V and Metal, and should be roughly equivalent elsewhere. switch (op.kind()) { case OperatorKind::SLASH: case OperatorKind::SLASHEQ: if (left.type().isMatrix() && right.type().isScalar()) { Operator multiplyOp = op.isAssignment() ? OperatorKind::STAREQ
: OperatorKind::STAR; return BinaryExpression::Make(context, pos,
left.clone(),
multiplyOp,
one_over_scalar(context, right));
} break;
default: break;
}
return nullptr;
}
static std::unique_ptr<Expression> fold_expression(Position pos, double result, const Type* resultType) { if (resultType->isNumber()) { if (result >= resultType->minimumValue() && result <= resultType->maximumValue()) { // This result will fit inside its type.
} else { // The value is outside the range or is NaN (all if-checks fail); do not optimize. return nullptr;
}
}
caseOperator::Kind::BITWISEAND: return RESULT(&); caseOperator::Kind::BITWISEOR: return RESULT(|); caseOperator::Kind::BITWISEXOR: return RESULT(^); caseOperator::Kind::EQEQ: return RESULT(==); caseOperator::Kind::NEQ: return RESULT(!=); caseOperator::Kind::GT: return RESULT(>); caseOperator::Kind::GTEQ: return RESULT(>=); caseOperator::Kind::LT: return RESULT(<); caseOperator::Kind::LTEQ: return RESULT(<=); caseOperator::Kind::SHL: if (rightVal >= 0 && rightVal <= 31) { // Left-shifting a negative (or really, any signed) value is undefined behavior // in C++, but not in GLSL. Do the shift on unsigned values to avoid triggering // an UBSAN error. return URESULT(<<);
}
context.fErrors->error(pos, "shift value out of range"); return nullptr;
caseOperator::Kind::SHR: if (rightVal >= 0 && rightVal <= 31) { return RESULT(>>);
}
context.fErrors->error(pos, "shift value out of range"); return nullptr;
default: break;
} #undef RESULT #undef URESULT
return nullptr;
}
// Handle pairs of floating-point literals. if (left->isFloatLiteral() && right->isFloatLiteral()) {
SKSL_FLOAT leftVal = left->as<Literal>().floatValue();
SKSL_FLOAT rightVal = right->as<Literal>().floatValue();
// Perform constant folding on pairs of vectors/matrices. if (is_vec_or_mat(leftType) && leftType.matches(rightType)) { return simplify_componentwise(context, pos, *left, op, *right);
}
// Perform constant folding on vectors/matrices against scalars, e.g.: half4(2) + 2 if (rightType.isScalar() && is_vec_or_mat(leftType) &&
leftType.componentType().matches(rightType)) { return simplify_componentwise(context, pos,
*left, op, *splat_scalar(context, *right, left->type()));
}
// Perform constant folding on scalars against vectors/matrices, e.g.: 2 + half4(2) if (leftType.isScalar() && is_vec_or_mat(rightType) &&
rightType.componentType().matches(leftType)) { return simplify_componentwise(context, pos,
*splat_scalar(context, *left, right->type()), op, *right);
}
// Perform constant folding on pairs of matrices, arrays or structs. if ((leftType.isMatrix() && rightType.isMatrix()) ||
(leftType.isArray() && rightType.isArray()) ||
(leftType.isStruct() && rightType.isStruct())) { return simplify_constant_equality(context, pos, *left, op, *right);
}
// We aren't able to constant-fold these expressions. return nullptr;
}
std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
Position pos, const Expression& leftExpr, Operator op, const Expression& rightExpr, const Type& resultType) { // Replace constant variables with their literal values. const Expression* left = GetConstantValueForVariable(leftExpr); const Expression* right = GetConstantValueForVariable(rightExpr);
// If this is the assignment operator, and both sides are the same trivial expression, this is // self-assignment (i.e., `var = var`) and can be reduced to just a variable reference (`var`). // This can happen when other parts of the assignment are optimized away. if (op.kind() == Operator::Kind::EQ && Analysis::IsSameExpressionTree(*left, *right)) { return right->clone(pos);
}
// Simplify the expression when both sides are constant Boolean literals. if (left->isBoolLiteral() && right->isBoolLiteral()) { bool leftVal = left->as<Literal>().boolValue(); bool rightVal = right->as<Literal>().boolValue(); bool result; switch (op.kind()) { caseOperator::Kind::LOGICALAND: result = leftVal && rightVal; break; caseOperator::Kind::LOGICALOR: result = leftVal || rightVal; break; caseOperator::Kind::LOGICALXOR: result = leftVal ^ rightVal; break; caseOperator::Kind::EQEQ: result = leftVal == rightVal; break; caseOperator::Kind::NEQ: result = leftVal != rightVal; break; default: return nullptr;
} return Literal::MakeBool(context, pos, result);
}
// If the left side is a Boolean literal, apply short-circuit optimizations. if (left->isBoolLiteral()) { return short_circuit_boolean(pos, *left, op, *right);
}
// If the right side is a Boolean literal... if (right->isBoolLiteral()) { // ... and the left side has no side effects... if (!Analysis::HasSideEffects(*left)) { // We can reverse the expressions and short-circuit optimizations are still valid. return short_circuit_boolean(pos, *right, op, *left);
}
// We can't use short-circuiting, but we can still optimize away no-op Boolean expressions. return eliminate_no_op_boolean(pos, *left, op, *right);
}
if (op.kind() == Operator::Kind::EQEQ && Analysis::IsSameExpressionTree(*left, *right)) { // With == comparison, if both sides are the same trivial expression, this is self- // comparison and is always true. (We are not concerned with NaN.) return Literal::MakeBool(context, pos, /*value=*/true);
}
if (op.kind() == Operator::Kind::NEQ && Analysis::IsSameExpressionTree(*left, *right)) { // With != comparison, if both sides are the same trivial expression, this is self- // comparison and is always false. (We are not concerned with NaN.) return Literal::MakeBool(context, pos, /*value=*/false);
}
if (error_on_divide_by_zero(context, pos, op, *right)) { return nullptr;
}
// Perform full constant folding when both sides are compile-time constants. bool leftSideIsConstant = Analysis::IsCompileTimeConstant(*left); bool rightSideIsConstant = Analysis::IsCompileTimeConstant(*right); if (leftSideIsConstant && rightSideIsConstant) { return fold_two_constants(context, pos, left, op, right, resultType);
}
if (context.fConfig->fSettings.fOptimize) { // If just one side is constant, we might still be able to simplify arithmetic expressions // like `x * 1`, `x *= 1`, `x + 0`, `x * 0`, `0 / x`, etc. if (leftSideIsConstant || rightSideIsConstant) { if (std::unique_ptr<Expression> expr = simplify_arithmetic(context, pos, *left, op,
*right, resultType)) { return expr;
}
}
// We can simplify some forms of matrix division even when neither side is constant. if (std::unique_ptr<Expression> expr = simplify_matrix_division(context, pos, *left, op,
*right, resultType)) { return expr;
}
}
// We aren't able to constant-fold. return nullptr;
}
} // namespace SkSL
Messung V0.5
¤ Dauer der Verarbeitung: 0.6 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.