Bug 848122 - Generalize CallScripted stubs once callee-specific stubs get too numerous. r=jandem

This commit is contained in:
Kannan Vijayan 2013-03-11 11:55:58 -04:00
parent 1f27eb1ac2
commit ad763d6e73
2 changed files with 192 additions and 70 deletions

View File

@ -5328,14 +5328,15 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, JSO
if (useNewType || op == JSOP_EVAL)
return true;
RootedValue callee(cx, vp[0]);
RootedValue thisv(cx, vp[1]);
if (stub->numOptimizedStubs() >= ICCall_Fallback::MAX_OPTIMIZED_STUBS) {
// TODO: Discard all stubs in this IC and replace with generic call stub.
// TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
// But for now we just bail.
return true;
}
RootedValue callee(cx, vp[0]);
RootedValue thisv(cx, vp[1]);
if (!callee.isObject())
return true;
@ -5349,12 +5350,37 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, JSO
if (!calleeScript->hasBaselineScript() && !calleeScript->hasIonScript())
return true;
// Check if this stub chain has already generalized scripted calls.
if (stub->scriptedStubsAreGeneralized()) {
IonSpew(IonSpew_BaselineIC, " Chain already has generalized scripted call stub!");
return true;
}
if (stub->scriptedStubCount() >= ICCall_Fallback::MAX_SCRIPTED_STUBS) {
// Create a Call_AnyScripted stub.
IonSpew(IonSpew_BaselineIC, " Generating Call_AnyScripted stub (cons=%s)",
constructing ? "yes" : "no");
ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
constructing);
ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
// Before adding new stub, unlink all previous Call_Scripted.
stub->unlinkStubsWithKind(cx, ICStub::Call_Scripted);
// Add new generalized stub.
stub->addNewStub(newStub);
return true;
}
IonSpew(IonSpew_BaselineIC,
" Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s)",
fun.get(), fun->nonLazyScript()->filename, fun->nonLazyScript()->lineno,
constructing ? "yes" : "no");
ICCall_Scripted::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
calleeScript, constructing);
ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
calleeScript, constructing);
ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
@ -5364,6 +5390,15 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, JSO
}
if (fun->isNative() && (!constructing || (constructing && fun->isNativeConstructor()))) {
// Generalied native call stubs are not here yet!
JS_ASSERT(!stub->nativeStubsAreGeneralized());
if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) {
IonSpew(IonSpew_BaselineIC, " Too many Call_Native stubs. TODO: add Call_AnyNative!");
return true;
}
IonSpew(IonSpew_BaselineIC, " Generating Call_Native stub (fun=%p, cons=%s)", fun.get(),
constructing ? "yes" : "no");
ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
@ -5517,14 +5552,17 @@ ICCall_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
leaveStubFrame(masm, true);
// R1 and R0 are taken.
regs = availableGeneralRegs(2);
Register scratch = regs.takeAny();
// If this is a |constructing| call, if the callee returns a non-object, we replace it with
// the |this| object passed in.
JS_ASSERT(JSReturnOperand == R0);
Label skipThisReplace;
masm.branch32(Assembler::Equal,
Address(BaselineStubReg, ICCall_Fallback::offsetOfIsConstructing()),
Imm32(0),
&skipThisReplace);
masm.load16ZeroExtend(Address(BaselineStubReg, ICStub::offsetOfExtra()), scratch);
masm.branchTest32(Assembler::Zero, scratch, Imm32(ICCall_Fallback::CONSTRUCTING_FLAG),
&skipThisReplace);
masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace);
masm.moveValue(R1, R0);
#ifdef DEBUG
@ -5557,7 +5595,7 @@ typedef bool (*CreateThisFn)(JSContext *cx, HandleObject callee, MutableHandleVa
static const VMFunction CreateThisInfo = FunctionInfo<CreateThisFn>(CreateThis);
bool
ICCall_Scripted::Compiler::generateStubCode(MacroAssembler &masm)
ICCallScriptedCompiler::generateStubCode(MacroAssembler &masm)
{
Label failure;
GeneralRegisterSet regs(availableGeneralRegs(0));
@ -5572,22 +5610,33 @@ ICCall_Scripted::Compiler::generateStubCode(MacroAssembler &masm)
regs.take(BaselineTailCallReg);
// Load the callee in R1.
// Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, +ICStackValueOffset+ ]
BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value));
masm.loadValue(calleeSlot, R1);
regs.take(R1);
// Ensure callee is an object.
masm.branchTestObject(Assembler::NotEqual, R1, &failure);
// Ensure callee matches this stub's callee.
// Ensure callee is a function.
Register callee = masm.extractObject(R1, ExtractTemp0);
masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &FunctionClass, &failure);
// Object is a function. Check if script matches.
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
Address expectedScript(BaselineStubReg, ICCall_Scripted::offsetOfCalleeScript());
masm.branchPtr(Assembler::NotEqual, expectedScript, callee, &failure);
// If calling a specific script, check if the script matches. Otherwise, ensure that
// callee function is scripted. Leave calleeScript in |callee| reg.
if (calleeScript_) {
JS_ASSERT(kind == ICStub::Call_Scripted);
// Call IonScript or BaselineScript.
// Callee is a function. Check if script matches.
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
Address expectedScript(BaselineStubReg, ICCall_Scripted::offsetOfCalleeScript());
masm.branchPtr(Assembler::NotEqual, expectedScript, callee, &failure);
} else {
masm.branchIfFunctionHasNoScript(callee, &failure);
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
}
// Load IonScript or BaselineScript.
masm.loadBaselineOrIonCode(callee, regs.getAny(), &failure);
// Load the start of the target IonCode.
@ -5620,9 +5669,18 @@ ICCall_Scripted::Compiler::generateStubCode(MacroAssembler &masm)
if (!callVM(CreateThisInfo, masm))
return false;
// Return of CreateThis must be an object.
#ifdef DEBUG
Label createdThisIsObject;
masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject);
masm.breakpoint();
masm.bind(&createdThisIsObject);
#endif
// Reset the register set from here on in.
JS_ASSERT(JSReturnOperand == R0);
regs = availableGeneralRegs(0);
regs.take(JSReturnOperand);
regs.take(R0);
regs.take(ArgumentsRectifierReg);
argcReg = regs.takeAny();
@ -5630,6 +5688,12 @@ ICCall_Scripted::Compiler::generateStubCode(MacroAssembler &masm)
// the resulting this object to.
masm.pop(argcReg);
// Save "this" value back into pushed arguments on stack. R0 can be clobbered after that.
// Stack now looks like:
// [..., Callee, ThisV, Arg0V, ..., ArgNV, StubFrameHeader ]
BaseIndex thisSlot(BaselineStackReg, argcReg, TimesEight, STUB_FRAME_SIZE);
masm.storeValue(R0, thisSlot);
// Restore the stub register from the baseline stub frame.
masm.loadPtr(Address(BaselineStackReg, STUB_FRAME_SAVED_STUB_OFFSET), BaselineStubReg);
@ -5637,19 +5701,22 @@ ICCall_Scripted::Compiler::generateStubCode(MacroAssembler &masm)
// have destroyed the callee BaselineScript and IonScript. CreateThis is
// safely repeatable though, so in this case we just leave the stub frame
// and jump to the next stub.
callee = regs.takeAny();
// Just need to load the script now. This can be done from the IC stub because
// it has already been verified that callee's script is the same as the stub's script.
masm.loadPtr(expectedScript, callee);
// Just need to load the script now.
BaseIndex calleeSlot3(BaselineStackReg, argcReg, TimesEight,
sizeof(Value) + STUB_FRAME_SIZE);
masm.loadValue(calleeSlot3, R0);
callee = masm.extractObject(R0, ExtractTemp0);
regs.add(R0);
regs.takeUnchecked(callee);
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee);
Register loadScratch = ArgumentsRectifierReg;
masm.loadBaselineOrIonCode(callee, loadScratch, &failureLeaveStubFrame);
regs.add(callee);
// Save "this" value back into pushed arguments on stack.
BaseIndex thisSlot(BaselineStackReg, argcReg, TimesEight, STUB_FRAME_SIZE);
masm.storeValue(JSReturnOperand, thisSlot);
regs.add(JSReturnOperand);
// Release callee register, but don't add ExtractTemp0 back into the pool
// ExtractTemp0 is used later, and if it's allocated to some other register at that
// point, it will get clobbered when used.
if (callee != ExtractTemp0)
regs.add(callee);
// Load the start of the target IonCode.
code = regs.takeAny();

View File

@ -321,6 +321,7 @@ class ICEntry
\
_(Call_Fallback) \
_(Call_Scripted) \
_(Call_AnyScripted) \
_(Call_Native) \
\
_(GetElem_Fallback) \
@ -713,6 +714,7 @@ class ICStub
static bool CanMakeCalls(ICStub::Kind kind) {
switch (kind) {
case Call_Scripted:
case Call_AnyScripted:
case Call_Native:
case Call_Fallback:
case UseCount_Fallback:
@ -799,26 +801,32 @@ class ICFallbackStub : public ICStub
lastStubPtrAddr_ = stub->addressOfNext();
numOptimizedStubs_++;
}
bool hasStub(ICStub::Kind kind) {
ICStub *stub = icEntry_->firstStub();
do {
if (stub->kind() == kind)
return true;
stub = stub->next();
} while (stub);
return false;
}
ICStubConstIterator beginChainConst() {
return ICStubConstIterator(this->icEntry()->firstStub());
ICStubConstIterator beginChainConst() const {
return ICStubConstIterator(icEntry_->firstStub());
}
ICStubIterator beginChain() {
return ICStubIterator(this);
}
bool hasStub(ICStub::Kind kind) const {
for (ICStubConstIterator iter = beginChainConst(); !iter.atEnd(); iter++) {
if (iter->kind() == kind)
return true;
}
return false;
}
unsigned numStubsWithKind(ICStub::Kind kind) const {
unsigned count = 0;
for (ICStubConstIterator iter = beginChainConst(); !iter.atEnd(); iter++) {
if (iter->kind() == kind)
count++;
}
return count;
}
void unlinkStub(Zone *zone, ICStub *prev, ICStub *stub);
void unlinkStubsWithKind(JSContext *cx, ICStub::Kind kind);
};
@ -4368,15 +4376,23 @@ class ICCallStubCompiler : public ICStubCompiler
class ICCall_Fallback : public ICMonitoredFallbackStub
{
friend class ICStubSpace;
uint32_t isConstructing_;
public:
static const unsigned CONSTRUCTING_FLAG = 0x0001;
static const uint32_t MAX_OPTIMIZED_STUBS = 16;
static const uint32_t MAX_SCRIPTED_STUBS = 7;
static const uint32_t MAX_NATIVE_STUBS = 7;
private:
ICCall_Fallback(IonCode *stubCode, bool isConstructing)
: ICMonitoredFallbackStub(ICStub::Call_Fallback, stubCode),
isConstructing_(isConstructing ? 1 : 0)
{ }
: ICMonitoredFallbackStub(ICStub::Call_Fallback, stubCode)
{
extra_ = 0;
if (isConstructing)
extra_ |= CONSTRUCTING_FLAG;
}
public:
static const uint32_t MAX_OPTIMIZED_STUBS = 8;
static inline ICCall_Fallback *New(ICStubSpace *space, IonCode *code, bool isConstructing)
{
@ -4386,10 +4402,22 @@ class ICCall_Fallback : public ICMonitoredFallbackStub
}
bool isConstructing() const {
return isConstructing_;
return extra_ & CONSTRUCTING_FLAG;
}
static size_t offsetOfIsConstructing() {
return offsetof(ICCall_Fallback, isConstructing_);
unsigned scriptedStubCount() const {
return numStubsWithKind(Call_Scripted);
}
bool scriptedStubsAreGeneralized() const {
return hasStub(Call_AnyScripted);
}
unsigned nativeStubCount() const {
return numStubsWithKind(Call_Native);
}
bool nativeStubsAreGeneralized() const {
// Return hasStub(Call_AnyNative) after Call_AnyNative stub is added.
return false;
}
// Compiler for this stub kind.
@ -4441,32 +4469,59 @@ class ICCall_Scripted : public ICMonitoredStub
HeapPtrScript &calleeScript() {
return calleeScript_;
}
};
// Compiler for this stub kind.
class Compiler : public ICCallStubCompiler {
protected:
ICStub *firstMonitorStub_;
bool isConstructing_;
RootedScript calleeScript_;
bool generateStubCode(MacroAssembler &masm);
class ICCall_AnyScripted : public ICMonitoredStub
{
friend class ICStubSpace;
virtual int32_t getKey() const {
return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16);
}
ICCall_AnyScripted(IonCode *stubCode, ICStub *firstMonitorStub)
: ICMonitoredStub(ICStub::Call_AnyScripted, stubCode, firstMonitorStub)
{ }
public:
Compiler(JSContext *cx, ICStub *firstMonitorStub, HandleScript calleeScript,
bool isConstructing)
: ICCallStubCompiler(cx, ICStub::Call_Scripted),
firstMonitorStub_(firstMonitorStub),
isConstructing_(isConstructing),
calleeScript_(cx, calleeScript)
{ }
public:
static inline ICCall_AnyScripted *New(ICStubSpace *space, IonCode *code,
ICStub *firstMonitorStub)
{
if (!code)
return NULL;
return space->allocate<ICCall_AnyScripted>(code, firstMonitorStub);
}
};
ICStub *getStub(ICStubSpace *space) {
// Compiler for Call_Scripted and Call_AnyScripted stubs.
class ICCallScriptedCompiler : public ICCallStubCompiler {
protected:
ICStub *firstMonitorStub_;
bool isConstructing_;
RootedScript calleeScript_;
bool generateStubCode(MacroAssembler &masm);
virtual int32_t getKey() const {
return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16);
}
public:
ICCallScriptedCompiler(JSContext *cx, ICStub *firstMonitorStub, HandleScript calleeScript,
bool isConstructing)
: ICCallStubCompiler(cx, ICStub::Call_Scripted),
firstMonitorStub_(firstMonitorStub),
isConstructing_(isConstructing),
calleeScript_(cx, calleeScript)
{ }
ICCallScriptedCompiler(JSContext *cx, ICStub *firstMonitorStub, bool isConstructing)
: ICCallStubCompiler(cx, ICStub::Call_AnyScripted),
firstMonitorStub_(firstMonitorStub),
isConstructing_(isConstructing),
calleeScript_(cx, NULL)
{ }
ICStub *getStub(ICStubSpace *space) {
if (calleeScript_)
return ICCall_Scripted::New(space, getStubCode(), firstMonitorStub_, calleeScript_);
}
};
return ICCall_AnyScripted::New(space, getStubCode(), firstMonitorStub_);
}
};
class ICCall_Native : public ICMonitoredStub