Bug 630865 - Resolve interpreted function prototypes more eagerly. r=dvander.

This commit is contained in:
Jason Orendorff 2011-02-14 14:04:07 -06:00
parent afb7e896ef
commit d064deac4d
15 changed files with 255 additions and 116 deletions

View File

@ -0,0 +1,6 @@
Object.defineProperty(Function.prototype, "prototype", {set:function(){}});
var x;
for (var i = 0; i < HOTLOOP + 2; i++)
x = new Function.prototype;
assertEq(toString.call(x), "[object Object]");
assertEq(Object.getPrototypeOf(x), Object.prototype);

View File

@ -0,0 +1,6 @@
Function.prototype.prototype = function () {};
var x;
for (var i = 0; i < HOTLOOP + 2; i++)
x = new Function.prototype;
assertEq(toString.call(x), "[object Object]");
assertEq(Object.getPrototypeOf(x), Function.prototype.prototype);

View File

@ -0,0 +1,12 @@
var a = [];
function next() {
var x = {};
a.push(x);
return x;
}
Object.defineProperty(Function.prototype, 'prototype', {get: next});
var b = [];
for (var i = 0; i < HOTLOOP + 2; i++)
b[i] = new Function.prototype;
for (var i = 0; i < HOTLOOP + 2; i++)
assertEq(Object.getPrototypeOf(b[i]), a[i]);

View File

@ -0,0 +1,10 @@
Object.defineProperty(Function.prototype, 'prototype',
{get: function () { if (i == HOTLOOP + 1) throw "X"; }});
var x;
try {
for (var i = 0; i < HOTLOOP + 2; i++)
x = new Function.prototype;
} catch (exc) {
assertEq(i, HOTLOOP + 1);
assertEq(exc, "X");
}

View File

@ -0,0 +1,13 @@
function C(a, b) {
this.a = a;
this.b = b;
}
var f = C.bind(null, 2);
Object.defineProperty(f, "prototype", {get: function () { throw "FAIL"; }});
var x;
for (var i = 0; i < HOTLOOP + 2; i++)
x = new f(i);
assertEq(toString.call(x), "[object Object]");
assertEq(Object.getPrototypeOf(x), C.prototype);
assertEq(x.a, 2);
assertEq(x.b, HOTLOOP + 1);

View File

@ -0,0 +1,11 @@
var a = [];
var x, i;
for (i = 0; i < HOTLOOP + 10; i++) {
a[i] = function (b) { this.b = b; };
if (i != HOTLOOP + 9)
x = a[i].prototype;
}
for (i = 0; i < HOTLOOP + 10; i++)
x = new a[i];
assertEq(toString.call(x), "[object Object]");
assertEq(Object.getPrototypeOf(x), a[HOTLOOP + 9].prototype);

View File

@ -0,0 +1,11 @@
function f(a, b) {
this.a = a;
assertEq(b, 'x');
}
for (var x = 0; x < RUNLOOP; ++x) {
f.prototype = {};
var obj = new f(x, 'x');
assertEq(obj.a, x);
assertEq(Object.getPrototypeOf(obj), f.prototype);
}

View File

@ -0,0 +1,12 @@
function f(a, b, c) {
this.a = a;
assertEq(b, 'x');
assertEq(c, void 0);
}
for (var x = 0; x < RUNLOOP; ++x) {
f.prototype = {};
var obj = new f(x, 'x'); // fewer than f.length arguments
assertEq(obj.a, x);
assertEq(Object.getPrototypeOf(obj), f.prototype);
}

View File

@ -0,0 +1,11 @@
function f(a) {
this.a = a;
assertEq(arguments[1], 'x');
}
for (var x = 0; x < RUNLOOP; ++x) {
f.prototype = {};
var obj = new f(x, 'x'); // more than f.length arguments
assertEq(obj.a, x);
assertEq(Object.getPrototypeOf(obj), f.prototype);
}

View File

@ -188,8 +188,8 @@ struct ClosureVarInfo;
#define _JS_CTYPE_THIS _JS_CTYPE(JSObject *, _JS_PTR,"T", "", INFALLIBLE)
#define _JS_CTYPE_THIS_DOUBLE _JS_CTYPE(jsdouble, _JS_F64,"D", "", INFALLIBLE)
#define _JS_CTYPE_THIS_STRING _JS_CTYPE(JSString *, _JS_PTR,"S", "", INFALLIBLE)
#define _JS_CTYPE_CALLEE _JS_CTYPE(JSObject *, _JS_PTR,"f","", INFALLIBLE)
#define _JS_CTYPE_CALLEE_PROTOTYPE _JS_CTYPE(JSObject *, _JS_PTR,"p","", INFALLIBLE)
#define _JS_CTYPE_CALLEE _JS_CTYPE(JSObject *, _JS_PTR,"f", "", INFALLIBLE)
#define _JS_CTYPE_CALLEE_PROTOTYPE _JS_CTYPE(JSObject *, _JS_PTR,"p", "", INFALLIBLE)
#define _JS_CTYPE_FUNCTION _JS_CTYPE(JSFunction *, _JS_PTR, --, --, INFALLIBLE)
#define _JS_CTYPE_PC _JS_CTYPE(jsbytecode *, _JS_PTR,"P", "", INFALLIBLE)
#define _JS_CTYPE_VALUEPTR _JS_CTYPE(js::Value *, _JS_PTR, --, --, INFALLIBLE)
@ -233,7 +233,8 @@ struct ClosureVarInfo;
#define _JS_CTYPE_CVIPTR _JS_CTYPE(const ClosureVarInfo *, _JS_PTR, --, --, INFALLIBLE)
#define _JS_CTYPE_FRAMEINFO _JS_CTYPE(FrameInfo *, _JS_PTR, --, --, INFALLIBLE)
#define _JS_CTYPE_PICTABLE _JS_CTYPE(PICTable *, _JS_PTR, --, --, INFALLIBLE)
#define _JS_CTYPE_UINTN _JS_CTYPE(uintN, _JS_PTR, --, --, INFALLIBLE)
/*
* The "VALUE" type is used to indicate that a native takes a js::Value
* parameter by value. Unfortunately, for technical reasons, we can't simply

View File

@ -1704,6 +1704,46 @@ fun_enumerate(JSContext *cx, JSObject *obj)
return true;
}
static JSObject *
ResolveInterpretedFunctionPrototype(JSContext *cx, JSObject *obj)
{
JSFunction *fun = obj->getFunctionPrivate();
JS_ASSERT(fun->isInterpreted());
JS_ASSERT(!fun->isFunctionPrototype());
/*
* Assert that fun is not a compiler-created function object, which
* must never leak to script or embedding code and then be mutated.
* Also assert that obj is not bound, per the ES5 15.3.4.5 ref above.
*/
JS_ASSERT(!IsInternalFunctionObject(obj));
JS_ASSERT(!obj->isBoundFunction());
/*
* Make the prototype object an instance of Object with the same parent
* as the function object itself.
*/
JSObject *parent = obj->getParent();
JSObject *proto;
if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
return NULL;
proto = NewNativeClassInstance(cx, &js_ObjectClass, proto, parent);
if (!proto)
return NULL;
/*
* ECMA (15.3.5.2) says that a user-defined function's .prototype property
* is non-configurable, non-enumerable, and (initially) writable. Hence
* JSPROP_PERMANENT below. By contrast, the built-in constructors, such as
* Object (15.2.3.1) and Function (15.3.3.1), have non-writable
* .prototype properties. Those are eagerly defined, with attributes
* JSPROP_PERMANENT | JSPROP_READONLY, in js_InitClass.
*/
if (!js_SetClassPrototype(cx, obj, proto, JSPROP_PERMANENT))
return NULL;
return proto;
}
static JSBool
fun_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
JSObject **objp)
@ -1730,36 +1770,8 @@ fun_resolve(JSContext *cx, JSObject *obj, jsid id, uintN flags,
if (fun->isNative() || fun->isFunctionPrototype())
return true;
/*
* Assert that fun is not a compiler-created function object, which
* must never leak to script or embedding code and then be mutated.
* Also assert that obj is not bound, per the ES5 15.3.4.5 ref above.
*/
JS_ASSERT(!IsInternalFunctionObject(obj));
JS_ASSERT(!obj->isBoundFunction());
/*
* Make the prototype object an instance of Object with the same parent
* as the function object itself.
*/
JSObject *parent = obj->getParent();
JSObject *proto;
if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
if (!ResolveInterpretedFunctionPrototype(cx, obj))
return false;
proto = NewNativeClassInstance(cx, &js_ObjectClass, proto, parent);
if (!proto)
return false;
/*
* ECMA (15.3.5.2) says that constructor.prototype is DontDelete for
* user-defined functions, but DontEnum | ReadOnly | DontDelete for
* native "system" constructors such as Object or Function. So lazily
* set the former here in fun_resolve, but eagerly define the latter
* in js_InitClass, with the right attributes.
*/
if (!js_SetClassPrototype(cx, obj, proto, JSPROP_PERMANENT))
return false;
*objp = obj;
return true;
}
@ -2630,6 +2642,28 @@ IsBuiltinFunctionConstructor(JSFunction *fun)
return fun->maybeNative() == Function;
}
const Shape *
LookupInterpretedFunctionPrototype(JSContext *cx, JSObject *funobj)
{
JSFunction *fun = funobj->getFunctionPrivate();
JS_ASSERT(fun->isInterpreted());
JS_ASSERT(!fun->isFunctionPrototype());
JS_ASSERT(!funobj->isBoundFunction());
jsid id = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
const Shape *shape = funobj->nativeLookup(id);
if (!shape) {
if (!ResolveInterpretedFunctionPrototype(cx, funobj))
return false;
shape = funobj->nativeLookup(id);
}
JS_ASSERT(!shape->configurable());
JS_ASSERT(shape->isDataDescriptor());
JS_ASSERT(shape->hasSlot());
JS_ASSERT(!shape->isMethod());
return shape;
}
}
static JSBool

View File

@ -432,6 +432,21 @@ GetFunctionNameBytes(JSContext *cx, JSFunction *fun, JSAutoByteString *bytes)
extern JS_FRIEND_API(bool)
IsBuiltinFunctionConstructor(JSFunction *fun);
/*
* Preconditions: funobj->isInterpreted() && !funobj->isFunctionPrototype() &&
* !funobj->isBoundFunction(). This is sufficient to establish that funobj has
* a non-configurable non-method .prototype data property, thought it might not
* have been resolved yet, and its value could be anything.
*
* Return the shape of the .prototype property of funobj, resolving it if
* needed. On error, return NULL.
*
* This is not safe to call on trace because it defines properties, which can
* trigger lookups that could reenter.
*/
const Shape *
LookupInterpretedFunctionPrototype(JSContext *cx, JSObject *funobj);
} /* namespace js */
extern JSString *

View File

@ -2996,59 +2996,37 @@ js_String_tn(JSContext* cx, JSObject* proto, JSString* str)
JS_DEFINE_CALLINFO_3(extern, OBJECT, js_String_tn, CONTEXT, CALLEE_PROTOTYPE, STRING, 0,
nanojit::ACCSET_STORE_ANY)
JSObject* FASTCALL
js_CreateThisFromTrace(JSContext *cx, Class *clasp, JSObject *ctor)
JSObject * FASTCALL
js_CreateThisFromTrace(JSContext *cx, JSObject *ctor, uintN protoSlot)
{
JS_ASSERT(JS_ON_TRACE(cx));
#ifdef DEBUG
JS_ASSERT(ctor->isFunction());
if (!ctor->ensureClassReservedSlots(cx))
return NULL;
jsid classPrototypeId = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
const Shape *shape = ctor->nativeLookup(classPrototypeId);
Value pval = shape ? ctor->getSlot(shape->slot) : MagicValue(JS_GENERIC_MAGIC);
JS_ASSERT(ctor->getFunctionPrivate()->isInterpreted());
jsid id = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
const Shape *shape = ctor->nativeLookup(id);
JS_ASSERT(shape->slot == protoSlot);
JS_ASSERT(!shape->configurable());
JS_ASSERT(!shape->isMethod());
#endif
JSObject *parent = ctor->getParent();
JSObject *proto;
if (pval.isObject()) {
/* An object in ctor.prototype, let's use it as the new instance's proto. */
proto = &pval.toObject();
const Value &protov = ctor->getSlotRef(protoSlot);
if (protov.isObject()) {
proto = &protov.toObject();
} else {
/* A hole or a primitive: either way, we need to get Object.prototype. */
/*
* GetInterpretedFunctionPrototype found that ctor.prototype is
* primitive. Use Object.prototype for proto, per ES5 13.2.2 step 7.
*/
if (!js_GetClassPrototype(cx, parent, JSProto_Object, &proto))
return NULL;
if (pval.isMagic(JS_GENERIC_MAGIC)) {
/*
* No ctor.prototype was set, so we inline-expand and optimize
* fun_resolve's prototype creation code.
*/
proto = NewNativeClassInstance(cx, clasp, proto, parent);
if (!proto)
return NULL;
JSFunction *fun = ctor->getFunctionPrivate();
if (!fun->isNative() && !fun->isFunctionPrototype()) {
if (!js_SetClassPrototype(cx, ctor, proto, JSPROP_ENUMERATE | JSPROP_PERMANENT))
return NULL;
}
} else {
/*
* A primitive value in .prototype means to use Object.prototype
* for proto. See ES5 13.2.2 step 7.
*/
}
}
/*
* FIXME: 561785 at least. Quasi-natives including XML objects prevent us
* from easily or unconditionally calling NewNativeClassInstance here.
*/
gc::FinalizeKind kind = NewObjectGCKind(cx, clasp);
return NewNonFunction<WithProto::Given>(cx, clasp, proto, parent, kind);
gc::FinalizeKind kind = NewObjectGCKind(cx, &js_ObjectClass);
return NewNativeClassInstance(cx, &js_ObjectClass, proto, parent, kind);
}
JS_DEFINE_CALLINFO_3(extern, CONSTRUCTOR_RETRY, js_CreateThisFromTrace, CONTEXT, CLASS, OBJECT, 0,
JS_DEFINE_CALLINFO_3(extern, CONSTRUCTOR_RETRY, js_CreateThisFromTrace, CONTEXT, OBJECT, UINTN, 0,
nanojit::ACCSET_STORE_ANY)
#else /* !JS_TRACER */

View File

@ -10483,13 +10483,6 @@ TraceRecorder::record_EnterFrame()
RETURN_STOP_A("recursion started inlining");
}
if (fp->isConstructing()) {
LIns* args[] = { callee_ins, w.nameImmpNonGC(&js_ObjectClass), cx_ins };
LIns* tv_ins = w.call(&js_CreateThisFromTrace_ci, args);
guard(false, w.eqp0(tv_ins), OOM_EXIT);
set(&fp->thisValue(), tv_ins);
}
return ARECORD_CONTINUE;
}
@ -11440,7 +11433,8 @@ TraceRecorder::callNative(uintN argc, JSOp mode)
Value* vp = &stackval(0 - (2 + argc));
JSObject* funobj = &vp[0].toObject();
JSFunction* fun = GET_FUNCTION_PRIVATE(cx, funobj);
JSFunction* fun = funobj->getFunctionPrivate();
JS_ASSERT(fun->isNative());
Native native = fun->u.n.native;
switch (argc) {
@ -11616,39 +11610,24 @@ TraceRecorder::callNative(uintN argc, JSOp mode)
clasp = &js_ObjectClass;
JS_ASSERT(((jsuword) clasp & 3) == 0);
// Abort on |new Function|. js_CreateThis would allocate a regular-
// sized JSObject, not a Function-sized one. (The Function ctor would
// deep-bail anyway but let's not go there.)
// Abort on |new Function|. (FIXME: This restriction might not
// unnecessary now that the constructor creates the new function object
// itself.)
if (clasp == &js_FunctionClass)
RETURN_STOP("new Function");
if (!clasp->isNative())
RETURN_STOP("new with non-native ops");
if (fun->isConstructor()) {
vp[1].setMagicWithObjectOrNullPayload(NULL);
newobj_ins = w.immpMagicNull();
// Don't trace |new Math.sin(0)|.
if (!fun->isConstructor())
RETURN_STOP("new with non-constructor native function");
/* Treat this as a regular call, the constructor will behave correctly. */
mode = JSOP_CALL;
} else {
args[0] = w.immpObjGC(funobj);
args[1] = w.immpNonGC(clasp);
args[2] = cx_ins;
newobj_ins = w.call(&js_CreateThisFromTrace_ci, args);
guard(false, w.eqp0(newobj_ins), OOM_EXIT);
vp[1].setMagicWithObjectOrNullPayload(NULL);
newobj_ins = w.immpMagicNull();
/*
* emitNativeCall may take a snapshot below. To avoid having a type
* mismatch (e.g., where get(&vp[1]) is an object and vp[1] is
* null), we make sure vp[1] is some object. The actual object
* doesn't matter; JSOP_NEW and InvokeConstructor both overwrite
* vp[1] without observing its value.
*
* N.B. tracing specializes for functions, so pick a non-function.
*/
vp[1].setObject(*globalObj);
}
/* Treat this as a regular call, the constructor will behave correctly. */
mode = JSOP_CALL;
this_ins = newobj_ins;
} else {
this_ins = get(&vp[1]);
@ -13741,6 +13720,42 @@ TraceRecorder::guardArguments(JSObject *obj, LIns* obj_ins, unsigned *depthp)
return afp;
}
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::createThis(JSObject& ctor, LIns* ctor_ins, LIns** thisobj_insp)
{
JS_ASSERT(ctor.getFunctionPrivate()->isInterpreted());
if (ctor.getFunctionPrivate()->isFunctionPrototype())
RETURN_STOP("new Function.prototype");
if (ctor.isBoundFunction())
RETURN_STOP("new applied to bound function");
// Given the above conditions, ctor.prototype is a non-configurable data
// property with a slot.
jsid id = ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom);
const Shape *shape = LookupInterpretedFunctionPrototype(cx, &ctor);
if (!shape)
RETURN_ERROR("new f: error resolving f.prototype");
// At run time ctor might be a different instance of the same function. Its
// .prototype property might not be resolved yet. Guard on the function
// object's shape to make sure .prototype is there.
//
// However, if ctor_ins is constant, which is usual, we don't need to
// guard: .prototype is non-configurable, and an object's non-configurable
// data properties always stay in the same slot for the life of the object.
if (!ctor_ins->isImmP())
guardShape(ctor_ins, &ctor, ctor.shape(), "ctor_shape", snapshot(MISMATCH_EXIT));
// Pass the slot of ctor.prototype to js_CreateThisFromTrace. We can only
// bake the slot into the trace, not the value, since .prototype is
// writable.
uintN protoSlot = shape->slot;
LIns* args[] = { w.nameImmw(protoSlot), ctor_ins, cx_ins };
*thisobj_insp = w.call(&js_CreateThisFromTrace_ci, args);
guard(false, w.eqp0(*thisobj_insp), OOM_EXIT);
return RECORD_CONTINUE;
}
JS_REQUIRES_STACK RecordingStatus
TraceRecorder::interpretedFunctionCall(Value& fval, JSFunction* fun, uintN argc, bool constructing)
{
@ -13752,14 +13767,10 @@ TraceRecorder::interpretedFunctionCall(Value& fval, JSFunction* fun, uintN argc,
*/
if (fun->script()->isEmpty()) {
LIns* rval_ins;
if (constructing) {
LIns* args[] = { get(&fval), w.nameImmpNonGC(&js_ObjectClass), cx_ins };
LIns* tv_ins = w.call(&js_CreateThisFromTrace_ci, args);
guard(false, w.eqp0(tv_ins), OOM_EXIT);
rval_ins = tv_ins;
} else {
if (constructing)
CHECK_STATUS(createThis(fval.toObject(), get(&fval), &rval_ins));
else
rval_ins = w.immiUndefined();
}
stack(-2 - argc, rval_ins);
return RECORD_CONTINUE;
}
@ -13769,6 +13780,12 @@ TraceRecorder::interpretedFunctionCall(Value& fval, JSFunction* fun, uintN argc,
JSStackFrame* const fp = cx->fp();
if (constructing) {
LIns* thisobj_ins;
CHECK_STATUS(createThis(fval.toObject(), get(&fval), &thisobj_ins));
stack(-argc - 1, thisobj_ins);
}
// Generate a type map for the outgoing frame and stash it in the LIR
unsigned stackSlots = NativeStackSlots(cx, 0 /* callDepth */);
FrameInfo* fi = (FrameInfo*)

View File

@ -1479,15 +1479,17 @@ class TraceRecorder
VMSideExit* exit);
JS_REQUIRES_STACK RecordingStatus guardPrototypeHasNoIndexedProperties(JSObject* obj,
nanojit::LIns* obj_ins,
VMSideExit *exit);
VMSideExit* exit);
JS_REQUIRES_STACK RecordingStatus guardNativeConversion(Value& v);
JS_REQUIRES_STACK void clearReturningFrameFromNativeTracker();
JS_REQUIRES_STACK void putActivationObjects();
JS_REQUIRES_STACK RecordingStatus createThis(JSObject& ctor, nanojit::LIns* ctor_ins,
nanojit::LIns** thisobj_insp);
JS_REQUIRES_STACK RecordingStatus guardCallee(Value& callee);
JS_REQUIRES_STACK JSStackFrame *guardArguments(JSObject *obj, nanojit::LIns* obj_ins,
unsigned *depthp);
JS_REQUIRES_STACK nanojit::LIns* guardArgsLengthNotAssigned(nanojit::LIns* argsobj_ins);
JS_REQUIRES_STACK void guardNotHole(nanojit::LIns *argsobj_ins, nanojit::LIns *ids_ins);
JS_REQUIRES_STACK void guardNotHole(nanojit::LIns* argsobj_ins, nanojit::LIns* ids_ins);
JS_REQUIRES_STACK RecordingStatus getClassPrototype(JSObject* ctor,
nanojit::LIns*& proto_ins);
JS_REQUIRES_STACK RecordingStatus getClassPrototype(JSProtoKey key,