Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7b92044

Browse files
johnstiles-googleSkia Commit-Bot
authored and
Skia Commit-Bot
committed
Replace inliner do-while loops with for loops.
do-while loops aren't compatible with GLSL ES2. For-loops which run only one time should work exactly the same for our purposes. We expect such a loop to be unrolled by every driver, so it shouldn't come at any performance cost. Change-Id: Ia8de5fcab8128c34da97eaeaf81f91ad1ac36ce4 Bug: skia:11097 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/345159 Reviewed-by: Brian Osman <[email protected]> Commit-Queue: Brian Osman <[email protected]> Auto-Submit: John Stiles <[email protected]>
1 parent e2f6245 commit 7b92044

8 files changed

+159
-104
lines changed

gn/sksl_tests.gni

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ sksl_settings_tests = [
447447
"$_tests/sksl/glsl/TypePrecision.sksl",
448448
"$_tests/sksl/inliner/ExponentialGrowth.sksl",
449449
"$_tests/sksl/inliner/InlinerCanBeDisabled.sksl",
450-
"$_tests/sksl/inliner/InlinerWrapsEarlyReturnsWithDoWhileBlock.sksl",
450+
"$_tests/sksl/inliner/InlinerWrapsEarlyReturnsWithForLoop.sksl",
451451
"$_tests/sksl/shared/Derivatives.sksl",
452452
"$_tests/sksl/shared/DerivativesFlipY.sksl",
453453
"$_tests/sksl/workarounds/AbsInt.sksl",

src/sksl/SkSLCompiler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class AutoSource {
8787
Compiler::Compiler(const ShaderCapsClass* caps, Flags flags)
8888
: fContext(std::make_shared<Context>())
8989
, fCaps(caps)
90-
, fInliner(fContext.get(), fCaps)
90+
, fInliner(fContext.get())
9191
, fFlags(flags)
9292
, fErrorCount(0) {
9393
SkASSERT(fCaps);

src/sksl/SkSLInliner.cpp

Lines changed: 117 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -520,15 +520,15 @@ std::unique_ptr<Statement> Inliner::inlineStatement(int offset,
520520
StatementArray block;
521521
block.reserve_back(2);
522522
block.push_back(std::move(assignment));
523-
block.push_back(std::make_unique<BreakStatement>(offset));
524-
return std::make_unique<Block>(offset, std::move(block), /*symbols=*/nullptr,
525-
/*isScope=*/true);
523+
block.push_back(std::make_unique<ContinueStatement>(offset));
524+
return std::make_unique<Block>(offset, std::move(block),
525+
/*symbols=*/nullptr, /*isScope=*/true);
526526
} else {
527527
return std::move(assignment);
528528
}
529529
} else {
530530
if (haveEarlyReturns) {
531-
return std::make_unique<BreakStatement>(offset);
531+
return std::make_unique<ContinueStatement>(offset);
532532
} else {
533533
return std::make_unique<Nop>();
534534
}
@@ -577,6 +577,51 @@ std::unique_ptr<Statement> Inliner::inlineStatement(int offset,
577577
}
578578
}
579579

580+
Inliner::InlineVariable Inliner::makeInlineVariable(const String& baseName,
581+
const Type* type,
582+
SymbolTable* symbolTable,
583+
Modifiers modifiers,
584+
bool isBuiltinCode,
585+
std::unique_ptr<Expression>* initialValue) {
586+
// $floatLiteral or $intLiteral aren't real types that we can use for scratch variables, so
587+
// replace them if they ever appear here. If this happens, we likely forgot to coerce a type
588+
// somewhere during compilation.
589+
if (type == fContext->fFloatLiteral_Type.get()) {
590+
SkDEBUGFAIL("found a $floatLiteral type while inlining");
591+
type = fContext->fFloat_Type.get();
592+
} else if (type == fContext->fIntLiteral_Type.get()) {
593+
SkDEBUGFAIL("found an $intLiteral type while inlining");
594+
type = fContext->fInt_Type.get();
595+
}
596+
597+
// Provide our new variable with a unique name, and add it to our symbol table.
598+
const String* namePtr = symbolTable->takeOwnershipOfString(
599+
std::make_unique<String>(this->uniqueNameForInlineVar(baseName, symbolTable)));
600+
StringFragment nameFrag{namePtr->c_str(), namePtr->length()};
601+
602+
// Create our new variable and add it to the symbol table.
603+
InlineVariable result;
604+
result.fVarSymbol =
605+
symbolTable->add(std::make_unique<Variable>(/*offset=*/-1,
606+
fModifiers->addToPool(Modifiers()),
607+
nameFrag,
608+
type,
609+
isBuiltinCode,
610+
Variable::Storage::kLocal,
611+
initialValue->get()));
612+
613+
// Prepare the variable declaration (taking extra care with `out` params to not clobber any
614+
// initial value).
615+
if (*initialValue && (modifiers.fFlags & Modifiers::kOut_Flag)) {
616+
result.fVarDecl = std::make_unique<VarDeclaration>(result.fVarSymbol, type, /*arraySize=*/0,
617+
(*initialValue)->clone());
618+
} else {
619+
result.fVarDecl = std::make_unique<VarDeclaration>(result.fVarSymbol, type, /*arraySize=*/0,
620+
std::move(*initialValue));
621+
}
622+
return result;
623+
}
624+
580625
Inliner::InlinedCall Inliner::inlineCall(FunctionCall* call,
581626
std::shared_ptr<SymbolTable> symbolTable,
582627
const FunctionDeclaration* caller) {
@@ -610,59 +655,20 @@ Inliner::InlinedCall Inliner::inlineCall(FunctionCall* call,
610655
1 + // Result variable
611656
arguments.size() + // Function arguments (passing in)
612657
arguments.size() + // Function arguments (copy out-params back)
613-
1); // Inlined code (Block or do-while loop)
658+
1); // Block for inlined code
614659

615660
inlinedBody.children().push_back(std::make_unique<InlineMarker>(&call->function()));
616661

617-
auto makeInlineVar =
618-
[&](const String& baseName, const Type* type, Modifiers modifiers,
619-
std::unique_ptr<Expression>* initialValue) -> std::unique_ptr<Expression> {
620-
// $floatLiteral or $intLiteral aren't real types that we can use for scratch variables, so
621-
// replace them if they ever appear here. If this happens, we likely forgot to coerce a type
622-
// somewhere during compilation.
623-
if (type == fContext->fFloatLiteral_Type.get()) {
624-
SkDEBUGFAIL("found a $floatLiteral type while inlining");
625-
type = fContext->fFloat_Type.get();
626-
} else if (type == fContext->fIntLiteral_Type.get()) {
627-
SkDEBUGFAIL("found an $intLiteral type while inlining");
628-
type = fContext->fInt_Type.get();
629-
}
630-
631-
// Provide our new variable with a unique name, and add it to our symbol table.
632-
const String* namePtr = symbolTable->takeOwnershipOfString(std::make_unique<String>(
633-
this->uniqueNameForInlineVar(baseName, symbolTable.get())));
634-
StringFragment nameFrag{namePtr->c_str(), namePtr->length()};
635-
636-
// Add our new variable to the symbol table.
637-
const Variable* variableSymbol = symbolTable->add(std::make_unique<Variable>(
638-
/*offset=*/-1, fModifiers->addToPool(Modifiers()),
639-
nameFrag, type, caller->isBuiltin(),
640-
Variable::Storage::kLocal, initialValue->get()));
641-
642-
// Prepare the variable declaration (taking extra care with `out` params to not clobber any
643-
// initial value).
644-
std::unique_ptr<Statement> variable;
645-
if (initialValue && (modifiers.fFlags & Modifiers::kOut_Flag)) {
646-
variable = std::make_unique<VarDeclaration>(variableSymbol, type, /*arraySize=*/0,
647-
(*initialValue)->clone());
648-
} else {
649-
variable = std::make_unique<VarDeclaration>(variableSymbol, type, /*arraySize=*/0,
650-
std::move(*initialValue));
651-
}
652-
653-
// Add the new variable-declaration statement to our block of extra statements.
654-
inlinedBody.children().push_back(std::move(variable));
655-
656-
return std::make_unique<VariableReference>(offset, variableSymbol);
657-
};
658-
659662
// Create a variable to hold the result in the extra statements (excepting void).
660663
std::unique_ptr<Expression> resultExpr;
661664
if (function.declaration().returnType() != *fContext->fVoid_Type) {
662665
std::unique_ptr<Expression> noInitialValue;
663-
resultExpr = makeInlineVar(String(function.declaration().name()),
664-
&function.declaration().returnType(),
665-
Modifiers{}, &noInitialValue);
666+
InlineVariable var = this->makeInlineVariable(function.declaration().name(),
667+
&function.declaration().returnType(),
668+
symbolTable.get(), Modifiers{},
669+
caller->isBuiltin(), &noInitialValue);
670+
inlinedBody.children().push_back(std::move(var.fVarDecl));
671+
resultExpr = std::make_unique<VariableReference>(/*offset=*/-1, var.fVarSymbol);
666672
}
667673

668674
// Create variables in the extra statements to hold the arguments, and assign the arguments to
@@ -682,44 +688,81 @@ Inliner::InlinedCall Inliner::inlineCall(FunctionCall* call,
682688
continue;
683689
}
684690
}
685-
686691
if (isOutParam) {
687692
argsToCopyBack.push_back(i);
688693
}
689-
690-
varMap[param] = makeInlineVar(String(param->name()), &arguments[i]->type(),
691-
param->modifiers(), &arguments[i]);
694+
InlineVariable var = this->makeInlineVariable(param->name(), &arguments[i]->type(),
695+
symbolTable.get(), param->modifiers(),
696+
caller->isBuiltin(), &arguments[i]);
697+
inlinedBody.children().push_back(std::move(var.fVarDecl));
698+
varMap[param] = std::make_unique<VariableReference>(/*offset=*/-1, var.fVarSymbol);
692699
}
693700

694701
const Block& body = function.body()->as<Block>();
695-
auto inlineBlock = std::make_unique<Block>(offset, StatementArray{},
696-
/*symbols=*/nullptr, /*isScope=*/hasEarlyReturn);
697-
inlineBlock->children().reserve_back(body.children().size());
698-
for (const std::unique_ptr<Statement>& stmt : body.children()) {
699-
inlineBlock->children().push_back(this->inlineStatement(offset, &varMap, symbolTable.get(),
700-
resultExpr.get(), hasEarlyReturn,
701-
*stmt, caller->isBuiltin()));
702-
}
702+
StatementArray* inlineStatements;
703+
703704
if (hasEarlyReturn) {
704705
// Since we output to backends that don't have a goto statement (which would normally be
705-
// used to perform an early return), we fake it by wrapping the function in a
706-
// do { } while (false); and then use break statements to jump to the end in order to
707-
// emulate a goto.
708-
inlinedBody.children().push_back(std::make_unique<DoStatement>(
706+
// used to perform an early return), we fake it by wrapping the function in a single-
707+
// iteration for loop, and use a continue statement to jump to the end of the loop
708+
// prematurely.
709+
710+
// int _1_loop = 0;
711+
symbolTable = std::make_shared<SymbolTable>(std::move(symbolTable), caller->isBuiltin());
712+
const Type* intType = fContext->fInt_Type.get();
713+
std::unique_ptr<Expression> initialValue = std::make_unique<IntLiteral>(/*offset=*/-1,
714+
/*value=*/0,
715+
intType);
716+
InlineVariable loopVar = this->makeInlineVariable("loop", intType, symbolTable.get(),
717+
Modifiers{}, caller->isBuiltin(),
718+
&initialValue);
719+
720+
// _1_loop < 1;
721+
std::unique_ptr<Expression> test = std::make_unique<BinaryExpression>(
709722
/*offset=*/-1,
710-
std::move(inlineBlock),
711-
std::make_unique<BoolLiteral>(*fContext, offset, /*value=*/false)));
723+
std::make_unique<VariableReference>(/*offset=*/-1, loopVar.fVarSymbol),
724+
Token::Kind::TK_LT,
725+
std::make_unique<IntLiteral>(/*offset=*/-1, /*value=*/1, intType),
726+
fContext->fBool_Type.get());
727+
728+
// _1_loop++
729+
std::unique_ptr<Expression> increment = std::make_unique<PostfixExpression>(
730+
std::make_unique<VariableReference>(/*offset=*/-1, loopVar.fVarSymbol,
731+
VariableReference::RefKind::kReadWrite),
732+
Token::Kind::TK_PLUSPLUS);
733+
734+
// {...}
735+
auto innerBlock = std::make_unique<Block>(offset, StatementArray{},
736+
/*symbols=*/nullptr, /*isScope=*/true);
737+
inlineStatements = &innerBlock->children();
738+
739+
// for (int _1_loop = 0; _1_loop < 1; _1_loop++) {...}
740+
inlinedBody.children().push_back(std::make_unique<ForStatement>(/*offset=*/-1,
741+
std::move(loopVar.fVarDecl),
742+
std::move(test),
743+
std::move(increment),
744+
std::move(innerBlock),
745+
symbolTable));
712746
} else {
713-
// No early returns, so we can just dump the code in. We still need to keep the block so we
714-
// don't get name conflicts with locals.
715-
inlinedBody.children().push_back(std::move(inlineBlock));
747+
// No early returns, so we can just dump the code into a scopeless block.
748+
auto innerBlock = std::make_unique<Block>(offset, StatementArray{},
749+
/*symbols=*/nullptr, /*isScope=*/false);
750+
inlineStatements = &innerBlock->children();
751+
inlinedBody.children().push_back(std::move(innerBlock));
752+
}
753+
754+
inlineStatements->reserve_back(body.children().size() + argsToCopyBack.size());
755+
for (const std::unique_ptr<Statement>& stmt : body.children()) {
756+
inlineStatements->push_back(this->inlineStatement(offset, &varMap, symbolTable.get(),
757+
resultExpr.get(), hasEarlyReturn, *stmt,
758+
caller->isBuiltin()));
716759
}
717760

718761
// Copy back the values of `out` parameters into their real destinations.
719762
for (int i : argsToCopyBack) {
720763
const Variable* p = function.declaration().parameters()[i];
721764
SkASSERT(varMap.find(p) != varMap.end());
722-
inlinedBody.children().push_back(
765+
inlineStatements->push_back(
723766
std::make_unique<ExpressionStatement>(std::make_unique<BinaryExpression>(
724767
offset,
725768
clone_with_ref_kind(*arguments[i], VariableReference::RefKind::kWrite),
@@ -762,18 +805,8 @@ bool Inliner::isSafeToInline(const FunctionDefinition* functionDef) {
762805
return false;
763806
}
764807

765-
if (!fCaps || !fCaps->canUseDoLoops()) {
766-
// We don't have do-while loops. We use do-while loops to simulate early returns, so we
767-
// can't inline functions that have an early return.
768-
bool hasEarlyReturn = has_early_return(*functionDef);
769-
770-
// If we didn't detect an early return, there shouldn't be any returns in breakable
771-
// constructs either.
772-
SkASSERT(hasEarlyReturn || count_returns_in_breakable_constructs(*functionDef) == 0);
773-
return !hasEarlyReturn;
774-
}
775-
// We have do-while loops, but we don't have any mechanism to simulate early returns within a
776-
// breakable construct (switch/for/do/while), so we can't inline if there's a return inside one.
808+
// We don't have any mechanism to simulate early returns within a breakable construct
809+
// (switch/for/do/while), so we can't inline if there's a return inside one.
777810
bool hasReturnInBreakableConstruct = (count_returns_in_breakable_constructs(*functionDef) > 0);
778811

779812
// If we detected returns in breakable constructs, we should also detect an early return.

src/sksl/SkSLInliner.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Variable;
3636
*/
3737
class Inliner {
3838
public:
39-
Inliner(const Context* context, const ShaderCapsClass* caps) : fContext(context), fCaps(caps) {}
39+
Inliner(const Context* context) : fContext(context) {}
4040

4141
void reset(ModifiersPool* modifiers, const Program::Settings*);
4242

@@ -85,6 +85,18 @@ class Inliner {
8585
std::shared_ptr<SymbolTable>,
8686
const FunctionDeclaration* caller);
8787

88+
/** Creates a scratch variable for the inliner to use. */
89+
struct InlineVariable {
90+
const Variable* fVarSymbol;
91+
std::unique_ptr<Statement> fVarDecl;
92+
};
93+
InlineVariable makeInlineVariable(const String& baseName,
94+
const Type* type,
95+
SymbolTable* symbolTable,
96+
Modifiers modifiers,
97+
bool isBuiltinCode,
98+
std::unique_ptr<Expression>* initialValue);
99+
88100
/** Adds a scope to inlined bodies returned by `inlineCall`, if one is required. */
89101
void ensureScopedBlocks(Statement* inlinedBody, Statement* parentStmt);
90102

@@ -94,7 +106,6 @@ class Inliner {
94106
const Context* fContext = nullptr;
95107
ModifiersPool* fModifiers = nullptr;
96108
const Program::Settings* fSettings = nullptr;
97-
const ShaderCapsClass* fCaps = nullptr;
98109
int fInlineVarCounter = 0;
99110
int fInlinedStatementCounter = 0;
100111
};

tests/sksl/inliner/golden/InlinerWrapsEarlyReturnsWithDoWhileBlockStandaloneSettings.glsl

Lines changed: 0 additions & 11 deletions
This file was deleted.

tests/sksl/inliner/golden/InlinerWrapsEarlyReturnsWithDoWhileBlock.glsl renamed to tests/sksl/inliner/golden/InlinerWrapsEarlyReturnsWithForLoop.glsl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ out vec4 sk_FragColor;
33
uniform vec4 color;
44
void main() {
55
vec4 _0_returny;
6-
do {
6+
for (int _1_loop = 0;_1_loop < 1; _1_loop++) {
77
if (color.x > color.y) {
88
_0_returny = color.xxxx;
9-
break;
9+
continue;
1010
}
1111
if (color.y > color.z) {
1212
_0_returny = color.yyyy;
13-
break;
13+
continue;
1414
}
1515
{
1616
_0_returny = color.zzzz;
17-
break;
17+
continue;
1818
}
19-
} while (false);
19+
}
2020
sk_FragColor = _0_returny;
2121

2222
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
out vec4 sk_FragColor;
3+
uniform vec4 color;
4+
void main() {
5+
vec4 _0_returny;
6+
for (int _1_loop = 0;_1_loop < 1; _1_loop++) {
7+
if (color.x > color.y) {
8+
_0_returny = color.xxxx;
9+
continue;
10+
}
11+
if (color.y > color.z) {
12+
_0_returny = color.yyyy;
13+
continue;
14+
}
15+
{
16+
_0_returny = color.zzzz;
17+
continue;
18+
}
19+
}
20+
sk_FragColor = _0_returny;
21+
22+
}

0 commit comments

Comments
 (0)