Bug 866878 - Support try-finally in the baseline compiler. r=djvj

--HG--
extra : rebase_source : c5bd88422ce39de94888fad060259ef2590acb5a
This commit is contained in:
Jan de Mooij 2013-06-17 14:05:36 +02:00
parent 50c72a104f
commit fa3bea4976
19 changed files with 462 additions and 19 deletions

View File

@ -429,6 +429,8 @@ static const VMFunction InterruptCheckInfo = FunctionInfo<InterruptCheckFn>(Inte
bool
BaselineCompiler::emitInterruptCheck()
{
frame.syncStack(0);
Label done;
void *interrupt = (void *)&cx->compartment()->rt->interrupt;
masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done);
@ -2316,6 +2318,45 @@ BaselineCompiler::emit_JSOP_TRY()
return true;
}
bool
BaselineCompiler::emit_JSOP_FINALLY()
{
// JSOP_FINALLY has a def count of 2, but these values are already on the
// stack (they're pushed by JSOP_GOSUB). Update the compiler's stack state.
frame.setStackDepth(frame.stackDepth() + 2);
// To match the interpreter, emit an interrupt check at the start of the
// finally block.
return emitInterruptCheck();
}
bool
BaselineCompiler::emit_JSOP_GOSUB()
{
// Push |false| so that RETSUB knows the value on top of the
// stack is not an exception but the offset to the op following
// this GOSUB.
frame.push(BooleanValue(false));
int32_t nextOffset = GetNextPc(pc) - script->code;
frame.push(Int32Value(nextOffset));
// Jump to the finally block.
frame.syncStack(0);
jsbytecode *target = pc + GET_JUMP_OFFSET(pc);
masm.jump(labelOf(target));
return true;
}
bool
BaselineCompiler::emit_JSOP_RETSUB()
{
frame.popRegsAndSync(2);
ICRetSub_Fallback::Compiler stubCompiler(cx);
return emitOpIC(stubCompiler.getStub(&stubSpace_));
}
typedef bool (*EnterBlockFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>);
static const VMFunction EnterBlockInfo = FunctionInfo<EnterBlockFn>(ion::EnterBlock);

View File

@ -154,6 +154,9 @@ namespace ion {
_(JSOP_SETCALL) \
_(JSOP_THROW) \
_(JSOP_TRY) \
_(JSOP_FINALLY) \
_(JSOP_GOSUB) \
_(JSOP_RETSUB) \
_(JSOP_ENTERBLOCK) \
_(JSOP_ENTERLET0) \
_(JSOP_ENTERLET1) \

View File

@ -132,6 +132,9 @@ BaselineFrame::initForOsr(StackFrame *fp, uint32_t numStackValues)
hookData_ = fp->hookData();
}
if (fp->hasReturnValue())
setReturnValue(fp->returnValue());
if (fp->hasPushedSPSFrame())
flags_ |= BaselineFrame::HAS_PUSHED_SPS_FRAME;

View File

@ -8197,6 +8197,111 @@ ICRest_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
return tailCallVM(DoCreateRestParameterInfo, masm);
}
static bool
DoRetSubFallback(JSContext *cx, BaselineFrame *frame, ICRetSub_Fallback *stub,
HandleValue val, uint8_t **resumeAddr)
{
FallbackICSpew(cx, stub, "RetSub");
// |val| is the bytecode offset where we should resume.
JS_ASSERT(val.isInt32());
JS_ASSERT(val.toInt32() >= 0);
JSScript *script = frame->script();
uint32_t offset = uint32_t(val.toInt32());
JS_ASSERT(offset < script->length);
*resumeAddr = script->baselineScript()->nativeCodeForPC(script, script->code + offset);
if (stub->numOptimizedStubs() >= ICRetSub_Fallback::MAX_OPTIMIZED_STUBS)
return true;
// Attach an optimized stub for this pc offset.
IonSpew(IonSpew_BaselineIC, " Generating RetSub stub for pc offset %u", offset);
ICRetSub_Resume::Compiler compiler(cx, offset, *resumeAddr);
ICStub *optStub = compiler.getStub(compiler.getStubSpace(script));
if (!optStub)
return false;
stub->addNewStub(optStub);
return true;
}
typedef bool(*DoRetSubFallbackFn)(JSContext *cx, BaselineFrame *, ICRetSub_Fallback *,
HandleValue, uint8_t **);
static const VMFunction DoRetSubFallbackInfo = FunctionInfo<DoRetSubFallbackFn>(DoRetSubFallback);
typedef bool (*ThrowFn)(JSContext *, HandleValue);
static const VMFunction ThrowInfo = FunctionInfo<ThrowFn>(js::Throw);
bool
ICRetSub_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
{
// If R0 is BooleanValue(true), rethrow R1.
Label rethrow;
masm.branchTestBooleanTruthy(true, R0, &rethrow);
{
// Call a stub to get the native code address for the pc offset in R1.
GeneralRegisterSet regs(availableGeneralRegs(0));
regs.take(R1);
regs.takeUnchecked(BaselineTailCallReg);
Register frame = regs.takeAny();
masm.movePtr(BaselineFrameReg, frame);
enterStubFrame(masm, regs.getAny());
masm.pushValue(R1);
masm.push(BaselineStubReg);
masm.pushBaselineFramePtr(frame, frame);
if (!callVM(DoRetSubFallbackInfo, masm))
return false;
leaveStubFrame(masm);
EmitChangeICReturnAddress(masm, ReturnReg);
EmitReturnFromIC(masm);
}
masm.bind(&rethrow);
EmitRestoreTailCallReg(masm);
masm.pushValue(R1);
return tailCallVM(ThrowInfo, masm);
}
bool
ICRetSub_Resume::Compiler::generateStubCode(MacroAssembler &masm)
{
// If R0 is BooleanValue(true), rethrow R1.
Label fail, rethrow;
masm.branchTestBooleanTruthy(true, R0, &rethrow);
// R1 is the pc offset. Ensure it matches this stub's offset.
Register offset = masm.extractInt32(R1, ExtractTemp0);
masm.branch32(Assembler::NotEqual,
Address(BaselineStubReg, ICRetSub_Resume::offsetOfPCOffset()),
offset,
&fail);
// pc offset matches, resume at the target pc.
masm.loadPtr(Address(BaselineStubReg, ICRetSub_Resume::offsetOfAddr()), R0.scratchReg());
EmitChangeICReturnAddress(masm, R0.scratchReg());
EmitReturnFromIC(masm);
// Rethrow the Value stored in R1.
masm.bind(&rethrow);
EmitRestoreTailCallReg(masm);
masm.pushValue(R1);
if (!tailCallVM(ThrowInfo, masm))
return false;
masm.bind(&fail);
EmitStubGuardFailure(masm);
return true;
}
ICProfiler_PushFunction::ICProfiler_PushFunction(IonCode *stubCode, const char *str,
HandleScript script)
: ICStub(ICStub::Profiler_PushFunction, stubCode),

View File

@ -392,7 +392,10 @@ class ICEntry
_(TypeOf_Fallback) \
_(TypeOf_Typed) \
\
_(Rest_Fallback)
_(Rest_Fallback) \
\
_(RetSub_Fallback) \
_(RetSub_Resume)
#define FORWARD_DECLARE_STUBS(kindName) class IC##kindName;
IC_STUB_KIND_LIST(FORWARD_DECLARE_STUBS)
@ -740,6 +743,7 @@ class ICStub
case GetProp_DOMProxyShadowed:
case SetProp_CallScripted:
case SetProp_CallNative:
case RetSub_Fallback:
return true;
default:
return false;
@ -5547,6 +5551,89 @@ class ICRest_Fallback : public ICFallbackStub
};
};
// Stub for JSOP_RETSUB ("returning" from a |finally| block).
class ICRetSub_Fallback : public ICFallbackStub
{
friend class ICStubSpace;
ICRetSub_Fallback(IonCode *stubCode)
: ICFallbackStub(ICStub::RetSub_Fallback, stubCode)
{ }
public:
static const uint32_t MAX_OPTIMIZED_STUBS = 8;
static inline ICRetSub_Fallback *New(ICStubSpace *space, IonCode *code) {
if (!code)
return NULL;
return space->allocate<ICRetSub_Fallback>(code);
}
class Compiler : public ICStubCompiler {
protected:
bool generateStubCode(MacroAssembler &masm);
public:
Compiler(JSContext *cx)
: ICStubCompiler(cx, ICStub::RetSub_Fallback)
{ }
ICStub *getStub(ICStubSpace *space) {
return ICRetSub_Fallback::New(space, getStubCode());
}
};
};
// Optimized JSOP_RETSUB stub. Every stub maps a single pc offset to its
// native code address.
class ICRetSub_Resume : public ICStub
{
friend class ICStubSpace;
protected:
uint32_t pcOffset_;
uint8_t *addr_;
ICRetSub_Resume(IonCode *stubCode, uint32_t pcOffset, uint8_t *addr)
: ICStub(ICStub::RetSub_Resume, stubCode),
pcOffset_(pcOffset),
addr_(addr)
{ }
public:
static ICRetSub_Resume *New(ICStubSpace *space, IonCode *code, uint32_t pcOffset,
uint8_t *addr) {
if (!code)
return NULL;
return space->allocate<ICRetSub_Resume>(code, pcOffset, addr);
}
static size_t offsetOfPCOffset() {
return offsetof(ICRetSub_Resume, pcOffset_);
}
static size_t offsetOfAddr() {
return offsetof(ICRetSub_Resume, addr_);
}
class Compiler : public ICStubCompiler {
uint32_t pcOffset_;
uint8_t *addr_;
bool generateStubCode(MacroAssembler &masm);
public:
Compiler(JSContext *cx, uint32_t pcOffset, uint8_t *addr)
: ICStubCompiler(cx, ICStub::RetSub_Resume),
pcOffset_(pcOffset),
addr_(addr)
{ }
ICStub *getStub(ICStubSpace *space) {
return ICRetSub_Resume::New(space, getStubCode(), pcOffset_, addr_);
}
};
};
} // namespace ion
} // namespace js

View File

@ -429,6 +429,17 @@ HandleException(JSContext *cx, const IonFrameIterator &frame, ResumeFromExceptio
}
break;
case JSTRY_FINALLY:
if (cx->isExceptionPending()) {
rfe->kind = ResumeFromException::RESUME_FINALLY;
jsbytecode *finallyPC = script->main() + tn->start + tn->length;
rfe->target = script->baselineScript()->nativeCodeForPC(script, finallyPC);
rfe->exception = cx->getPendingException();
cx->clearPendingException();
return;
}
break;
case JSTRY_ITER: {
Value iterValue(* (Value *) rfe->stackPointer);
RootedObject iterObject(cx, &iterValue.toObject());

View File

@ -259,12 +259,16 @@ struct ResumeFromException
{
static const uint32_t RESUME_ENTRY_FRAME = 0;
static const uint32_t RESUME_CATCH = 1;
static const uint32_t RESUME_FORCED_RETURN = 2;
static const uint32_t RESUME_FINALLY = 2;
static const uint32_t RESUME_FORCED_RETURN = 3;
uint8_t *framePointer;
uint8_t *stackPointer;
uint8_t *target;
uint32_t kind;
// Value to push when resuming into a |finally| block.
Value exception;
};
void HandleException(ResumeFromException *rfe);

View File

@ -20,6 +20,7 @@ enum DataType {
Type_Void,
Type_Bool,
Type_Int32,
Type_Pointer,
Type_Object,
Type_Value,
Type_Handle,
@ -328,6 +329,7 @@ template <class> struct OutParamToDataType { static const DataType result = Type
template <> struct OutParamToDataType<Value *> { static const DataType result = Type_Value; };
template <> struct OutParamToDataType<int *> { static const DataType result = Type_Int32; };
template <> struct OutParamToDataType<uint32_t *> { static const DataType result = Type_Int32; };
template <> struct OutParamToDataType<uint8_t **> { static const DataType result = Type_Pointer; };
template <> struct OutParamToDataType<MutableHandleValue> { static const DataType result = Type_Handle; };
template <> struct OutParamToDataType<MutableHandleObject> { static const DataType result = Type_Handle; };

View File

@ -3219,13 +3219,15 @@ MacroAssemblerARMCompat::handleFailureWithHandler(void *handler)
passABIArg(r0);
callWithABI(handler);
Label catch_;
Label entryFrame;
Label catch_;
Label finally;
Label return_;
ma_ldr(Operand(sp, offsetof(ResumeFromException, kind)), r0);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
branch32(Assembler::Equal, r0, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
breakpoint(); // Invalid kind.
@ -3248,6 +3250,21 @@ MacroAssemblerARMCompat::handleFailureWithHandler(void *handler)
ma_ldr(Operand(sp, offsetof(ResumeFromException, stackPointer)), sp);
jump(r0);
// If we found a finally block, this must be a baseline frame. Push
// two values expected by JSOP_RETSUB: BooleanValue(true) and the
// exception.
bind(&finally);
ValueOperand exception = ValueOperand(r1, r2);
loadValue(Operand(sp, offsetof(ResumeFromException, exception)), exception);
ma_ldr(Operand(sp, offsetof(ResumeFromException, target)), r0);
ma_ldr(Operand(sp, offsetof(ResumeFromException, framePointer)), r11);
ma_ldr(Operand(sp, offsetof(ResumeFromException, stackPointer)), sp);
pushValue(BooleanValue(true));
pushValue(exception);
jump(r0);
// Only used in debug mode. Return BaselineFrame->returnValue() to the caller.
bind(&return_);
ma_ldr(Operand(sp, offsetof(ResumeFromException, framePointer)), r11);

View File

@ -634,6 +634,7 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
break;
case Type_Int32:
case Type_Pointer:
outReg = r4;
regs.take(outReg);
masm.reserveStack(sizeof(int32_t));
@ -710,6 +711,7 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
break;
case Type_Int32:
case Type_Pointer:
masm.load32(Address(sp, 0), ReturnReg);
masm.freeStack(sizeof(int32_t));
break;

View File

@ -189,16 +189,16 @@ MacroAssemblerX64::handleFailureWithHandler(void *handler)
passABIArg(rax);
callWithABI(handler);
Label catch_;
Label entryFrame;
Label catch_;
Label finally;
Label return_;
branch32(Assembler::Equal, Address(rsp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, Address(rsp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, Address(esp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
loadPtr(Address(rsp, offsetof(ResumeFromException, kind)), rax);
branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
branch32(Assembler::Equal, rax, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
breakpoint(); // Invalid kind.
@ -217,6 +217,21 @@ MacroAssemblerX64::handleFailureWithHandler(void *handler)
movq(Operand(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
jmp(Operand(rax));
// If we found a finally block, this must be a baseline frame. Push
// two values expected by JSOP_RETSUB: BooleanValue(true) and the
// exception.
bind(&finally);
ValueOperand exception = ValueOperand(rcx);
loadValue(Operand(esp, offsetof(ResumeFromException, exception)), exception);
movq(Operand(rsp, offsetof(ResumeFromException, target)), rax);
movq(Operand(rsp, offsetof(ResumeFromException, framePointer)), rbp);
movq(Operand(rsp, offsetof(ResumeFromException, stackPointer)), rsp);
pushValue(BooleanValue(true));
pushValue(exception);
jmp(Operand(rax));
// Only used in debug mode. Return BaselineFrame->returnValue() to the caller.
bind(&return_);
movq(Operand(rsp, offsetof(ResumeFromException, framePointer)), rbp);

View File

@ -538,6 +538,12 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
masm.movq(esp, outReg);
break;
case Type_Pointer:
outReg = regs.takeAny();
masm.reserveStack(sizeof(uintptr_t));
masm.movq(esp, outReg);
break;
default:
JS_ASSERT(f.outParam == Type_Void);
break;
@ -612,6 +618,11 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
masm.freeStack(sizeof(int32_t));
break;
case Type_Pointer:
masm.loadPtr(Address(esp, 0), ReturnReg);
masm.freeStack(sizeof(uintptr_t));
break;
default:
JS_ASSERT(f.outParam == Type_Void);
break;

View File

@ -204,16 +204,16 @@ MacroAssemblerX86::handleFailureWithHandler(void *handler)
passABIArg(eax);
callWithABI(handler);
Label catch_;
Label entryFrame;
Label catch_;
Label finally;
Label return_;
branch32(Assembler::Equal, Address(esp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, Address(esp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, Address(esp, offsetof(ResumeFromException, kind)),
Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
loadPtr(Address(esp, offsetof(ResumeFromException, kind)), eax);
branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame);
branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_CATCH), &catch_);
branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FINALLY), &finally);
branch32(Assembler::Equal, eax, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_);
breakpoint(); // Invalid kind.
@ -232,6 +232,21 @@ MacroAssemblerX86::handleFailureWithHandler(void *handler)
movl(Operand(esp, offsetof(ResumeFromException, stackPointer)), esp);
jmp(Operand(eax));
// If we found a finally block, this must be a baseline frame. Push
// two values expected by JSOP_RETSUB: BooleanValue(true) and the
// exception.
bind(&finally);
ValueOperand exception = ValueOperand(ecx, edx);
loadValue(Operand(esp, offsetof(ResumeFromException, exception)), exception);
movl(Operand(esp, offsetof(ResumeFromException, target)), eax);
movl(Operand(esp, offsetof(ResumeFromException, framePointer)), ebp);
movl(Operand(esp, offsetof(ResumeFromException, stackPointer)), esp);
pushValue(BooleanValue(true));
pushValue(exception);
jmp(Operand(eax));
// Only used in debug mode. Return BaselineFrame->returnValue() to the caller.
bind(&return_);
movl(Operand(esp, offsetof(ResumeFromException, framePointer)), ebp);

View File

@ -550,6 +550,7 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
break;
case Type_Int32:
case Type_Pointer:
outReg = regs.takeAny();
masm.reserveStack(sizeof(int32_t));
masm.movl(esp, outReg);
@ -630,8 +631,9 @@ IonRuntime::generateVMWrapper(JSContext *cx, const VMFunction &f)
break;
case Type_Int32:
case Type_Pointer:
masm.load32(Address(esp, 0), ReturnReg);
masm.freeStack(sizeof(JSBool));
masm.freeStack(sizeof(int32_t));
break;
default:

View File

@ -0,0 +1,28 @@
function test1() {
try {
return "try";
} finally {
return "finally";
}
}
assertEq(test1(), "finally");
function test2() {
try {
throw 4;
} catch(e) {
return "catch";
} finally {
return "finally";
}
}
assertEq(test2(), "finally");
function test3() {
try {
throw 4;
} finally {
return "finally"; // Don't rethrow.
}
}
assertEq(test3(), "finally");

View File

@ -0,0 +1,37 @@
var count = 0;
function f() {
try {
try {
try {
count += 2;
} finally {
count += 3;
throw 3;
}
} catch(e) {
count += 4;
throw 4;
}
} finally {
count += 5;
try {
count += 6;
} catch(e) {
count += 7;
throw 123;
} finally {
count += 8;
}
count += 9;
}
count += 10;
}
for (var i=0; i<3; i++) {
try {
f();
assertEq(0, 1);
} catch(e) {
assertEq(e, 4);
}
}
assertEq(count, 111);

View File

@ -0,0 +1,30 @@
// Test optimized RetSub stubs.
var count = 0;
function f(x) {
try {
if (x < 0)
throw "negative";
if (x & 1)
return "odd";
count++;
} finally {
count += 3;
}
return "even";
}
for (var i=0; i<15; i++) {
var res = f(i);
if ((i % 2) === 0)
assertEq(res, "even");
else
assertEq(res, "odd");
}
try {
f(-1);
assertEq(0, 1);
} catch(e) {
assertEq(e, "negative");
}
assertEq(count, 56);

View File

@ -0,0 +1,29 @@
var count = 0;
// OSR into a finally block should not throw away the frame's
// return value.
function test1() {
try {
return [1, 2, 3];
} finally {
for (var i=0; i<20; i++) { count++; }
}
}
assertEq(test1().toString(), "1,2,3");
assertEq(count, 20);
// OSR into the finally block, with exception pending.
function test2() {
try {
throw 3;
} finally {
for (var i=0; i<20; i++) { count++; }
}
}
try {
test2();
assertEq(0, 1);
} catch(e) {
assertEq(e, 3);
}
assertEq(count, 40);

View File

@ -22,7 +22,8 @@ if (typeof findReferences == "function") {
try {
return o;
} finally {
rvalueCorrect = referencesVia(null, 'rval', o);
rvalueCorrect = referencesVia(null, 'rval', o) ||
referencesVia(null, 'baseline-rval', o);
}
}
rvalueCorrect = false;