diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index d8b8fbd33a2..c963152821a 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -351,16 +351,16 @@ static inline bool isInt32(jsval v) return JSDOUBLE_IS_INT(d, i); } -static inline bool asInt32(jsval v, jsint& rv) +static inline jsint asInt32(jsval v) { - if (!isNumber(v)) - return false; - if (JSVAL_IS_INT(v)) { - rv = JSVAL_TO_INT(v); - return true; - } - jsdouble d = asNumber(v); - return JSDOUBLE_IS_INT(d, rv); + JS_ASSERT(isNumber(v)); + if (JSVAL_IS_INT(v)) + return JSVAL_TO_INT(v); +#ifdef DEBUG + jsint i; + JS_ASSERT(JSDOUBLE_IS_INT(*JSVAL_TO_DOUBLE(v), i)); +#endif + return jsint(*JSVAL_TO_DOUBLE(v)); } /* Return JSVAL_DOUBLE for all numbers (int and double) and the tag otherwise. */ @@ -6040,68 +6040,12 @@ TraceRecorder::guardDenseArrayIndex(JSObject* obj, jsint idx, LIns* obj_ins, return cond; } -/* - * Guard that a computed property access via an element op (JSOP_GETELEM, etc.) - * does not find an alias to a global variable, or a property without a slot, - * or a slot-ful property with a getter or setter (depending on op_offset in - * JSObjectOps). Finally, beware resolve hooks mutating objects. Oh, and watch - * out for bears too ;-). - * - * One win here is that we do not need to generate a guard that obj_ins does - * not result in the global object on trace, because we guard on shape and rule - * out obj's shape being the global object's shape at recording time. This is - * safe because the global shape cannot change on trace. - */ -JS_REQUIRES_STACK bool -TraceRecorder::guardElemOp(JSObject* obj, LIns* obj_ins, jsid id, size_t op_offset, jsval* vp) +bool +TraceRecorder::guardNotGlobalObject(JSObject* obj, LIns* obj_ins) { - JS_ASSERT(op_offset == offsetof(JSObjectOps, getProperty) || - op_offset == offsetof(JSObjectOps, setProperty)); - - LIns* map_ins = lir->insLoad(LIR_ldp, obj_ins, (int)offsetof(JSObject, map)); - LIns* ops_ins; - if (!map_is_native(obj->map, map_ins, ops_ins, op_offset)) - ABORT_TRACE("non-native map"); - - uint32 shape = OBJ_SHAPE(obj); - if (JSID_IS_ATOM(id) && shape == treeInfo->globalShape) - ABORT_TRACE("elem op probably aliases global"); - - JSObject* pobj; - JSProperty* prop; - if (!js_LookupProperty(cx, obj, id, &pobj, &prop)) - return false; - - if (vp) - *vp = JSVAL_VOID; - if (prop) { - bool traceable_slot = true; - if (pobj == obj) { - JSScopeProperty* sprop = (JSScopeProperty*) prop; - traceable_slot = ((op_offset == offsetof(JSObjectOps, getProperty)) - ? SPROP_HAS_STUB_GETTER(sprop) - : SPROP_HAS_STUB_SETTER(sprop)) && - SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(obj)); - if (vp && traceable_slot) - *vp = LOCKED_OBJ_GET_SLOT(obj, sprop->slot); - } - - OBJ_DROP_PROPERTY(cx, pobj, prop); - if (pobj != obj) - ABORT_TRACE("elem op hit prototype property, can't shape-guard"); - if (!traceable_slot) - ABORT_TRACE("elem op hit direct and slotless getter or setter"); - } - - if (wasDeepAborted()) - ABORT_TRACE("deep abort from property lookup"); - - // If we got this far, we're almost safe -- but we must check for a rogue resolve hook. - if (OBJ_SHAPE(obj) != shape) - ABORT_TRACE("resolve hook mutated elem op base object"); - - LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)), "shape"); - guard(true, addName(lir->ins2i(LIR_eq, shape_ins, shape), "guard(shape)"), BRANCH_EXIT); + if (obj == globalObj) + ABORT_TRACE("reference aliases global object"); + guard(false, lir->ins2(LIR_eq, obj_ins, globalObj_ins), MISMATCH_EXIT); return true; } @@ -7205,9 +7149,10 @@ TraceRecorder::record_JSOP_GETELEM() LIns* obj_ins = get(&lval); LIns* idx_ins = get(&idx); - if (JSVAL_IS_STRING(lval) && JSVAL_IS_INT(idx)) { - int i = JSVAL_TO_INT(idx); - if ((size_t)i >= JSSTRING_LENGTH(JSVAL_TO_STRING(lval))) + // Special case for array-like access of strings. + if (JSVAL_IS_STRING(lval) && isInt32(idx)) { + int i = asInt32(idx); + if (size_t(i) >= JSSTRING_LENGTH(JSVAL_TO_STRING(lval))) ABORT_TRACE("Invalid string index in JSOP_GETELEM"); idx_ins = makeNumberInt32(idx_ins); LIns* args[] = { idx_ins, obj_ins, cx_ins }; @@ -7222,43 +7167,35 @@ TraceRecorder::record_JSOP_GETELEM() JSObject* obj = JSVAL_TO_OBJECT(lval); jsval id; - jsval v; LIns* v_ins; - /* Property access using a string name. */ - if (JSVAL_IS_STRING(idx)) { - if (!js_ValueToStringId(cx, idx, &id)) - return false; + /* Property access using a string name or something we have to stringify. */ + if (!JSVAL_IS_INT(idx)) { + // If index is not a string, turn it into a string. + if (!js_InternNonIntElementId(cx, obj, idx, &id)) + ABORT_TRACE("failed to intern non-int element id"); + set(&idx, stringify(idx)); + // Store the interned string to the stack to save the interpreter from redoing this work. idx = ID_TO_VALUE(id); - jsuint index; - if (js_IdIsIndex(idx, &index) && guardDenseArray(obj, obj_ins, BRANCH_EXIT)) { - v = (index >= js_DenseArrayCapacity(obj)) ? JSVAL_HOLE : obj->dslots[index]; - if (v == JSVAL_HOLE) - ABORT_TRACE("can't see through hole in dense array"); - } else { - if (!guardElemOp(obj, obj_ins, id, offsetof(JSObjectOps, getProperty), &v)) - return false; - } + + // The object is not guaranteed to be a dense array at this point, so it might be the + // global object, which we have to guard against. + if (!guardNotGlobalObject(obj, obj_ins)) + return false; + return call_imacro(getelem_imacros.getprop); } - /* At this point we expect a whole number or we bail. */ - if (!JSVAL_IS_INT(idx)) - ABORT_TRACE("non-string, non-int JSOP_GETELEM index"); - if (JSVAL_TO_INT(idx) < 0) - ABORT_TRACE("negative JSOP_GETELEM index"); + // Invalid dense array index or not a dense array. + if (JSVAL_TO_INT(idx) < 0 || !OBJ_IS_DENSE_ARRAY(cx, obj)) { + if (!guardNotGlobalObject(obj, obj_ins)) + return false; - /* Accessing an object using integer index but not a dense array. */ - if (!OBJ_IS_DENSE_ARRAY(cx, obj)) { - idx_ins = makeNumberInt32(idx_ins); - if (!js_IndexToId(cx, JSVAL_TO_INT(idx), &id)) - return false; - if (!guardElemOp(obj, obj_ins, id, offsetof(JSObjectOps, getProperty), &v)) - return false; return call_imacro(getelem_imacros.getelem); } + // Fast path for dense arrays accessed with a non-negative integer index. jsval* vp; LIns* addr_ins; if (!elem(lval, idx, vp, v_ins, addr_ins)) @@ -7353,46 +7290,50 @@ TraceRecorder::record_JSOP_SETELEM() LIns* v_ins = get(&v); jsid id; + if (!JSVAL_IS_INT(idx)) { + // If index is not a string, turn it into a string. + if (!js_InternNonIntElementId(cx, obj, idx, &id)) + ABORT_TRACE("failed to intern non-int element id"); + set(&idx, stringify(idx)); + + // Store the interned string to the stack to save the interpreter from redoing this work. + idx = ID_TO_VALUE(id); + + // The object is not guaranteed to be a dense array at this point, so it might be the + // global object, which we have to guard against. + if (!guardNotGlobalObject(obj, obj_ins)) + return false; + + return call_imacro(setelem_imacros.setprop); + } + + if (JSVAL_TO_INT(idx) < 0 || !OBJ_IS_DENSE_ARRAY(cx, obj)) { + if (!guardNotGlobalObject(obj, obj_ins)) + return false; + + return call_imacro((*cx->fp->regs->pc == JSOP_INITELEM) + ? initelem_imacros.initelem + : setelem_imacros.setelem); + } + + // Fast path for dense arrays accessed with a non-negative integer index. In case the trace + // calculated the index using the FPU, force it to be an integer. + idx_ins = makeNumberInt32(idx_ins); + + // Box the value so we can use one builtin instead of having to add one builtin for every + // storage type. LIns* boxed_v_ins = v_ins; box_jsval(v, boxed_v_ins); - if (JSVAL_IS_STRING(idx)) { - if (!js_ValueToStringId(cx, idx, &id)) - return false; - // Store the interned string to the stack to save the interpreter from redoing this work. - idx = ID_TO_VALUE(id); - if (!guardElemOp(obj, obj_ins, id, offsetof(JSObjectOps, setProperty), NULL)) - return false; - return call_imacro(setelem_imacros.setprop); - } - if (JSVAL_IS_INT(idx)) { - if (JSVAL_TO_INT(idx) < 0) - ABORT_TRACE("negative JSOP_SETELEM index"); - idx_ins = makeNumberInt32(idx_ins); + LIns* args[] = { boxed_v_ins, idx_ins, obj_ins, cx_ins }; + LIns* res_ins = lir->insCall(&js_Array_dense_setelem_ci, args); + guard(false, lir->ins_eq0(res_ins), MISMATCH_EXIT); - if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) { - if (!js_IndexToId(cx, JSVAL_TO_INT(idx), &id)) - return false; - idx = ID_TO_VALUE(id); - if (!guardElemOp(obj, obj_ins, id, offsetof(JSObjectOps, setProperty), NULL)) - return false; - jsbytecode* pc = cx->fp->regs->pc; - return call_imacro((*pc == JSOP_INITELEM) - ? initelem_imacros.initelem - : setelem_imacros.setelem); - } + jsbytecode* pc = cx->fp->regs->pc; + if (*pc == JSOP_SETELEM && pc[JSOP_SETELEM_LENGTH] != JSOP_POP) + set(&lval, v_ins); - LIns* args[] = { boxed_v_ins, idx_ins, obj_ins, cx_ins }; - LIns* res_ins = lir->insCall(&js_Array_dense_setelem_ci, args); - guard(false, lir->ins_eq0(res_ins), MISMATCH_EXIT); - - jsbytecode* pc = cx->fp->regs->pc; - if (*pc == JSOP_SETELEM && pc[JSOP_SETELEM_LENGTH] != JSOP_POP) - set(&lval, v_ins); - - return true; - } - ABORT_TRACE("non-string, non-int JSOP_SETELEM index"); + return true; } JS_REQUIRES_STACK bool @@ -7712,9 +7653,8 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins) * Can't specialize to assert obj != global, must guard to avoid aliasing * stale homes of stacked global variables. */ - if (obj == globalObj) - ABORT_TRACE("prop op aliases global"); - guard(false, lir->ins2(LIR_eq, obj_ins, globalObj_ins), MISMATCH_EXIT); + if (!guardNotGlobalObject(obj, obj_ins)) + return false; /* * Property cache ensures that we are dealing with an existing property, diff --git a/js/src/jstracer.h b/js/src/jstracer.h index d1a19893470..a26bd9de729 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -536,8 +536,7 @@ class TraceRecorder : public avmplus::GCObject { JS_REQUIRES_STACK bool guardDenseArrayIndex(JSObject* obj, jsint idx, nanojit::LIns* obj_ins, nanojit::LIns* dslots_ins, nanojit::LIns* idx_ins, ExitType exitType); - JS_REQUIRES_STACK bool guardElemOp(JSObject* obj, nanojit::LIns* obj_ins, jsid id, - size_t op_offset, jsval* vp); + bool guardNotGlobalObject(JSObject* obj, nanojit::LIns* obj_ins); void clearFrameSlotsFromCache(); JS_REQUIRES_STACK bool guardCallee(jsval& callee); JS_REQUIRES_STACK bool getClassPrototype(JSObject* ctor, nanojit::LIns*& proto_ins);