Bug 516255 - Eagerly copy initial parameter values into the arguments object when a function's parameters might be mutated, and rely on normal resolution behavior in the remaining cases when parameters are never modified. r=dmandelin

This commit is contained in:
Jeff Walden 2010-08-11 23:27:33 -07:00
Родитель 580ef13257
Коммит e841d02d4c
7 изменённых файлов: 113 добавлений и 44 удалений

Просмотреть файл

@ -2821,7 +2821,9 @@ EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
return JS_FALSE;
if (left->pn_op == JSOP_ARGUMENTS &&
JSDOUBLE_IS_INT32(next->pn_dval, &slot) &&
(jsuint)slot < JS_BIT(16)) {
jsuint(slot) < JS_BIT(16) &&
(!cg->inStrictMode() ||
(!cg->mutatesParameter() && !cg->callsEval()))) {
/*
* arguments[i]() requires arguments object as "this".
* Check that we never generates list for that usage.
@ -2895,7 +2897,9 @@ EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
return JS_FALSE;
if (left->pn_op == JSOP_ARGUMENTS &&
JSDOUBLE_IS_INT32(right->pn_dval, &slot) &&
(jsuint)slot < JS_BIT(16)) {
jsuint(slot) < JS_BIT(16) &&
(!cg->inStrictMode() ||
(!cg->mutatesParameter() && !cg->callsEval()))) {
left->pn_offset = right->pn_offset = top;
EMIT_UINT16_IMM_OP(JSOP_ARGSUB, (jsatomid)slot);
return JS_TRUE;
@ -3547,6 +3551,13 @@ js_EmitFunctionScript(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body)
return false;
}
if (cg->needsEagerArguments()) {
CG_SWITCH_TO_PROLOG(cg);
if (js_Emit1(cx, cg, JSOP_ARGUMENTS) < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
return false;
CG_SWITCH_TO_MAIN(cg);
}
if (cg->flags & TCF_FUN_UNBRAND_THIS) {
if (js_Emit1(cx, cg, JSOP_UNBRANDTHIS) < 0)
return false;

Просмотреть файл

@ -197,6 +197,7 @@ NewArguments(JSContext *cx, JSObject *parent, uint32 argc, JSObject *callee)
static void
PutArguments(JSContext *cx, JSObject *argsobj, Value *args)
{
JS_ASSERT(argsobj->isNormalArguments());
uint32 argc = argsobj->getArgsInitialLength();
for (uint32 i = 0; i != argc; ++i) {
if (!argsobj->getArgsElement(i).isMagic(JS_ARGS_HOLE))
@ -229,8 +230,22 @@ js_GetArgsObject(JSContext *cx, JSStackFrame *fp)
if (!argsobj)
return argsobj;
/* Link the new object to fp so it can get actual argument values. */
argsobj->setPrivate(fp);
/*
* Strict mode functions have arguments which copy the initial parameter
* values. It is the caller's responsibility to get the arguments object
* before any parameters are modified! (The emitter ensures this by
* synthesizing an arguments access at the start of any strict mode
* function which contains an assignment to a parameter or which calls
* eval.) Non-strict mode arguments use the frame pointer to retrieve
* up-to-date parameter values.
*/
if (argsobj->isStrictArguments()) {
JS_ASSERT_IF(fp->argc > 0, argsobj->dslots[-1].toPrivateUint32() >= fp->argc);
memcpy(argsobj->dslots, fp->argv, fp->argc * sizeof(Value));
} else {
argsobj->setPrivate(fp);
}
fp->setArgsObj(argsobj);
return argsobj;
}
@ -239,9 +254,13 @@ void
js_PutArgsObject(JSContext *cx, JSStackFrame *fp)
{
JSObject *argsobj = fp->getArgsObj();
JS_ASSERT(argsobj->getPrivate() == fp);
PutArguments(cx, argsobj, fp->argv);
argsobj->setPrivate(NULL);
if (argsobj->isNormalArguments()) {
JS_ASSERT(argsobj->getPrivate() == fp);
PutArguments(cx, argsobj, fp->argv);
argsobj->setPrivate(NULL);
} else {
JS_ASSERT(!argsobj->getPrivate());
}
fp->setArgsObj(NULL);
}
@ -256,7 +275,17 @@ js_Arguments(JSContext *cx, JSObject *parent, uint32 argc, JSObject *callee)
JSObject *argsobj = NewArguments(cx, parent, argc, callee);
if (!argsobj)
return NULL;
argsobj->setPrivate(JS_ARGUMENT_OBJECT_ON_TRACE);
if (callee->getFunctionPrivate()->inStrictMode()) {
/*
* Strict mode callers must copy arguments into the created arguments
* object.
*/
JS_ASSERT(!argsobj->getPrivate());
} else {
argsobj->setPrivate(JS_ARGUMENT_OBJECT_ON_TRACE);
}
return argsobj;
}
#endif
@ -268,6 +297,7 @@ JS_DEFINE_CALLINFO_4(extern, OBJECT, js_Arguments, CONTEXT, OBJECT, UINT32, OBJE
JSBool JS_FASTCALL
js_PutArguments(JSContext *cx, JSObject *argsobj, Value *args)
{
JS_ASSERT(argsobj->isNormalArguments());
JS_ASSERT(argsobj->getPrivate() == JS_ARGUMENT_OBJECT_ON_TRACE);
PutArguments(cx, argsobj, args);
argsobj->setPrivate(NULL);
@ -637,14 +667,9 @@ StrictArgGetter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
*/
uintN arg = uintN(JSID_TO_INT(id));
if (arg < obj->getArgsInitialLength()) {
JSStackFrame *fp = (JSStackFrame *) obj->getPrivate();
if (fp) {
*vp = fp->argv[arg];
} else {
const Value &v = obj->getArgsElement(arg);
if (!v.isMagic(JS_ARGS_HOLE))
*vp = v;
}
const Value &v = obj->getArgsElement(arg);
if (!v.isMagic(JS_ARGS_HOLE))
*vp = v;
}
} else {
JS_ASSERT(JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom));

Просмотреть файл

@ -488,6 +488,16 @@ js_GetArgsValue(JSContext *cx, JSStackFrame *fp, js::Value *vp);
extern JSBool
js_GetArgsProperty(JSContext *cx, JSStackFrame *fp, jsid id, js::Value *vp);
/*
* Get the arguments object for the given frame. If the frame is strict mode
* code, its current arguments will be copied into the arguments object.
*
* NB: Callers *must* get the arguments object before any parameters are
* mutated when the frame is strict mode code! The emitter ensures this
* occurs for strict mode functions containing syntax which might mutate a
* named parameter by synthesizing an arguments access at the start of the
* function.
*/
extern JSObject *
js_GetArgsObject(JSContext *cx, JSStackFrame *fp);

Просмотреть файл

@ -1131,8 +1131,7 @@ CheckFinalReturn(JSContext *cx, JSTreeContext *tc, JSParseNode *pn)
bool
CheckStrictAssignment(JSContext *cx, JSTreeContext *tc, JSParseNode *lhs)
{
if (tc->needStrictChecks() &&
lhs->pn_type == TOK_NAME) {
if (tc->needStrictChecks() && lhs->pn_type == TOK_NAME) {
JSAtom *atom = lhs->pn_atom;
JSAtomState *atomState = &cx->runtime->atomState;
if (atom == atomState->evalAtom || atom == atomState->argumentsAtom) {
@ -2821,8 +2820,8 @@ Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda)
JSFunction *fun = (JSFunction *) funbox->object;
/* Now parse formal argument list and compute fun->nargs. */
JSParseNode *list = NULL;
if (!functionArguments(funtc, funbox, fun, &list))
JSParseNode *prolog = NULL;
if (!functionArguments(funtc, funbox, fun, &prolog))
return NULL;
if (type == GETTER && fun->nargs > 0) {
@ -2866,6 +2865,24 @@ Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda)
#endif
pn->pn_pos.end = tokenStream.currentToken().pos.end;
/*
* Strict mode functions' arguments objects copy initial parameter values.
* We create arguments objects lazily -- but that doesn't work for strict
* mode functions where a parameter might be modified and arguments might
* be accessed. For such functions we synthesize an access to arguments to
* initialize it with the original parameter values.
*/
if (funtc.inStrictMode()) {
/*
* Fruit of the poisonous tree: eval forces eager arguments
* creation in (strict mode) parent functions.
*/
if (outertc->inFunction() && outertc->inStrictMode()) {
if (funtc.callsEval())
outertc->noteCallsEval();
}
}
#if JS_HAS_DESTRUCTURING
/*
* If there were destructuring formal parameters, prepend the initializing
@ -2874,7 +2891,7 @@ Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda)
* parameter destructuring code without bracing the decompilation of the
* function body's lexical scope.
*/
if (list) {
if (prolog) {
if (body->pn_arity != PN_LIST) {
JSParseNode *block;
@ -2894,7 +2911,7 @@ Parser::functionDef(JSAtom *funAtom, FunctionType type, uintN lambda)
item->pn_type = TOK_SEMI;
item->pn_pos.begin = item->pn_pos.end = body->pn_pos.begin;
item->pn_kid = list;
item->pn_kid = prolog;
item->pn_next = body->pn_head;
body->pn_head = item;
if (body->pn_tail == &body->pn_head)
@ -5663,15 +5680,6 @@ Parser::statement()
return MatchOrInsertSemicolon(context, &tokenStream) ? pn : NULL;
}
static void
NoteArgumentsUse(JSTreeContext *tc)
{
JS_ASSERT(tc->inFunction());
tc->flags |= TCF_FUN_USES_ARGUMENTS;
if (tc->funbox)
tc->funbox->node->pn_dflags |= PND_FUNARG;
}
JSParseNode *
Parser::variables(bool inLetHead)
{
@ -5837,7 +5845,7 @@ Parser::variables(bool inLetHead)
if (tc->inFunction() &&
atom == context->runtime->atomState.argumentsAtom) {
NoteArgumentsUse(tc);
tc->noteArgumentsUse();
if (!let)
tc->flags |= TCF_FUN_HEAVYWEIGHT;
}
@ -8188,7 +8196,7 @@ Parser::primaryExpr(TokenKind tt, JSBool afterDot)
* a reference of the form foo.arguments, which ancient code may
* still use instead of arguments (more hate).
*/
NoteArgumentsUse(tc);
tc->noteArgumentsUse();
/*
* Bind early to JSOP_ARGUMENTS to relieve later code from having

Просмотреть файл

@ -458,9 +458,9 @@ public:
#define PNX_DESTRUCT 0x200 /* destructuring special cases:
1. shorthand syntax used, at present
object destructuring ({x,y}) only;
2. the first child of function body
is code evaluating destructuring
arguments */
2. code evaluating destructuring
arguments occurs before function
body */
#define PNX_HOLEY 0x400 /* array initialiser has holes */
uintN frameLevel() const {

Просмотреть файл

@ -79,6 +79,7 @@
#include "jsatominlines.h"
#include "jscntxtinlines.h"
#include "jsfuninlines.h"
#include "jspropertycacheinlines.h"
#include "jsobjinlines.h"
#include "jsscopeinlines.h"
@ -3449,8 +3450,10 @@ FlushNativeStackFrame(JSContext* cx, unsigned callDepth, const JSValueType* mp,
for (; n != 0; fp = fp->down) {
--n;
if (fp->argv) {
if (fp->hasArgsObj() && fp->getArgsObj()->getPrivate() == JS_ARGUMENT_OBJECT_ON_TRACE)
if (fp->hasArgsObj() && fp->getArgsObj()->getPrivate() == JS_ARGUMENT_OBJECT_ON_TRACE) {
JS_ASSERT(fp->getArgsObj()->isNormalArguments());
fp->getArgsObj()->setPrivate(fp);
}
JS_ASSERT(fp->argv[-1].isObjectOrNull());
JS_ASSERT(fp->callee()->isFunction());
@ -10289,7 +10292,7 @@ TraceRecorder::clearCurrentFrameSlotsFromTracker(Tracker& which)
JS_REQUIRES_STACK void
TraceRecorder::putActivationObjects()
{
bool have_args = cx->fp->hasArgsObj() && cx->fp->argc;
bool have_args = cx->fp->hasArgsObj() && !cx->fp->getArgsObj()->isStrictArguments() && cx->fp->argc > 0;
bool have_call = cx->fp->hasFunction() &&
JSFUN_HEAVYWEIGHT_TEST(cx->fp->getFunction()->flags) &&
cx->fp->getFunction()->countArgsAndVars();
@ -10645,7 +10648,7 @@ TraceRecorder::record_JSOP_IFNE()
}
LIns*
TraceRecorder::newArguments(LIns* callee_ins)
TraceRecorder::newArguments(LIns* callee_ins, bool strict)
{
LIns* global_ins = INS_CONSTOBJ(globalObj);
LIns* argc_ins = INS_CONST(cx->fp->argc);
@ -10653,6 +10656,15 @@ TraceRecorder::newArguments(LIns* callee_ins)
LIns* args[] = { callee_ins, argc_ins, global_ins, cx_ins };
LIns* call_ins = lir->insCall(&js_Arguments_ci, args);
guard(false, lir->insEqP_0(call_ins), OOM_EXIT);
if (strict) {
JSStackFrame* fp = cx->fp;
uintN argc = fp->argc;
LIns* argsSlots_ins = NULL;
for (uintN i = 0; i < argc; i++)
stobj_set_dslot(call_ins, i, argsSlots_ins, fp->argv[i], get(&fp->argv[i]));
}
return call_ins;
}
@ -10665,12 +10677,15 @@ TraceRecorder::record_JSOP_ARGUMENTS()
if (cx->fp->flags & JSFRAME_OVERRIDE_ARGS)
RETURN_STOP_A("Can't trace |arguments| if |arguments| is assigned to");
JSStackFrame* const fp = cx->fp;
LIns* a_ins = getFrameObjPtr(cx->fp->addressArgsObj());
LIns* args_ins;
LIns* callee_ins = get(&cx->fp->argv[-2]);
LIns* callee_ins = get(&fp->argv[-2]);
bool strict = fp->getFunction()->inStrictMode();
if (a_ins->isImmP()) {
// |arguments| is set to 0 by EnterFrame on this trace, so call to create it.
args_ins = newArguments(callee_ins);
args_ins = newArguments(callee_ins, strict);
} else {
// Generate LIR to create arguments only if it has not already been created.
@ -10683,7 +10698,7 @@ TraceRecorder::record_JSOP_ARGUMENTS()
LIns* label1 = lir->ins0(LIR_label);
br1->setTarget(label1);
LIns* call_ins = newArguments(callee_ins);
LIns* call_ins = newArguments(callee_ins, strict);
lir->insStore(call_ins, mem_ins, 0, ACCSET_OTHER);
LIns* label2 = lir->ins0(LIR_label);
@ -10693,7 +10708,7 @@ TraceRecorder::record_JSOP_ARGUMENTS()
}
stack(0, args_ins);
setFrameObjPtr(cx->fp->addressArgsObj(), args_ins);
setFrameObjPtr(fp->addressArgsObj(), args_ins);
return ARECORD_CONTINUE;
}

Просмотреть файл

@ -1159,7 +1159,7 @@ class TraceRecorder
JS_REQUIRES_STACK nanojit::LIns* makeNumberInt32(nanojit::LIns* f);
JS_REQUIRES_STACK nanojit::LIns* stringify(const Value& v);
JS_REQUIRES_STACK nanojit::LIns* newArguments(nanojit::LIns* callee_ins);
JS_REQUIRES_STACK nanojit::LIns* newArguments(nanojit::LIns* callee_ins, bool strict);
JS_REQUIRES_STACK bool canCallImacro() const;
JS_REQUIRES_STACK RecordingStatus callImacro(jsbytecode* imacro);