Bug 1142669 part 4 - Fix some inlining issues and inline scripts with loops. r=h4writer

This commit is contained in:
Jan de Mooij 2015-03-19 15:10:07 +01:00
parent bf95f782eb
commit 6980cbf56d
5 changed files with 82 additions and 57 deletions

View File

@ -203,8 +203,6 @@ namespace JS {
"can't inline: type has unknown properties") \
_(CantInlineExceededDepth, \
"can't inline: exceeded inlining depth") \
_(CantInlineBigLoop, \
"can't inline: big function with a loop") \
_(CantInlineExceededTotalBytecodeLength, \
"can't inline: exceeded max total bytecode length") \
_(CantInlineBigCaller, \

View File

@ -65,7 +65,8 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
traceLoggerScriptEvent_(),
#endif
postDebugPrologueOffset_(postDebugPrologueOffset),
flags_(0)
flags_(0),
maxInliningDepth_(UINT8_MAX)
{ }
static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000;

View File

@ -212,6 +212,13 @@ struct BaselineScript
// instruction.
uint32_t yieldEntriesOffset_;
// The max inlining depth where we can still inline all functions we inlined
// when we Ion-compiled this script. This starts as UINT8_MAX, since we have
// no data yet, and won't affect inlining heuristics in that case. The value
// is updated when we Ion-compile this script. See makeInliningDecision for
// more info.
uint8_t maxInliningDepth_;
public:
// Do not call directly, use BaselineScript::New. This is public for cx->new_.
BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset,
@ -424,6 +431,17 @@ struct BaselineScript
MOZ_ASSERT(bytecodeTypeMapOffset_);
return reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(this) + bytecodeTypeMapOffset_);
}
uint8_t maxInliningDepth() const {
return maxInliningDepth_;
}
void setMaxInliningDepth(uint32_t depth) {
MOZ_ASSERT(depth <= UINT8_MAX);
maxInliningDepth_ = depth;
}
void resetMaxInliningDepth() {
maxInliningDepth_ = UINT8_MAX;
}
};
static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0,
"The data attached to the script must be aligned for fast JIT access.");

View File

@ -776,6 +776,9 @@ IonBuilder::build()
if (!init())
return false;
if (script()->hasBaselineScript())
script()->baselineScript()->resetMaxInliningDepth();
if (!setCurrentAndSpecializePhis(newBlock(pc)))
return false;
if (!current)
@ -4841,56 +4844,6 @@ IonBuilder::makeInliningDecision(JSObject *targetArg, CallInfo &callInfo)
// Heuristics!
JSScript *targetScript = target->nonLazyScript();
// Cap the inlining depth.
if (js_JitOptions.isSmallFunction(targetScript)) {
if (inliningDepth_ >= optimizationInfo().smallFunctionMaxInlineDepth()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript, "Vetoed: exceeding allowed inline depth");
}
} else {
if (inliningDepth_ >= optimizationInfo().maxInlineDepth()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript, "Vetoed: exceeding allowed inline depth");
}
if (targetScript->hasLoops()) {
// Currently, we are not inlining function which have loops because
// the cost inherent to inlining the function overcome the cost
// calling it. The reason is not yet clear to everybody, and we
// hope that we might be able to remove this restriction in the
// future.
//
// In the mean time, if we have opportunities to optimize the
// loop better, then we should try it. Such opportunity might be
// suggested by:
//
// - Constant as argument. Inlining a function called with a
// constant, might help GVN, as well as UCE, and potentially
// improve bound check removal.
//
// - Inner function as argument. Inlining a function called
// with an inner function might help scalar replacement at
// removing the scope chain, and thus using registers within
// the loop instead of writting everything back to memory.
bool hasOpportunities = false;
for (size_t i = 0, e = callInfo.argv().length(); !hasOpportunities && i < e; i++) {
MDefinition *arg = callInfo.argv()[i];
hasOpportunities = arg->isLambda() || arg->isConstantValue();
}
if (!hasOpportunities) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBigLoop);
return DontInline(targetScript, "Vetoed: big function that contains a loop");
}
}
// Caller must not be excessively large.
if (script()->length() >= optimizationInfo().inliningMaxCallerBytecodeLength()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller);
return DontInline(targetScript, "Vetoed: caller excessively large");
}
}
// Callee must not be excessively large.
// This heuristic also applies to the callsite as a whole.
bool offThread = options.offThreadCompilationAvailable();
@ -4922,6 +4875,65 @@ IonBuilder::makeInliningDecision(JSObject *targetArg, CallInfo &callInfo)
return DontInline(targetScript, "Vetoed: exceeding max total bytecode length");
}
// Cap the inlining depth.
uint32_t maxInlineDepth;
if (js_JitOptions.isSmallFunction(targetScript)) {
maxInlineDepth = optimizationInfo().smallFunctionMaxInlineDepth();
} else {
maxInlineDepth = optimizationInfo().maxInlineDepth();
// Caller must not be excessively large.
if (script()->length() >= optimizationInfo().inliningMaxCallerBytecodeLength()) {
trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller);
return DontInline(targetScript, "Vetoed: caller excessively large");
}
}
BaselineScript *outerBaseline = outermostBuilder()->script()->baselineScript();
if (inliningDepth_ >= maxInlineDepth) {
// We hit the depth limit and won't inline this function. Give the
// outermost script a max inlining depth of 0, so that it won't be
// inlined in other scripts. This heuristic is currently only used
// when we're inlining scripts with loops, see the comment below.
outerBaseline->setMaxInliningDepth(0);
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript, "Vetoed: exceeding allowed inline depth");
}
// Inlining functions with loops can be complicated. For instance, if we're
// close to the inlining depth limit and we inline the function f below, we
// can no longer inline the call to g:
//
// function f() {
// while (cond) {
// g();
// }
// }
//
// If the loop has many iterations, it's more efficient to call f and inline
// g in f.
//
// To avoid this problem, we record a separate max inlining depth for each
// script, indicating at which depth we won't be able to inline all functions
// we inlined this time. This solves the issue above, because we will only
// inline f if it means we can also inline g.
if (targetScript->hasLoops() &&
inliningDepth_ >= targetScript->baselineScript()->maxInliningDepth())
{
trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth);
return DontInline(targetScript, "Vetoed: exceeding allowed script inline depth");
}
// Update the max depth at which we can inline the outer script.
MOZ_ASSERT(maxInlineDepth > inliningDepth_);
uint32_t scriptInlineDepth = maxInlineDepth - inliningDepth_ - 1;
if (scriptInlineDepth < outerBaseline->maxInliningDepth())
outerBaseline->setMaxInliningDepth(scriptInlineDepth);
// End of heuristics, we will inline this function.
// TI calls ObjectStateChange to trigger invalidation of the caller.
TypeSet::ObjectKey *targetKey = TypeSet::ObjectKey::get(target);
targetKey->watchStateChangeForInlinedCall(constraints());

View File

@ -160,10 +160,6 @@ JitOptions::JitOptions()
SET_DEFAULT(osrPcMismatchesBeforeRecompile, 6000);
// The bytecode length limit for small function.
//
// The default for this was arrived at empirically via benchmarking.
// We may want to tune it further after other optimizations have gone
// in.
SET_DEFAULT(smallFunctionMaxBytecodeLength_, 100);
}