Trace apply and call (462482, r=brendan).

This commit is contained in:
Andreas Gal 2008-12-04 18:07:18 -08:00
parent a5c2753bb6
commit e367780a2f
4 changed files with 182 additions and 121 deletions

View File

@ -4904,6 +4904,7 @@ js_Interpret(JSContext *cx)
argc = applylen; argc = applylen;
} }
regs.sp = vp + 2 + argc; regs.sp = vp + 2 + argc;
TRACE_1(ApplyComplete, argc);
goto do_call_with_specified_vp_and_argc; goto do_call_with_specified_vp_and_argc;
} }

View File

@ -1017,7 +1017,6 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag
this->callDepth = _anchor ? _anchor->calldepth : 0; this->callDepth = _anchor ? _anchor->calldepth : 0;
this->atoms = cx->fp->script->atomMap.vector; this->atoms = cx->fp->script->atomMap.vector;
this->deepAborted = false; this->deepAborted = false;
this->applyingArguments = false;
this->trashSelf = false; this->trashSelf = false;
this->global_dslots = this->globalObj->dslots; this->global_dslots = this->globalObj->dslots;
this->terminate = false; this->terminate = false;
@ -3808,9 +3807,8 @@ js_MonitorRecording(TraceRecorder* tr)
if (tr->walkedOutOfLoop()) if (tr->walkedOutOfLoop())
return js_CloseLoop(cx); return js_CloseLoop(cx);
// Clear one-shot state used to communicate between record_JSOP_CALL and mid- and post- // Clear one-shot state used to communicate between record_JSOP_CALL and post-
// opcode-case-guts record hooks (record_EnterFrame, record_FastNativeCallComplete). // opcode-case-guts record hook (record_FastNativeCallComplete).
tr->applyingArguments = false;
tr->pendingTraceableNative = NULL; tr->pendingTraceableNative = NULL;
// In the future, handle dslots realloc by computing an offset from dslots instead. // In the future, handle dslots realloc by computing an offset from dslots instead.
@ -5132,6 +5130,7 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
v_ins = lir->ins2(LIR_piand, v_ins, INS_CONST(~JSVAL_TAGMASK)); v_ins = lir->ins2(LIR_piand, v_ins, INS_CONST(~JSVAL_TAGMASK));
return true; return true;
} }
JS_NOT_REACHED("unbox_jsval");
return false; return false;
} }
@ -5318,21 +5317,11 @@ TraceRecorder::record_EnterFrame()
jsval* vp = &fp->argv[fp->argc]; jsval* vp = &fp->argv[fp->argc];
jsval* vpstop = vp + ptrdiff_t(fp->fun->nargs) - ptrdiff_t(fp->argc); jsval* vpstop = vp + ptrdiff_t(fp->fun->nargs) - ptrdiff_t(fp->argc);
if (applyingArguments) {
applyingArguments = false;
while (vp < vpstop) {
JS_ASSERT(vp >= fp->down->regs->sp);
nativeFrameTracker.set(vp, (LIns*)0);
LIns* arg_ins = get(&fp->down->argv[fp->argc + (vp - vpstop)]);
set(vp++, arg_ins, true);
}
} else {
while (vp < vpstop) { while (vp < vpstop) {
if (vp >= fp->down->regs->sp) if (vp >= fp->down->regs->sp)
nativeFrameTracker.set(vp, (LIns*)0); nativeFrameTracker.set(vp, (LIns*)0);
set(vp++, void_ins, true); set(vp++, void_ins, true);
} }
}
vp = &fp->slots[0]; vp = &fp->slots[0];
vpstop = vp + fp->script->nfixed; vpstop = vp + fp->script->nfixed;
@ -5738,15 +5727,11 @@ js_Object(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool JSBool
js_Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval); js_Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
JSBool
js_fun_apply(JSContext* cx, uintN argc, jsval* vp);
bool bool
TraceRecorder::functionCall(bool constructing) TraceRecorder::functionCall(bool constructing, uintN argc)
{ {
JSStackFrame* fp = cx->fp; JSStackFrame* fp = cx->fp;
jsbytecode *pc = fp->regs->pc; jsbytecode *pc = fp->regs->pc;
uintN argc = GET_ARGC(pc);
jsval& fval = stackval(0 - (2 + argc)); jsval& fval = stackval(0 - (2 + argc));
JS_ASSERT(&fval >= StackBase(fp)); JS_ASSERT(&fval >= StackBase(fp));
@ -5762,7 +5747,7 @@ TraceRecorder::functionCall(bool constructing)
* at recording time, the call at this point will always be shapeless so we * at recording time, the call at this point will always be shapeless so we
* can make the decision based on recording-time introspection of this. * can make the decision based on recording-time introspection of this.
*/ */
if (tval == JSVAL_NULL && !guardShapelessCallee(fval)) if (tval == JSVAL_NULL && !guardCallee(fval))
return false; return false;
/* /*
@ -5788,87 +5773,6 @@ TraceRecorder::functionCall(bool constructing)
return interpretedFunctionCall(fval, fun, argc, constructing); return interpretedFunctionCall(fval, fun, argc, constructing);
} }
LIns* arg1_ins = NULL;
jsval arg1 = JSVAL_VOID;
jsval thisval = tval;
if (!constructing && FUN_FAST_NATIVE(fun) == js_fun_apply) {
if (argc != 2)
ABORT_TRACE("can't trace Function.prototype.apply with other than 2 args");
if (!guardShapelessCallee(tval))
return false;
JSObject* tfunobj = JSVAL_TO_OBJECT(tval);
JSFunction* tfun = GET_FUNCTION_PRIVATE(cx, tfunobj);
jsval& oval = stackval(-2);
if (JSVAL_IS_PRIMITIVE(oval))
ABORT_TRACE("can't trace Function.prototype.apply with primitive 1st arg");
jsval& aval = stackval(-1);
if (JSVAL_IS_PRIMITIVE(aval))
ABORT_TRACE("can't trace Function.prototype.apply with primitive 2nd arg");
JSObject* aobj = JSVAL_TO_OBJECT(aval);
LIns* aval_ins = get(&aval);
if (!aval_ins->isCall())
ABORT_TRACE("can't trace Function.prototype.apply on non-builtin-call 2nd arg");
if (aval_ins->callInfo() == &js_Arguments_ci) {
JS_ASSERT(OBJ_GET_CLASS(cx, aobj) == &js_ArgumentsClass);
JS_ASSERT(OBJ_GET_PRIVATE(cx, aobj) == fp);
if (!FUN_INTERPRETED(tfun))
ABORT_TRACE("can't trace Function.prototype.apply(native_function, arguments)");
// We can only fasttrack applys where the argument array we pass in has the
// same length (fp->argc) as the number of arguments the function expects (tfun->nargs).
argc = fp->argc;
if (tfun->nargs != argc || fp->fun->nargs != argc)
ABORT_TRACE("can't trace Function.prototype.apply(scripted_function, arguments)");
jsval* sp = fp->regs->sp - 4;
set(sp, get(&tval));
*sp++ = tval;
set(sp, get(&oval));
*sp++ = oval;
jsval* newsp = sp + argc;
if (newsp > fp->slots + fp->script->nslots) {
JSArena* a = cx->stackPool.current;
if (jsuword(newsp) > a->limit)
ABORT_TRACE("can't grow stack for Function.prototype.apply");
if (jsuword(newsp) > a->avail)
a->avail = jsuword(newsp);
}
jsval* argv = fp->argv;
for (uintN i = 0; i < JS_MIN(argc, 2); i++) {
set(&sp[i], get(&argv[i]));
sp[i] = argv[i];
}
applyingArguments = true;
return interpretedFunctionCall(tval, tfun, argc, false);
}
if (aval_ins->callInfo() != &js_Array_1str_ci)
ABORT_TRACE("can't trace Function.prototype.apply on other than [str] 2nd arg");
JS_ASSERT(OBJ_IS_ARRAY(cx, aobj));
JS_ASSERT(aobj->fslots[JSSLOT_ARRAY_LENGTH] == 1);
JS_ASSERT(JSVAL_IS_STRING(aobj->dslots[0]));
if (FUN_INTERPRETED(tfun))
ABORT_TRACE("can't trace Function.prototype.apply for scripted functions");
if (!(tfun->flags & JSFUN_TRACEABLE))
ABORT_TRACE("Function.prototype.apply on untraceable native");
thisval = oval;
this_ins = get(&oval);
arg1_ins = callArgN(aval_ins, 2);
arg1 = aobj->dslots[0];
fun = tfun;
argc = 1;
}
if (!constructing && !(fun->flags & JSFUN_TRACEABLE)) if (!constructing && !(fun->flags & JSFUN_TRACEABLE))
ABORT_TRACE("untraceable native"); ABORT_TRACE("untraceable native");
@ -5906,11 +5810,11 @@ TraceRecorder::functionCall(bool constructing)
if (argtype == 'C') { if (argtype == 'C') {
*argp = cx_ins; *argp = cx_ins;
} else if (argtype == 'T') { /* this, as an object */ } else if (argtype == 'T') { /* this, as an object */
if (!JSVAL_IS_OBJECT(thisval)) if (!JSVAL_IS_OBJECT(tval))
goto next_specialization; goto next_specialization;
*argp = this_ins; *argp = this_ins;
} else if (argtype == 'S') { /* this, as a string */ } else if (argtype == 'S') { /* this, as a string */
if (!JSVAL_IS_STRING(thisval)) if (!JSVAL_IS_STRING(tval))
goto next_specialization; goto next_specialization;
*argp = this_ins; *argp = this_ins;
} else if (argtype == 'f') { } else if (argtype == 'f') {
@ -5932,7 +5836,7 @@ TraceRecorder::functionCall(bool constructing)
} else if (argtype == 'P') { } else if (argtype == 'P') {
*argp = INS_CONSTPTR(pc); *argp = INS_CONSTPTR(pc);
} else if (argtype == 'D') { /* this, as a number */ } else if (argtype == 'D') { /* this, as a number */
if (!isNumber(thisval)) if (!isNumber(tval))
goto next_specialization; goto next_specialization;
*argp = this_ins; *argp = this_ins;
} else { } else {
@ -5942,8 +5846,8 @@ TraceRecorder::functionCall(bool constructing)
} }
for (i = knownargc; i--; ) { for (i = knownargc; i--; ) {
jsval& arg = (!constructing && i == 0 && arg1_ins) ? arg1 : stackval(0 - (i + 1)); jsval& arg = stackval(0 - (i + 1));
*argp = (!constructing && i == 0 && arg1_ins) ? arg1_ins : get(&arg); *argp = get(&arg);
argtype = known->argtypes[i]; argtype = known->argtypes[i];
if (argtype == 'd' || argtype == 'i') { if (argtype == 'd' || argtype == 'i') {
@ -5977,10 +5881,10 @@ TraceRecorder::functionCall(bool constructing)
* isn't going to return a NaN. * isn't going to return a NaN.
*/ */
if (!constructing && known->builtin == &js_String_p_charCodeAt_ci) { if (!constructing && known->builtin == &js_String_p_charCodeAt_ci) {
JSString* str = JSVAL_TO_STRING(thisval); JSString* str = JSVAL_TO_STRING(tval);
jsval& arg = arg1_ins ? arg1 : stackval(-1); jsval& arg = stackval(-1);
JS_ASSERT(JSVAL_IS_STRING(thisval)); JS_ASSERT(JSVAL_IS_STRING(tval));
JS_ASSERT(isNumber(arg)); JS_ASSERT(isNumber(arg));
if (JSVAL_IS_INT(arg)) { if (JSVAL_IS_INT(arg)) {
@ -6045,7 +5949,7 @@ success:
bool bool
TraceRecorder::record_JSOP_NEW() TraceRecorder::record_JSOP_NEW()
{ {
return functionCall(true); return functionCall(true, GET_ARGC(cx->fp->regs->pc));
} }
bool bool
@ -6465,7 +6369,7 @@ TraceRecorder::record_JSOP_CALLUPVAR()
} }
bool bool
TraceRecorder::guardShapelessCallee(jsval& callee) TraceRecorder::guardCallee(jsval& callee)
{ {
LIns* exit = snapshot(BRANCH_EXIT); LIns* exit = snapshot(BRANCH_EXIT);
JSObject* callee_obj = JSVAL_TO_OBJECT(callee); JSObject* callee_obj = JSVAL_TO_OBJECT(callee);
@ -6541,13 +6445,160 @@ TraceRecorder::interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc,
bool bool
TraceRecorder::record_JSOP_CALL() TraceRecorder::record_JSOP_CALL()
{ {
return functionCall(false); return functionCall(false, GET_ARGC(cx->fp->regs->pc));
} }
bool bool
TraceRecorder::record_JSOP_APPLY() TraceRecorder::record_JSOP_APPLY()
{ {
return functionCall(false); JSStackFrame* fp = cx->fp;
jsbytecode *pc = fp->regs->pc;
uintN argc = GET_ARGC(pc);
jsval* vp = fp->regs->sp - (argc + 2);
JS_ASSERT(vp >= StackBase(fp));
jsuint length = 0;
JSObject* aobj = NULL;
LIns* aobj_ins = NULL;
LIns* dslots_ins = NULL;
if (!VALUE_IS_FUNCTION(cx, vp[0]))
return record_JSOP_CALL();
JSObject* obj = JSVAL_TO_OBJECT(vp[0]);
JSFunction* fun = GET_FUNCTION_PRIVATE(cx, obj);
if (FUN_INTERPRETED(fun))
return record_JSOP_CALL();
bool apply = (JSFastNative)fun->u.n.native == js_fun_apply;
if (!apply && (JSFastNative)fun->u.n.native != js_fun_call)
return record_JSOP_CALL();
/*
* If this is apply, and the argument is too long and would need
* a separate stack chunk, do a heavy-weight apply.
*/
if (apply && argc >= 2) {
if (JSVAL_IS_PRIMITIVE(vp[3]))
ABORT_TRACE("arguments parameter of apply is primitive");
aobj = JSVAL_TO_OBJECT(vp[3]);
aobj_ins = get(&vp[3]);
/*
* We expect a dense array for the arguments (the other
* frequent case is the arguments object, but that we
* don't trace at the moment).
*/
guard(false, lir->ins_eq0(aobj_ins), MISMATCH_EXIT);
if (!guardDenseArray(aobj, aobj_ins))
ABORT_TRACE("arguments parameter of apply is not a dense array");
/*
* Make sure the array has the same length at runtime.
*/
length = jsuint(aobj->fslots[JSSLOT_ARRAY_LENGTH]);
guard(true, lir->ins2i(LIR_eq,
stobj_get_fslot(aobj_ins, JSSLOT_ARRAY_LENGTH),
length),
BRANCH_EXIT);
/*
* Make sure dslots is not NULL and guard the array's capacity.
*/
dslots_ins = lir->insLoad(LIR_ldp, aobj_ins, offsetof(JSObject, dslots));
guard(false,
lir->ins_eq0(dslots_ins),
MISMATCH_EXIT);
guard(true,
lir->ins2(LIR_ult,
lir->insImm(length),
lir->insLoad(LIR_ldp, dslots_ins, 0 - (int)sizeof(jsval))),
MISMATCH_EXIT);
/*
* The interpreter deoptimizes if we are about to cross a stack chunk by
* re-entering itself indirectly from js_Invoke, and we can't trace that
* case.
*/
length = (uintN)JS_MIN(length, ARRAY_INIT_LIMIT - 1);
jsval* newsp = vp + 2 + length;
JS_ASSERT(newsp >= vp + 2);
JSArena *a = cx->stackPool.current;
if (jsuword(newsp) > a->limit)
ABORT_TRACE("apply or call across stack-chunks");
}
/*
* Guard on the identity of this, which is the function we
* are applying.
*/
if (!guardCallee(vp[1]))
return false;
LIns* callee_ins = get(&vp[1]);
LIns* this_ins = NULL;
if (argc > 0) {
this_ins = get(&vp[2]);
if (JSVAL_IS_PRIMITIVE(vp[2]))
ABORT_TRACE("apply with primitive this");
} else {
this_ins = lir->insImm(0);
}
LIns** argv;
if (!apply) {
--argc;
argv = (LIns**)alloca(sizeof(LIns*) * argc);
for (jsuint n = 0; n < argc; ++n)
argv[n] = get(&vp[3 + n]); /* skip over the this parameter */
} else {
if (argc >= 2) {
/*
* We already established that argments is a dense array
* and we know its length and we know dslots is not NULL
* and the length is not beyond the dense array's capacity.
*/
argc = length;
argv = (LIns**)alloca(sizeof(LIns*) * argc);
for (unsigned n = 0; n < argc; ++n) {
/* Load the value and guard on its type to unbox it. */
LIns* v_ins = lir->insLoadi(dslots_ins, n * sizeof(jsval));
/*
* We can't "see through" a hole to a possible Array.prototype property, so
* we abort here and guard below (after unboxing).
*/
jsval* dp = &aobj->dslots[n];
if (*dp == JSVAL_HOLE)
ABORT_TRACE("can't see through hole in dense array");
if (!unbox_jsval(*dp, v_ins))
return false;
if (JSVAL_TAG(*dp) == JSVAL_BOOLEAN) {
// Optimize to guard for a hole only after untagging, so we know that
// we have a boolean, to avoid an extra guard for non-boolean values.
guard(false, lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_TO_BOOLEAN(JSVAL_HOLE))),
MISMATCH_EXIT);
}
argv[n] = v_ins;
}
} else {
argc = 0;
}
}
/*
* We have all arguments unpacked and we are now ready to modify the
* tracker.
*/
tracker.set(&vp[0], callee_ins);
tracker.set(&vp[1], this_ins);
for (unsigned n = 0; n < argc; ++n)
tracker.set(&vp[2 + n], argv[n]);
return true;
}
bool
TraceRecorder::record_ApplyComplete(uintN argc)
{
return functionCall(false, argc);
} }
bool bool

View File

@ -288,7 +288,6 @@ class TraceRecorder : public avmplus::GCObject {
nanojit::LIns* rval_ins; nanojit::LIns* rval_ins;
nanojit::LIns* inner_sp_ins; nanojit::LIns* inner_sp_ins;
bool deepAborted; bool deepAborted;
bool applyingArguments;
bool trashSelf; bool trashSelf;
Queue<nanojit::Fragment*> whichTreesToTrash; Queue<nanojit::Fragment*> whichTreesToTrash;
Queue<jsbytecode*> cfgMerges; Queue<jsbytecode*> cfgMerges;
@ -400,9 +399,9 @@ class TraceRecorder : public avmplus::GCObject {
ExitType exitType); ExitType exitType);
bool guardElemOp(JSObject* obj, nanojit::LIns* obj_ins, jsid id, size_t op_offset, jsval* vp); bool guardElemOp(JSObject* obj, nanojit::LIns* obj_ins, jsid id, size_t op_offset, jsval* vp);
void clearFrameSlotsFromCache(); void clearFrameSlotsFromCache();
bool guardShapelessCallee(jsval& callee); bool guardCallee(jsval& callee);
bool interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc, bool constructing); bool interpretedFunctionCall(jsval& fval, JSFunction* fun, uintN argc, bool constructing);
bool functionCall(bool constructing); bool functionCall(bool constructing, uintN argc);
void trackCfgMerges(jsbytecode* pc); void trackCfgMerges(jsbytecode* pc);
void flipIf(jsbytecode* pc, bool& cond); void flipIf(jsbytecode* pc, bool& cond);
@ -458,6 +457,7 @@ public:
bool record_DefLocalFunSetSlot(uint32 slot, JSObject* obj); bool record_DefLocalFunSetSlot(uint32 slot, JSObject* obj);
bool record_FastNativeCallComplete(); bool record_FastNativeCallComplete();
bool record_IteratorNextComplete(); bool record_IteratorNextComplete();
bool record_ApplyComplete(uintN argc);
nanojit::Fragment* getOuterToBlacklist() { return outerToBlacklist; } nanojit::Fragment* getOuterToBlacklist() { return outerToBlacklist; }
void deepAbort() { deepAborted = true; } void deepAbort() { deepAborted = true; }

View File

@ -2718,6 +2718,15 @@ function testIncDec() {
testIncDec.expected = "0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4"; testIncDec.expected = "0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4";
test(testIncDec); test(testIncDec);
function testApply() {
var q = [];
for (var i = 0; i < 10; ++i)
Array.prototype.push.apply(q, [5]);
return q.join(",");
}
testApply.expected = "5,5,5,5,5,5,5,5,5,5";
test(testApply);
/* NOTE: Keep this test last, since it screws up all for...in loops after it. */ /* NOTE: Keep this test last, since it screws up all for...in loops after it. */
function testGlobalProtoAccess() { function testGlobalProtoAccess() {
return "ok"; return "ok";