Bug 1030095 - Remove restriction on inlining recursive calls. r=hv1989

This commit is contained in:
Spenser Andrew Bauman 2015-07-14 09:36:00 -04:00
parent e25f29c619
commit 92c1f56d86
3 changed files with 60 additions and 9 deletions

View File

@ -150,7 +150,6 @@ namespace JS {
_(CantInlineClassConstructor) \
_(CantInlineDisabledIon) \
_(CantInlineTooManyArgs) \
_(CantInlineRecursive) \
_(CantInlineHeavyweight) \
_(CantInlineNeedsArgsObj) \
_(CantInlineDebuggee) \
@ -168,6 +167,7 @@ namespace JS {
_(CantInlineNativeNoTemplateObj) \
_(CantInlineBound) \
_(CantInlineNativeNoSpecialization) \
_(HasCommonInliningPath) \
\
_(GenericSuccess) \
_(Inlined) \

View File

@ -372,6 +372,59 @@ IonBuilder::DontInline(JSScript* targetScript, const char* reason)
return InliningDecision_DontInline;
}
/*
* |hasCommonInliningPath| determines whether the current inlining path has been
* seen before based on the sequence of scripts in the chain of |IonBuilder|s.
*
* An inlining path for a function |f| is the sequence of functions whose
* inlinings precede |f| up to any previous occurrences of |f|.
* So, if we have the chain of inlinings
*
* f1 -> f2 -> f -> f3 -> f4 -> f5 -> f
* -------- --------------
*
* the inlining paths for |f| are [f2, f1] and [f5, f4, f3].
* When attempting to inline |f|, we find all existing inlining paths for |f|
* and check whether they share a common prefix with the path created were |f|
* inlined.
*
* For example, given mutually recursive functions |f| and |g|, a possible
* inlining is
*
* +---- Inlining stopped here...
* |
* v
* a -> f -> g -> f \ -> g -> f -> g -> ...
*
* where the vertical bar denotes the termination of inlining.
* Inlining is terminated because we have already observed the inlining path
* [f] when inlining function |g|. Note that this will inline recursive
* functions such as |fib| only one level, as |fib| has a zero length inlining
* path which trivially prefixes all inlining paths.
*
*/
bool
IonBuilder::hasCommonInliningPath(const JSScript* scriptToInline)
{
if (this->script() == scriptToInline)
return true;
// Find all previous inlinings of the |scriptToInline| and check for common
// inlining paths with the top of the inlining stack.
for (IonBuilder* it = this; it; it = it->callerBuilder_) {
if (it->script() != scriptToInline)
continue;
// This only needs to check the top of each stack for a match,
// as a match of length one ensures a common prefix.
IonBuilder* path = it->callerBuilder_;
if (!path || this->script() == path->script())
return true;
}
return false;
}
IonBuilder::InliningDecision
IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo)
{
@ -468,14 +521,9 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo)
return DontInline(inlineScript, "Too many actual args");
}
// Allow inlining of recursive calls, but only one level deep.
IonBuilder* builder = callerBuilder_;
while (builder) {
if (builder->script() == inlineScript) {
trackOptimizationOutcome(TrackedOutcome::CantInlineRecursive);
return DontInline(inlineScript, "Recursive call");
}
builder = builder->callerBuilder_;
if (hasCommonInliningPath(inlineScript)) {
trackOptimizationOutcome(TrackedOutcome::HasCommonInliningPath);
return DontInline(inlineScript, "Common inlining path");
}
if (target->isHeavyweight()) {

View File

@ -720,6 +720,9 @@ class IonBuilder
static InliningDecision DontInline(JSScript* targetScript, const char* reason);
// Helper function for canInlineTarget
bool hasCommonInliningPath(const JSScript* scriptToInline);
// Oracles.
InliningDecision canInlineTarget(JSFunction* target, CallInfo& callInfo);
InliningDecision makeInliningDecision(JSObject* target, CallInfo& callInfo);