Bug 911738 - IonMonkey: Recompile function when a non-inlined function gets hot enough to inline, r=jandem

This commit is contained in:
Hannes Verschore 2014-05-16 12:37:13 +02:00
parent 2acaa3f79f
commit d037572d5a
10 changed files with 140 additions and 45 deletions

View File

@ -3400,6 +3400,7 @@ CodeGenerator::emitDebugResultChecks(LInstruction *ins)
default:
return true;
}
return true;
}
#endif
@ -6854,10 +6855,18 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints)
// Check to make sure we didn't have a mid-build invalidation. If so, we
// will trickle to jit::Compile() and return Method_Skipped.
uint32_t warmUpCount = script->getWarmUpCount();
types::RecompileInfo recompileInfo;
if (!types::FinishCompilation(cx, script, executionMode, constraints, &recompileInfo))
return true;
// IonMonkey could have inferred better type information during
// compilation. Since adding the new information to the actual type
// information can reset the usecount, increase it back to what it was
// before.
if (warmUpCount > script->getWarmUpCount())
script->incWarmUpCounter(warmUpCount - script->getWarmUpCount());
uint32_t scriptFrameSize = frameClass_ == FrameSizeClass::None()
? frameDepth_
: FrameSizeClass::FromDepth(frameDepth_).frameSize();
@ -9179,18 +9188,31 @@ CodeGenerator::visitAsmJSInterruptCheck(LAsmJSInterruptCheck *lir)
typedef bool (*RecompileFn)(JSContext *);
static const VMFunction RecompileFnInfo = FunctionInfo<RecompileFn>(Recompile);
typedef bool (*ForcedRecompileFn)(JSContext *);
static const VMFunction ForcedRecompileFnInfo = FunctionInfo<ForcedRecompileFn>(ForcedRecompile);
bool
CodeGenerator::visitRecompileCheck(LRecompileCheck *ins)
{
Label done;
Register tmp = ToRegister(ins->scratch());
OutOfLineCode *ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
OutOfLineCode *ool;
if (ins->mir()->forceRecompilation())
ool = oolCallVM(ForcedRecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
else
ool = oolCallVM(RecompileFnInfo, ins, (ArgList()), StoreRegisterTo(tmp));
// Check if warm-up counter is high enough.
masm.movePtr(ImmPtr(ins->mir()->script()->addressOfWarmUpCounter()), tmp);
Address ptr(tmp, 0);
masm.add32(Imm32(1), tmp);
masm.branch32(Assembler::BelowOrEqual, ptr, Imm32(ins->mir()->recompileThreshold()), &done);
AbsoluteAddress warmUpCount = AbsoluteAddress(ins->mir()->script()->addressOfWarmUpCounter());
if (ins->mir()->increaseWarmUpCounter()) {
masm.load32(warmUpCount, tmp);
masm.add32(Imm32(1), tmp);
masm.store32(tmp, warmUpCount);
masm.branch32(Assembler::BelowOrEqual, tmp, Imm32(ins->mir()->recompileThreshold()), &done);
} else {
masm.branch32(Assembler::BelowOrEqual, warmUpCount, Imm32(ins->mir()->recompileThreshold()),
&done);
}
// Check if not yet recompiling.
CodeOffsetLabel label = masm.movWithPatch(ImmWord(uintptr_t(-1)), tmp);

View File

@ -2191,7 +2191,7 @@ GetOptimizationLevel(HandleScript script, jsbytecode *pc, ExecutionMode executio
static MethodStatus
Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
bool constructing, ExecutionMode executionMode)
bool constructing, ExecutionMode executionMode, bool forceRecompile = false)
{
JS_ASSERT(jit::IsIonEnabled(cx));
JS_ASSERT(jit::IsBaselineEnabled(cx));
@ -2228,35 +2228,17 @@ Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode
if (!scriptIon->method())
return Method_CantCompile;
MethodStatus failedState = Method_Compiled;
// If we keep failing to enter the script due to an OSR pc mismatch,
// recompile with the right pc.
if (osrPc && script->ionScript()->osrPc() != osrPc) {
uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
return Method_Skipped;
failedState = Method_Skipped;
}
// Don't recompile/overwrite higher optimized code,
// with a lower optimization level.
if (optimizationLevel < scriptIon->optimizationLevel())
return failedState;
if (optimizationLevel == scriptIon->optimizationLevel() &&
(!osrPc || script->ionScript()->osrPc() == osrPc))
{
return failedState;
}
if (optimizationLevel <= scriptIon->optimizationLevel() && !forceRecompile)
return Method_Compiled;
// Don't start compiling if already compiling
if (scriptIon->isRecompiling())
return failedState;
return Method_Compiled;
if (osrPc)
script->ionScript()->resetOsrPcMismatchCounter();
scriptIon->resetOsrPcMismatchCounter();
recompile = true;
}
@ -2275,11 +2257,8 @@ Compile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode
}
// Compilation succeeded or we invalidated right away or an inlining/alloc abort
if (HasIonScript(script, executionMode)) {
if (osrPc && script->ionScript()->osrPc() != osrPc)
return Method_Skipped;
if (HasIonScript(script, executionMode))
return Method_Compiled;
}
return Method_Skipped;
}
@ -2317,6 +2296,16 @@ jit::CanEnterAtBranch(JSContext *cx, JSScript *script, BaselineFrame *osrFrame,
return Method_CantCompile;
}
// By default a recompilation doesn't happen on osr mismatch.
// Decide if we want to force a recompilation if this happens too much.
bool force = false;
if (script->hasIonScript() && pc != script->ionScript()->osrPc()) {
uint32_t count = script->ionScript()->incrOsrPcMismatchCounter();
if (count <= js_JitOptions.osrPcMismatchesBeforeRecompile)
return Method_Skipped;
force = true;
}
// Attempt compilation.
// - Returns Method_Compiled if the right ionscript is present
// (Meaning it was present or a sequantial compile finished)
@ -2324,13 +2313,20 @@ jit::CanEnterAtBranch(JSContext *cx, JSScript *script, BaselineFrame *osrFrame,
// (This means a background thread compilation with that pc could have started or not.)
RootedScript rscript(cx, script);
MethodStatus status = Compile(cx, rscript, osrFrame, pc, osrFrame->isConstructing(),
SequentialExecution);
SequentialExecution, force);
if (status != Method_Compiled) {
if (status == Method_CantCompile)
ForbidCompilation(cx, script);
return status;
}
// Return the compilation was skipped when the osr pc wasn't adjusted.
// This can happen when there was still an IonScript available and a
// background compilation started, but hasn't finished yet.
// Or when we didn't force a recompile.
if (pc != script->ionScript()->osrPc())
return Method_Skipped;
return Method_Compiled;
}
@ -2431,14 +2427,14 @@ jit::CompileFunctionForBaseline(JSContext *cx, HandleScript script, BaselineFram
MethodStatus
jit::Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
bool constructing)
bool constructing, bool force)
{
JS_ASSERT(script->hasIonScript());
if (script->ionScript()->isRecompiling())
return Method_Compiled;
MethodStatus status =
Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution);
Compile(cx, script, osrFrame, osrPc, constructing, SequentialExecution, force);
if (status != Method_Compiled) {
if (status == Method_CantCompile)
ForbidCompilation(cx, script);

View File

@ -93,7 +93,7 @@ MethodStatus CanEnterInParallel(JSContext *cx, HandleScript script);
MethodStatus
Recompile(JSContext *cx, HandleScript script, BaselineFrame *osrFrame, jsbytecode *osrPc,
bool constructing);
bool constructing, bool force);
enum IonExecStatus
{

View File

@ -4346,7 +4346,9 @@ IonBuilder::makeInliningDecision(JSFunction *target, CallInfo &callInfo)
!targetScript->baselineScript()->ionCompiledOrInlined() &&
info().executionMode() != DefinitePropertiesAnalysis)
{
return DontInline(targetScript, "Vetoed: callee is insufficiently hot.");
JitSpew(JitSpew_Inlining, "Cannot inline %s:%u: callee is insufficiently hot.",
targetScript->filename(), targetScript->lineno());
return InliningDecision_WarmUpCountTooLow;
}
}
@ -4381,6 +4383,7 @@ IonBuilder::selectInliningTargets(ObjectVector &targets, CallInfo &callInfo, Boo
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
inlineable = false;
break;
case InliningDecision_Inline:
@ -4555,6 +4558,8 @@ IonBuilder::inlineCallsite(ObjectVector &targets, ObjectVector &originals,
return InliningStatus_Error;
case InliningDecision_DontInline:
return InliningStatus_NotInlined;
case InliningDecision_WarmUpCountTooLow:
return InliningStatus_WarmUpCountTooLow;
case InliningDecision_Inline:
break;
}
@ -5167,6 +5172,7 @@ IonBuilder::jsop_funcall(uint32_t argc)
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline:
if (target->isInterpreted())
@ -5307,6 +5313,7 @@ IonBuilder::jsop_funapplyarguments(uint32_t argc)
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline:
if (target->isInterpreted())
@ -5377,6 +5384,14 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing)
if (targets.length() == 1)
target = &targets[0]->as<JSFunction>();
if (target && status == InliningStatus_WarmUpCountTooLow) {
MRecompileCheck *check =
MRecompileCheck::New(alloc(), target->nonLazyScript(),
optimizationInfo().inliningRecompileThreshold(),
MRecompileCheck::RecompileCheck_Inlining);
current->add(check);
}
return makeCall(target, callInfo, hasClones);
}
@ -6361,7 +6376,9 @@ IonBuilder::insertRecompileCheck()
OptimizationLevel nextLevel = js_IonOptimizations.nextLevel(curLevel);
const OptimizationInfo *info = js_IonOptimizations.get(nextLevel);
uint32_t warmUpThreshold = info->compilerWarmUpThreshold(topBuilder->script());
current->add(MRecompileCheck::New(alloc(), topBuilder->script(), warmUpThreshold));
MRecompileCheck *check = MRecompileCheck::New(alloc(), topBuilder->script(), warmUpThreshold,
MRecompileCheck::RecompileCheck_OptimizationLevel);
current->add(check);
}
JSObject *
@ -9247,6 +9264,7 @@ IonBuilder::getPropTryCommonGetter(bool *emitted, MDefinition *obj, PropertyName
switch (status) {
case InliningStatus_Error:
return false;
case InliningStatus_WarmUpCountTooLow:
case InliningStatus_NotInlined:
break;
case InliningStatus_Inlined:
@ -9263,6 +9281,7 @@ IonBuilder::getPropTryCommonGetter(bool *emitted, MDefinition *obj, PropertyName
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline:
inlineable = true;
@ -9686,6 +9705,7 @@ IonBuilder::setPropTryCommonSetter(bool *emitted, MDefinition *obj,
case InliningDecision_Error:
return false;
case InliningDecision_DontInline:
case InliningDecision_WarmUpCountTooLow:
break;
case InliningDecision_Inline:
if (!inlineScriptedCall(callInfo, commonSetter))

View File

@ -660,6 +660,7 @@ class IonBuilder
{
InliningStatus_Error,
InliningStatus_NotInlined,
InliningStatus_WarmUpCountTooLow,
InliningStatus_Inlined
};
@ -667,7 +668,8 @@ class IonBuilder
{
InliningDecision_Error,
InliningDecision_Inline,
InliningDecision_DontInline
InliningDecision_DontInline,
InliningDecision_WarmUpCountTooLow
};
static InliningDecision DontInline(JSScript *targetScript, const char *reason);

View File

@ -43,6 +43,7 @@ OptimizationInfo::initNormalOptimizationInfo()
smallFunctionMaxInlineDepth_ = 10;
compilerWarmUpThreshold_ = 1000;
inliningWarmUpThresholdFactor_ = 0.125;
inliningRecompileThresholdFactor_ = 4;
}
void

View File

@ -111,6 +111,11 @@ class OptimizationInfo
// are inlined, as a fraction of compilerWarmUpThreshold.
double inliningWarmUpThresholdFactor_;
// How many invocations or loop iterations are needed before a function
// is hot enough to recompile the outerScript to inline that function,
// as a multiplication of inliningWarmUpThreshold.
uint32_t inliningRecompileThresholdFactor_;
OptimizationInfo()
{ }
@ -201,6 +206,10 @@ class OptimizationInfo
compilerWarmUpThreshold = js_JitOptions.forcedDefaultIonWarmUpThreshold;
return compilerWarmUpThreshold * inliningWarmUpThresholdFactor_;
}
uint32_t inliningRecompileThreshold() const {
return inliningWarmUpThreshold() * inliningRecompileThresholdFactor_;
}
};
class OptimizationInfos

View File

@ -11314,21 +11314,45 @@ class MHasClass
// outermost script (i.e. not the inlined script).
class MRecompileCheck : public MNullaryInstruction
{
public:
enum RecompileCheckType {
RecompileCheck_OptimizationLevel,
RecompileCheck_Inlining
};
private:
JSScript *script_;
uint32_t recompileThreshold_;
bool forceRecompilation_;
bool increaseWarmUpCounter_;
MRecompileCheck(JSScript *script, uint32_t recompileThreshold)
MRecompileCheck(JSScript *script, uint32_t recompileThreshold, RecompileCheckType type)
: script_(script),
recompileThreshold_(recompileThreshold)
{
switch (type) {
case RecompileCheck_OptimizationLevel:
forceRecompilation_ = false;
increaseWarmUpCounter_ = true;
break;
case RecompileCheck_Inlining:
forceRecompilation_ = true;
increaseWarmUpCounter_ = false;
break;
default:
MOZ_ASSUME_UNREACHABLE("Unexpected recompile check type");
}
setGuard();
}
public:
INSTRUCTION_HEADER(RecompileCheck);
static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_, uint32_t recompileThreshold) {
return new(alloc) MRecompileCheck(script_, recompileThreshold);
static MRecompileCheck *New(TempAllocator &alloc, JSScript *script_, uint32_t recompileThreshold,
RecompileCheckType type)
{
return new(alloc) MRecompileCheck(script_, recompileThreshold, type);
}
JSScript *script() const {
@ -11339,6 +11363,14 @@ class MRecompileCheck : public MNullaryInstruction
return recompileThreshold_;
}
bool forceRecompilation() const {
return forceRecompilation_;
}
bool increaseWarmUpCounter() const {
return increaseWarmUpCounter_;
}
AliasSet getAliasSet() const {
return AliasSet::None();
}

View File

@ -1081,8 +1081,8 @@ StringReplace(JSContext *cx, HandleString string, HandleString pattern, HandleSt
return rval.toString();
}
bool
Recompile(JSContext *cx)
static bool
RecompileImpl(JSContext *cx, bool force)
{
JS_ASSERT(cx->currentlyRunningInJit());
JitActivationIterator activations(cx->runtime());
@ -1098,13 +1098,25 @@ Recompile(JSContext *cx)
if (!IsIonEnabled(cx))
return true;
MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing);
MethodStatus status = Recompile(cx, script, nullptr, nullptr, isConstructing, force);
if (status == Method_Error)
return false;
return true;
}
bool
ForcedRecompile(JSContext *cx)
{
return RecompileImpl(cx, /* force = */ true);
}
bool
Recompile(JSContext *cx)
{
return RecompileImpl(cx, /* force = */ false);
}
bool
SetDenseElement(JSContext *cx, HandleObject obj, int32_t index, HandleValue value,
bool strict)

View File

@ -711,6 +711,7 @@ JSObject *CreateDerivedTypedObj(JSContext *cx, HandleObject descr,
bool ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount);
bool Recompile(JSContext *cx);
bool ForcedRecompile(JSContext *cx);
JSString *RegExpReplace(JSContext *cx, HandleString string, HandleObject regexp,
HandleString repl);
JSString *StringReplace(JSContext *cx, HandleString string, HandleString pattern,