77
88#include " src/sksl/SkSLInliner.h"
99
10- #include " limits.h"
10+ #include < limits.h>
1111#include < memory>
1212#include < unordered_set>
1313
@@ -592,7 +592,7 @@ Inliner::InlinedCall Inliner::inlineCall(FunctionCall* call,
592592 SkASSERT (fSettings );
593593 SkASSERT (fContext );
594594 SkASSERT (call);
595- SkASSERT (this ->isSafeToInline (* call, /* inlineThreshold= */ INT_MAX ));
595+ SkASSERT (this ->isSafeToInline (call-> fFunction . fDefinition ));
596596
597597 std::vector<std::unique_ptr<Expression>>& arguments = call->fArguments ;
598598 const int offset = call->fOffset ;
@@ -745,58 +745,52 @@ Inliner::InlinedCall Inliner::inlineCall(FunctionCall* call,
745745 return inlinedCall;
746746}
747747
748- bool Inliner::isSafeToInline (const FunctionCall& functionCall, int inlineThreshold ) {
748+ bool Inliner::isSafeToInline (const FunctionDefinition* functionDef ) {
749749 SkASSERT (fSettings );
750750
751- if (functionCall. fFunction . fDefinition == nullptr ) {
751+ if (functionDef == nullptr ) {
752752 // Can't inline something if we don't actually have its definition.
753753 return false ;
754754 }
755- const FunctionDefinition& functionDef = *functionCall.fFunction .fDefinition ;
756- if (inlineThreshold < INT_MAX) {
757- if (!(functionDef.fDeclaration .fModifiers .fFlags & Modifiers::kInline_Flag ) &&
758- Analysis::NodeCountExceeds (functionDef, inlineThreshold)) {
759- // The function exceeds our maximum inline size and is not flagged 'inline'.
760- return false ;
761- }
762- }
755+
763756 if (!fSettings ->fCaps || !fSettings ->fCaps ->canUseDoLoops ()) {
764757 // We don't have do-while loops. We use do-while loops to simulate early returns, so we
765758 // can't inline functions that have an early return.
766- bool hasEarlyReturn = has_early_return (functionDef);
759+ bool hasEarlyReturn = has_early_return (* functionDef);
767760
768761 // If we didn't detect an early return, there shouldn't be any returns in breakable
769762 // constructs either.
770- SkASSERT (hasEarlyReturn || count_returns_in_breakable_constructs (functionDef) == 0 );
763+ SkASSERT (hasEarlyReturn || count_returns_in_breakable_constructs (* functionDef) == 0 );
771764 return !hasEarlyReturn;
772765 }
773766 // We have do-while loops, but we don't have any mechanism to simulate early returns within a
774767 // breakable construct (switch/for/do/while), so we can't inline if there's a return inside one.
775- bool hasReturnInBreakableConstruct = (count_returns_in_breakable_constructs (functionDef) > 0 );
768+ bool hasReturnInBreakableConstruct = (count_returns_in_breakable_constructs (* functionDef) > 0 );
776769
777770 // If we detected returns in breakable constructs, we should also detect an early return.
778- SkASSERT (!hasReturnInBreakableConstruct || has_early_return (functionDef));
771+ SkASSERT (!hasReturnInBreakableConstruct || has_early_return (* functionDef));
779772 return !hasReturnInBreakableConstruct;
780773}
781774
782- bool Inliner::analyze (Program& program) {
783- // A candidate function for inlining, containing everything that `inlineCall` needs.
784- struct InlineCandidate {
785- SymbolTable* fSymbols ; // the SymbolTable of the candidate
786- std::unique_ptr<Statement>* fParentStmt ; // the parent Statement of the enclosing stmt
787- std::unique_ptr<Statement >* fEnclosingStmt ; // the Statement containing the candidate
788- std::unique_ptr<Expression>* fCandidateExpr ; // the candidate FunctionCall to be inlined
789- FunctionDefinition* fEnclosingFunction ; // the Function containing the candidate
790- };
791-
792- // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor.
793- // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so
794- // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a
795- // `const T&`.
796- class InlineCandidateAnalyzer {
775+ // A candidate function for inlining, containing everything that `inlineCall` needs.
776+ struct InlineCandidate {
777+ SymbolTable* fSymbols ; // the SymbolTable of the candidate
778+ std::unique_ptr<Statement>* fParentStmt ; // the parent Statement of the enclosing stmt
779+ std::unique_ptr<Statement>* fEnclosingStmt ; // the Statement containing the candidate
780+ std::unique_ptr<Expression >* fCandidateExpr ; // the candidate FunctionCall to be inlined
781+ FunctionDefinition* fEnclosingFunction ; // the Function containing the candidate
782+ bool fIsLargeFunction ; // does candidate exceed the inline threshold?
783+ };
784+
785+ struct InlineCandidateList {
786+ std::vector<InlineCandidate> fCandidates ;
787+ };
788+
789+ class InlineCandidateAnalyzer {
797790 public:
798791 // A list of all the inlining candidates we found during analysis.
799- std::vector<InlineCandidate> fInlineCandidates ;
792+ InlineCandidateList* fCandidateList ;
793+
800794 // A stack of the symbol tables; since most nodes don't have one, expected to be shallower
801795 // than the enclosing-statement stack.
802796 std::vector<SymbolTable*> fSymbolTableStack ;
@@ -807,14 +801,16 @@ bool Inliner::analyze(Program& program) {
807801 // The function that we're currently processing (i.e. inlining into).
808802 FunctionDefinition* fEnclosingFunction = nullptr ;
809803
810- void visit (Program& program) {
804+ void visit (Program& program, InlineCandidateList* candidateList) {
805+ fCandidateList = candidateList;
811806 fSymbolTableStack .push_back (program.fSymbols .get ());
812807
813808 for (ProgramElement& pe : program) {
814809 this ->visitProgramElement (&pe);
815810 }
816811
817812 fSymbolTableStack .pop_back ();
813+ fCandidateList = nullptr ;
818814 }
819815
820816 void visitProgramElement (ProgramElement* pe) {
@@ -1072,42 +1068,87 @@ bool Inliner::analyze(Program& program) {
10721068 }
10731069
10741070 void addInlineCandidate (std::unique_ptr<Expression>* candidate) {
1075- fInlineCandidates .push_back (InlineCandidate{fSymbolTableStack .back (),
1076- find_parent_statement (fEnclosingStmtStack ),
1077- fEnclosingStmtStack .back (),
1078- candidate,
1079- fEnclosingFunction });
1071+ fCandidateList ->fCandidates .push_back (
1072+ InlineCandidate{fSymbolTableStack .back (),
1073+ find_parent_statement (fEnclosingStmtStack ),
1074+ fEnclosingStmtStack .back (),
1075+ candidate,
1076+ fEnclosingFunction ,
1077+ /* isLargeFunction=*/ false });
10801078 }
1081- };
1079+ };
10821080
1083- InlineCandidateAnalyzer analyzer;
1084- analyzer. visit (program) ;
1081+ bool Inliner::candidateCanBeInlined ( const InlineCandidate& candidate, InlinabilityCache* cache) {
1082+ const FunctionDeclaration& funcDecl = (*candidate. fCandidateExpr )-> as <FunctionCall>(). fFunction ;
10851083
1086- // For each of our candidate function-call sites, check if it is actually safe to inline.
1087- // Memoize our results so we don't check a function more than once.
1088- std::unordered_map<const FunctionDeclaration*, bool > inlinableMap; // <function, safe-to-inline>
1089- for (const InlineCandidate& candidate : analyzer.fInlineCandidates ) {
1090- const FunctionCall& funcCall = (*candidate.fCandidateExpr )->as <FunctionCall>();
1091- const FunctionDeclaration* funcDecl = &funcCall.fFunction ;
1092- if (inlinableMap.find (funcDecl) == inlinableMap.end ()) {
1093- // We do not perform inlining on recursive calls to avoid an infinite death spiral of
1094- // inlining.
1095- int inlineThreshold = (funcDecl->fCallCount .load () > 1 ) ? fSettings ->fInlineThreshold
1096- : INT_MAX;
1097- inlinableMap[funcDecl] = this ->isSafeToInline (funcCall, inlineThreshold) &&
1098- !contains_recursive_call (*funcDecl);
1099- }
1084+ auto [iter, wasInserted] = cache->insert ({&funcDecl, false });
1085+ if (wasInserted) {
1086+ // Recursion is forbidden here to avoid an infinite death spiral of inlining.
1087+ iter->second = this ->isSafeToInline (funcDecl.fDefinition ) &&
1088+ !contains_recursive_call (funcDecl);
1089+ }
1090+
1091+ return iter->second ;
1092+ }
1093+
1094+ bool Inliner::isLargeFunction (const FunctionDefinition* functionDef) {
1095+ return Analysis::NodeCountExceeds (*functionDef, fSettings ->fInlineThreshold );
1096+ }
1097+
1098+ bool Inliner::isLargeFunction (const InlineCandidate& candidate, LargeFunctionCache* cache) {
1099+ const FunctionDeclaration& funcDecl = (*candidate.fCandidateExpr )->as <FunctionCall>().fFunction ;
1100+
1101+ auto [iter, wasInserted] = cache->insert ({&funcDecl, false });
1102+ if (wasInserted) {
1103+ iter->second = this ->isLargeFunction (funcDecl.fDefinition );
11001104 }
11011105
1106+ return iter->second ;
1107+ }
1108+
1109+ void Inliner::buildCandidateList (Program& program, InlineCandidateList* candidateList) {
1110+ // This is structured much like a ProgramVisitor, but does not actually use ProgramVisitor.
1111+ // The analyzer needs to keep track of the `unique_ptr<T>*` of statements and expressions so
1112+ // that they can later be replaced, and ProgramVisitor does not provide this; it only provides a
1113+ // `const T&`.
1114+ InlineCandidateAnalyzer analyzer;
1115+ analyzer.visit (program, candidateList);
1116+
1117+ // Remove candidates that are not safe to inline.
1118+ std::vector<InlineCandidate>& candidates = candidateList->fCandidates ;
1119+ InlinabilityCache cache;
1120+ candidates.erase (std::remove_if (candidates.begin (),
1121+ candidates.end (),
1122+ [&](const InlineCandidate& candidate) {
1123+ return !this ->candidateCanBeInlined (candidate, &cache);
1124+ }),
1125+ candidates.end ());
1126+
1127+ // Determine whether each candidate function exceeds our inlining size threshold or not. These
1128+ // can still be valid candidates if they are only called one time, so we don't remove them from
1129+ // the candidate list, but they will not be inlined if they're called more than once.
1130+ LargeFunctionCache largeFunctionCache;
1131+ for (InlineCandidate& candidate : candidates) {
1132+ candidate.fIsLargeFunction = this ->isLargeFunction (candidate, &largeFunctionCache);
1133+ }
1134+ }
1135+
1136+ bool Inliner::analyze (Program& program) {
1137+ InlineCandidateList candidateList;
1138+ this ->buildCandidateList (program, &candidateList);
1139+
11021140 // Inline the candidates where we've determined that it's safe to do so.
11031141 std::unordered_set<const std::unique_ptr<Statement>*> enclosingStmtSet;
11041142 bool madeChanges = false ;
1105- for (const InlineCandidate& candidate : analyzer. fInlineCandidates ) {
1143+ for (const InlineCandidate& candidate : candidateList. fCandidates ) {
11061144 FunctionCall& funcCall = (*candidate.fCandidateExpr )->as <FunctionCall>();
11071145 const FunctionDeclaration* funcDecl = &funcCall.fFunction ;
11081146
1109- // If we determined that this candidate was not actually inlinable, skip it.
1110- if (!inlinableMap[funcDecl]) {
1147+ // If the function is large, not marked `inline`, and is called more than once, it's a bad
1148+ // idea to inline it.
1149+ if (candidate.fIsLargeFunction &&
1150+ !(funcDecl->fModifiers .fFlags & Modifiers::kInline_Flag ) &&
1151+ funcDecl->fCallCount .load () > 1 ) {
11111152 continue ;
11121153 }
11131154
0 commit comments