Add GetConstantValueForVariable to constant-folder.

This can be used in places which simplify an expression at IR generation
time. e.g. `const bool x = true; @if (x) {...}` could be evaluated at
compile time by calling GetConstantValueForVariable before checking for
a BoolLiteral and flattening the if. It is also used at the top of
ConstantFolder::Simplify to reduce either side of the binary
expression being simplified, in case it's a const variable.

We don't use a lot of const variables in our tests, but this does
improve the code generation in ConstVariableComparison.sksl
when constant-propagation is disabled: http://screen/gnWPhQG8Jc5cER9

Change-Id: I26309769cd16a6833b74b11a115b87c3dc312514
Bug: skia:11343
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/378017
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
John Stiles 2021-03-02 17:02:25 -05:00
parent 12e760e958
commit e80e169ba4
2 changed files with 85 additions and 61 deletions

View File

@ -123,33 +123,21 @@ static Constructor splat_scalar(const Expression& scalar, const Type& type) {
}
bool ConstantFolder::GetConstantInt(const Expression& value, SKSL_INT* out) {
switch (value.kind()) {
case Expression::Kind::kIntLiteral:
*out = value.as<IntLiteral>().value();
return true;
case Expression::Kind::kVariableReference: {
const Variable& var = *value.as<VariableReference>().variable();
return (var.modifiers().fFlags & Modifiers::kConst_Flag) &&
var.initialValue() && GetConstantInt(*var.initialValue(), out);
}
default:
return false;
const Expression* expr = GetConstantValueForVariable(value);
if (!expr->is<IntLiteral>()) {
return false;
}
*out = expr->as<IntLiteral>().value();
return true;
}
bool ConstantFolder::GetConstantFloat(const Expression& value, SKSL_FLOAT* out) {
switch (value.kind()) {
case Expression::Kind::kFloatLiteral:
*out = value.as<FloatLiteral>().value();
return true;
case Expression::Kind::kVariableReference: {
const Variable& var = *value.as<VariableReference>().variable();
return (var.modifiers().fFlags & Modifiers::kConst_Flag) &&
var.initialValue() && GetConstantFloat(*var.initialValue(), out);
}
default:
return false;
const Expression* expr = GetConstantValueForVariable(value);
if (!expr->is<FloatLiteral>()) {
return false;
}
*out = expr->as<FloatLiteral>().value();
return true;
}
static bool contains_constant_zero(const Expression& expr) {
@ -161,10 +149,9 @@ static bool contains_constant_zero(const Expression& expr) {
}
return false;
}
SKSL_INT intValue;
SKSL_FLOAT floatValue;
return (ConstantFolder::GetConstantInt(expr, &intValue) && intValue == 0) ||
(ConstantFolder::GetConstantFloat(expr, &floatValue) && floatValue == 0.0f);
const Expression* value = ConstantFolder::GetConstantValueForVariable(expr);
return (value->is<IntLiteral>() && value->as<IntLiteral>().value() == 0.0) ||
(value->is<FloatLiteral>() && value->as<FloatLiteral>().value() == 0.0);
}
bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Operator op,
@ -184,28 +171,58 @@ bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Ope
}
}
const Expression* ConstantFolder::GetConstantValueForVariable(const Expression& inExpr) {
for (const Expression* expr = &inExpr;;) {
if (!expr->is<VariableReference>()) {
break;
}
const VariableReference& varRef = expr->as<VariableReference>();
if (varRef.refKind() != VariableRefKind::kRead) {
break;
}
const Variable& var = *varRef.variable();
if (!(var.modifiers().fFlags & Modifiers::kConst_Flag)) {
break;
}
expr = var.initialValue();
SkASSERT(expr);
if (expr->isCompileTimeConstant()) {
return expr;
}
if (!expr->is<VariableReference>()) {
break;
}
}
// We didn't find a compile-time constant at the end. Return the expression as-is.
return &inExpr;
}
std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
int offset,
const Expression& left,
const Expression& leftExpr,
Operator op,
const Expression& right) {
const Expression& rightExpr) {
// Replace constant variables with trivial initial-values.
const Expression* left = GetConstantValueForVariable(leftExpr);
const Expression* right = GetConstantValueForVariable(rightExpr);
// If this is the comma operator, the left side is evaluated but not otherwise used in any way.
// So if the left side has no side effects, it can just be eliminated entirely.
if (op.kind() == Token::Kind::TK_COMMA && !left.hasSideEffects()) {
return right.clone();
if (op.kind() == Token::Kind::TK_COMMA && !left->hasSideEffects()) {
return right->clone();
}
// 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() == Token::Kind::TK_EQ && Analysis::IsSelfAssignment(left, right)) {
return right.clone();
if (op.kind() == Token::Kind::TK_EQ && Analysis::IsSelfAssignment(*left, *right)) {
return right->clone();
}
// Simplify the expression when both sides are constant Boolean literals.
if (left.is<BoolLiteral>() && right.is<BoolLiteral>()) {
bool leftVal = left.as<BoolLiteral>().value();
bool rightVal = right.as<BoolLiteral>().value();
if (left->is<BoolLiteral>() && right->is<BoolLiteral>()) {
bool leftVal = left->as<BoolLiteral>().value();
bool rightVal = right->as<BoolLiteral>().value();
bool result;
switch (op.kind()) {
case Token::Kind::TK_LOGICALAND: result = leftVal && rightVal; break;
@ -219,28 +236,28 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
}
// If the left side is a Boolean literal, apply short-circuit optimizations.
if (left.is<BoolLiteral>()) {
return short_circuit_boolean(left, op, right);
if (left->is<BoolLiteral>()) {
return short_circuit_boolean(*left, op, *right);
}
// If the right side is a Boolean literal...
if (right.is<BoolLiteral>()) {
if (right->is<BoolLiteral>()) {
// ... and the left side has no side effects...
if (!left.hasSideEffects()) {
if (!left->hasSideEffects()) {
// We can reverse the expressions and short-circuit optimizations are still valid.
return short_circuit_boolean(right, op, left);
return short_circuit_boolean(*right, op, *left);
}
// We can't use short-circuiting, but we can still optimize away no-op Boolean expressions.
return eliminate_no_op_boolean(left, op, right);
return eliminate_no_op_boolean(*left, op, *right);
}
if (ErrorOnDivideByZero(context, offset, op, right)) {
if (ErrorOnDivideByZero(context, offset, op, *right)) {
return nullptr;
}
// Other than the short-circuit cases above, constant folding requires both sides to be constant
if (!left.isCompileTimeConstant() || !right.isCompileTimeConstant()) {
if (!left->isCompileTimeConstant() || !right->isCompileTimeConstant()) {
return nullptr;
}
@ -253,9 +270,9 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
#define URESULT(t, op) std::make_unique<t ## Literal>(context, offset, \
(SKSL_UINT) leftVal op \
(SKSL_UINT) rightVal)
if (left.is<IntLiteral>() && right.is<IntLiteral>()) {
SKSL_INT leftVal = left.as<IntLiteral>().value();
SKSL_INT rightVal = right.as<IntLiteral>().value();
if (left->is<IntLiteral>() && right->is<IntLiteral>()) {
SKSL_INT leftVal = left->as<IntLiteral>().value();
SKSL_INT rightVal = right->as<IntLiteral>().value();
switch (op.kind()) {
case Token::Kind::TK_PLUS: return URESULT(Int, +);
case Token::Kind::TK_MINUS: return URESULT(Int, -);
@ -302,9 +319,9 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
}
// Perform constant folding on pairs of floating-point literals.
if (left.is<FloatLiteral>() && right.is<FloatLiteral>()) {
SKSL_FLOAT leftVal = left.as<FloatLiteral>().value();
SKSL_FLOAT rightVal = right.as<FloatLiteral>().value();
if (left->is<FloatLiteral>() && right->is<FloatLiteral>()) {
SKSL_FLOAT leftVal = left->as<FloatLiteral>().value();
SKSL_FLOAT rightVal = right->as<FloatLiteral>().value();
switch (op.kind()) {
case Token::Kind::TK_PLUS: return RESULT(Float, +);
case Token::Kind::TK_MINUS: return RESULT(Float, -);
@ -321,14 +338,14 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
}
// Perform constant folding on pairs of vectors.
const Type& leftType = left.type();
const Type& rightType = right.type();
const Type& leftType = left->type();
const Type& rightType = right->type();
if (leftType.isVector() && leftType == rightType) {
if (leftType.componentType().isFloat()) {
return simplify_vector<SKSL_FLOAT>(context, left, op, right);
return simplify_vector<SKSL_FLOAT>(context, *left, op, *right);
}
if (leftType.componentType().isInteger()) {
return simplify_vector<SKSL_INT, SKSL_UINT>(context, left, op, right);
return simplify_vector<SKSL_INT, SKSL_UINT>(context, *left, op, *right);
}
return nullptr;
}
@ -336,11 +353,12 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
// Perform constant folding on vectors against scalars, e.g.: half4(2) + 2
if (leftType.isVector() && leftType.componentType() == rightType) {
if (rightType.isFloat()) {
return simplify_vector<SKSL_FLOAT>(context, left, op, splat_scalar(right, left.type()));
return simplify_vector<SKSL_FLOAT>(context, *left, op,
splat_scalar(*right, left->type()));
}
if (rightType.isInteger()) {
return simplify_vector<SKSL_INT, SKSL_UINT>(context, left, op,
splat_scalar(right, left.type()));
return simplify_vector<SKSL_INT, SKSL_UINT>(context, *left, op,
splat_scalar(*right, left->type()));
}
return nullptr;
}
@ -348,12 +366,12 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
// Perform constant folding on scalars against vectors, e.g.: 2 + half4(2)
if (rightType.isVector() && rightType.componentType() == leftType) {
if (leftType.isFloat()) {
return simplify_vector<SKSL_FLOAT>(context, splat_scalar(left, right.type()), op,
right);
return simplify_vector<SKSL_FLOAT>(context, splat_scalar(*left, right->type()), op,
*right);
}
if (leftType.isInteger()) {
return simplify_vector<SKSL_INT, SKSL_UINT>(context, splat_scalar(left, right.type()),
op, right);
return simplify_vector<SKSL_INT, SKSL_UINT>(context, splat_scalar(*left, right->type()),
op, *right);
}
return nullptr;
}
@ -372,7 +390,7 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
return nullptr;
}
switch (left.compareConstant(right)) {
switch (left->compareConstant(*right)) {
case Expression::ComparisonResult::kNotEqual:
equality = !equality;
[[fallthrough]];

View File

@ -37,6 +37,12 @@ public:
*/
static bool GetConstantFloat(const Expression& value, SKSL_FLOAT* out);
/**
* If the expression is a const variable with a known compile-time-constant value, returns that
* value. If not, returns the original expression as-is.
*/
static const Expression* GetConstantValueForVariable(const Expression& value);
/**
* Reports an error and returns true if op is a division / mod operator and right is zero or
* contains a zero element.