Align SkSL const rules more closely with GLSL
This adds Analysis::IsConstantExpression, to determine if an expression
is a constant-expression. It now expands to cover 'const' local and
global variables, because we also enforce that the initializer on those
variables is - in turn - a constant expression.
This fixes 10837 - previously you could initialize a const variable with
a non-constant expression, and we'd emit GLSL that contained that same
pattern, which would fail to compile at the driver level. That should
not be possible any longer.
Bug: skia:10679
Bug: skia:10837
Change-Id: I517820ef4da57fff45768c0b04c55aebc18d3272
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/375856
Reviewed-by: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index 084e829..88ba6f1 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -86,6 +86,7 @@
"/sksl/errors/ArrayUnspecifiedDimensions.sksl",
"/sksl/errors/AssignmentTypeMismatch.sksl",
"/sksl/errors/BadCaps.sksl",
+ "/sksl/errors/BadConstInitializers.sksl",
"/sksl/errors/BadFieldAccess.sksl",
"/sksl/errors/BadIndex.sksl",
"/sksl/errors/BadModifiers.sksl",
diff --git a/resources/sksl/errors/BadConstInitializers.sksl b/resources/sksl/errors/BadConstInitializers.sksl
new file mode 100644
index 0000000..5c45f76
--- /dev/null
+++ b/resources/sksl/errors/BadConstInitializers.sksl
@@ -0,0 +1,22 @@
+uniform float u;
+
+const float c = 1;
+ float f = 1;
+
+struct S { float f; } s;
+
+// Valid constant-expression initializers. Should not produce errors:
+void from_literal() { const float x = 0; }
+void from_const_global() { const float x = c; }
+void from_const_local() { const float x = 0; const float y = x; }
+void from_const_constructor() { const int i = int(c); }
+void from_expression() { const float x = 2 * c; }
+
+// Invalid constant-expression initializers. Should all produce errors:
+void from_uniform() { const float x = u; }
+void from_parameter(float p) { const float x = p; }
+void from_const_parameter(const float p) { const float x = p; }
+void from_non_const_local() { float x = u; const float y = x; }
+void from_non_const_expression() { const float x = u + u; }
+void from_mixed_expression() { const float x = c + u; }
+void from_non_const_struct_field() { const float x = s.f; }
diff --git a/resources/sksl/runtime_errors/IllegalIndexing.rte b/resources/sksl/runtime_errors/IllegalIndexing.rte
index 0a1ee83..9b3789a 100644
--- a/resources/sksl/runtime_errors/IllegalIndexing.rte
+++ b/resources/sksl/runtime_errors/IllegalIndexing.rte
@@ -1,7 +1,8 @@
-// Expect 4 errors
+// Expect 3 errors
// Legal cases that should not produce errors:
void literal_index() { int a[2]; a[0] = 0; a[1] = a[0]; }
+void const_var_index() { int a[2]; const int b = 0; a[b] = 0; }
void loop_index() { int a[2]; for (int i = 0; i < 2; ++i) { a[i] = i; } }
void loop_expr_binary() { int a[2]; for (int i = 0; i < 2; ++i) { a[1 - i] = i; } }
void loop_expr_unary() { int a[2]; for (int i = 0; i > -2; --i) { a[-i] = i; } }
@@ -26,4 +27,3 @@
// Legal in GLSL, not yet in SkSL:
void func_index() { int a[2]; a[int(abs(-1))] = 0; } // skbug.com/10835
-void const_var_index() { int a[2]; const int b = 0; a[b] = 0; } // skbug.com/10837
diff --git a/resources/sksl/shared/ConstVariableComparison.sksl b/resources/sksl/shared/ConstVariableComparison.sksl
index 177ebf3..80fdf95 100644
--- a/resources/sksl/shared/ConstVariableComparison.sksl
+++ b/resources/sksl/shared/ConstVariableComparison.sksl
@@ -3,7 +3,9 @@
half4 main() {
const float4 a = float4(0);
const float4 b = float4(1);
- const float4 c = abs(b); // still a Constant Expression, but SkSL cannot eliminate it (today)
+ // This is a constant-expression in GLSL, but not in SkSL (yet).
+ // We can't declare this const, and we can't eliminate it. skbug.com/10835
+ /*const*/ float4 c = abs(b);
if (a == b || b != c) {
return colorRed;
} else {
diff --git a/src/gpu/tessellate/GrStencilPathShader.cpp b/src/gpu/tessellate/GrStencilPathShader.cpp
index 0de7925..ba9039d 100644
--- a/src/gpu/tessellate/GrStencilPathShader.cpp
+++ b/src/gpu/tessellate/GrStencilPathShader.cpp
@@ -351,8 +351,8 @@
const auto& shader = args.fGP.cast<GrMiddleOutCubicShader>();
args.fVaryingHandler->emitAttributes(shader);
args.fVertBuilder->defineConstantf("int", "kMaxVertexID", "%i", 1 << kMaxResolveLevel);
- args.fVertBuilder->defineConstantf("float", "kInverseMaxVertexID", "exp2(-%i.0)",
- kMaxResolveLevel);
+ args.fVertBuilder->defineConstantf("float", "kInverseMaxVertexID",
+ "(1.0 / float(kMaxVertexID))");
args.fVertBuilder->insertFunction(kUnpackRationalCubicFn);
args.fVertBuilder->insertFunction(kEvalRationalCubicFn);
args.fVertBuilder->codeAppend(R"(
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index dbb95f6..8002ea5 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -732,9 +732,11 @@
return true;
}
-class ES2IndexExpressionVisitor : public ProgramVisitor {
+// Checks for ES2 constant-expression rules, and (optionally) constant-index-expression rules
+// (if loopIndices is non-nullptr)
+class ConstantExpressionVisitor : public ProgramVisitor {
public:
- ES2IndexExpressionVisitor(const std::set<const Variable*>* loopIndices)
+ ConstantExpressionVisitor(const std::set<const Variable*>* loopIndices)
: fLoopIndices(loopIndices) {}
bool visitExpression(const Expression& e) override {
@@ -746,14 +748,17 @@
case Expression::Kind::kFloatLiteral:
return false;
- // ... loop indices as defined in section 4. Today, SkSL allows no other variables,
- // but GLSL allows 'const' variables, because it requires that const variables be
- // initialized with constant-expressions. We could support that usage by checking
- // for variables that are const, and whose initializing expression also pass our
- // checks. For now, we'll be conservative. (skbug.com/10837)
- case Expression::Kind::kVariableReference:
- return fLoopIndices->find(e.as<VariableReference>().variable()) ==
- fLoopIndices->end();
+ // ... a global or local variable qualified as 'const', excluding function parameters.
+ // ... loop indices as defined in section 4. [constant-index-expression]
+ case Expression::Kind::kVariableReference: {
+ const Variable* v = e.as<VariableReference>().variable();
+ if ((v->storage() == Variable::Storage::kGlobal ||
+ v->storage() == Variable::Storage::kLocal) &&
+ (v->modifiers().fFlags & Modifiers::kConst_Flag)) {
+ return false;
+ }
+ return !fLoopIndices || fLoopIndices->find(v) == fLoopIndices->end();
+ }
// ... expressions composed of both of the above
case Expression::Kind::kBinary:
@@ -766,7 +771,7 @@
case Expression::Kind::kTernary:
return INHERITED::visitExpression(e);
- // These are completely disallowed in SkSL constant-index-expressions. GLSL allows
+ // These are completely disallowed in SkSL constant-(index)-expressions. GLSL allows
// calls to built-in functions where the arguments are all constant-expressions, but
// we don't guarantee that behavior. (skbug.com/10835)
case Expression::Kind::kExternalFunctionCall:
@@ -811,7 +816,7 @@
bool visitExpression(const Expression& e) override {
if (e.is<IndexExpression>()) {
const IndexExpression& i = e.as<IndexExpression>();
- ES2IndexExpressionVisitor indexerInvalid(&fLoopIndices);
+ ConstantExpressionVisitor indexerInvalid(&fLoopIndices);
if (indexerInvalid.visitExpression(*i.index())) {
fErrors.error(i.fOffset, "index expression must be constant");
return true;
@@ -834,6 +839,11 @@
visitor.visitProgramElement(pe);
}
+bool Analysis::IsConstantExpression(const Expression& expr) {
+ ConstantExpressionVisitor visitor(/*loopIndices=*/nullptr);
+ return !visitor.visitExpression(expr);
+}
+
////////////////////////////////////////////////////////////////////////////////
// ProgramVisitor
diff --git a/src/sksl/SkSLAnalysis.h b/src/sksl/SkSLAnalysis.h
index ee78274..190593b 100644
--- a/src/sksl/SkSLAnalysis.h
+++ b/src/sksl/SkSLAnalysis.h
@@ -83,6 +83,21 @@
// - myStruct.myArrayField[7].xyz
static bool IsTrivialExpression(const Expression& expr);
+ // Is 'expr' a constant-expression, as defined by GLSL 1.0, section 5.10? A constant expression
+ // is one of:
+ //
+ // - A literal value
+ // - A global or local variable qualified as 'const', excluding function parameters
+ // - An expression formed by an operator on operands that are constant expressions, including
+ // getting an element of a constant vector or a constant matrix, or a field of a constant
+ // structure
+ // - A constructor whose arguments are all constant expressions
+ //
+ // GLSL (but not SkSL, yet - skbug.com/10835) also provides:
+ // - A built-in function call whose arguments are all constant expressions, with the exception
+ // of the texture lookup functions
+ static bool IsConstantExpression(const Expression& expr);
+
struct UnrollableLoopInfo {
const Variable* fIndex;
double fStart;
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index d1f954e..c6881fd 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -406,9 +406,16 @@
std::unique_ptr<Statement> IRGenerator::convertVarDeclaration(std::unique_ptr<Variable> var,
std::unique_ptr<Expression> value) {
- if ((var->modifiers().fFlags & Modifiers::kConst_Flag) && !value) {
- this->errorReporter().error(var->fOffset, "'const' variables must be initialized");
- return nullptr;
+ if (var->modifiers().fFlags & Modifiers::kConst_Flag) {
+ if (!value) {
+ this->errorReporter().error(var->fOffset, "'const' variables must be initialized");
+ return nullptr;
+ }
+ if (!Analysis::IsConstantExpression(*value)) {
+ this->errorReporter().error(
+ value->fOffset, "'const' variable initializer must be a constant expression");
+ return nullptr;
+ }
}
if (value) {
if (var->type().isOpaque()) {
diff --git a/tests/sksl/errors/BadConstInitializers.glsl b/tests/sksl/errors/BadConstInitializers.glsl
new file mode 100644
index 0000000..25fcb94
--- /dev/null
+++ b/tests/sksl/errors/BadConstInitializers.glsl
@@ -0,0 +1,10 @@
+### Compilation failed:
+
+error: 16: 'const' variable initializer must be a constant expression
+error: 17: 'const' variable initializer must be a constant expression
+error: 18: 'const' variable initializer must be a constant expression
+error: 19: 'const' variable initializer must be a constant expression
+error: 20: 'const' variable initializer must be a constant expression
+error: 21: 'const' variable initializer must be a constant expression
+error: 22: 'const' variable initializer must be a constant expression
+7 errors
diff --git a/tests/sksl/runtime_errors/IllegalIndexing.skvm b/tests/sksl/runtime_errors/IllegalIndexing.skvm
index a04fd78..cf4620b 100644
--- a/tests/sksl/runtime_errors/IllegalIndexing.skvm
+++ b/tests/sksl/runtime_errors/IllegalIndexing.skvm
@@ -1,7 +1,6 @@
### Compilation failed:
-error: 24: index expression must be constant
error: 25: index expression must be constant
-error: 28: index expression must be constant
+error: 26: index expression must be constant
error: 29: index expression must be constant
-4 errors
+3 errors
diff --git a/tests/sksl/shared/ConstVariableComparison.glsl b/tests/sksl/shared/ConstVariableComparison.glsl
index 5022fba..176f9f0 100644
--- a/tests/sksl/shared/ConstVariableComparison.glsl
+++ b/tests/sksl/shared/ConstVariableComparison.glsl
@@ -3,7 +3,7 @@
uniform vec4 colorGreen;
uniform vec4 colorRed;
vec4 main() {
- const vec4 c = abs(vec4(1.0));
+ vec4 c = abs(vec4(1.0));
if (vec4(1.0) != c) {
return colorRed;
} else {
diff --git a/tests/sksl/shared/ConstVariableComparison.metal b/tests/sksl/shared/ConstVariableComparison.metal
index 577347d..5473abb 100644
--- a/tests/sksl/shared/ConstVariableComparison.metal
+++ b/tests/sksl/shared/ConstVariableComparison.metal
@@ -15,7 +15,7 @@
fragment Outputs fragmentMain(Inputs _in [[stage_in]], constant Uniforms& _uniforms [[buffer(0)]], bool _frontFacing [[front_facing]], float4 _fragCoord [[position]]) {
Outputs _out;
(void)_out;
- const float4 c = abs(float4(1.0));
+ float4 c = abs(float4(1.0));
if (any(float4(1.0) != c)) {
_out.sk_FragColor = _uniforms.colorRed;
return _out;