diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index bb6d1f0e9ad..0ee3b2a107d 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2717,30 +2717,6 @@ CheckSideEffects(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool *ans return ok; } -bool -BytecodeEmitter::needsImplicitThis() -{ - if (!compileAndGo()) - return true; - if (!inFunction()) { - JSObject *scope = scopeChain(); - while (scope) { - if (scope->isWith()) - return true; - scope = scope->enclosingScope(); - } - } - for (const FunctionBox *funbox = this->funbox; funbox; funbox = funbox->parent) { - if (funbox->tcflags & TCF_IN_WITH) - return true; - } - for (StmtInfo *stmt = topStmt; stmt; stmt = stmt->down) { - if (stmt->type == STMT_WITH) - return true; - } - return false; -} - static JSBool EmitNameOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool callContext) { @@ -2776,6 +2752,9 @@ EmitNameOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool callContex if (op == JSOP_ARGUMENTS || op == JSOP_CALLEE) { if (Emit1(cx, bce, op) < 0) return JS_FALSE; + /* Need to provide |this| value for call */ + if (callContext && Emit1(cx, bce, JSOP_UNDEFINED) < 0) + return JS_FALSE; } else { if (!pn->pn_cookie.isFree()) { EMIT_UINT16_IMM_OP(op, pn->pn_cookie.asInteger()); @@ -2785,17 +2764,6 @@ EmitNameOp(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSBool callContex } } - /* Need to provide |this| value for call */ - if (callContext) { - if (op == JSOP_CALLNAME && bce->needsImplicitThis()) { - if (!EmitAtomOp(cx, pn, JSOP_IMPLICITTHIS, bce)) - return false; - } else { - if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) - return false; - } - } - return JS_TRUE; } @@ -2817,10 +2785,7 @@ EmitXMLName(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - pn2->pn_offset) < 0) return false; - if (Emit1(cx, bce, op) < 0) - return false; - - return true; + return Emit1(cx, bce, op) >= 0; } #endif @@ -2830,8 +2795,6 @@ EmitElemOpBase(JSContext *cx, BytecodeEmitter *bce, JSOp op) if (Emit1(cx, bce, op) < 0) return false; CheckTypeSet(cx, bce, op); - if (op == JSOP_CALLELEM) - return Emit1(cx, bce, JSOP_SWAP) >= 0; return true; } @@ -2848,17 +2811,7 @@ EmitSpecialPropOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) return false; if (!EmitIndexOp(cx, JSOP_QNAMEPART, index, bce)) return false; - - if (op == JSOP_CALLELEM && Emit1(cx, bce, JSOP_DUP) < 0) - return false; - - if (!EmitElemOpBase(cx, bce, op)) - return false; - - if (op == JSOP_CALLELEM && Emit1(cx, bce, JSOP_SWAP) < 0) - return false; - - return true; + return EmitElemOpBase(cx, bce, op); } static bool @@ -2938,19 +2891,10 @@ EmitPropOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce, return false; } - if (op == JSOP_CALLPROP && Emit1(cx, bce, JSOP_DUP) < 0) - return false; - if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - pn2->pn_offset) < 0) return false; - if (!EmitAtomOp(cx, pn, op, bce, psuffix)) - return false; - - if (op == JSOP_CALLPROP && Emit1(cx, bce, JSOP_SWAP) < 0) - return false; - - return true; + return EmitAtomOp(cx, pn, op, bce, psuffix); } static bool @@ -3114,9 +3058,6 @@ EmitElemOp(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) if (!EmitTree(cx, bce, left)) return false; - if (op == JSOP_CALLELEM && Emit1(cx, bce, JSOP_DUP) < 0) - return false; - /* The right side of the descendant operator is implicitly quoted. */ JS_ASSERT(op != JSOP_DESCENDANTS || !right->isKind(PNK_STRING) || right->isOp(JSOP_QNAMEPART)); diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 43757d7924b..aa337933cec 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -745,8 +745,6 @@ struct BytecodeEmitter : public TreeContext return true; } - bool needsImplicitThis(); - TokenStream *tokenStream() { return &parser->tokenStream; } jsbytecode *base() const { return current->base; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 999ed0ee27f..cb6b74c7efb 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -242,14 +242,6 @@ Parser::newFunctionBox(JSObject *obj, ParseNode *fn, TreeContext *tc) funbox->tcflags = (TCF_IN_FUNCTION | (tc->flags & (TCF_COMPILE_N_GO | TCF_STRICT_MODE_CODE))); if (tc->innermostWith) funbox->tcflags |= TCF_IN_WITH; - if (!tc->inFunction()) { - JSObject *scope = tc->scopeChain(); - while (scope) { - if (scope->isWith()) - funbox->tcflags |= TCF_IN_WITH; - scope = scope->enclosingScope(); - } - } return funbox; } diff --git a/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js b/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js index 2b2439be2b9..b9d62794303 100644 --- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js +++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap-newvar.js @@ -6,5 +6,5 @@ function caller(code, obj) { eval(code); // Make the compiler give up on binding analysis. return x; } -trap(caller, 13, "var x = 'success'; nop()"); +trap(caller, 12, "var x = 'success'; nop()"); assertEq(caller("var y = 'ignominy'", this), "success"); diff --git a/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js b/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js index e83941e1be8..43d013da935 100644 --- a/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js +++ b/js/src/jit-test/tests/jaeger/bug563000/eif-trap.js @@ -7,5 +7,5 @@ function caller(obj) { var x = "failure"; return x; } -trap(caller, 15, "x = 'success'; nop()"); +trap(caller, 14, "x = 'success'; nop()"); assertEq(caller(this), "success"); diff --git a/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js b/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js index 35ffb68d16f..151ab536d56 100644 --- a/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js +++ b/js/src/jit-test/tests/jaeger/bug563000/trap-self-as-parent.js @@ -5,7 +5,7 @@ x = "notset"; function myparent(nested) { if (nested) { /* noop call in myparent */ - trap(myparent, 48, "success()"); + trap(myparent, 49, "success()"); } else { myparent(true); x = "failure"; diff --git a/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js b/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js index 7ab4eaab5b2..b5c45f5add6 100644 --- a/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js +++ b/js/src/jit-test/tests/jaeger/bug563000/trap-self-from-trap.js @@ -7,14 +7,14 @@ function doNothing() { } function myparent(nested) { if (nested) { /* JSOP_CALL to doNothing in myparent with nested = true. */ - trap(myparent, 26, "success()"); + trap(myparent, 24, "success()"); doNothing(); } else { doNothing(); } } /* JSOP_CALL to doNothing in myparent with nested = false. */ -trap(myparent, 37, "myparent(true)"); +trap(myparent, 34, "myparent(true)"); function success() { x = "success"; diff --git a/js/src/jsanalyze.cpp b/js/src/jsanalyze.cpp index 862f5fabace..cc5a691e06e 100644 --- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -1392,6 +1392,14 @@ ScriptAnalysis::analyzeSSA(JSContext *cx) break; } + case JSOP_CALLARG: + case JSOP_CALLLOCAL: { + uint32_t slot = GetBytecodeSlot(script, pc); + if (trackSlot(slot)) + stack[stackDepth - 2] = code->poppedValues[0] = values[slot]; + break; + } + /* Short circuit ops which push back one of their operands. */ case JSOP_MOREITER: @@ -1851,6 +1859,21 @@ CrossScriptSSA::foldValue(const CrossSSAValue &cv) break; } + case JSOP_CALLPROP: { + /* + * The second value pushed by CALLPROP is the same as its popped + * value. We don't do this folding during the SSA analysis itself + * as we still need to distinguish the two values during type + * inference --- any popped null or undefined value will throw an + * exception, and not actually end up in the pushed set. + */ + if (v.pushedIndex() == 1) { + ScriptAnalysis *analysis = frame.script->analysis(); + return foldValue(CrossSSAValue(cv.frame, analysis->poppedValue(pc, 0))); + } + break; + } + case JSOP_TOID: { /* * TOID acts as identity for integers, so to get better precision diff --git a/js/src/jsanalyze.h b/js/src/jsanalyze.h index 5ec6ba9e3d7..17061a082a4 100644 --- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -1067,12 +1067,9 @@ class ScriptAnalysis /* For a JSOP_CALL* op, get the pc of the corresponding JSOP_CALL/NEW/etc. */ jsbytecode *getCallPC(jsbytecode *pc) { - SSAUseChain *uses = useChain(SSAValue::PushedValue(pc - script->code, 0)); - JS_ASSERT(uses && uses->popped); - JS_ASSERT_IF(uses->next, - !uses->next->next && - uses->next->popped && - script->code[uses->next->offset] == JSOP_SWAP); + JS_ASSERT(js_CodeSpec[*pc].format & JOF_CALLOP); + SSAUseChain *uses = useChain(SSAValue::PushedValue(pc - script->code, 1)); + JS_ASSERT(uses && !uses->next && uses->popped); return script->code + uses->offset; } diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index e6d33dd983b..b3b26ab3e62 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -3572,8 +3572,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, /* Handle as a property access. */ PropertyAccess(cx, script, pc, script->global()->getType(cx), false, seen, id); - if (op == JSOP_CALLGNAME) + if (op == JSOP_CALLGNAME) { + pushed[1].addType(cx, Type::UnknownType()); pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType()); + } if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); @@ -3601,8 +3603,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, addTypeBarrier(cx, pc, seen, Type::UnknownType()); } - if (op == JSOP_CALLNAME) + if (op == JSOP_CALLNAME) { + pushed[1].addType(cx, Type::UnknownType()); pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType()); + } break; } @@ -3642,8 +3646,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeSet *seen = bytecodeTypes(pc); addTypeBarrier(cx, pc, seen, Type::UnknownType()); seen->addSubset(cx, &pushed[0]); - if (op == JSOP_CALLFCSLOT) + if (op == JSOP_CALLFCSLOT) { + pushed[1].addType(cx, Type::UndefinedType()); pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType()); + } break; } @@ -3666,8 +3672,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, /* Local 'let' variable. Punt on types for these, for now. */ pushed[0].addType(cx, Type::UnknownType()); } - if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) + if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) { + pushed[1].addType(cx, Type::UndefinedType()); pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType()); + } break; } @@ -3744,6 +3752,8 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, poppedTypes(pc, 0)->addCallProperty(cx, script, pc, id); seen->addSubset(cx, &pushed[0]); + if (op == JSOP_CALLPROP) + poppedTypes(pc, 0)->addFilterPrimitives(cx, &pushed[1], TypeSet::FILTER_NULL_VOID); if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); break; @@ -3761,8 +3771,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID); seen->addSubset(cx, &pushed[0]); - if (op == JSOP_CALLELEM) - pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType(), poppedTypes(pc, 1)); + if (op == JSOP_CALLELEM) { + poppedTypes(pc, 1)->addFilterPrimitives(cx, &pushed[1], TypeSet::FILTER_NULL_VOID); + pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType(), &pushed[1]); + } if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); break; @@ -4034,7 +4046,6 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, /* Pushes information about whether an exception was thrown. */ break; - case JSOP_IMPLICITTHIS: case JSOP_EXCEPTION: pushed[0].addType(cx, Type::UnknownType()); break; @@ -4077,8 +4088,10 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, pushed[0].addType(cx, Type::UnknownType()); break; - case JSOP_XMLNAME: case JSOP_CALLXMLNAME: + pushed[1].addType(cx, Type::UnknownType()); + /* FALLTHROUGH */ + case JSOP_XMLNAME: pushed[0].addType(cx, Type::UnknownType()); break; @@ -4654,7 +4667,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun, JSO if (calleev.kind() != SSAValue::PUSHED) return false; jsbytecode *calleepc = script->code + calleev.pushedOffset(); - if (JSOp(*calleepc) != JSOP_CALLPROP) + if (JSOp(*calleepc) != JSOP_CALLPROP || calleev.pushedIndex() != 0) return false; /* @@ -4664,8 +4677,8 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun, JSO analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0)); analysis->breakTypeBarriers(cx, calleepc - script->code, true); - TypeSet *funcallTypes = analysis->poppedTypes(pc, GET_ARGC(pc) + 1); - TypeSet *scriptTypes = analysis->poppedTypes(pc, GET_ARGC(pc)); + TypeSet *funcallTypes = analysis->pushedTypes(calleepc, 0); + TypeSet *scriptTypes = analysis->pushedTypes(calleepc, 1); /* Need to definitely be calling Function.call on a specific script. */ JSObject *funcallObj = funcallTypes->getSingleton(cx, false); @@ -4682,9 +4695,9 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun, JSO * Generate constraints to clear definite properties from the type * should the Function.call or callee itself change in the future. */ - funcallTypes->add(cx, + analysis->pushedTypes(calleev.pushedOffset(), 0)->add(cx, cx->typeLifoAlloc().new_(type)); - scriptTypes->add(cx, + analysis->pushedTypes(calleev.pushedOffset(), 1)->add(cx, cx->typeLifoAlloc().new_(type)); TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset); diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp index f47582af3df..0076f657ed5 100644 --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -386,8 +386,11 @@ Class js_NoSuchMethodClass = { * parameters. */ bool -js::OnUnknownMethod(JSContext *cx, JSObject *obj, Value idval, Value *vp) +js::OnUnknownMethod(JSContext *cx, Value *vp) { + JS_ASSERT(!vp[1].isPrimitive()); + + JSObject *obj = &vp[1].toObject(); jsid id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); AutoValueRooter tvr(cx); if (!js_GetMethod(cx, obj, id, JSGET_NO_METHOD_BARRIER, tvr.addr())) @@ -395,14 +398,14 @@ js::OnUnknownMethod(JSContext *cx, JSObject *obj, Value idval, Value *vp) TypeScript::MonitorUnknown(cx, cx->fp()->script(), cx->regs().pc); if (tvr.value().isPrimitive()) { - *vp = tvr.value(); + vp[0] = tvr.value(); } else { #if JS_HAS_XML_SUPPORT /* Extract the function name from function::name qname. */ - if (idval.isObject()) { - obj = &idval.toObject(); + if (vp[0].isObject()) { + obj = &vp[0].toObject(); if (js_GetLocalNameFromFunctionQName(obj, &id, cx)) - idval = IdToValue(id); + vp[0] = IdToValue(id); } #endif @@ -411,8 +414,8 @@ js::OnUnknownMethod(JSContext *cx, JSObject *obj, Value idval, Value *vp) return false; obj->setSlot(JSSLOT_FOUND_FUNCTION, tvr.value()); - obj->setSlot(JSSLOT_SAVED_ID, idval); - vp->setObject(*obj); + obj->setSlot(JSSLOT_SAVED_ID, vp[0]); + vp[0].setObject(*obj); } return true; } @@ -1259,15 +1262,25 @@ inline InterpreterFrames::~InterpreterFrames() JS_THREAD_DATA(context)->interpreterFrames = older; } -#if defined(DEBUG) && !defined(JS_THREADSAFE) -void -js::AssertValidPropertyCacheHit(JSContext *cx, - JSObject *start, JSObject *found, - PropertyCacheEntry *entry) -{ - JSScript *script = cx->fp()->script(); - FrameRegs& regs = cx->regs(); +/* + * Deadlocks or else bad races are likely if JS_THREADSAFE, so we must rely on + * single-thread DEBUG js shell testing to verify property cache hits. + */ +#if defined DEBUG && !defined JS_THREADSAFE +# define ASSERT_VALID_PROPERTY_CACHE_HIT(obj,pobj,entry) \ + JS_BEGIN_MACRO \ + if (!AssertValidPropertyCacheHit(cx, script, regs, obj, pobj, \ + entry)) { \ + goto error; \ + } \ + JS_END_MACRO + +static bool +AssertValidPropertyCacheHit(JSContext *cx, JSScript *script, FrameRegs& regs, + JSObject *start, JSObject *found, + PropertyCacheEntry *entry) +{ uint32_t sample = cx->runtime->gcNumber; PropertyCacheEntry savedEntry = *entry; @@ -1285,8 +1298,8 @@ js::AssertValidPropertyCacheHit(JSContext *cx, obj = start; ok = LookupProperty(cx, obj, name, &pobj, &prop); } - JS_ASSERT(ok); - + if (!ok) + return false; if (cx->runtime->gcNumber != sample) JS_PROPERTY_CACHE(cx).restore(&savedEntry); JS_ASSERT(prop); @@ -1294,8 +1307,13 @@ js::AssertValidPropertyCacheHit(JSContext *cx, const Shape *shape = (Shape *) prop; JS_ASSERT(entry->prop == shape); + + return true; } -#endif /* DEBUG && !JS_THREADSAFE */ + +#else +# define ASSERT_VALID_PROPERTY_CACHE_HIT(obj,pobj,entry) ((void) 0) +#endif /* * Ensure that the intrepreter switch can close call-bytecode cases in the @@ -2219,6 +2237,35 @@ BEGIN_CASE(JSOP_PICK) } END_CASE(JSOP_PICK) +#define NATIVE_GET(cx,obj,pobj,shape,getHow,vp) \ + JS_BEGIN_MACRO \ + if (shape->isDataDescriptor() && shape->hasDefaultGetter()) { \ + /* Fast path for Object instance properties. */ \ + JS_ASSERT((shape)->slot() != SHAPE_INVALID_SLOT || \ + !shape->hasDefaultSetter()); \ + if (((shape)->slot() != SHAPE_INVALID_SLOT)) \ + *(vp) = (pobj)->nativeGetSlot((shape)->slot()); \ + else \ + (vp)->setUndefined(); \ + } else { \ + if (!js_NativeGet(cx, obj, pobj, shape, getHow, vp)) \ + goto error; \ + } \ + JS_END_MACRO + +#define NATIVE_SET(cx,obj,shape,entry,strict,vp) \ + JS_BEGIN_MACRO \ + if (shape->hasDefaultSetter() && \ + (shape)->hasSlot() && \ + !(shape)->isMethod()) { \ + /* Fast path for, e.g., plain Object instance properties. */ \ + (obj)->nativeSetSlotWithType(cx, shape, *vp); \ + } else { \ + if (!js_NativeSet(cx, obj, shape, false, strict, vp)) \ + goto error; \ + } \ + JS_END_MACRO + BEGIN_CASE(JSOP_SETCONST) { PropertyName *name; @@ -2279,8 +2326,14 @@ BEGIN_CASE(JSOP_BINDNAME) if (obj->isGlobal()) break; + PropertyCacheEntry *entry; + JSObject *obj2; PropertyName *name; - LOAD_NAME(0, name); + JS_PROPERTY_CACHE(cx).test(cx, regs.pc, obj, obj2, entry, name); + if (!name) { + ASSERT_VALID_PROPERTY_CACHE_HIT(obj, obj2, entry); + break; + } obj = FindIdentifierBase(cx, ®s.fp()->scopeChain(), name); if (!obj) @@ -2852,11 +2905,72 @@ END_CASE(JSOP_THIS) BEGIN_CASE(JSOP_GETPROP) BEGIN_CASE(JSOP_GETXPROP) BEGIN_CASE(JSOP_LENGTH) -BEGIN_CASE(JSOP_CALLPROP) { Value rval; - if (!GetPropertyOperation(cx, regs.pc, regs.sp[-1], &rval)) - goto error; + do { + Value *vp = ®s.sp[-1]; + + if (op == JSOP_LENGTH) { + /* Optimize length accesses on strings, arrays, and arguments. */ + if (vp->isString()) { + rval = Int32Value(vp->toString()->length()); + break; + } + if (vp->isMagic(JS_LAZY_ARGUMENTS)) { + rval = Int32Value(regs.fp()->numActualArgs()); + break; + } + if (vp->isObject()) { + JSObject *obj = &vp->toObject(); + if (obj->isArray()) { + jsuint length = obj->getArrayLength(); + rval = NumberValue(length); + break; + } + + if (obj->isArguments()) { + ArgumentsObject &argsobj = obj->asArguments(); + if (!argsobj.hasOverriddenLength()) { + uint32_t length = argsobj.initialLength(); + JS_ASSERT(length < INT32_MAX); + rval = Int32Value(int32_t(length)); + break; + } + } + + if (js_IsTypedArray(obj)) { + JSObject *tarray = TypedArray::getTypedArray(obj); + rval = Int32Value(TypedArray::getLength(tarray)); + break; + } + } + } + + JSObject *obj; + VALUE_TO_OBJECT(cx, vp, obj); + JSObject *aobj = js_GetProtoIfDenseArray(obj); + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, regs.pc, aobj, obj2, entry, name); + if (!name) { + ASSERT_VALID_PROPERTY_CACHE_HIT(aobj, obj2, entry); + NATIVE_GET(cx, obj, obj2, entry->prop, JSGET_METHOD_BARRIER, &rval); + break; + } + + if (JS_LIKELY(!aobj->getOps()->getProperty) + ? !GetPropertyHelper(cx, obj, name, + (regs.pc[JSOP_GETPROP_LENGTH] == JSOP_IFEQ) + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_CACHE_RESULT | JSGET_METHOD_BARRIER, + &rval) + : !obj->getProperty(cx, name, &rval)) + { + goto error; + } + } while (0); TypeScript::Monitor(cx, script, regs.pc, rval); @@ -2865,16 +2979,177 @@ BEGIN_CASE(JSOP_CALLPROP) } END_CASE(JSOP_GETPROP) +BEGIN_CASE(JSOP_CALLPROP) +{ + Value lval = regs.sp[-1]; + + Value objv; + if (lval.isObject()) { + objv = lval; + } else { + GlobalObject &global = regs.fp()->scopeChain().global(); + JSObject *pobj; + if (lval.isString()) { + pobj = global.getOrCreateStringPrototype(cx); + } else if (lval.isNumber()) { + pobj = global.getOrCreateNumberPrototype(cx); + } else if (lval.isBoolean()) { + pobj = global.getOrCreateBooleanPrototype(cx); + } else { + JS_ASSERT(lval.isNull() || lval.isUndefined()); + js_ReportIsNullOrUndefined(cx, -1, lval, NULL); + goto error; + } + if (!pobj) + goto error; + objv.setObject(*pobj); + } + + JSObject *aobj = js_GetProtoIfDenseArray(&objv.toObject()); + Value rval; + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, regs.pc, aobj, obj2, entry, name); + if (!name) { + ASSERT_VALID_PROPERTY_CACHE_HIT(aobj, obj2, entry); + NATIVE_GET(cx, &objv.toObject(), obj2, entry->prop, JSGET_NO_METHOD_BARRIER, &rval); + regs.sp[-1] = rval; + assertSameCompartment(cx, regs.sp[-1]); + PUSH_COPY(lval); + } else { + /* Cache miss: use the name loaded for us under PropertyCache::test. */ + PUSH_NULL(); + if (lval.isObject()) { + if (!GetMethod(cx, &objv.toObject(), name, + JS_LIKELY(!aobj->getOps()->getProperty) + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_NO_METHOD_BARRIER, + &rval)) + { + goto error; + } + regs.sp[-1] = objv; + regs.sp[-2] = rval; + } else { + JS_ASSERT(!objv.toObject().getOps()->getProperty); + if (!GetPropertyHelper(cx, &objv.toObject(), name, + JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER, &rval)) + { + goto error; + } + regs.sp[-1] = lval; + regs.sp[-2] = rval; + } + assertSameCompartment(cx, regs.sp[-1], regs.sp[-2]); + } +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(rval.isPrimitive()) && regs.sp[-1].isObject()) { + LOAD_NAME(0, name); + regs.sp[-2].setString(name); + if (!OnUnknownMethod(cx, regs.sp - 2)) + goto error; + } +#endif + TypeScript::Monitor(cx, script, regs.pc, rval); +} +END_CASE(JSOP_CALLPROP) + BEGIN_CASE(JSOP_SETGNAME) BEGIN_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_SETPROP) BEGIN_CASE(JSOP_SETMETHOD) { - const Value &rval = regs.sp[-1]; - const Value &lval = regs.sp[-2]; + Value rval = regs.sp[-1]; + JS_ASSERT_IF(op == JSOP_SETMETHOD, IsFunctionObject(rval)); + Value &lref = regs.sp[-2]; + JS_ASSERT_IF(op == JSOP_SETNAME, lref.isObject()); + JSObject *obj; + VALUE_TO_OBJECT(cx, &lref, obj); - if (!SetPropertyOperation(cx, regs.pc, lval, rval)) - goto error; + JS_ASSERT_IF(op == JSOP_SETGNAME, obj == ®s.fp()->scopeChain().global()); + + do { + PropertyCache *cache = &JS_PROPERTY_CACHE(cx); + + /* + * Probe the property cache, specializing for two important + * set-property cases. First: + * + * function f(a, b, c) { + * var o = {p:a, q:b, r:c}; + * return o; + * } + * + * or similar real-world cases, which evolve a newborn native + * object predicatably through some bounded number of property + * additions. And second: + * + * o.p = x; + * + * in a frequently executed method or loop body, where p will + * (possibly after the first iteration) always exist in native + * object o. + */ + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + if (cache->testForSet(cx, regs.pc, obj, &entry, &obj2, &name)) { + /* + * Property cache hit, only partially confirmed by testForSet. We + * know that the entry applies to regs.pc and that obj's shape + * matches. + * + * The entry predicts a set either an existing "own" property, or + * on a prototype property that has a setter. + */ + const Shape *shape = entry->prop; + + if (entry->isOwnPropertyHit() || + ((obj2 = obj->getProto()) && obj2->lastProperty() == entry->pshape)) + { + JS_ASSERT_IF(shape->isDataDescriptor(), shape->writable()); + JS_ASSERT_IF(shape->hasSlot(), entry->isOwnPropertyHit()); + +#ifdef DEBUG + if (entry->isOwnPropertyHit()) { + JS_ASSERT(obj->nativeContains(cx, *shape)); + } else { + JS_ASSERT(obj2->nativeContains(cx, *shape)); + JS_ASSERT(entry->isPrototypePropertyHit()); + JS_ASSERT(entry->kshape != entry->pshape); + JS_ASSERT(!shape->hasSlot()); + } +#endif + + PCMETER(cache->pchits++); + PCMETER(cache->setpchits++); + NATIVE_SET(cx, obj, shape, entry, script->strictModeCode, &rval); + break; + } + PCMETER(cache->setpcmisses++); + + LOAD_NAME(0, name); + } else { + JS_ASSERT(name); + } + + if (entry && JS_LIKELY(!obj->getOps()->setProperty)) { + uintN defineHow; + if (op == JSOP_SETMETHOD) + defineHow = DNP_CACHE_RESULT | DNP_SET_METHOD; + else if (op == JSOP_SETNAME) + defineHow = DNP_CACHE_RESULT | DNP_UNQUALIFIED; + else + defineHow = DNP_CACHE_RESULT; + if (!SetPropertyHelper(cx, obj, name, defineHow, &rval, script->strictModeCode)) + goto error; + } else { + if (!obj->setProperty(cx, name, &rval, script->strictModeCode)) + goto error; + } + } while (0); regs.sp[-2] = regs.sp[-1]; regs.sp--; @@ -2979,14 +3254,18 @@ BEGIN_CASE(JSOP_CALLELEM) #if JS_HAS_NO_SUCH_METHOD if (JS_UNLIKELY(regs.sp[-2].isPrimitive()) && thisv.isObject()) { - if (!OnUnknownMethod(cx, &thisv.toObject(), regs.sp[-1], regs.sp - 2)) + /* For OnUnknownMethod, sp[-2] is the index, and sp[-1] is the object missing it. */ + regs.sp[-2] = regs.sp[-1]; + regs.sp[-1].setObject(*thisObj); + if (!OnUnknownMethod(cx, regs.sp - 2)) goto error; - } + } else #endif + { + regs.sp[-1] = thisv; + } - regs.sp--; - - TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-2]); } END_CASE(JSOP_CALLELEM) @@ -3152,34 +3431,82 @@ BEGIN_CASE(JSOP_SETCALL) } END_CASE(JSOP_SETCALL) -BEGIN_CASE(JSOP_IMPLICITTHIS) -{ - PropertyName *name; - LOAD_NAME(0, name); - - JSObject *obj, *obj2; - JSProperty *prop; - if (!FindPropertyHelper(cx, name, false, false, &obj, &obj2, &prop)) - goto error; - - Value v; - if (!ComputeImplicitThis(cx, obj, &v)) - goto error; - PUSH_COPY(v); -} -END_CASE(JSOP_IMPLICITTHIS) +#define PUSH_IMPLICIT_THIS(cx, obj, funval) \ + JS_BEGIN_MACRO \ + Value v; \ + if (!ComputeImplicitThis(cx, obj, funval, &v)) \ + goto error; \ + PUSH_COPY(v); \ + JS_END_MACRO \ BEGIN_CASE(JSOP_GETGNAME) BEGIN_CASE(JSOP_CALLGNAME) BEGIN_CASE(JSOP_NAME) BEGIN_CASE(JSOP_CALLNAME) { + JSObject *obj = ®s.fp()->scopeChain(); + + bool global = js_CodeSpec[op].format & JOF_GNAME; + if (global) + obj = &obj->global(); + Value rval; - if (!NameOperation(cx, regs.pc, &rval)) + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, regs.pc, obj, obj2, entry, name); + if (!name) { + ASSERT_VALID_PROPERTY_CACHE_HIT(obj, obj2, entry); + NATIVE_GET(cx, obj, obj2, entry->prop, JSGET_METHOD_BARRIER, &rval); + PUSH_COPY(rval); + + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); + + JS_ASSERT(obj->isGlobal() || IsCacheableNonGlobalScope(obj)); + if (op == JSOP_CALLNAME || op == JSOP_CALLGNAME) + PUSH_IMPLICIT_THIS(cx, obj, regs.sp[-1]); + len = JSOP_NAME_LENGTH; + DO_NEXT_OP(len); + } + + JSProperty *prop; + if (!FindPropertyHelper(cx, name, true, global, &obj, &obj2, &prop)) goto error; + if (!prop) { + /* Kludge to allow (typeof foo == "undefined") tests. */ + JSOp op2 = JSOp(regs.pc[JSOP_NAME_LENGTH]); + if (op2 == JSOP_TYPEOF) { + PUSH_UNDEFINED(); + TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); + len = JSOP_NAME_LENGTH; + DO_NEXT_OP(len); + } + + JSAutoByteString bytes; + if (js_AtomToPrintableString(cx, name, &bytes)) + js_ReportIsNotDefined(cx, bytes.ptr()); + goto error; + } + + /* Take the slow path if prop was not found in a native object. */ + if (!obj->isNative() || !obj2->isNative()) { + if (!obj->getProperty(cx, name, &rval)) + goto error; + } else { + Shape *shape = (Shape *)prop; + JSObject *normalized = obj; + if (normalized->isWith() && !shape->hasDefaultGetter()) + normalized = &normalized->asWith().object(); + NATIVE_GET(cx, normalized, obj2, shape, JSGET_METHOD_BARRIER, &rval); + } PUSH_COPY(rval); TypeScript::Monitor(cx, script, regs.pc, rval); + + /* obj must be on the scope chain, thus not a function. */ + if (op == JSOP_CALLNAME || op == JSOP_CALLGNAME) + PUSH_IMPLICIT_THIS(cx, obj, rval); } END_CASE(JSOP_NAME) @@ -3456,6 +3783,8 @@ BEGIN_CASE(JSOP_CALLARG) uint32_t slot = GET_ARGNO(regs.pc); JS_ASSERT(slot < regs.fp()->numFormalArgs()); PUSH_COPY(argv[slot]); + if (op == JSOP_CALLARG) + PUSH_UNDEFINED(); } END_CASE(JSOP_GETARG) @@ -3468,7 +3797,6 @@ BEGIN_CASE(JSOP_SETARG) END_CASE(JSOP_SETARG) BEGIN_CASE(JSOP_GETLOCAL) -BEGIN_CASE(JSOP_CALLLOCAL) { /* * Skip the same-compartment assertion if the local will be immediately @@ -3487,6 +3815,15 @@ BEGIN_CASE(JSOP_CALLLOCAL) } END_CASE(JSOP_GETLOCAL) +BEGIN_CASE(JSOP_CALLLOCAL) +{ + uint32_t slot = GET_SLOTNO(regs.pc); + JS_ASSERT(slot < script->nslots); + PUSH_COPY(regs.fp()->slots()[slot]); + PUSH_UNDEFINED(); +} +END_CASE(JSOP_CALLLOCAL) + BEGIN_CASE(JSOP_SETLOCAL) { uint32_t slot = GET_SLOTNO(regs.pc); @@ -3504,6 +3841,8 @@ BEGIN_CASE(JSOP_CALLFCSLOT) PUSH_COPY(obj->toFunction()->getFlatClosureUpvar(index)); TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]); + if (op == JSOP_CALLFCSLOT) + PUSH_UNDEFINED(); } END_CASE(JSOP_GETFCSLOT) @@ -4059,18 +4398,37 @@ BEGIN_CASE(JSOP_INITMETHOD) JSObject *obj = ®s.sp[-2].toObject(); JS_ASSERT(obj->isObject()); - JSAtom *atom; - LOAD_ATOM(0, atom); - jsid id = ATOM_TO_JSID(atom); + /* + * Probe the property cache to see if this is a set on an existing property + * added by a NEWOBJECT or a previous INITPROP. If the cached shape has a + * non-default setter, it must be __proto__, so don't handle this. + */ + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + if (JS_PROPERTY_CACHE(cx).testForSet(cx, regs.pc, obj, &entry, &obj2, &name) && + entry->prop->hasDefaultSetter() && + entry->isOwnPropertyHit()) + { + JS_ASSERT(obj == obj2); + /* Fast path. Property cache hit. */ + obj->nativeSetSlotWithType(cx, entry->prop, rval); + } else { + PCMETER(JS_PROPERTY_CACHE(cx).inipcmisses++); + LOAD_NAME(0, name); - uintN defineHow = (op == JSOP_INITMETHOD) ? DNP_SET_METHOD : 0; - if (JS_UNLIKELY(atom == cx->runtime->atomState.protoAtom) - ? !js_SetPropertyHelper(cx, obj, id, defineHow, &rval, script->strictModeCode) - : !DefineNativeProperty(cx, obj, id, rval, NULL, NULL, - JSPROP_ENUMERATE, 0, 0, defineHow)) { - goto error; + uintN defineHow = (op == JSOP_INITMETHOD) + ? DNP_CACHE_RESULT | DNP_SET_METHOD + : DNP_CACHE_RESULT; + if (JS_UNLIKELY(name == cx->runtime->atomState.protoAtom) + ? !SetPropertyHelper(cx, obj, name, defineHow, &rval, script->strictModeCode) + : !DefineNativeProperty(cx, obj, name, rval, NULL, NULL, + JSPROP_ENUMERATE, 0, 0, defineHow)) { + goto error; + } } + /* Common tail for property cache hit and miss cases. */ regs.sp--; } END_CASE(JSOP_INITPROP); @@ -4476,12 +4834,8 @@ BEGIN_CASE(JSOP_XMLNAME) if (!obj->getGeneric(cx, id, &rval)) goto error; regs.sp[-1] = rval; - if (op == JSOP_CALLXMLNAME) { - Value v; - if (!ComputeImplicitThis(cx, obj, &v)) - goto error; - PUSH_COPY(v); - } + if (op == JSOP_CALLXMLNAME) + PUSH_IMPLICIT_THIS(cx, obj, rval); } END_CASE(JSOP_XMLNAME) diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index c26b412ba8f..4ba9c382456 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -325,7 +325,7 @@ extern void UnwindScope(JSContext *cx, uint32_t stackDepth); extern bool -OnUnknownMethod(JSContext *cx, JSObject *obj, Value idval, Value *vp); +OnUnknownMethod(JSContext *cx, js::Value *vp); extern bool IsActiveWithOrBlock(JSContext *cx, JSObject &obj, uint32_t stackDepth); diff --git a/js/src/jsinterpinlines.h b/js/src/jsinterpinlines.h index 3f9e726e1a7..43e32d4fa4d 100644 --- a/js/src/jsinterpinlines.h +++ b/js/src/jsinterpinlines.h @@ -51,8 +51,6 @@ #include "methodjit/MethodJIT.h" #include "jsfuninlines.h" -#include "jspropertycacheinlines.h" -#include "jstypedarrayinlines.h" #include "vm/Stack-inl.h" @@ -81,28 +79,34 @@ class AutoPreserveEnumerators { * We can avoid computing |this| eagerly and push the implicit callee-coerced * |this| value, undefined, if any of these conditions hold: * - * 1. The nominal |this|, obj, is a global object. + * 1. The callee funval is not an object. * - * 2. The nominal |this|, obj, has one of Block, Call, or DeclEnv class (this + * 2. The nominal |this|, obj, is a global object. + * + * 3. The nominal |this|, obj, has one of Block, Call, or DeclEnv class (this * is what IsCacheableNonGlobalScope tests). Such objects-as-scopes must be * censored with undefined. * - * Otherwise, we bind |this| to obj->thisObject(). Only names inside |with| - * statements and embedding-specific scope objects fall into this category. + * Only if funval is an object and obj is neither a declarative scope object to + * be censored, nor a global object, do we bind |this| to obj->thisObject(). + * Only |with| statements and embedding-specific scope objects fall into this + * last ditch. * - * If the callee is a strict mode function, then code implementing JSOP_THIS - * in the interpreter and JITs will leave undefined as |this|. If funval is a - * function not in strict mode, JSOP_THIS code replaces undefined with funval's - * global. + * If funval is a strict mode function, then code implementing JSOP_THIS in the + * interpreter and JITs will leave undefined as |this|. If funval is a function + * not in strict mode, JSOP_THIS code replaces undefined with funval's global. * * We set *vp to undefined early to reduce code size and bias this code for the * common and future-friendly cases. */ inline bool -ComputeImplicitThis(JSContext *cx, JSObject *obj, Value *vp) +ComputeImplicitThis(JSContext *cx, JSObject *obj, const Value &funval, Value *vp) { vp->setUndefined(); + if (!funval.isObject()) + return true; + if (obj->isGlobal()) return true; @@ -174,264 +178,6 @@ ValuePropertyBearer(JSContext *cx, const Value &v, int spindex) return pobj; } -inline bool -NativeGet(JSContext *cx, JSObject *obj, JSObject *pobj, const Shape *shape, uintN getHow, Value *vp) -{ - if (shape->isDataDescriptor() && shape->hasDefaultGetter()) { - /* Fast path for Object instance properties. */ - JS_ASSERT(shape->hasSlot()); - *vp = pobj->nativeGetSlot(shape->slot()); - } else { - if (!js_NativeGet(cx, obj, pobj, shape, getHow, vp)) - return false; - } - return true; -} - -#if defined(DEBUG) && !defined(JS_THREADSAFE) -extern void -AssertValidPropertyCacheHit(JSContext *cx, JSObject *start, JSObject *found, - PropertyCacheEntry *entry); -#else -inline void -AssertValidPropertyCacheHit(JSContext *cx, JSObject *start, JSObject *found, - PropertyCacheEntry *entry) -{} -#endif - -inline bool -GetPropertyGenericMaybeCallXML(JSContext *cx, JSOp op, JSObject *obj, jsid id, Value *vp) -{ - /* - * Various XML properties behave differently when accessed in a - * call vs. normal context, and getGeneric will not work right. - */ -#if JS_HAS_XML_SUPPORT - if (op == JSOP_CALLPROP && obj->isXML()) - return js_GetXMLMethod(cx, obj, id, vp); -#endif - - return obj->getGeneric(cx, id, vp); -} - -inline bool -GetPropertyOperation(JSContext *cx, jsbytecode *pc, const Value &lval, Value *vp) -{ - JS_ASSERT(vp != &lval); - - JSOp op = JSOp(*pc); - - if (op == JSOP_LENGTH) { - /* Optimize length accesses on strings, arrays, and arguments. */ - if (lval.isString()) { - *vp = Int32Value(lval.toString()->length()); - return true; - } - if (lval.isMagic(JS_LAZY_ARGUMENTS)) { - *vp = Int32Value(cx->fp()->numActualArgs()); - return true; - } - if (lval.isObject()) { - JSObject *obj = &lval.toObject(); - if (obj->isArray()) { - jsuint length = obj->getArrayLength(); - *vp = NumberValue(length); - return true; - } - - if (obj->isArguments()) { - ArgumentsObject *argsobj = &obj->asArguments(); - if (!argsobj->hasOverriddenLength()) { - uint32_t length = argsobj->initialLength(); - JS_ASSERT(length < INT32_MAX); - *vp = Int32Value(int32_t(length)); - return true; - } - } - - if (js_IsTypedArray(obj)) { - JSObject *tarray = TypedArray::getTypedArray(obj); - *vp = Int32Value(TypedArray::getLength(tarray)); - return true; - } - } - } - - JSObject *obj = ValueToObjectOrPrototype(cx, lval); - if (!obj) - return false; - - uintN flags = (op == JSOP_CALLPROP) - ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER - : JSGET_CACHE_RESULT | JSGET_METHOD_BARRIER; - - PropertyCacheEntry *entry; - JSObject *obj2; - PropertyName *name; - JS_PROPERTY_CACHE(cx).test(cx, pc, obj, obj2, entry, name); - if (!name) { - AssertValidPropertyCacheHit(cx, obj, obj2, entry); - if (!NativeGet(cx, obj, obj2, entry->prop, flags, vp)) - return false; - return true; - } - - jsid id = ATOM_TO_JSID(name); - - if (obj->getOps()->getProperty) { - if (!GetPropertyGenericMaybeCallXML(cx, op, obj, id, vp)) - return false; - } else { - if (!GetPropertyHelper(cx, obj, id, flags, vp)) - return false; - } - -#if JS_HAS_NO_SUCH_METHOD - if (op == JSOP_CALLPROP && - JS_UNLIKELY(vp->isPrimitive()) && - lval.isObject()) - { - if (!OnUnknownMethod(cx, obj, IdToValue(id), vp)) - return false; - } -#endif - - return true; -} - -inline bool -SetPropertyOperation(JSContext *cx, jsbytecode *pc, const Value &lval, const Value &rval) -{ - JSObject *obj = ValueToObject(cx, lval); - if (!obj) - return false; - - JS_ASSERT_IF(*pc == JSOP_SETMETHOD, IsFunctionObject(rval)); - JS_ASSERT_IF(*pc == JSOP_SETNAME || *pc == JSOP_SETGNAME, lval.isObject()); - JS_ASSERT_IF(*pc == JSOP_SETGNAME, obj == &cx->fp()->scopeChain().global()); - - PropertyCacheEntry *entry; - JSObject *obj2; - PropertyName *name; - if (JS_PROPERTY_CACHE(cx).testForSet(cx, pc, obj, &entry, &obj2, &name)) { - /* - * Property cache hit, only partially confirmed by testForSet. We - * know that the entry applies to regs.pc and that obj's shape - * matches. - * - * The entry predicts a set either an existing "own" property, or - * on a prototype property that has a setter. - */ - const Shape *shape = entry->prop; - JS_ASSERT_IF(shape->isDataDescriptor(), shape->writable()); - JS_ASSERT_IF(shape->hasSlot(), entry->isOwnPropertyHit()); - - if (entry->isOwnPropertyHit() || - ((obj2 = obj->getProto()) && obj2->lastProperty() == entry->pshape)) { -#ifdef DEBUG - if (entry->isOwnPropertyHit()) { - JS_ASSERT(obj->nativeContains(cx, *shape)); - } else { - JS_ASSERT(obj2->nativeContains(cx, *shape)); - JS_ASSERT(entry->isPrototypePropertyHit()); - JS_ASSERT(entry->kshape != entry->pshape); - JS_ASSERT(!shape->hasSlot()); - } -#endif - - if (shape->hasDefaultSetter() && shape->hasSlot() && !shape->isMethod()) { - /* Fast path for, e.g., plain Object instance properties. */ - obj->nativeSetSlotWithType(cx, shape, rval); - } else { - Value rref = rval; - bool strict = cx->stack.currentScript()->strictModeCode; - if (!js_NativeSet(cx, obj, shape, false, strict, &rref)) - return false; - } - return true; - } - - GET_NAME_FROM_BYTECODE(cx->stack.currentScript(), pc, 0, name); - } - - bool strict = cx->stack.currentScript()->strictModeCode; - Value rref = rval; - - JSOp op = JSOp(*pc); - - jsid id = ATOM_TO_JSID(name); - if (JS_LIKELY(!obj->getOps()->setProperty)) { - uintN defineHow; - if (op == JSOP_SETMETHOD) - defineHow = DNP_CACHE_RESULT | DNP_SET_METHOD; - else if (op == JSOP_SETNAME) - defineHow = DNP_CACHE_RESULT | DNP_UNQUALIFIED; - else - defineHow = DNP_CACHE_RESULT; - if (!js_SetPropertyHelper(cx, obj, id, defineHow, &rref, strict)) - return false; - } else { - if (!obj->setGeneric(cx, id, &rref, strict)) - return false; - } - - return true; -} - -inline bool -NameOperation(JSContext *cx, jsbytecode *pc, Value *vp) -{ - JSObject *obj = cx->stack.currentScriptedScopeChain(); - - bool global = js_CodeSpec[*pc].format & JOF_GNAME; - if (global) - obj = &obj->global(); - - PropertyCacheEntry *entry; - JSObject *obj2; - PropertyName *name; - JS_PROPERTY_CACHE(cx).test(cx, pc, obj, obj2, entry, name); - if (!name) { - AssertValidPropertyCacheHit(cx, obj, obj2, entry); - if (!NativeGet(cx, obj, obj2, entry->prop, JSGET_METHOD_BARRIER, vp)) - return false; - return true; - } - - jsid id = ATOM_TO_JSID(name); - - JSProperty *prop; - if (!FindPropertyHelper(cx, name, true, global, &obj, &obj2, &prop)) - return false; - if (!prop) { - /* Kludge to allow (typeof foo == "undefined") tests. */ - JSOp op2 = JSOp(pc[JSOP_NAME_LENGTH]); - if (op2 == JSOP_TYPEOF) { - vp->setUndefined(); - return true; - } - JSAutoByteString printable; - if (js_AtomToPrintableString(cx, name, &printable)) - js_ReportIsNotDefined(cx, printable.ptr()); - return false; - } - - /* Take the slow path if prop was not found in a native object. */ - if (!obj->isNative() || !obj2->isNative()) { - if (!obj->getGeneric(cx, id, vp)) - return false; - } else { - Shape *shape = (Shape *)prop; - JSObject *normalized = obj; - if (normalized->getClass() == &WithClass && !shape->hasDefaultGetter()) - normalized = &normalized->asWith().object(); - if (!NativeGet(cx, normalized, obj2, shape, JSGET_METHOD_BARRIER, vp)) - return false; - } - - return true; -} - inline bool FunctionNeedsPrologue(JSContext *cx, JSFunction *fun) { diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 78b10112fb1..f0e1950213f 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -4643,6 +4643,7 @@ PurgeProtoChain(JSContext *cx, JSObject *obj, jsid id) } shape = obj->nativeLookup(cx, id); if (shape) { + PCMETER(JS_PROPERTY_CACHE(cx).pcpurges++); if (!obj->shadowingShapeChange(cx, *shape)) return false; @@ -5083,12 +5084,13 @@ js::LookupPropertyWithFlags(JSContext *cx, JSObject *obj, jsid id, uintN flags, return LookupPropertyWithFlagsInline(cx, obj, id, flags, objp, propp); } -bool +PropertyCacheEntry * js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool global, JSObject **objp, JSObject **pobjp, JSProperty **propp) { jsid id = ATOM_TO_JSID(name); JSObject *scopeChain, *obj, *parent, *pobj; + PropertyCacheEntry *entry; int scopeIndex; JSProperty *prop; @@ -5108,6 +5110,7 @@ js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool } /* Scan entries on the scope chain that we can cache across. */ + entry = JS_NO_PROP_CACHE_FILL; obj = scopeChain; parent = obj->enclosingScope(); for (scopeIndex = 0; @@ -5116,7 +5119,7 @@ js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool : !obj->getOps()->lookupProperty; ++scopeIndex) { if (!LookupPropertyWithFlags(cx, obj, id, cx->resolveFlags, &pobj, &prop)) - return false; + return NULL; if (prop) { #ifdef DEBUG @@ -5139,16 +5142,14 @@ js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool JS_ASSERT(obj->isNative()); } #endif - /* * We must check if pobj is native as a global object can have * non-native prototype. */ if (cacheResult && pobj->isNative()) { - JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, pobj, - (Shape *) prop); + entry = JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, pobj, + (Shape *) prop); } - goto out; } @@ -5162,9 +5163,11 @@ js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool for (;;) { if (!obj->lookupGeneric(cx, id, &pobj, &prop)) - return false; - if (prop) + return NULL; + if (prop) { + PCMETER(JS_PROPERTY_CACHE(cx).nofills++); goto out; + } /* * We conservatively assume that a resolve hook could mutate the scope @@ -5183,7 +5186,7 @@ js::FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool *objp = obj; *pobjp = pobj; *propp = prop; - return true; + return entry; } /* @@ -5230,7 +5233,9 @@ js::FindIdentifierBase(JSContext *cx, JSObject *scopeChain, PropertyName *name) return obj; } JS_ASSERT_IF(obj->isScope(), pobj->getClass() == obj->getClass()); - JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, pobj, (Shape *) prop); + DebugOnly entry = + JS_PROPERTY_CACHE(cx).fill(cx, scopeChain, scopeIndex, pobj, (Shape *) prop); + JS_ASSERT(entry); return obj; } @@ -5379,6 +5384,8 @@ js_GetPropertyHelperInline(JSContext *cx, JSObject *obj, JSObject *receiver, jsi if (!CallJSPropertyOp(cx, obj->getClass()->getProperty, obj, id, vp)) return JS_FALSE; + PCMETER(getHow & JSGET_CACHE_RESULT && JS_PROPERTY_CACHE(cx).nofills++); + /* Record non-undefined values produced by the class getter hook. */ if (!vp->isUndefined()) AddTypePropertyId(cx, obj, id, *vp); @@ -5505,6 +5512,7 @@ js_GetMethod(JSContext *cx, JSObject *obj, jsid id, uintN getHow, Value *vp) #endif return GetPropertyHelper(cx, obj, id, getHow, vp); } + JS_ASSERT_IF(getHow & JSGET_CACHE_RESULT, obj->isDenseArray()); #if JS_HAS_XML_SUPPORT if (obj->isXML()) return js_GetXMLMethod(cx, obj, id, vp); @@ -5667,6 +5675,8 @@ js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, uintN defineHow, JS_ASSERT(shape->isDataDescriptor()); if (!shape->writable()) { + PCMETER((defineHow & JSDNP_CACHE_RESULT) && JS_PROPERTY_CACHE(cx).rofills++); + /* Error in strict mode code, warn with strict option, otherwise do nothing. */ if (strict) return obj->reportReadOnly(cx, id); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 4b36f6998ac..1cdb42580d7 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1822,7 +1822,7 @@ static const uintN RESOLVE_INFER = 0xffff; /* * If cacheResult is false, return JS_NO_PROP_CACHE_FILL on success. */ -extern bool +extern PropertyCacheEntry * FindPropertyHelper(JSContext *cx, PropertyName *name, bool cacheResult, bool global, JSObject **objp, JSObject **pobjp, JSProperty **propp); @@ -1855,9 +1855,9 @@ js_FindVariableScope(JSContext *cx, JSFunction **funp); * barrier, which is not needed when invoking a lambda that otherwise does not * leak its callee reference (via arguments.callee or its name). */ +const uintN JSGET_CACHE_RESULT = 1; // from a caching interpreter opcode const uintN JSGET_METHOD_BARRIER = 0; // get can leak joined function object -const uintN JSGET_NO_METHOD_BARRIER = 1; // call to joined function can't leak -const uintN JSGET_CACHE_RESULT = 2; // from a caching interpreter opcode +const uintN JSGET_NO_METHOD_BARRIER = 2; // call to joined function can't leak /* * NB: js_NativeGet and js_NativeSet are called with the scope containing shape @@ -1941,13 +1941,13 @@ js_IsDelegate(JSContext *cx, JSObject *obj, const js::Value &v); extern JSBool js_PrimitiveToObject(JSContext *cx, js::Value *vp); +/* + * v and vp may alias. On successful return, vp->isObjectOrNull(). If vp is not + * rooted, the caller must root vp before the next possible GC. + */ extern JSBool js_ValueToObjectOrNull(JSContext *cx, const js::Value &v, JSObject **objp); -/* Throws if v could not be converted to an object. */ -extern JSObject * -js_ValueToNonNullObject(JSContext *cx, const js::Value &v); - namespace js { /* @@ -1965,17 +1965,15 @@ ToObject(JSContext *cx, Value *vp) return ToObjectSlow(cx, vp); } -/* As for ToObject, but preserves the original value. */ -inline JSObject * -ValueToObject(JSContext *cx, const Value &v) -{ - if (v.isObject()) - return &v.toObject(); - return js_ValueToNonNullObject(cx, v); -} - } /* namespace js */ +/* + * v and vp may alias. On successful return, vp->isObject(). If vp is not + * rooted, the caller must root vp before the next possible GC. + */ +extern JSObject * +js_ValueToNonNullObject(JSContext *cx, const js::Value &v); + extern JSBool js_XDRObject(JSXDRState *xdr, JSObject **objp); diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index c372566ad9e..b6e6da6d00d 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -633,23 +633,6 @@ JSObject::denseArrayHasInlineSlots() const namespace js { -inline JSObject * -ValueToObjectOrPrototype(JSContext *cx, const Value &v) -{ - if (v.isObject()) - return &v.toObject(); - GlobalObject *global = &cx->fp()->scopeChain().global(); - if (v.isString()) - return global->getOrCreateStringPrototype(cx); - if (v.isNumber()) - return global->getOrCreateNumberPrototype(cx); - if (v.isBoolean()) - return global->getOrCreateBooleanPrototype(cx); - JS_ASSERT(v.isNull() || v.isUndefined()); - js_ReportIsNullOrUndefined(cx, JSDVG_SEARCH_STACK, v, NULL); - return NULL; -} - /* * Any name atom for a function which will be added as a DeclEnv object to the * scope chain above call objects for fun. diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index eb270c2c823..2c36f820ce7 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -2467,15 +2467,6 @@ InitSprintStack(JSContext *cx, SprintStack *ss, JSPrinter *jp, uintN depth) return JS_TRUE; } -template -void -Swap(T &a, T &b) -{ - T tmp = a; - a = b; - b = tmp; -} - /* * If nb is non-negative, decompile nb bytecodes starting at pc. Otherwise * the decompiler starts at pc and continues until it reaches an opcode for @@ -4217,13 +4208,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) todo = SprintCString(&ss->sprinter, rval); break; - case JSOP_SWAP: - Swap(ss->offsets[ss->top-1], ss->offsets[ss->top-2]); - Swap(ss->opcodes[ss->top-1], ss->opcodes[ss->top-2]); - Swap(ss->bytecodes[ss->top-1], ss->bytecodes[ss->top-2]); - todo = -2; - break; - case JSOP_SETARG: atom = GetArgOrVarAtom(jp, GET_ARGNO(pc)); LOCAL_ASSERT(atom); @@ -4305,8 +4289,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) op == JSOP_EVAL || op == JSOP_FUNCALL || op == JSOP_FUNAPPLY || - op == JSOP_CALLPROP || - op == JSOP_CALLELEM)) + (js_CodeSpec[op].format & JOF_CALLOP))) ? JSOP_NAME : saveop, &lvalpc); @@ -5394,6 +5377,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb) CopyDecompiledTextForDecomposedOp(jp, pc); } + if (cs->format & JOF_CALLOP) { + todo = Sprint(&ss->sprinter, ""); + if (todo < 0 || !PushOff(ss, todo, saveop)) + return NULL; + } + pc += len; } @@ -5819,16 +5808,6 @@ DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun, } default:; } - - /* - * Include the trailing SWAP when decompiling CALLPROP or CALLELEM ops, - * so that the result is the entire access rather than the lvalue. - */ - if (op == JSOP_CALLPROP || op == JSOP_CALLELEM) { - JS_ASSERT(*end == JSOP_SWAP); - end += JSOP_SWAP_LENGTH; - } - ptrdiff_t len = end - begin; if (len <= 0) return FAILED_EXPRESSION_DECOMPILER; diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 91bc5095093..a7d35165ec8 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -117,6 +117,8 @@ typedef enum JSOp { #define JOF_LEFTASSOC (1U<<16) /* left-associative operator */ #define JOF_DECLARING (1U<<17) /* var, const, or function declaration op */ #define JOF_INDEXBASE (1U<<18) /* atom segment base setting prefix op */ +#define JOF_CALLOP (1U<<19) /* call operation that pushes function and + this */ #define JOF_PARENHEAD (1U<<20) /* opcode consumes value of expression in parenthesized statement head */ #define JOF_INVOKE (1U<<21) /* JSOP_CALL, JSOP_NEW, JSOP_EVAL */ diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 3515ab1b5cc..af9047d1521 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -176,7 +176,7 @@ OPDEF(JSOP_GETPROP, 53, "getprop", NULL, 3, 1, 1, 18, JOF_ATOM|J OPDEF(JSOP_SETPROP, 54, "setprop", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) OPDEF(JSOP_GETELEM, 55, "getelem", NULL, 1, 2, 1, 18, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC) OPDEF(JSOP_SETELEM, 56, "setelem", NULL, 1, 3, 1, 3, JOF_BYTE |JOF_ELEM|JOF_SET|JOF_DETECTING) -OPDEF(JSOP_CALLNAME, 57, "callname", NULL, 3, 0, 1, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET) +OPDEF(JSOP_CALLNAME, 57, "callname", NULL, 3, 0, 2, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_CALLOP) OPDEF(JSOP_CALL, 58, "call", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) OPDEF(JSOP_NAME, 59, "name", NULL, 3, 0, 1, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET) OPDEF(JSOP_DOUBLE, 60, "double", NULL, 3, 0, 1, 16, JOF_ATOM) @@ -367,10 +367,11 @@ OPDEF(JSOP_FINALLY, 135,"finally", NULL, 1, 0, 2, 0, JOF_BYTE) /* * Get a slot from a flat closure function object that contains a snapshot of * the closure-invariant upvar values. The immediate operand indexes the upvar - * in the function's u.i.script->upvars() array. + * in the function's u.i.script->upvars() array. The CALL variant computes the + * callee and this-object in preparation for a JSOP_CALL. */ OPDEF(JSOP_GETFCSLOT, 136,"getfcslot", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME|JOF_TYPESET) -OPDEF(JSOP_CALLFCSLOT, 137,"callfcslot", NULL, 3, 0, 1, 19, JOF_UINT16|JOF_NAME|JOF_TYPESET) +OPDEF(JSOP_CALLFCSLOT, 137,"callfcslot", NULL, 3, 0, 2, 19, JOF_UINT16|JOF_NAME|JOF_TYPESET|JOF_CALLOP) /* * Define a local function object as a local variable. @@ -438,7 +439,7 @@ OPDEF(JSOP_XMLCOMMENT, 181,"xmlcomment", NULL, 3, 0, 1, 19, JOF_ATOM) OPDEF(JSOP_XMLPI, 182,"xmlpi", NULL, 3, 1, 1, 19, JOF_ATOM) OPDEF(JSOP_DELDESC, 183,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|JOF_ELEM|JOF_DEL) -OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_TMPSLOT3) +OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 3, 1, 2, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3) /* Enter a let block/expr whose slots are at the top of the stack. */ OPDEF(JSOP_ENTERLET0, 185,"enterlet0", NULL, 3, -1, -1, 0, JOF_OBJECT) @@ -467,7 +468,7 @@ OPDEF(JSOP_RESETBASE0, 190,"resetbase0", NULL, 1, 0, 0, 0, JOF_BYTE) OPDEF(JSOP_STARTXML, 191,"startxml", NULL, 1, 0, 0, 0, JOF_BYTE) OPDEF(JSOP_STARTXMLEXPR, 192,"startxmlexpr",NULL, 1, 0, 0, 0, JOF_BYTE) -OPDEF(JSOP_CALLELEM, 193, "callelem", NULL, 1, 2, 1, 18, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC) +OPDEF(JSOP_CALLELEM, 193, "callelem", NULL, 1, 2, 2, 18, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC|JOF_CALLOP) /* * Stop interpretation, emitted at end of script to save the threaded bytecode @@ -481,7 +482,7 @@ OPDEF(JSOP_STOP, 194,"stop", NULL, 1, 0, 0, 0, JOF_BYTE) */ OPDEF(JSOP_GETXPROP, 195,"getxprop", NULL, 3, 1, 1, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET) -OPDEF(JSOP_CALLXMLNAME, 196, "callxmlname", NULL, 1, 1, 2, 19, JOF_BYTE) +OPDEF(JSOP_CALLXMLNAME, 196, "callxmlname", NULL, 1, 1, 2, 19, JOF_BYTE|JOF_CALLOP) /* * Specialized JSOP_TYPEOF to avoid reporting undefined for typeof(0, undef). @@ -529,9 +530,9 @@ OPDEF(JSOP_INDEXBASE1, 208,"indexbase1", NULL, 1, 0, 0, 0, JOF_BYTE | OPDEF(JSOP_INDEXBASE2, 209,"indexbase2", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) OPDEF(JSOP_INDEXBASE3, 210,"indexbase3", NULL, 1, 0, 0, 0, JOF_BYTE |JOF_INDEXBASE) -OPDEF(JSOP_CALLGNAME, 211, "callgname", NULL, 3, 0, 1, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_GNAME) -OPDEF(JSOP_CALLLOCAL, 212, "calllocal", NULL, 3, 0, 1, 19, JOF_LOCAL|JOF_NAME) -OPDEF(JSOP_CALLARG, 213, "callarg", NULL, 3, 0, 1, 19, JOF_QARG |JOF_NAME) +OPDEF(JSOP_CALLGNAME, 211, "callgname", NULL, 3, 0, 2, 19, JOF_ATOM|JOF_NAME|JOF_TYPESET|JOF_CALLOP|JOF_GNAME) +OPDEF(JSOP_CALLLOCAL, 212, "calllocal", NULL, 3, 0, 2, 19, JOF_LOCAL|JOF_NAME|JOF_CALLOP) +OPDEF(JSOP_CALLARG, 213, "callarg", NULL, 3, 0, 2, 19, JOF_QARG |JOF_NAME|JOF_CALLOP) OPDEF(JSOP_BINDGNAME, 214, "bindgname", NULL, 3, 0, 1, 0, JOF_ATOM|JOF_NAME|JOF_SET|JOF_GNAME) /* @@ -569,6 +570,3 @@ OPDEF(JSOP_SHARPINIT, 224,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16 /* Pop the stack, convert to a jsid (int or string), and push back. */ OPDEF(JSOP_TOID, 225, "toid", NULL, 1, 1, 1, 0, JOF_BYTE) - -/* Push the implicit 'this' value for calls to the associated name. */ -OPDEF(JSOP_IMPLICITTHIS, 226, "implicitthis", "", 3, 0, 1, 0, JOF_ATOM) diff --git a/js/src/jsprvtd.h b/js/src/jsprvtd.h index eef713c4f09..21c368ff369 100644 --- a/js/src/jsprvtd.h +++ b/js/src/jsprvtd.h @@ -199,6 +199,9 @@ class InlineMap; class LifoAlloc; +class PropertyCache; +struct PropertyCacheEntry; + class BaseShape; class UnownedBaseShape; struct Shape; diff --git a/js/src/jsxdrapi.h b/js/src/jsxdrapi.h index d2a3444cf07..70c3ab8a7f9 100644 --- a/js/src/jsxdrapi.h +++ b/js/src/jsxdrapi.h @@ -226,7 +226,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32_t id); * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -#define JSXDR_BYTECODE_VERSION (0xb973c0de - 102) +#define JSXDR_BYTECODE_VERSION (0xb973c0de - 101) /* * Library-private functions. diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 6301e7aed30..088950627f1 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -1348,7 +1348,8 @@ mjit::Compiler::finishThisUp(JITScript **jitp) if (pics[i].kind == ic::PICInfo::SET || pics[i].kind == ic::PICInfo::SETMETHOD) { jitPics[i].u.vr = pics[i].vr; - } else if (pics[i].kind != ic::PICInfo::NAME) { + } else if (pics[i].kind != ic::PICInfo::NAME && + pics[i].kind != ic::PICInfo::CALLNAME) { if (pics[i].hasTypeCheck) { int32_t distance = stubcc.masm.distanceOf(pics[i].typeCheck) - stubcc.masm.distanceOf(pics[i].slowPathStart); @@ -2007,17 +2008,15 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_VOID) BEGIN_CASE(JSOP_GETPROP) - BEGIN_CASE(JSOP_CALLPROP) BEGIN_CASE(JSOP_LENGTH) if (!jsop_getprop(script->getName(fullAtomIndex(PC)), knownPushedType(0))) return Compile_Error; END_CASE(JSOP_GETPROP) BEGIN_CASE(JSOP_GETELEM) - BEGIN_CASE(JSOP_CALLELEM) if (script->pcCounters) updateElemCounters(PC, frame.peek(-2), frame.peek(-1)); - if (!jsop_getelem()) + if (!jsop_getelem(false)) return Compile_Error; END_CASE(JSOP_GETELEM) @@ -2084,22 +2083,20 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_CALL) BEGIN_CASE(JSOP_NAME) - BEGIN_CASE(JSOP_CALLNAME) { PropertyName *name = script->getName(fullAtomIndex(PC)); - jsop_name(name, knownPushedType(0)); + jsop_name(name, knownPushedType(0), false); frame.extra(frame.peek(-1)).name = name; } END_CASE(JSOP_NAME) - BEGIN_CASE(JSOP_IMPLICITTHIS) + BEGIN_CASE(JSOP_CALLNAME) { - prepareStubCall(Uses(0)); - masm.move(ImmPtr(script->getName(fullAtomIndex(PC))), Registers::ArgReg1); - INLINE_STUBCALL(stubs::ImplicitThis, REJOIN_FALLTHROUGH); - frame.pushSynced(JSVAL_TYPE_UNKNOWN); + PropertyName *name = script->getName(fullAtomIndex(PC)); + jsop_name(name, knownPushedType(0), true); + frame.extra(frame.peek(-2)).name = name; } - END_CASE(JSOP_IMPLICITTHIS) + END_CASE(JSOP_CALLNAME) BEGIN_CASE(JSOP_DOUBLE) { @@ -2248,6 +2245,13 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_POP) BEGIN_CASE(JSOP_GETARG) + { + restoreVarType(); + uint32_t arg = GET_SLOTNO(PC); + frame.pushArg(arg); + } + END_CASE(JSOP_GETARG) + BEGIN_CASE(JSOP_CALLARG) { restoreVarType(); @@ -2256,6 +2260,7 @@ mjit::Compiler::generateMethod() frame.push(ObjectValue(*singleton)); else frame.pushArg(arg); + frame.push(UndefinedValue()); } END_CASE(JSOP_GETARG) @@ -2279,7 +2284,6 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_SETARG) BEGIN_CASE(JSOP_GETLOCAL) - BEGIN_CASE(JSOP_CALLLOCAL) { /* * Update the var type unless we are about to pop the variable. @@ -2290,10 +2294,7 @@ mjit::Compiler::generateMethod() if (JSOp(*next) != JSOP_POP || analysis->jumpTarget(next)) restoreVarType(); uint32_t slot = GET_SLOTNO(PC); - if (JSObject *singleton = pushedSingleton(0)) - frame.push(ObjectValue(*singleton)); - else - frame.pushLocal(slot); + frame.pushLocal(slot); } END_CASE(JSOP_GETLOCAL) @@ -2401,14 +2402,14 @@ mjit::Compiler::generateMethod() END_CASE(JSOP_LOCALDEC) BEGIN_CASE(JSOP_BINDNAME) - jsop_bindname(script->getName(fullAtomIndex(PC))); + jsop_bindname(script->getName(fullAtomIndex(PC)), true); END_CASE(JSOP_BINDNAME) BEGIN_CASE(JSOP_SETPROP) { jsbytecode *next = &PC[JSOP_SETPROP_LENGTH]; bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); - if (!jsop_setprop(script->getName(fullAtomIndex(PC)), pop)) + if (!jsop_setprop(script->getName(fullAtomIndex(PC)), true, pop)) return Compile_Error; } END_CASE(JSOP_SETPROP) @@ -2418,7 +2419,7 @@ mjit::Compiler::generateMethod() { jsbytecode *next = &PC[JSOP_SETNAME_LENGTH]; bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); - if (!jsop_setprop(script->getName(fullAtomIndex(PC)), pop)) + if (!jsop_setprop(script->getName(fullAtomIndex(PC)), true, pop)) return Compile_Error; } END_CASE(JSOP_SETNAME) @@ -2601,6 +2602,9 @@ mjit::Compiler::generateMethod() BarrierState barrier = pushAddressMaybeBarrier(Address(reg, index * sizeof(Value)), knownPushedType(0), true); finishBarrier(barrier, REJOIN_GETTER, 0); + + if (op == JSOP_CALLFCSLOT) + frame.push(UndefinedValue()); } END_CASE(JSOP_CALLFCSLOT) @@ -2630,6 +2634,8 @@ mjit::Compiler::generateMethod() uint32_t index = fullAtomIndex(PC); jsop_getgname(index); frame.extra(frame.peek(-1)).name = script->getName(index); + if (op == JSOP_CALLGNAME) + jsop_callgname_epilogue(); } END_CASE(JSOP_GETGNAME) @@ -2637,7 +2643,7 @@ mjit::Compiler::generateMethod() { jsbytecode *next = &PC[JSOP_SETGNAME_LENGTH]; bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); - jsop_setgname(script->getName(fullAtomIndex(PC)), pop); + jsop_setgname(script->getName(fullAtomIndex(PC)), true, pop); } END_CASE(JSOP_SETGNAME) @@ -2655,10 +2661,21 @@ mjit::Compiler::generateMethod() } END_CASE(JSOP_OBJECT) + BEGIN_CASE(JSOP_CALLPROP) + if (!jsop_callprop(script->getName(fullAtomIndex(PC)))) + return Compile_Error; + END_CASE(JSOP_CALLPROP) + BEGIN_CASE(JSOP_UINT24) frame.push(Value(Int32Value((int32_t) GET_UINT24(PC)))); END_CASE(JSOP_UINT24) + BEGIN_CASE(JSOP_CALLELEM) + if (script->pcCounters) + updateElemCounters(PC, frame.peek(-2), frame.peek(-1)); + jsop_getelem(true); + END_CASE(JSOP_CALLELEM) + BEGIN_CASE(JSOP_STOP) if (script->pcCounters) updatePCCounters(PC, &codeStart, &countersUpdated); @@ -2681,6 +2698,18 @@ mjit::Compiler::generateMethod() leaveBlock(); END_CASE(JSOP_LEAVEBLOCK) + BEGIN_CASE(JSOP_CALLLOCAL) + { + restoreVarType(); + uint32_t slot = GET_SLOTNO(PC); + if (JSObject *singleton = pushedSingleton(0)) + frame.push(ObjectValue(*singleton)); + else + frame.pushLocal(slot); + frame.push(UndefinedValue()); + } + END_CASE(JSOP_CALLLOCAL) + BEGIN_CASE(JSOP_INT8) frame.push(Value(Int32Value(GET_INT8(PC)))); END_CASE(JSOP_INT8) @@ -4357,11 +4386,14 @@ mjit::Compiler::emitStubCmpOp(BoolStub stub, jsbytecode *target, JSOp fused) } void -mjit::Compiler::jsop_setprop_slow(PropertyName *name) +mjit::Compiler::jsop_setprop_slow(PropertyName *name, bool usePropCache) { prepareStubCall(Uses(2)); masm.move(ImmPtr(name), Registers::ArgReg1); - INLINE_STUBCALL(STRICT_VARIANT(stubs::SetName), REJOIN_FALLTHROUGH); + if (usePropCache) + INLINE_STUBCALL(STRICT_VARIANT(stubs::SetName), REJOIN_FALLTHROUGH); + else + INLINE_STUBCALL(STRICT_VARIANT(stubs::SetPropNoCache), REJOIN_FALLTHROUGH); JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH); frame.shimmy(1); if (script->pcCounters) @@ -4369,17 +4401,19 @@ mjit::Compiler::jsop_setprop_slow(PropertyName *name) } void -mjit::Compiler::jsop_getprop_slow(PropertyName *name, bool forPrototype) +mjit::Compiler::jsop_getprop_slow(PropertyName *name, bool usePropCache) { /* See ::jsop_getprop */ - RejoinState rejoin = forPrototype ? REJOIN_THIS_PROTOTYPE : REJOIN_GETTER; + RejoinState rejoin = usePropCache ? REJOIN_GETTER : REJOIN_THIS_PROTOTYPE; prepareStubCall(Uses(1)); - masm.move(ImmPtr(name), Registers::ArgReg1); - INLINE_STUBCALL(forPrototype ? stubs::GetPropNoCache : stubs::GetProp, rejoin); - - if (!forPrototype) + if (usePropCache) { + INLINE_STUBCALL(stubs::GetProp, rejoin); testPushedType(rejoin, -1, /* ool = */ false); + } else { + masm.move(ImmPtr(name), Registers::ArgReg1); + INLINE_STUBCALL(stubs::GetPropNoCache, rejoin); + } frame.pop(); frame.pushSynced(JSVAL_TYPE_UNKNOWN); @@ -4388,6 +4422,21 @@ mjit::Compiler::jsop_getprop_slow(PropertyName *name, bool forPrototype) bumpPropCounter(PC, OpcodeCounts::PROP_OTHER); } +bool +mjit::Compiler::jsop_callprop_slow(PropertyName *name) +{ + prepareStubCall(Uses(1)); + masm.move(ImmPtr(name), Registers::ArgReg1); + INLINE_STUBCALL(stubs::CallProp, REJOIN_FALLTHROUGH); + testPushedType(REJOIN_FALLTHROUGH, -1, /* ool = */ false); + frame.pop(); + pushSyncedEntry(0); + pushSyncedEntry(1); + if (script->pcCounters) + bumpPropCounter(PC, OpcodeCounts::PROP_OTHER); + return true; +} + #ifdef JS_MONOIC void mjit::Compiler::passMICAddress(GlobalNameICInfo &ic) @@ -4405,7 +4454,7 @@ mjit::Compiler::passICAddress(BaseICInfo *ic) bool mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, - bool doTypeCheck, bool forPrototype) + bool doTypeCheck, bool usePropCache) { FrameEntry *top = frame.peek(-1); @@ -4415,7 +4464,7 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, * fetching the 'this' value. */ RejoinState rejoin = REJOIN_GETTER; - if (forPrototype) { + if (!usePropCache) { JS_ASSERT(top->isType(JSVAL_TYPE_OBJECT) && name == cx->runtime->atomState.classPrototypeAtom); rejoin = REJOIN_THIS_PROTOTYPE; @@ -4441,8 +4490,15 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, return true; } - if (top->mightBeType(JSVAL_TYPE_OBJECT) && - JSOp(*PC) == JSOP_LENGTH && cx->typeInferenceEnabled() && + /* If the incoming type will never PIC, take slow path. */ + if (top->isNotType(JSVAL_TYPE_OBJECT)) { + jsop_getprop_slow(name, usePropCache); + return true; + } + + frame.forgetMismatchedObject(top); + + if (JSOp(*PC) == JSOP_LENGTH && cx->typeInferenceEnabled() && !hasTypeBarriers(PC) && knownPushedType(0) == JSVAL_TYPE_INT32) { /* Check if this is an array we can make a loop invariant entry for. */ if (loop && loop->generatingInvariants()) { @@ -4471,7 +4527,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, Jump notObject = frame.testObject(Assembler::NotEqual, top); stubcc.linkExit(notObject, Uses(1)); stubcc.leave(); - stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); OOL_STUBCALL(stubs::GetProp, rejoin); if (rejoin == REJOIN_GETTER) testPushedType(rejoin, -1); @@ -4499,7 +4554,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, Jump notObject = frame.testObject(Assembler::NotEqual, top); stubcc.linkExit(notObject, Uses(1)); stubcc.leave(); - stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); OOL_STUBCALL(stubs::GetProp, rejoin); if (rejoin == REJOIN_GETTER) testPushedType(rejoin, -1); @@ -4529,33 +4583,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, } } - /* If the access will definitely be fetching a particular value, nop it. */ - bool testObject; - JSObject *singleton = - (*PC == JSOP_GETPROP || *PC == JSOP_CALLPROP) ? pushedSingleton(0) : NULL; - if (singleton && singleton->isFunction() && !hasTypeBarriers(PC) && - testSingletonPropertyTypes(top, ATOM_TO_JSID(name), &testObject)) { - if (testObject) { - Jump notObject = frame.testObject(Assembler::NotEqual, top); - stubcc.linkExit(notObject, Uses(1)); - stubcc.leave(); - stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); - OOL_STUBCALL(stubs::GetProp, REJOIN_FALLTHROUGH); - testPushedType(REJOIN_FALLTHROUGH, -1); - } - - frame.pop(); - frame.push(ObjectValue(*singleton)); - - if (script->pcCounters && cx->typeInferenceEnabled()) - bumpPropCounter(PC, OpcodeCounts::PROP_STATIC); - - if (testObject) - stubcc.rejoin(Changes(1)); - - return true; - } - /* Check if this is a property access we can make a loop invariant entry for. */ if (loop && loop->generatingInvariants() && !hasTypeBarriers(PC)) { CrossSSAValue topv(a->inlineIndex, analysis->poppedValue(PC, 0)); @@ -4570,14 +4597,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, } } - /* If the incoming type will never PIC, take slow path. */ - if (top->isNotType(JSVAL_TYPE_OBJECT)) { - jsop_getprop_slow(name, forPrototype); - return true; - } - - frame.forgetMismatchedObject(top); - /* * Check if we are accessing a known type which always has the property * in a particular inline slot. Get the property directly in this case, @@ -4590,7 +4609,7 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, types->getTypeObject(0) != NULL && !types->getTypeObject(0)->unknownProperties() && id == types::MakeTypeId(cx, id)) { - JS_ASSERT(!forPrototype); + JS_ASSERT(usePropCache); types::TypeObject *object = types->getTypeObject(0); types::TypeSet *propertyTypes = object->getProperty(cx, id, false); if (!propertyTypes) @@ -4604,7 +4623,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, Jump notObject = frame.testObject(Assembler::NotEqual, top); stubcc.linkExit(notObject, Uses(1)); stubcc.leave(); - stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); OOL_STUBCALL(stubs::GetProp, rejoin); if (rejoin == REJOIN_GETTER) testPushedType(rejoin, -1); @@ -4625,12 +4643,6 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, } } - /* Check for a dynamic dispatch. */ - if (cx->typeInferenceEnabled()) { - if (*PC == JSOP_CALLPROP && jsop_getprop_dispatch(name)) - return true; - } - if (script->pcCounters) bumpPropCounter(PC, OpcodeCounts::PROP_OTHER); @@ -4639,12 +4651,16 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, * wants to read it, and the shapeReg because it could cause a spill that * the string path wouldn't sink back. */ - RegisterID objReg = frame.copyDataIntoReg(top); - RegisterID shapeReg = frame.allocReg(); + RegisterID objReg = Registers::ReturnReg; + RegisterID shapeReg = Registers::ReturnReg; + if (name == cx->runtime->atomState.lengthAtom) { + objReg = frame.copyDataIntoReg(top); + shapeReg = frame.allocReg(); + } RESERVE_IC_SPACE(masm); - PICGenInfo pic(ic::PICInfo::GET, JSOp(*PC)); + PICGenInfo pic(ic::PICInfo::GET, JSOp(*PC), usePropCache); /* Guard that the type is an object. */ Label typeCheck; @@ -4666,6 +4682,11 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, pic.typeReg = Registers::ReturnReg; } + if (name != cx->runtime->atomState.lengthAtom) { + objReg = frame.copyDataIntoReg(top); + shapeReg = frame.allocReg(); + } + /* * If this access has been on a shape with a getter hook, make preparations * so that we can generate a stub to call the hook directly (rather than be @@ -4675,7 +4696,7 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, * reflect properties with getter hooks). */ pic.canCallHook = pic.forcedTypeBarrier = - !forPrototype && + usePropCache && JSOp(*PC) == JSOP_GETPROP && name != cx->runtime->atomState.lengthAtom && analysis->getCode(PC).accessGetter; @@ -4699,7 +4720,7 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, stubcc.leave(); passICAddress(&pic); - pic.slowPathCall = OOL_STUBCALL(forPrototype ? ic::GetPropNoCache : ic::GetProp, rejoin); + pic.slowPathCall = OOL_STUBCALL(usePropCache ? ic::GetProp : ic::GetPropNoCache, rejoin); CHECK_OOL_SPACE(); if (rejoin == REJOIN_GETTER) testPushedType(rejoin, -1); @@ -4741,6 +4762,277 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, return true; } +bool +mjit::Compiler::jsop_callprop_generic(PropertyName *name) +{ + FrameEntry *top = frame.peek(-1); + + if (script->pcCounters) + bumpPropCounter(PC, OpcodeCounts::PROP_OTHER); + + /* + * These two must be loaded first. The objReg because the string path + * wants to read it, and the shapeReg because it could cause a spill that + * the string path wouldn't sink back. + */ + RegisterID objReg = frame.copyDataIntoReg(top); + RegisterID shapeReg = frame.allocReg(); + + PICGenInfo pic(ic::PICInfo::CALL, JSOp(*PC), true); + + pic.pc = PC; + + /* Guard that the type is an object. */ + pic.typeReg = frame.copyTypeIntoReg(top); + + pic.canCallHook = pic.forcedTypeBarrier = analysis->getCode(PC).accessGetter; + if (pic.canCallHook) + frame.syncAndKillEverything(); + + RESERVE_IC_SPACE(masm); + + /* Start the hot path where it's easy to patch it. */ + pic.fastPathStart = masm.label(); + + /* + * Guard that the value is an object. This part needs some extra gunk + * because the leave() after the shape guard will emit a jump from this + * path to the final call. We need a label in between that jump, which + * will be the target of patched jumps in the PIC. + */ + Jump typeCheckJump = masm.testObject(Assembler::NotEqual, pic.typeReg); + Label typeCheck = masm.label(); + RETURN_IF_OOM(false); + + pic.typeCheck = stubcc.linkExit(typeCheckJump, Uses(1)); + pic.hasTypeCheck = true; + pic.objReg = objReg; + pic.shapeReg = shapeReg; + pic.name = name; + + /* + * Store the type and object back. Don't bother keeping them in registers, + * since a sync will be needed for the upcoming call. + */ + uint32_t thisvSlot = frame.totalDepth(); + Address thisv = Address(JSFrameReg, sizeof(StackFrame) + thisvSlot * sizeof(Value)); + +#if defined JS_NUNBOX32 + masm.storeValueFromComponents(pic.typeReg, pic.objReg, thisv); +#elif defined JS_PUNBOX64 + masm.orPtr(pic.objReg, pic.typeReg); + masm.storePtr(pic.typeReg, thisv); +#endif + + frame.freeReg(pic.typeReg); + + /* Guard on shape. */ + masm.loadShape(objReg, shapeReg); + pic.shapeGuard = masm.label(); + + DataLabelPtr inlineShapeLabel; + Jump j = masm.branchPtrWithPatch(Assembler::NotEqual, shapeReg, + inlineShapeLabel, ImmPtr(NULL)); + Label inlineShapeJump = masm.label(); + + /* Slow path. */ + RESERVE_OOL_SPACE(stubcc.masm); + pic.slowPathStart = stubcc.linkExit(j, Uses(1)); + stubcc.leave(); + passICAddress(&pic); + pic.slowPathCall = OOL_STUBCALL(ic::CallProp, REJOIN_FALLTHROUGH); + CHECK_OOL_SPACE(); + + testPushedType(REJOIN_FALLTHROUGH, -1); + + /* Load the base slot address. */ + Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, JSObject::offsetOfSlots()), + objReg); + + /* Copy the slot value to the expression stack. */ + Address slot(objReg, 1 << 24); + + Label fastValueLoad = masm.loadValueWithAddressOffsetPatch(slot, shapeReg, objReg); + pic.fastPathRejoin = masm.label(); + + RETURN_IF_OOM(false); + + /* + * Initialize op labels. We use GetPropLabels here because we have the same patching + * requirements for CallProp. + */ + GetPropLabels &labels = pic.getPropLabels(); + labels.setDslotsLoadOffset(masm.differenceBetween(pic.fastPathRejoin, dslotsLoadLabel)); + labels.setInlineShapeOffset(masm.differenceBetween(pic.shapeGuard, inlineShapeLabel)); + labels.setValueLoad(masm, pic.fastPathRejoin, fastValueLoad); + labels.setInlineTypeJump(masm, pic.fastPathStart, typeCheck); + labels.setInlineShapeJump(masm, pic.shapeGuard, inlineShapeJump); + + CHECK_IC_SPACE(); + + /* Adjust the frame. */ + frame.pop(); + frame.pushRegs(shapeReg, objReg, knownPushedType(0)); + BarrierState barrier = testBarrier(pic.shapeReg, pic.objReg, false, false, + /* force = */ pic.canCallHook); + + pushSyncedEntry(1); + + stubcc.rejoin(Changes(2)); + pics.append(pic); + + finishBarrier(barrier, REJOIN_FALLTHROUGH, 1); + return true; +} + +bool +mjit::Compiler::jsop_callprop_str(PropertyName *name) +{ + if (!globalObj) { + jsop_callprop_slow(name); + return true; + } + + /* Bake in String.prototype. This is safe because of compileAndGo. */ + JSObject *obj = globalObj->getOrCreateStringPrototype(cx); + if (!obj) + return false; + + /* Force into a register because getprop won't expect a constant. */ + RegisterID reg = frame.allocReg(); + + masm.move(ImmPtr(obj), reg); + frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg); + + /* Get the property. */ + if (!jsop_getprop(name, knownPushedType(0))) + return false; + + /* Perform a swap. */ + frame.dup2(); + frame.shift(-3); + frame.shift(-1); + + /* + * See bug 584579 - need to forget string type, since wrapping could + * create an object. forgetType() alone is not valid because it cannot be + * used on copies or constants. + */ + RegisterID strReg; + FrameEntry *strFe = frame.peek(-1); + if (strFe->isConstant()) { + strReg = frame.allocReg(); + masm.move(ImmPtr(strFe->getValue().toString()), strReg); + } else { + strReg = frame.ownRegForData(strFe); + } + frame.pop(); + frame.pushTypedPayload(JSVAL_TYPE_STRING, strReg); + frame.forgetType(frame.peek(-1)); + + return true; +} + +bool +mjit::Compiler::jsop_callprop_obj(PropertyName *name) +{ + FrameEntry *top = frame.peek(-1); + + if (script->pcCounters) + bumpPropCounter(PC, OpcodeCounts::PROP_OTHER); + + PICGenInfo pic(ic::PICInfo::CALL, JSOp(*PC), true); + + JS_ASSERT(top->isTypeKnown()); + JS_ASSERT(top->getKnownType() == JSVAL_TYPE_OBJECT); + + RESERVE_IC_SPACE(masm); + + pic.pc = PC; + pic.fastPathStart = masm.label(); + pic.hasTypeCheck = false; + pic.typeReg = Registers::ReturnReg; + + RegisterID shapeReg = frame.allocReg(); + pic.shapeReg = shapeReg; + pic.name = name; + + RegisterID objReg; + if (top->isConstant()) { + objReg = frame.allocReg(); + masm.move(ImmPtr(&top->getValue().toObject()), objReg); + } else { + objReg = frame.copyDataIntoReg(top); + } + + pic.canCallHook = pic.forcedTypeBarrier = analysis->getCode(PC).accessGetter; + if (pic.canCallHook) + frame.syncAndKillEverything(); + + /* Guard on shape. */ + masm.loadShape(objReg, shapeReg); + pic.shapeGuard = masm.label(); + + DataLabelPtr inlineShapeLabel; + Jump j = masm.branchPtrWithPatch(Assembler::NotEqual, shapeReg, + inlineShapeLabel, ImmPtr(NULL)); + Label inlineShapeJump = masm.label(); + + /* Slow path. */ + RESERVE_OOL_SPACE(stubcc.masm); + pic.slowPathStart = stubcc.linkExit(j, Uses(1)); + stubcc.leave(); + passICAddress(&pic); + pic.slowPathCall = OOL_STUBCALL(ic::CallProp, REJOIN_FALLTHROUGH); + CHECK_OOL_SPACE(); + + testPushedType(REJOIN_FALLTHROUGH, -1); + + /* Load the base slot address. */ + Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, JSObject::offsetOfSlots()), + objReg); + + /* Copy the slot value to the expression stack. */ + Address slot(objReg, 1 << 24); + + Label fastValueLoad = masm.loadValueWithAddressOffsetPatch(slot, shapeReg, objReg); + + pic.fastPathRejoin = masm.label(); + pic.objReg = objReg; + + CHECK_IC_SPACE(); + + /* + * 1) Dup the |this| object. + * 2) Store the property value below the |this| value. + * This is safe as a stack transition, because JSOP_CALLPROP has + * JOF_TMPSLOT. It is also safe for correctness, because if we know the LHS + * is an object, it is the resulting vp[1]. + */ + frame.dup(); + frame.storeRegs(-2, shapeReg, objReg, knownPushedType(0)); + BarrierState barrier = testBarrier(shapeReg, objReg, false, false, + /* force = */ pic.canCallHook); + + /* + * Assert correctness of hardcoded offsets. + * No type guard: type is asserted. + */ + RETURN_IF_OOM(false); + + GetPropLabels &labels = pic.getPropLabels(); + labels.setDslotsLoadOffset(masm.differenceBetween(pic.fastPathRejoin, dslotsLoadLabel)); + labels.setInlineShapeOffset(masm.differenceBetween(pic.shapeGuard, inlineShapeLabel)); + labels.setValueLoad(masm, pic.fastPathRejoin, fastValueLoad); + labels.setInlineShapeJump(masm, pic.shapeGuard, inlineShapeJump); + + stubcc.rejoin(Changes(2)); + pics.append(pic); + + finishBarrier(barrier, REJOIN_FALLTHROUGH, 1); + return true; +} + bool mjit::Compiler::testSingletonProperty(JSObject *obj, jsid id) { @@ -4847,7 +5139,7 @@ mjit::Compiler::testSingletonPropertyTypes(FrameEntry *top, jsid id, bool *testO } bool -mjit::Compiler::jsop_getprop_dispatch(PropertyName *name) +mjit::Compiler::jsop_callprop_dispatch(PropertyName *name) { /* * Check for a CALLPROP which is a dynamic dispatch: every value it can @@ -4982,11 +5274,17 @@ mjit::Compiler::jsop_getprop_dispatch(PropertyName *name) stubcc.leave(); stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); - OOL_STUBCALL(stubs::GetProp, REJOIN_FALLTHROUGH); + OOL_STUBCALL(stubs::CallProp, REJOIN_FALLTHROUGH); testPushedType(REJOIN_FALLTHROUGH, -1); - frame.pop(); + frame.dup(); + // THIS THIS + frame.pushTypedPayload(JSVAL_TYPE_OBJECT, pushreg); + // THIS THIS FUN + + frame.shift(-2); + // FUN THIS if (script->pcCounters) bumpPropCounter(PC, OpcodeCounts::PROP_DEFINITE); @@ -4996,14 +5294,71 @@ mjit::Compiler::jsop_getprop_dispatch(PropertyName *name) } bool -mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) +mjit::Compiler::jsop_callprop(PropertyName *name) +{ + FrameEntry *top = frame.peek(-1); + + /* If the CALLPROP will definitely be fetching a particular value, nop it. */ + bool testObject; + JSObject *singleton = pushedSingleton(0); + if (singleton && singleton->isFunction() && !hasTypeBarriers(PC) && + testSingletonPropertyTypes(top, ATOM_TO_JSID(name), &testObject)) { + if (testObject) { + Jump notObject = frame.testObject(Assembler::NotEqual, top); + stubcc.linkExit(notObject, Uses(1)); + stubcc.leave(); + stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); + OOL_STUBCALL(stubs::CallProp, REJOIN_FALLTHROUGH); + testPushedType(REJOIN_FALLTHROUGH, -1); + } + + // THIS + + frame.dup(); + // THIS THIS + + frame.push(ObjectValue(*singleton)); + // THIS THIS FUN + + frame.shift(-2); + // FUN THIS + + if (script->pcCounters && cx->typeInferenceEnabled()) + bumpPropCounter(PC, OpcodeCounts::PROP_STATIC); + + if (testObject) + stubcc.rejoin(Changes(2)); + + return true; + } + + /* Check for a dynamic dispatch. */ + if (cx->typeInferenceEnabled()) { + if (jsop_callprop_dispatch(name)) + return true; + } + + /* If the incoming type will never PIC, take slow path. */ + if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_OBJECT) { + if (top->getKnownType() == JSVAL_TYPE_STRING) + return jsop_callprop_str(name); + return jsop_callprop_slow(name); + } + + if (top->isTypeKnown()) + return jsop_callprop_obj(name); + return jsop_callprop_generic(name); +} + +bool +mjit::Compiler::jsop_setprop(PropertyName *name, bool usePropCache, bool popGuaranteed) { FrameEntry *lhs = frame.peek(-2); FrameEntry *rhs = frame.peek(-1); /* If the incoming type will never PIC, take slow path. */ if (lhs->isTypeKnown() && lhs->getKnownType() != JSVAL_TYPE_OBJECT) { - jsop_setprop_slow(name); + jsop_setprop_slow(name, usePropCache); return true; } @@ -5048,6 +5403,7 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) types->getObjectCount() == 1 && types->getTypeObject(0) != NULL && !types->getTypeObject(0)->unknownProperties()) { + JS_ASSERT(usePropCache); types::TypeObject *object = types->getTypeObject(0); types::TypeSet *propertyTypes = object->getProperty(cx, id, false); if (!propertyTypes) @@ -5097,7 +5453,7 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) #ifdef JSGC_INCREMENTAL_MJ /* Write barrier. */ if (cx->compartment->needsBarrier() && (!types || types->propertyNeedsBarrier(cx, id))) { - jsop_setprop_slow(name); + jsop_setprop_slow(name, usePropCache); return true; } #endif @@ -5107,7 +5463,7 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) ic::PICInfo::Kind kind = (op == JSOP_SETMETHOD) ? ic::PICInfo::SETMETHOD : ic::PICInfo::SET; - PICGenInfo pic(kind, op); + PICGenInfo pic(kind, op, usePropCache); pic.name = name; if (monitored(PC)) { @@ -5143,8 +5499,10 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) stubcc.leave(); stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); - OOL_STUBCALL(STRICT_VARIANT(stubs::SetName), REJOIN_FALLTHROUGH); - + if (usePropCache) + OOL_STUBCALL(STRICT_VARIANT(stubs::SetName), REJOIN_FALLTHROUGH); + else + OOL_STUBCALL(STRICT_VARIANT(stubs::SetPropNoCache), REJOIN_FALLTHROUGH); typeCheck = stubcc.masm.jump(); pic.hasTypeCheck = true; } else { @@ -5222,7 +5580,7 @@ mjit::Compiler::jsop_setprop(PropertyName *name, bool popGuaranteed) } void -mjit::Compiler::jsop_name(PropertyName *name, JSValueType type) +mjit::Compiler::jsop_name(PropertyName *name, JSValueType type, bool isCall) { /* * If this is a NAME for a variable of a non-reentrant outer function, get @@ -5238,11 +5596,13 @@ mjit::Compiler::jsop_name(PropertyName *name, JSValueType type) BarrierState barrier = pushAddressMaybeBarrier(address, type, true, /* testUndefined = */ true); finishBarrier(barrier, REJOIN_GETTER, 0); + if (isCall) + jsop_callgname_epilogue(); return; } } - PICGenInfo pic(ic::PICInfo::NAME, JSOp(*PC)); + PICGenInfo pic(isCall ? ic::PICInfo::CALLNAME : ic::PICInfo::NAME, JSOp(*PC), true); RESERVE_IC_SPACE(masm); @@ -5253,6 +5613,8 @@ mjit::Compiler::jsop_name(PropertyName *name, JSValueType type) pic.hasTypeCheck = false; pic.fastPathStart = masm.label(); + RejoinState rejoin = isCall ? REJOIN_FALLTHROUGH : REJOIN_GETTER; + /* There is no inline implementation, so we always jump to the slow path or to a stub. */ pic.shapeGuard = masm.label(); Jump inlineJump = masm.jump(); @@ -5261,9 +5623,9 @@ mjit::Compiler::jsop_name(PropertyName *name, JSValueType type) pic.slowPathStart = stubcc.linkExit(inlineJump, Uses(0)); stubcc.leave(); passICAddress(&pic); - pic.slowPathCall = OOL_STUBCALL(ic::Name, REJOIN_GETTER); + pic.slowPathCall = OOL_STUBCALL(isCall ? ic::CallName : ic::Name, rejoin); CHECK_OOL_SPACE(); - testPushedType(REJOIN_GETTER, 0); + testPushedType(rejoin, 0); } pic.fastPathRejoin = masm.label(); @@ -5286,13 +5648,15 @@ mjit::Compiler::jsop_name(PropertyName *name, JSValueType type) } else { frame.pushRegs(pic.shapeReg, pic.objReg, type); } + if (isCall) + frame.pushSynced(JSVAL_TYPE_UNKNOWN); BarrierState barrier = testBarrier(pic.shapeReg, pic.objReg, /* testUndefined = */ true); - stubcc.rejoin(Changes(1)); + stubcc.rejoin(Changes(isCall ? 2 : 1)); pics.append(pic); - finishBarrier(barrier, REJOIN_GETTER, 0); + finishBarrier(barrier, rejoin, isCall ? 1 : 0); } bool @@ -5316,7 +5680,7 @@ mjit::Compiler::jsop_xname(PropertyName *name) } } - PICGenInfo pic(ic::PICInfo::XNAME, JSOp(*PC)); + PICGenInfo pic(ic::PICInfo::XNAME, JSOp(*PC), true); FrameEntry *fe = frame.peek(-1); if (fe->isNotType(JSVAL_TYPE_OBJECT)) { @@ -5376,7 +5740,7 @@ mjit::Compiler::jsop_xname(PropertyName *name) } void -mjit::Compiler::jsop_bindname(PropertyName *name) +mjit::Compiler::jsop_bindname(PropertyName *name, bool usePropCache) { /* * If this is a BINDNAME for a variable of a non-reentrant outer function, @@ -5395,7 +5759,7 @@ mjit::Compiler::jsop_bindname(PropertyName *name) } } - PICGenInfo pic(ic::PICInfo::BIND, JSOp(*PC)); + PICGenInfo pic(ic::PICInfo::BIND, JSOp(*PC), usePropCache); // This code does not check the frame flags to see if scopeChain has been // set. Rather, it relies on the up-front analysis statically determining @@ -5463,21 +5827,27 @@ mjit::Compiler::jsop_xname(PropertyName *name) bool mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType, types::TypeSet *typeSet, - bool typecheck, bool forPrototype) + bool typecheck, bool usePropCache) { - jsop_getprop_slow(name, forPrototype); + jsop_getprop_slow(name, usePropCache); return true; } bool -mjit::Compiler::jsop_setprop(PropertyName *name) +mjit::Compiler::jsop_callprop(PropertyName *name) { - jsop_setprop_slow(name); + return jsop_callprop_slow(name); +} + +bool +mjit::Compiler::jsop_setprop(PropertyName *name, bool usePropCache) +{ + jsop_setprop_slow(name, usePropCache); return true; } void -mjit::Compiler::jsop_bindname(PropertyName *name) +mjit::Compiler::jsop_bindname(PropertyName *name, bool usePropCache) { RegisterID reg = frame.allocReg(); Address scopeChain(JSFrameReg, StackFrame::offsetOfScopeChain()); @@ -5489,8 +5859,12 @@ mjit::Compiler::jsop_bindname(PropertyName *name) stubcc.linkExit(j, Uses(0)); stubcc.leave(); - stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); - OOL_STUBCALL(stubs::BindName, REJOIN_FALLTHROUGH); + if (usePropCache) { + OOL_STUBCALL(stubs::BindName, REJOIN_FALLTHROUGH); + } else { + stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); + OOL_STUBCALL(stubs::BindNameNoCache, REJOIN_FALLTHROUGH); + } frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg); @@ -5823,7 +6197,7 @@ void mjit::Compiler::jsop_getgname_slow(uint32_t index) { prepareStubCall(Uses(0)); - INLINE_STUBCALL(stubs::Name, REJOIN_GETTER); + INLINE_STUBCALL(stubs::GetGlobalName, REJOIN_GETTER); testPushedType(REJOIN_GETTER, 0, /* ool = */ false); frame.pushSynced(JSVAL_TYPE_UNKNOWN); } @@ -5908,6 +6282,8 @@ mjit::Compiler::jsop_getgname(uint32_t index) RegisterID objReg; Jump shapeGuard; + ic.usePropertyCache = true; + ic.fastPathStart = masm.label(); if (fe->isConstant()) { JSObject *obj = &fe->getValue().toObject(); @@ -5973,22 +6349,125 @@ mjit::Compiler::jsop_getgname(uint32_t index) } +/* + * Generate just the epilogue code that is specific to callgname. The rest + * is shared with getgname. + */ void -mjit::Compiler::jsop_setgname_slow(PropertyName *name) +mjit::Compiler::jsop_callgname_epilogue() +{ + /* + * This slow path does the same thing as the interpreter. + */ + if (!globalObj) { + prepareStubCall(Uses(1)); + INLINE_STUBCALL(stubs::PushImplicitThisForGlobal, REJOIN_NONE); + frame.pushSynced(JSVAL_TYPE_UNKNOWN); + return; + } + + /* Fast path for known-not-an-object callee. */ + FrameEntry *fval = frame.peek(-1); + if (fval->isNotType(JSVAL_TYPE_OBJECT)) { + frame.push(UndefinedValue()); + return; + } + + /* Paths for known object callee. */ + if (fval->isConstant()) { + JSObject *obj = &fval->getValue().toObject(); + if (&obj->global() == globalObj) { + frame.push(UndefinedValue()); + } else { + prepareStubCall(Uses(1)); + INLINE_STUBCALL(stubs::PushImplicitThisForGlobal, REJOIN_NONE); + frame.pushSynced(JSVAL_TYPE_UNKNOWN); + } + return; + } + + /* + * Fast path for functions whose global is statically known to be the + * current global. This is primarily for calls on inner functions within + * nestings, whose direct parent is a call object rather than the global + * and which will make a stub call in the path below. + */ + if (cx->typeInferenceEnabled()) { + types::TypeSet *types = analysis->pushedTypes(PC, 0); + if (types->hasGlobalObject(cx, globalObj)) { + frame.push(UndefinedValue()); + return; + } + } + + /* + * Optimized version. This inlines the common case, calling a + * (non-proxied) function that has the same global as the current + * script. To make the code simpler, we: + * 1. test the stronger property that the callee's parent is + * equal to the global of the current script, and + * 2. bake in the global of the current script, which is why + * this optimized path requires compile-and-go. + */ + + /* If the callee is not an object, jump to the inline fast path. */ + MaybeRegisterID typeReg = frame.maybePinType(fval); + RegisterID objReg = frame.copyDataIntoReg(fval); + RegisterID tempReg = frame.allocReg(); + + MaybeJump isNotObj; + if (!fval->isType(JSVAL_TYPE_OBJECT)) { + isNotObj = frame.testObject(Assembler::NotEqual, fval); + frame.maybeUnpinReg(typeReg); + } + + /* + * If the callee is not a function, jump to OOL slow path. + */ + Jump notFunction = masm.testFunction(Assembler::NotEqual, objReg, tempReg); + stubcc.linkExit(notFunction, Uses(1)); + + /* + * If the callee's parent is not equal to the global, jump to + * OOL slow path. + */ + masm.loadPtr(Address(objReg, JSFunction::offsetOfEnvironment()), objReg); + Jump globalMismatch = masm.branchPtr(Assembler::NotEqual, objReg, ImmPtr(globalObj)); + stubcc.linkExit(globalMismatch, Uses(1)); + frame.freeReg(objReg); + frame.freeReg(tempReg); + + /* OOL stub call path. */ + stubcc.leave(); + OOL_STUBCALL(stubs::PushImplicitThisForGlobal, REJOIN_NONE); + + /* Fast path. */ + if (isNotObj.isSet()) + isNotObj.getJump().linkTo(masm.label(), &masm); + frame.pushUntypedValue(UndefinedValue()); + + stubcc.rejoin(Changes(1)); +} + +void +mjit::Compiler::jsop_setgname_slow(PropertyName *name, bool usePropertyCache) { prepareStubCall(Uses(2)); masm.move(ImmPtr(name), Registers::ArgReg1); - INLINE_STUBCALL(STRICT_VARIANT(stubs::SetGlobalName), REJOIN_FALLTHROUGH); + if (usePropertyCache) + INLINE_STUBCALL(STRICT_VARIANT(stubs::SetGlobalName), REJOIN_FALLTHROUGH); + else + INLINE_STUBCALL(STRICT_VARIANT(stubs::SetGlobalNameNoCache), REJOIN_FALLTHROUGH); frame.popn(2); pushSyncedEntry(0); } void -mjit::Compiler::jsop_setgname(PropertyName *name, bool popGuaranteed) +mjit::Compiler::jsop_setgname(PropertyName *name, bool usePropertyCache, bool popGuaranteed) { if (monitored(PC)) { /* Global accesses are monitored only for a few names like __proto__. */ - jsop_setgname_slow(name); + jsop_setgname_slow(name, usePropertyCache); return; } @@ -6032,7 +6511,7 @@ mjit::Compiler::jsop_setgname(PropertyName *name, bool popGuaranteed) #ifdef JSGC_INCREMENTAL_MJ /* Write barrier. */ if (cx->compartment->needsBarrier()) { - jsop_setgname_slow(name); + jsop_setgname_slow(name, usePropertyCache); return; } #endif @@ -6085,6 +6564,8 @@ mjit::Compiler::jsop_setgname(PropertyName *name, bool popGuaranteed) /* Garbage value. */ uint32_t slot = 1 << 24; + ic.usePropertyCache = usePropertyCache; + masm.loadPtr(Address(ic.objReg, JSObject::offsetOfSlots()), ic.objReg); Address address(ic.objReg, slot); @@ -6106,7 +6587,7 @@ mjit::Compiler::jsop_setgname(PropertyName *name, bool popGuaranteed) ic.fastPathRejoin = masm.label(); setGlobalNames.append(ic); #else - jsop_setgname_slow(name); + jsop_setgname_slow(name, usePropertyCache); #endif } @@ -6177,7 +6658,7 @@ mjit::Compiler::jsop_instanceof() /* This is sadly necessary because the error case needs the object. */ frame.dup(); - if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, JSVAL_TYPE_UNKNOWN)) + if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, JSVAL_TYPE_UNKNOWN, false)) return false; /* Primitive prototypes are invalid. */ @@ -6755,7 +7236,7 @@ mjit::Compiler::constructThis() frame.pushCallee(); // Get callee.prototype. - if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, JSVAL_TYPE_UNKNOWN, false, /* forPrototype = */ true)) + if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, JSVAL_TYPE_UNKNOWN, false, false)) return false; // Reach into the proto Value and grab a register for its data. @@ -6872,6 +7353,17 @@ mjit::Compiler::jsop_tableswitch(jsbytecode *pc) #endif } +void +mjit::Compiler::jsop_callelem_slow() +{ + prepareStubCall(Uses(2)); + INLINE_STUBCALL(stubs::CallElem, REJOIN_FALLTHROUGH); + testPushedType(REJOIN_FALLTHROUGH, -2, /* ool = */ false); + frame.popn(2); + pushSyncedEntry(0); + pushSyncedEntry(1); +} + void mjit::Compiler::jsop_toid() { diff --git a/js/src/methodjit/Compiler.h b/js/src/methodjit/Compiler.h index 601ccb1f82a..3a4b5740793 100644 --- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -103,6 +103,7 @@ class Compiler : public BaseCompiler Call slowPathCall; DataLabelPtr shape; DataLabelPtr addrLabel; + bool usePropertyCache; void copyTo(ic::GlobalNameIC &to, JSC::LinkBuffer &full, JSC::LinkBuffer &stub) { to.fastPathStart = full.locationOf(fastPathStart); @@ -112,6 +113,7 @@ class Compiler : public BaseCompiler JS_ASSERT(to.shapeOffset == offset); to.slowPathCall = stub.locationOf(slowPathCall); + to.usePropertyCache = usePropertyCache; } }; @@ -235,14 +237,15 @@ class Compiler : public BaseCompiler }; struct PICGenInfo : public BaseICInfo { - PICGenInfo(ic::PICInfo::Kind kind, JSOp op) - : BaseICInfo(op), kind(kind), typeMonitored(false) + PICGenInfo(ic::PICInfo::Kind kind, JSOp op, bool usePropCache) + : BaseICInfo(op), kind(kind), usePropCache(usePropCache), typeMonitored(false) { } ic::PICInfo::Kind kind; Label typeCheck; RegisterID shapeReg; RegisterID objReg; RegisterID typeReg; + bool usePropCache; Label shapeGuard; jsbytecode *pc; PropertyName *name; @@ -258,7 +261,7 @@ class Compiler : public BaseCompiler }; ic::GetPropLabels &getPropLabels() { - JS_ASSERT(kind == ic::PICInfo::GET); + JS_ASSERT(kind == ic::PICInfo::GET || kind == ic::PICInfo::CALL); return getPropLabels_; } ic::SetPropLabels &setPropLabels() { @@ -270,7 +273,7 @@ class Compiler : public BaseCompiler return bindNameLabels_; } ic::ScopeNameLabels &scopeNameLabels() { - JS_ASSERT(kind == ic::PICInfo::NAME || + JS_ASSERT(kind == ic::PICInfo::NAME || kind == ic::PICInfo::CALLNAME || kind == ic::PICInfo::XNAME); return scopeNameLabels_; } @@ -280,6 +283,7 @@ class Compiler : public BaseCompiler ic.shapeReg = shapeReg; ic.objReg = objReg; ic.name = name; + ic.usePropCache = usePropCache; if (ic.isSet()) { ic.u.vr = vr; } else if (ic.isGet()) { @@ -594,9 +598,9 @@ private: bool jumpAndRun(Jump j, jsbytecode *target, Jump *slow = NULL, bool *trampoline = NULL); bool startLoop(jsbytecode *head, Jump entry, jsbytecode *entryTarget); bool finishLoop(jsbytecode *head); - void jsop_bindname(PropertyName *name); + void jsop_bindname(PropertyName *name, bool usePropCache); void jsop_setglobal(uint32_t index); - void jsop_getprop_slow(PropertyName *name, bool forPrototype = false); + void jsop_getprop_slow(PropertyName *name, bool usePropCache = true); void jsop_getarg(uint32_t slot); void jsop_setarg(uint32_t slot, bool popped); void jsop_this(); @@ -618,18 +622,25 @@ private: void fixPrimitiveReturn(Assembler *masm, FrameEntry *fe); void jsop_getgname(uint32_t index); void jsop_getgname_slow(uint32_t index); - void jsop_setgname(PropertyName *name, bool popGuaranteed); - void jsop_setgname_slow(PropertyName *name); + void jsop_callgname_epilogue(); + void jsop_setgname(PropertyName *name, bool usePropertyCache, bool popGuaranteed); + void jsop_setgname_slow(PropertyName *name, bool usePropertyCache); void jsop_bindgname(); void jsop_setelem_slow(); void jsop_getelem_slow(); + void jsop_callelem_slow(); bool jsop_getprop(PropertyName *name, JSValueType type, - bool typeCheck = true, bool forPrototype = false); - bool jsop_getprop_dispatch(PropertyName *name); - bool jsop_setprop(PropertyName *name, bool popGuaranteed); - void jsop_setprop_slow(PropertyName *name); + bool typeCheck = true, bool usePropCache = true); + bool jsop_setprop(PropertyName *name, bool usePropCache, bool popGuaranteed); + void jsop_setprop_slow(PropertyName *name, bool usePropCache = true); + bool jsop_callprop_slow(PropertyName *name); + bool jsop_callprop(PropertyName *name); + bool jsop_callprop_obj(PropertyName *name); + bool jsop_callprop_str(PropertyName *name); + bool jsop_callprop_generic(PropertyName *name); + bool jsop_callprop_dispatch(PropertyName *name); bool jsop_instanceof(); - void jsop_name(PropertyName *name, JSValueType type); + void jsop_name(PropertyName *name, JSValueType type, bool isCall); bool jsop_xname(PropertyName *name); void enterBlock(JSObject *obj); void leaveBlock(); @@ -687,7 +698,7 @@ private: void convertForTypedArray(int atype, ValueRemat *vr, bool *allocated); #endif bool jsop_setelem(bool popGuaranteed); - bool jsop_getelem(); + bool jsop_getelem(bool isCall); void jsop_getelem_dense(bool isPacked); void jsop_getelem_args(); #ifdef JS_METHODJIT_TYPED_ARRAY diff --git a/js/src/methodjit/FastOps.cpp b/js/src/methodjit/FastOps.cpp index 17ea2937af7..6ddb58b893b 100644 --- a/js/src/methodjit/FastOps.cpp +++ b/js/src/methodjit/FastOps.cpp @@ -2088,19 +2088,22 @@ mjit::Compiler::jsop_getelem_typed(int atype) #endif /* JS_METHODJIT_TYPED_ARRAY */ bool -mjit::Compiler::jsop_getelem() +mjit::Compiler::jsop_getelem(bool isCall) { FrameEntry *obj = frame.peek(-2); FrameEntry *id = frame.peek(-1); if (!IsCacheableGetElem(obj, id)) { - jsop_getelem_slow(); + if (isCall) + jsop_callelem_slow(); + else + jsop_getelem_slow(); return true; } // If the object is definitely an arguments object, a dense array or a typed array // we can generate code directly without using an inline cache. - if (cx->typeInferenceEnabled() && !id->isType(JSVAL_TYPE_STRING)) { + if (cx->typeInferenceEnabled() && !id->isType(JSVAL_TYPE_STRING) && !isCall) { types::TypeSet *types = analysis->poppedTypes(PC, 1); if (types->isLazyArguments(cx) && !outerScript->analysis()->modifiesArguments()) { // Inline arguments path. @@ -2134,7 +2137,10 @@ mjit::Compiler::jsop_getelem() frame.forgetMismatchedObject(obj); if (id->isType(JSVAL_TYPE_DOUBLE) || !globalObj) { - jsop_getelem_slow(); + if (isCall) + jsop_callelem_slow(); + else + jsop_getelem_slow(); return true; } @@ -2162,6 +2168,14 @@ mjit::Compiler::jsop_getelem() // Get a mutable register for the object. This will be the data reg. ic.objReg = frame.copyDataIntoReg(obj); + // For potential dense array calls, grab an extra reg to save the + // outgoing object. + MaybeRegisterID thisReg; + if (isCall && id->mightBeType(JSVAL_TYPE_INT32)) { + thisReg = frame.allocReg(); + masm.move(ic.objReg, thisReg.reg()); + } + // Get a mutable register for pushing the result type. We kill two birds // with one stone by making sure, if the key type is not known, to be loaded // into this register. In this case it is both an input and an output. @@ -2212,6 +2226,14 @@ mjit::Compiler::jsop_getelem() Assembler::FastArrayLoadFails fails = masm.fastArrayLoad(ic.objReg, key, ic.typeReg, ic.objReg); + // Store the object back to sp[-1] for calls. This must occur after + // all guards because otherwise sp[-1] will be clobbered. + if (isCall) { + Address thisSlot = frame.addressOf(id); + masm.storeValueFromComponents(ImmType(JSVAL_TYPE_OBJECT), thisReg.reg(), thisSlot); + frame.freeReg(thisReg.reg()); + } + stubcc.linkExitDirect(fails.rangeCheck, ic.slowPathStart); stubcc.linkExitDirect(fails.holeCheck, ic.slowPathStart); } else { @@ -2226,9 +2248,15 @@ mjit::Compiler::jsop_getelem() objTypeGuard.get().linkTo(stubcc.masm.label(), &stubcc.masm); #ifdef JS_POLYIC passICAddress(&ic); - ic.slowPathCall = OOL_STUBCALL(ic::GetElement, REJOIN_FALLTHROUGH); + if (isCall) + ic.slowPathCall = OOL_STUBCALL(ic::CallElement, REJOIN_FALLTHROUGH); + else + ic.slowPathCall = OOL_STUBCALL(ic::GetElement, REJOIN_FALLTHROUGH); #else - ic.slowPathCall = OOL_STUBCALL(stubs::GetElem, REJOIN_FALLTHROUGH); + if (isCall) + ic.slowPathCall = OOL_STUBCALL(stubs::CallElem, REJOIN_FALLTHROUGH); + else + ic.slowPathCall = OOL_STUBCALL(stubs::GetElem, REJOIN_FALLTHROUGH); #endif testPushedType(REJOIN_FALLTHROUGH, -2); @@ -2242,15 +2270,17 @@ mjit::Compiler::jsop_getelem() frame.pushRegs(ic.typeReg, ic.objReg, knownPushedType(0)); BarrierState barrier = testBarrier(ic.typeReg, ic.objReg, false, false, /* force = */ ic.forcedTypeBarrier); + if (isCall) + frame.pushSynced(knownPushedType(1)); - stubcc.rejoin(Changes(1)); + stubcc.rejoin(Changes(isCall ? 2 : 1)); #ifdef JS_POLYIC if (!getElemICs.append(ic)) return false; #endif - finishBarrier(barrier, REJOIN_FALLTHROUGH, 0); + finishBarrier(barrier, REJOIN_FALLTHROUGH, isCall ? 1 : 0); return true; } diff --git a/js/src/methodjit/InvokeHelpers.cpp b/js/src/methodjit/InvokeHelpers.cpp index f8de1319f08..b509878e3a5 100644 --- a/js/src/methodjit/InvokeHelpers.cpp +++ b/js/src/methodjit/InvokeHelpers.cpp @@ -51,6 +51,7 @@ #include "jsiter.h" #include "jstypes.h" #include "methodjit/StubCalls.h" +#include "jspropertycache.h" #include "methodjit/MonoIC.h" #include "jsanalyze.h" #include "methodjit/BaseCompiler.h" @@ -58,6 +59,7 @@ #include "vm/Debugger.h" #include "jsinterpinlines.h" +#include "jspropertycacheinlines.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" #include "jsobjinlines.h" @@ -860,10 +862,25 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM /* * We don't rejoin until after the native stub finishes execution, in * which case the return value will be in memory. For lowered natives, - * the return value will be in the 'this' value's slot. + * the return value will be in the 'this' value's slot. For getters, + * the result is at nextsp[0] (see ic::CallProp). */ - if (rejoin != REJOIN_NATIVE) + if (rejoin == REJOIN_NATIVE_LOWERED) { nextsp[-1] = nextsp[0]; + } else if (rejoin == REJOIN_NATIVE_GETTER) { + if (js_CodeSpec[op].format & JOF_CALLOP) { + /* + * If we went through jsop_callprop_obj then the 'this' value + * is still in its original slot and hasn't been shifted yet, + * so fix that now. Yuck. + */ + if (nextsp[-2].isObject()) + nextsp[-1] = nextsp[-2]; + nextsp[-2] = nextsp[0]; + } else { + nextsp[-1] = nextsp[0]; + } + } /* Release this reference on the orphaned native stub. */ RemoveOrphanedNative(cx, fp); @@ -1012,6 +1029,42 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM * fused opcode which needs to be finished. */ switch (op) { + case JSOP_NAME: + case JSOP_GETGNAME: + case JSOP_GETFCSLOT: + case JSOP_GETPROP: + case JSOP_GETXPROP: + case JSOP_LENGTH: + /* Non-fused opcode, state is already correct for the next op. */ + f.regs.pc = nextpc; + break; + + case JSOP_CALLGNAME: + case JSOP_CALLNAME: + if (!ComputeImplicitThis(cx, &fp->scopeChain(), nextsp[-2], &nextsp[-1])) + return js_InternalThrow(f); + f.regs.pc = nextpc; + break; + + case JSOP_CALLFCSLOT: + /* |this| is always undefined for CALLGLOBAL/CALLFCSLOT. */ + nextsp[-1].setUndefined(); + f.regs.pc = nextpc; + break; + + case JSOP_CALLPROP: { + /* + * CALLPROP is compiled in terms of GETPROP for known strings. + * In such cases the top two entries are in place, but are swapped. + */ + JS_ASSERT(nextsp[-2].isString()); + Value tmp = nextsp[-2]; + nextsp[-2] = nextsp[-1]; + nextsp[-1] = tmp; + f.regs.pc = nextpc; + break; + } + case JSOP_INSTANCEOF: { /* * If we recompiled from a getprop used within JSOP_INSTANCEOF, @@ -1028,8 +1081,7 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM } default: - f.regs.pc = nextpc; - break; + JS_NOT_REACHED("Bad rejoin getter op"); } break; @@ -1084,8 +1136,10 @@ js_InternalInterpret(void *returnData, void *returnType, void *returnReg, js::VM * The result may not have been marked if we bailed out while inside a stub * for the op. */ - if (f.regs.pc == nextpc && (js_CodeSpec[op].format & JOF_TYPESET)) - types::TypeScript::Monitor(cx, script, pc, f.regs.sp[-1]); + if (f.regs.pc == nextpc && (js_CodeSpec[op].format & JOF_TYPESET)) { + int which = (js_CodeSpec[op].format & JOF_CALLOP) ? -2 : -1; /* Yuck. */ + types::TypeScript::Monitor(cx, script, pc, f.regs.sp[which]); + } /* Mark the entry frame as unfinished, and update the regs to resume at. */ JaegerStatus status = skipTrap ? Jaeger_UnfinishedAtTrap : Jaeger_Unfinished; diff --git a/js/src/methodjit/MethodJIT.cpp b/js/src/methodjit/MethodJIT.cpp index 71cf64357d9..d66b6d5bc7d 100644 --- a/js/src/methodjit/MethodJIT.cpp +++ b/js/src/methodjit/MethodJIT.cpp @@ -1363,6 +1363,9 @@ PICPCComparator(const void *key, const void *entry) const jsbytecode *pc = (const jsbytecode *)key; const ic::PICInfo *pic = (const ic::PICInfo *)entry; + if (ic::PICInfo::CALL != pic->kind) + return ic::PICInfo::CALL - pic->kind; + /* * We can't just return |pc - pic->pc| because the pointers may be * far apart and an int (or even a ptrdiff_t) may not be large diff --git a/js/src/methodjit/MonoIC.cpp b/js/src/methodjit/MonoIC.cpp index fbba7736dc7..dbac5be40c6 100644 --- a/js/src/methodjit/MonoIC.cpp +++ b/js/src/methodjit/MonoIC.cpp @@ -79,7 +79,7 @@ static void PatchGetFallback(VMFrame &f, ic::GetGlobalNameIC *ic) { Repatcher repatch(f.jit()); - JSC::FunctionPtr fptr(JS_FUNC_TO_DATA_PTR(void *, stubs::Name)); + JSC::FunctionPtr fptr(JS_FUNC_TO_DATA_PTR(void *, stubs::GetGlobalName)); repatch.relink(ic->slowPathCall, fptr); } @@ -94,7 +94,7 @@ ic::GetGlobalName(VMFrame &f, ic::GetGlobalNameIC *ic) const Shape *shape = obj.nativeLookup(f.cx, js_CheckForStringIndex(ATOM_TO_JSID(name))); if (monitor.recompiled()) { - stubs::Name(f); + stubs::GetGlobalName(f); return; } @@ -104,7 +104,7 @@ ic::GetGlobalName(VMFrame &f, ic::GetGlobalNameIC *ic) { if (shape) PatchGetFallback(f, ic); - stubs::Name(f); + stubs::GetGlobalName(f); return; } uint32_t slot = shape->slot(); @@ -119,7 +119,7 @@ ic::GetGlobalName(VMFrame &f, ic::GetGlobalNameIC *ic) repatcher.patchAddressOffsetForValueLoad(label, index * sizeof(Value)); /* Do load anyway... this time. */ - stubs::Name(f); + stubs::GetGlobalName(f); } template @@ -132,12 +132,24 @@ DisabledSetGlobal(VMFrame &f, ic::SetGlobalNameIC *ic) template void JS_FASTCALL DisabledSetGlobal(VMFrame &f, ic::SetGlobalNameIC *ic); template void JS_FASTCALL DisabledSetGlobal(VMFrame &f, ic::SetGlobalNameIC *ic); +template +static void JS_FASTCALL +DisabledSetGlobalNoCache(VMFrame &f, ic::SetGlobalNameIC *ic) +{ + stubs::SetGlobalNameNoCache(f, f.script()->getName(GET_INDEX(f.pc()))); +} + +template void JS_FASTCALL DisabledSetGlobalNoCache(VMFrame &f, ic::SetGlobalNameIC *ic); +template void JS_FASTCALL DisabledSetGlobalNoCache(VMFrame &f, ic::SetGlobalNameIC *ic); + static void PatchSetFallback(VMFrame &f, ic::SetGlobalNameIC *ic) { JSScript *script = f.script(); Repatcher repatch(f.jit()); - VoidStubSetGlobal stub = STRICT_VARIANT(DisabledSetGlobal); + VoidStubSetGlobal stub = ic->usePropertyCache + ? STRICT_VARIANT(DisabledSetGlobal) + : STRICT_VARIANT(DisabledSetGlobalNoCache); JSC::FunctionPtr fptr(JS_FUNC_TO_DATA_PTR(void *, stub)); repatch.relink(ic->slowPathCall, fptr); } @@ -205,7 +217,10 @@ ic::SetGlobalName(VMFrame &f, ic::SetGlobalNameIC *ic) THROW(); } - STRICT_VARIANT(stubs::SetGlobalName)(f, name); + if (ic->usePropertyCache) + STRICT_VARIANT(stubs::SetGlobalName)(f, name); + else + STRICT_VARIANT(stubs::SetGlobalNameNoCache)(f, name); } class EqualityICLinker : public LinkerHelper diff --git a/js/src/methodjit/MonoIC.h b/js/src/methodjit/MonoIC.h index 71aec95d361..59ccd9b2d3b 100644 --- a/js/src/methodjit/MonoIC.h +++ b/js/src/methodjit/MonoIC.h @@ -128,6 +128,7 @@ struct GlobalNameIC */ int32_t loadStoreOffset : 15; int32_t shapeOffset : 15; + bool usePropertyCache : 1; }; struct GetGlobalNameIC : public GlobalNameIC diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index 61a17bd8ee0..21e52c2ba98 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -49,6 +49,8 @@ #include "jsatominlines.h" #include "jsobjinlines.h" #include "jsscopeinlines.h" +#include "jspropertycache.h" +#include "jspropertycacheinlines.h" #include "jsinterpinlines.h" #include "jsautooplen.h" @@ -111,6 +113,12 @@ class PICStubCompiler : public BaseCompiler gcNumber(f.cx->runtime->gcNumber), canCallHook(pic.canCallHook) { } + bool isCallOp() const { + if (pic.kind == ic::PICInfo::CALL) + return true; + return !!(js_CodeSpec[pic.op].format & JOF_CALLOP); + } + LookupStatus error() { /* * N.B. Do not try to disable the IC, we do not want to guard on @@ -776,7 +784,7 @@ struct GetPropHelper { LookupStatus testForGet() { if (!shape->hasDefaultGetter()) { if (shape->isMethod()) { - if (JSOp(*f.pc()) != JSOP_CALLPROP) + if (!ic.isCallOp()) return ic.disable(cx, "method valued shape"); } else { if (shape->hasGetterValue()) @@ -819,9 +827,9 @@ class GetPropCompiler : public PICStubCompiler int lastStubSecondShapeGuard; public: - GetPropCompiler(VMFrame &f, JSScript *script, JSObject *obj, ic::PICInfo &pic, PropertyName *name, - VoidStubPIC stub) - : PICStubCompiler("getprop", f, script, pic, + GetPropCompiler(VMFrame &f, JSScript *script, JSObject *obj, ic::PICInfo &pic, + PropertyName *name, VoidStubPIC stub) + : PICStubCompiler(pic.kind == ic::PICInfo::CALL ? "callprop" : "getprop", f, script, pic, JS_FUNC_TO_DATA_PTR(void *, stub)), obj(obj), name(name), @@ -844,9 +852,20 @@ class GetPropCompiler : public PICStubCompiler repatcher.relink(labels.getInlineTypeJump(pic.fastPathStart), pic.getSlowTypeCheck()); } - JS_ASSERT(pic.kind == ic::PICInfo::GET); + VoidStubPIC stub; + switch (pic.kind) { + case ic::PICInfo::GET: + stub = ic::GetProp; + break; + case ic::PICInfo::CALL: + stub = ic::CallProp; + break; + default: + JS_NOT_REACHED("invalid pic kind for GetPropCompiler::reset"); + return; + } - FunctionPtr target(JS_FUNC_TO_DATA_PTR(void *, ic::GetProp)); + FunctionPtr target(JS_FUNC_TO_DATA_PTR(void *, stub)); repatcher.relink(pic.slowPathCall, target); } @@ -969,20 +988,14 @@ class GetPropCompiler : public PICStubCompiler return Lookup_Cacheable; } - LookupStatus generateStringPropertyStub() + LookupStatus generateStringCallStub() { + JS_ASSERT(pic.hasTypeCheck()); + JS_ASSERT(pic.kind == ic::PICInfo::CALL); + if (!f.fp()->script()->hasGlobal()) return disable("String.prototype without compile-and-go global"); - RecompilationMonitor monitor(f.cx); - - JSObject *obj = f.fp()->scopeChain().global().getOrCreateStringPrototype(f.cx); - if (!obj) - return error(); - - if (monitor.recompiled()) - return Lookup_Uncacheable; - GetPropHelper getprop(cx, obj, name, *this, f); LookupStatus status = getprop.lookupAndTest(); if (status != Lookup_Cacheable) @@ -1000,6 +1013,18 @@ class GetPropCompiler : public PICStubCompiler Jump notString = masm.branchPtr(Assembler::NotEqual, pic.typeReg(), ImmType(JSVAL_TYPE_STRING)); + /* + * Sink pic.objReg, since we're about to lose it. + * + * Note: This is really hacky, and relies on f.regs.sp being set + * correctly in ic::CallProp. Should we just move the store higher + * up in the fast path, or put this offset in PICInfo? + */ + uint32_t thisvOffset = uint32_t(f.regs.sp - f.fp()->slots()) - 1; + Address thisv(JSFrameReg, sizeof(StackFrame) + thisvOffset * sizeof(Value)); + masm.storeValueFromComponents(ImmType(JSVAL_TYPE_STRING), + pic.objReg, thisv); + /* * Clobber objReg with String.prototype and do some PIC stuff. Well, * really this is now a MIC, except it won't ever be patched, so we @@ -1041,6 +1066,7 @@ class GetPropCompiler : public PICStubCompiler /* Disable the PIC so we don't keep generating stubs on the above shape mismatch. */ disable("generated string call stub"); + return Lookup_Cacheable; } @@ -1446,6 +1472,9 @@ class ScopeNameCompiler : public PICStubCompiler case ic::PICInfo::XNAME: stub = ic::XName; break; + case ic::PICInfo::CALLNAME: + stub = ic::CallName; + break; default: JS_NOT_REACHED("Invalid pic kind in ScopeNameCompiler::reset"); return; @@ -1461,7 +1490,7 @@ class ScopeNameCompiler : public PICStubCompiler ScopeNameLabels &labels = pic.scopeNameLabels(); /* For GETXPROP, the object is already in objReg. */ - if (pic.kind == ic::PICInfo::NAME) + if (pic.kind == ic::PICInfo::NAME || pic.kind == ic::PICInfo::CALLNAME) masm.loadPtr(Address(JSFrameReg, StackFrame::offsetOfScopeChain()), pic.objReg); JS_ASSERT(obj == getprop.holder); @@ -1473,7 +1502,7 @@ class ScopeNameCompiler : public PICStubCompiler /* If a scope chain walk was required, the final object needs a NULL test. */ MaybeJump finalNull; - if (pic.kind == ic::PICInfo::NAME) + if (pic.kind == ic::PICInfo::NAME || pic.kind == ic::PICInfo::CALLNAME) finalNull = masm.branchTestPtr(Assembler::Zero, pic.objReg, pic.objReg); masm.loadShape(pic.objReg, pic.shapeReg); Jump finalShape = masm.branchPtr(Assembler::NotEqual, pic.shapeReg, @@ -1481,6 +1510,18 @@ class ScopeNameCompiler : public PICStubCompiler masm.loadObjProp(obj, pic.objReg, getprop.shape, pic.shapeReg, pic.objReg); + /* + * For CALLNAME we must store undefined as the this-value. A non-strict + * mode callee function replaces undefined with its global on demand in + * code generated for JSOP_THIS. + */ + if (pic.kind == ic::PICInfo::CALLNAME) { + /* Store undefined this-value. */ + Value *thisVp = &cx->regs().sp[1]; + Address thisSlot(JSFrameReg, StackFrame::offsetOfFixed(thisVp - cx->fp()->slots())); + masm.storeValue(UndefinedValue(), thisSlot); + } + Jump done = masm.jump(); /* All failures flow to here, so there is a common point to patch. */ @@ -1533,7 +1574,7 @@ class ScopeNameCompiler : public PICStubCompiler ScopeNameLabels &labels = pic.scopeNameLabels(); /* For GETXPROP, the object is already in objReg. */ - if (pic.kind == ic::PICInfo::NAME) + if (pic.kind == ic::PICInfo::NAME || pic.kind == ic::PICInfo::CALLNAME) masm.loadPtr(Address(JSFrameReg, StackFrame::offsetOfScopeChain()), pic.objReg); JS_ASSERT(obj == getprop.holder); @@ -1555,12 +1596,24 @@ class ScopeNameCompiler : public PICStubCompiler /* If a scope chain walk was required, the final object needs a NULL test. */ MaybeJump finalNull; - if (pic.kind == ic::PICInfo::NAME) + if (pic.kind == ic::PICInfo::NAME || pic.kind == ic::PICInfo::CALLNAME) finalNull = masm.branchTestPtr(Assembler::Zero, pic.objReg, pic.objReg); masm.loadShape(pic.objReg, pic.shapeReg); Jump finalShape = masm.branchPtr(Assembler::NotEqual, pic.shapeReg, ImmPtr(getprop.holder->lastProperty())); + /* + * For CALLNAME we have to store the this-value. Since we guarded on + * IsCacheableNonGlobalScope it's always undefined. This matches the decision + * tree in ComputeImplicitThis. + */ + if (pic.kind == ic::PICInfo::CALLNAME) { + JS_ASSERT(obj->isCall()); + Value *thisVp = &cx->regs().sp[1]; + Address thisSlot(JSFrameReg, StackFrame::offsetOfFixed(thisVp - cx->fp()->slots())); + masm.storeValue(UndefinedValue(), thisSlot); + } + /* Get callobj's stack frame. */ masm.loadObjPrivate(pic.objReg, pic.shapeReg, getprop.holder->numFixedSlots()); @@ -1671,7 +1724,7 @@ class ScopeNameCompiler : public PICStubCompiler return disable("scope object not handled yet"); } - bool retrieve(Value *vp, PICInfo::Kind kind) + bool retrieve(Value *vp, Value *thisvp, PICInfo::Kind kind) { JSObject *obj = getprop.obj; JSObject *holder = getprop.holder; @@ -1695,6 +1748,8 @@ class ScopeNameCompiler : public PICStubCompiler if (!getprop.shape) { if (!obj->getProperty(cx, name, vp)) return false; + if (thisvp) + return ComputeImplicitThis(cx, obj, *vp, thisvp); return true; } @@ -1703,6 +1758,8 @@ class ScopeNameCompiler : public PICStubCompiler if (obj->isWith() && !shape->hasDefaultGetter()) normalized = &obj->asWith().object(); NATIVE_GET(cx, normalized, holder, shape, JSGET_METHOD_BARRIER, vp, return false); + if (thisvp) + return ComputeImplicitThis(cx, normalized, *vp, thisvp); return true; } }; @@ -1835,14 +1892,34 @@ class BindNameCompiler : public PICStubCompiler } }; -static inline void -GetPropWithStub(VMFrame &f, ic::PICInfo *pic, VoidStubPIC stub) +static void JS_FASTCALL +DisabledGetPropIC(VMFrame &f, ic::PICInfo *pic) +{ + stubs::GetProp(f); +} + +static void JS_FASTCALL +DisabledGetPropICNoCache(VMFrame &f, ic::PICInfo *pic) +{ + stubs::GetPropNoCache(f, pic->name); +} + +void JS_FASTCALL +ic::GetProp(VMFrame &f, ic::PICInfo *pic) { JSScript *script = f.fp()->script(); PropertyName *name = pic->name; if (name == f.cx->runtime->atomState.lengthAtom) { - if (f.regs.sp[-1].isMagic(JS_LAZY_ARGUMENTS)) { + if (f.regs.sp[-1].isString()) { + GetPropCompiler cc(f, script, NULL, *pic, NULL, DisabledGetPropIC); + LookupStatus status = cc.generateStringLengthStub(); + if (status == Lookup_Error) + THROW(); + JSString *str = f.regs.sp[-1].toString(); + f.regs.sp[-1].setInt32(str->length()); + return; + } else if (f.regs.sp[-1].isMagic(JS_LAZY_ARGUMENTS)) { f.regs.sp[-1].setInt32(f.regs.fp()->numActualArgs()); return; } else if (!f.regs.sp[-1].isPrimitive()) { @@ -1850,7 +1927,7 @@ GetPropWithStub(VMFrame &f, ic::PICInfo *pic, VoidStubPIC stub) if (obj->isArray() || (obj->isArguments() && !obj->asArguments().hasOverriddenLength()) || obj->isString()) { - GetPropCompiler cc(f, script, obj, *pic, NULL, stub); + GetPropCompiler cc(f, script, obj, *pic, NULL, DisabledGetPropIC); if (obj->isArray()) { LookupStatus status = cc.generateArrayLengthStub(); if (status == Lookup_Error) @@ -1871,70 +1948,45 @@ GetPropWithStub(VMFrame &f, ic::PICInfo *pic, VoidStubPIC stub) return; } } + name = f.cx->runtime->atomState.lengthAtom; } - if (f.regs.sp[-1].isString()) { - GetPropCompiler cc(f, script, NULL, *pic, name, stub); - if (name == f.cx->runtime->atomState.lengthAtom) { - LookupStatus status = cc.generateStringLengthStub(); - if (status == Lookup_Error) - THROW(); - JSString *str = f.regs.sp[-1].toString(); - f.regs.sp[-1].setInt32(str->length()); - } else { - LookupStatus status = cc.generateStringPropertyStub(); - if (status == Lookup_Error) - THROW(); - JSObject *obj = ValueToObject(f.cx, f.regs.sp[-1]); - if (!obj) - THROW(); - if (!obj->getProperty(f.cx, name, &f.regs.sp[-1])) - THROW(); - } - return; - } - + /* + * ValueToObject can trigger recompilations if it lazily initializes any + * of the primitive classes (Boolean, Number, String). :XXX: if these + * classes are made eager then this monitoring is not necessary. + */ RecompilationMonitor monitor(f.cx); - JSObject *obj = ValueToObject(f.cx, f.regs.sp[-1]); + JSObject *obj = ValueToObject(f.cx, &f.regs.sp[-1]); if (!obj) THROW(); if (!monitor.recompiled() && pic->shouldUpdate(f.cx)) { + VoidStubPIC stub = pic->usePropCache + ? DisabledGetPropIC + : DisabledGetPropICNoCache; GetPropCompiler cc(f, script, obj, *pic, name, stub); if (!cc.update()) THROW(); } Value v; - if (!GetPropertyGenericMaybeCallXML(f.cx, JSOp(*f.pc()), obj, ATOM_TO_JSID(name), &v)) + if (!obj->getProperty(f.cx, name, &v)) THROW(); f.regs.sp[-1] = v; } -static void JS_FASTCALL -DisabledGetPropIC(VMFrame &f, ic::PICInfo *pic) -{ - stubs::GetProp(f, pic->name); -} - -static void JS_FASTCALL -DisabledGetPropNoCacheIC(VMFrame &f, ic::PICInfo *pic) -{ - stubs::GetPropNoCache(f, pic->name); -} - -void JS_FASTCALL -ic::GetProp(VMFrame &f, ic::PICInfo *pic) -{ - GetPropWithStub(f, pic, DisabledGetPropIC); -} - void JS_FASTCALL ic::GetPropNoCache(VMFrame &f, ic::PICInfo *pic) { - GetPropWithStub(f, pic, DisabledGetPropNoCacheIC); + /* + * The PIC stores whether to use the property cache or not. We use two different + * stub calls so we can distinguish uncached calls made to construct this from + * any cached calls at the first opcode in a script. + */ + GetProp(f, pic); } template @@ -1944,21 +1996,32 @@ DisabledSetPropIC(VMFrame &f, ic::PICInfo *pic) stubs::SetName(f, pic->name); } +template +static void JS_FASTCALL +DisabledSetPropICNoCache(VMFrame &f, ic::PICInfo *pic) +{ + stubs::SetPropNoCache(f, pic->name); +} + void JS_FASTCALL ic::SetProp(VMFrame &f, ic::PICInfo *pic) { JSScript *script = f.fp()->script(); JS_ASSERT(pic->isSet()); - VoidStubPIC stub = STRICT_VARIANT(DisabledSetPropIC); + VoidStubPIC stub = pic->usePropCache + ? STRICT_VARIANT(DisabledSetPropIC) + : STRICT_VARIANT(DisabledSetPropICNoCache); // Save this in case the compiler triggers a recompilation of this script. PropertyName *name = pic->name; - VoidStubName nstub = STRICT_VARIANT(stubs::SetName); + VoidStubName nstub = pic->usePropCache + ? STRICT_VARIANT(stubs::SetName) + : STRICT_VARIANT(stubs::SetPropNoCache); RecompilationMonitor monitor(f.cx); - JSObject *obj = ValueToObject(f.cx, f.regs.sp[-2]); + JSObject *obj = ValueToObject(f.cx, &f.regs.sp[-2]); if (!obj) THROW(); @@ -1974,6 +2037,122 @@ ic::SetProp(VMFrame &f, ic::PICInfo *pic) nstub(f, name); } +static void JS_FASTCALL +DisabledCallPropIC(VMFrame &f, ic::PICInfo *pic) +{ + stubs::CallProp(f, pic->name); +} + +void JS_FASTCALL +ic::CallProp(VMFrame &f, ic::PICInfo *pic) +{ + JSContext *cx = f.cx; + FrameRegs ®s = f.regs; + + JSScript *script = f.fp()->script(); + RecompilationMonitor monitor(cx); + + Value lval; + lval = regs.sp[-1]; + + // Do this first in case js_GetClassPrototype triggers a recompilation. + PropertyName *name = pic->name; + + Value objv; + if (lval.isObject()) { + objv = lval; + } else { + GlobalObject &global = f.fp()->scopeChain().global(); + JSObject *pobj; + if (lval.isString()) { + pobj = global.getOrCreateStringPrototype(cx); + } else if (lval.isNumber()) { + pobj = global.getOrCreateNumberPrototype(cx); + } else if (lval.isBoolean()) { + pobj = global.getOrCreateBooleanPrototype(cx); + } else { + JS_ASSERT(lval.isNull() || lval.isUndefined()); + js_ReportIsNullOrUndefined(cx, -1, lval, NULL); + THROW(); + } + if (!pobj) + THROW(); + objv.setObject(*pobj); + } + + JSObject *aobj = js_GetProtoIfDenseArray(&objv.toObject()); + Value rval; + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name_; + JS_PROPERTY_CACHE(cx).test(cx, f.pc(), aobj, obj2, entry, name_); + if (!name_) { + NATIVE_GET(cx, &objv.toObject(), obj2, entry->prop, JSGET_NO_METHOD_BARRIER, &rval, + THROW()); + /* + * Adjust the stack to reflect the height after the GETPROP, here and + * below. Getter hook ICs depend on this to know which value of sp they + * are updating for consistent rejoins, don't modify this! + */ + regs.sp++; + regs.sp[-2] = rval; + regs.sp[-1] = lval; + } else { + /* Cache miss: use the name loaded for us under PropertyCache::test. */ + regs.sp++; + regs.sp[-1].setNull(); + if (lval.isObject()) { + if (!GetMethod(cx, &objv.toObject(), name, + JS_LIKELY(!objv.toObject().getOps()->getProperty) + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_NO_METHOD_BARRIER, + &rval)) + { + THROW(); + } + regs.sp[-1] = objv; + regs.sp[-2] = rval; + } else { + JS_ASSERT(!objv.toObject().getOps()->getProperty); + if (!GetPropertyHelper(cx, &objv.toObject(), name, + JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER, + &rval)) + { + THROW(); + } + regs.sp[-1] = lval; + regs.sp[-2] = rval; + } + } + +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(rval.isPrimitive()) && regs.sp[-1].isObject()) { + regs.sp[-2].setString(name); + if (!OnUnknownMethod(cx, regs.sp - 2)) + THROW(); + } +#endif + + if (monitor.recompiled()) + return; + + GetPropCompiler cc(f, script, &objv.toObject(), *pic, pic->name, DisabledCallPropIC); + if (lval.isObject()) { + if (pic->shouldUpdate(cx)) { + LookupStatus status = cc.update(); + if (status == Lookup_Error) + THROW(); + } + } else if (lval.isString()) { + LookupStatus status = cc.generateStringCallStub(); + if (status == Lookup_Error) + THROW(); + } else { + cc.disable("non-string primitive"); + } +} + static void JS_FASTCALL DisabledNameIC(VMFrame &f, ic::PICInfo *pic) { @@ -1983,7 +2162,7 @@ DisabledNameIC(VMFrame &f, ic::PICInfo *pic) static void JS_FASTCALL DisabledXNameIC(VMFrame &f, ic::PICInfo *pic) { - stubs::GetProp(f, pic->name); + stubs::GetProp(f); } void JS_FASTCALL @@ -2001,7 +2180,7 @@ ic::XName(VMFrame &f, ic::PICInfo *pic) THROW(); Value rval; - if (!cc.retrieve(&rval, PICInfo::XNAME)) + if (!cc.retrieve(&rval, NULL, PICInfo::XNAME)) THROW(); f.regs.sp[-1] = rval; } @@ -2018,15 +2197,46 @@ ic::Name(VMFrame &f, ic::PICInfo *pic) THROW(); Value rval; - if (!cc.retrieve(&rval, PICInfo::NAME)) + if (!cc.retrieve(&rval, NULL, PICInfo::NAME)) THROW(); f.regs.sp[0] = rval; } +static void JS_FASTCALL +DisabledCallNameIC(VMFrame &f, ic::PICInfo *pic) +{ + stubs::CallName(f); +} + +void JS_FASTCALL +ic::CallName(VMFrame &f, ic::PICInfo *pic) +{ + JSScript *script = f.fp()->script(); + + ScopeNameCompiler cc(f, script, &f.fp()->scopeChain(), *pic, pic->name, DisabledCallNameIC); + + LookupStatus status = cc.updateForName(); + if (status == Lookup_Error) + THROW(); + + Value rval, thisval; + if (!cc.retrieve(&rval, &thisval, PICInfo::CALLNAME)) + THROW(); + + f.regs.sp[0] = rval; + f.regs.sp[1] = thisval; +} + static void JS_FASTCALL DisabledBindNameIC(VMFrame &f, ic::PICInfo *pic) { - stubs::BindName(f, pic->name); + stubs::BindName(f); +} + +static void JS_FASTCALL +DisabledBindNameICNoCache(VMFrame &f, ic::PICInfo *pic) +{ + stubs::BindNameNoCache(f, pic->name); } void JS_FASTCALL @@ -2034,7 +2244,9 @@ ic::BindName(VMFrame &f, ic::PICInfo *pic) { JSScript *script = f.fp()->script(); - VoidStubPIC stub = DisabledBindNameIC; + VoidStubPIC stub = pic->usePropCache + ? DisabledBindNameIC + : DisabledBindNameICNoCache; BindNameCompiler cc(f, script, &f.fp()->scopeChain(), *pic, pic->name, stub); JSObject *obj = cc.update(); @@ -2044,6 +2256,12 @@ ic::BindName(VMFrame &f, ic::PICInfo *pic) f.regs.sp[0].setObject(*obj); } +bool +BaseIC::isCallOp() +{ + return !!(js_CodeSpec[op].format & JOF_CALLOP); +} + void BaseIC::spew(JSContext *cx, const char *event, const char *message) { @@ -2115,6 +2333,12 @@ DisabledGetElem(VMFrame &f, ic::GetElementIC *ic) stubs::GetElem(f); } +static void JS_FASTCALL +DisabledCallElem(VMFrame &f, ic::GetElementIC *ic) +{ + stubs::CallElem(f); +} + bool GetElementIC::shouldUpdate(JSContext *cx) { @@ -2131,7 +2355,9 @@ LookupStatus GetElementIC::disable(JSContext *cx, const char *reason) { slowCallPatched = true; - void *stub = JS_FUNC_TO_DATA_PTR(void *, DisabledGetElem); + void *stub = (op == JSOP_GETELEM) + ? JS_FUNC_TO_DATA_PTR(void *, DisabledGetElem) + : JS_FUNC_TO_DATA_PTR(void *, DisabledCallElem); BaseIC::disable(cx, reason, stub); return Lookup_Uncacheable; } @@ -2152,8 +2378,13 @@ GetElementIC::purge(Repatcher &repatcher) repatcher.relink(fastPathStart.jumpAtOffset(inlineShapeGuard), slowPathStart); if (slowCallPatched) { - repatcher.relink(slowPathCall, - FunctionPtr(JS_FUNC_TO_DATA_PTR(void *, ic::GetElement))); + if (op == JSOP_GETELEM) { + repatcher.relink(slowPathCall, + FunctionPtr(JS_FUNC_TO_DATA_PTR(void *, ic::GetElement))); + } else if (op == JSOP_CALLELEM) { + repatcher.relink(slowPathCall, + FunctionPtr(JS_FUNC_TO_DATA_PTR(void *, ic::CallElement))); + } } reset(); @@ -2610,6 +2841,65 @@ GetElementIC::update(VMFrame &f, JSObject *obj, const Value &v, jsid id, Value * return disable(f.cx, "unhandled object and key type"); } +void JS_FASTCALL +ic::CallElement(VMFrame &f, ic::GetElementIC *ic) +{ + JSContext *cx = f.cx; + + // Right now, we don't optimize for strings. + if (!f.regs.sp[-2].isObject()) { + ic->disable(cx, "non-object"); + stubs::CallElem(f); + return; + } + + RecompilationMonitor monitor(cx); + + Value thisv = f.regs.sp[-2]; + JSObject *thisObj = ValuePropertyBearer(cx, thisv, -2); + if (!thisObj) + THROW(); + + jsid id; + Value idval = f.regs.sp[-1]; + if (idval.isInt32() && INT_FITS_IN_JSID(idval.toInt32())) + id = INT_TO_JSID(idval.toInt32()); + else if (!js_InternNonIntElementId(cx, thisObj, idval, &id)) + THROW(); + + if (!monitor.recompiled() && ic->shouldUpdate(cx)) { +#ifdef DEBUG + f.regs.sp[-2] = MagicValue(JS_GENERIC_MAGIC); +#endif + LookupStatus status = ic->update(f, thisObj, idval, id, &f.regs.sp[-2]); + if (status != Lookup_Uncacheable) { + if (status == Lookup_Error) + THROW(); + + // If the result can be cached, the value was already retrieved. + JS_ASSERT(!f.regs.sp[-2].isMagic()); + f.regs.sp[-1].setObject(*thisObj); + return; + } + } + + /* Get or set the element. */ + if (!js_GetMethod(cx, thisObj, id, JSGET_NO_METHOD_BARRIER, &f.regs.sp[-2])) + THROW(); + +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(f.regs.sp[-2].isPrimitive()) && thisv.isObject()) { + f.regs.sp[-2] = f.regs.sp[-1]; + f.regs.sp[-1].setObject(*thisObj); + if (!OnUnknownMethod(cx, f.regs.sp - 2)) + THROW(); + } else +#endif + { + f.regs.sp[-1] = thisv; + } +} + void JS_FASTCALL ic::GetElement(VMFrame &f, ic::GetElementIC *ic) { @@ -2626,7 +2916,7 @@ ic::GetElement(VMFrame &f, ic::GetElementIC *ic) RecompilationMonitor monitor(cx); - JSObject *obj = ValueToObject(cx, f.regs.sp[-2]); + JSObject *obj = ValueToObject(cx, &f.regs.sp[-2]); if (!obj) THROW(); diff --git a/js/src/methodjit/PolyIC.h b/js/src/methodjit/PolyIC.h index 0bf212b6173..52353cdbbef 100644 --- a/js/src/methodjit/PolyIC.h +++ b/js/src/methodjit/PolyIC.h @@ -384,11 +384,13 @@ struct PICInfo : public BasePolyIC { #endif { GET, // JSOP_GETPROP + CALL, // JSOP_CALLPROP SET, // JSOP_SETPROP, JSOP_SETNAME SETMETHOD, // JSOP_SETMETHOD NAME, // JSOP_NAME BIND, // JSOP_BINDNAME - XNAME // JSOP_GETXPROP + XNAME, // JSOP_GETXPROP + CALLNAME // JSOP_CALLNAME }; union { @@ -465,13 +467,13 @@ struct PICInfo : public BasePolyIC { return kind == SET || kind == SETMETHOD; } inline bool isGet() const { - return kind == GET; + return kind == GET || kind == CALL; } inline bool isBind() const { return kind == BIND; } inline bool isScopeName() const { - return kind == NAME || kind == XNAME; + return kind == NAME || kind == CALLNAME || kind == XNAME; } inline RegisterID typeReg() { JS_ASSERT(isGet()); @@ -484,6 +486,10 @@ struct PICInfo : public BasePolyIC { inline bool shapeNeedsRemat() { return !shapeRegHasBaseShape; } + inline bool isFastCall() { + JS_ASSERT(kind == CALL); + return !hasTypeCheck(); + } union { GetPropLabels getPropLabels_; @@ -504,7 +510,7 @@ struct PICInfo : public BasePolyIC { bindNameLabels_ = labels; } void setLabels(const ic::ScopeNameLabels &labels) { - JS_ASSERT(kind == NAME || kind == XNAME); + JS_ASSERT(kind == NAME || kind == CALLNAME || kind == XNAME); scopeNameLabels_ = labels; } @@ -521,7 +527,7 @@ struct PICInfo : public BasePolyIC { return bindNameLabels_; } ScopeNameLabels &scopeNameLabels() { - JS_ASSERT(kind == NAME || kind == XNAME); + JS_ASSERT(kind == NAME || kind == CALLNAME || kind == XNAME); return scopeNameLabels_; } @@ -544,10 +550,13 @@ struct PICInfo : public BasePolyIC { void JS_FASTCALL GetProp(VMFrame &f, ic::PICInfo *); void JS_FASTCALL GetPropNoCache(VMFrame &f, ic::PICInfo *); void JS_FASTCALL SetProp(VMFrame &f, ic::PICInfo *); +void JS_FASTCALL CallProp(VMFrame &f, ic::PICInfo *); void JS_FASTCALL Name(VMFrame &f, ic::PICInfo *); +void JS_FASTCALL CallName(VMFrame &f, ic::PICInfo *); void JS_FASTCALL XName(VMFrame &f, ic::PICInfo *); void JS_FASTCALL BindName(VMFrame &f, ic::PICInfo *); void JS_FASTCALL GetElement(VMFrame &f, ic::GetElementIC *); +void JS_FASTCALL CallElement(VMFrame &f, ic::GetElementIC *); template void JS_FASTCALL SetElement(VMFrame &f, ic::SetElementIC *); #endif diff --git a/js/src/methodjit/StubCalls-inl.h b/js/src/methodjit/StubCalls-inl.h index 39f9d112386..e59f8d8177d 100644 --- a/js/src/methodjit/StubCalls-inl.h +++ b/js/src/methodjit/StubCalls-inl.h @@ -54,6 +54,14 @@ ThrowException(VMFrame &f) #define THROW() do { mjit::ThrowException(f); return; } while (0) #define THROWV(v) do { mjit::ThrowException(f); return v; } while (0) +static inline JSObject * +ValueToObject(JSContext *cx, Value *vp) +{ + if (vp->isObject()) + return &vp->toObject(); + return js_ValueToNonNullObject(cx, *vp); +} + static inline void ReportAtomNotDefined(JSContext *cx, JSAtom *atom) { diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index 4eac044a185..2bd6dbd341f 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -57,6 +57,8 @@ #include "methodjit/Retcon.h" #include "jsinterpinlines.h" +#include "jspropertycache.h" +#include "jspropertycacheinlines.h" #include "jsscopeinlines.h" #include "jsscriptinlines.h" #include "jsnuminlines.h" @@ -82,7 +84,29 @@ using namespace js::types; using namespace JSC; void JS_FASTCALL -stubs::BindName(VMFrame &f, PropertyName *name) +stubs::BindName(VMFrame &f) +{ + PropertyCacheEntry *entry; + + /* Fast-path should have caught this. See comment in interpreter. */ + JS_ASSERT(!f.fp()->scopeChain().isGlobal()); + + PropertyName *name; + JSObject *obj2; + JSContext *cx = f.cx; + JSObject *obj = &f.fp()->scopeChain(); + JS_PROPERTY_CACHE(cx).test(cx, f.pc(), obj, obj2, entry, name); + if (name) { + obj = FindIdentifierBase(cx, &f.fp()->scopeChain(), name); + if (!obj) + THROW(); + } + f.regs.sp++; + f.regs.sp[-1].setObject(*obj); +} + +void JS_FASTCALL +stubs::BindNameNoCache(VMFrame &f, PropertyName *name) { JSObject *obj = FindIdentifierBase(f.cx, &f.fp()->scopeChain(), name); if (!obj) @@ -98,21 +122,137 @@ stubs::BindGlobalName(VMFrame &f) template void JS_FASTCALL -stubs::SetName(VMFrame &f, PropertyName *name) +stubs::SetName(VMFrame &f, PropertyName *origName) { JSContext *cx = f.cx; - const Value &rval = f.regs.sp[-1]; - const Value &lval = f.regs.sp[-2]; - if (!SetPropertyOperation(cx, f.pc(), lval, rval)) + Value rval = f.regs.sp[-1]; + Value &lref = f.regs.sp[-2]; + JSObject *obj = ValueToObject(cx, &lref); + if (!obj) THROW(); + do { + PropertyCache *cache = &JS_PROPERTY_CACHE(cx); + + /* + * Probe the property cache, specializing for two important + * set-property cases. First: + * + * function f(a, b, c) { + * var o = {p:a, q:b, r:c}; + * return o; + * } + * + * or similar real-world cases, which evolve a newborn native + * object predicatably through some bounded number of property + * additions. And second: + * + * o.p = x; + * + * in a frequently executed method or loop body, where p will + * (possibly after the first iteration) always exist in native + * object o. + */ + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + if (cache->testForSet(cx, f.pc(), obj, &entry, &obj2, &name)) { + /* + * Property cache hit, only partially confirmed by testForSet. We + * know that the entry applies to regs.pc and that obj's shape + * matches. + * + * The entry predicts a set either an existing "own" property, or + * on a prototype property that has a setter. + */ + const Shape *shape = entry->prop; + JS_ASSERT_IF(shape->isDataDescriptor(), shape->writable()); + JS_ASSERT_IF(shape->hasSlot(), entry->isOwnPropertyHit()); + + if (entry->isOwnPropertyHit() || + ((obj2 = obj->getProto()) && obj2->lastProperty() == entry->pshape)) { +#ifdef DEBUG + if (entry->isOwnPropertyHit()) { + JS_ASSERT(obj->nativeContains(cx, *shape)); + } else { + JS_ASSERT(obj2->nativeContains(cx, *shape)); + JS_ASSERT(entry->isPrototypePropertyHit()); + JS_ASSERT(entry->kshape != entry->pshape); + JS_ASSERT(!shape->hasSlot()); + } +#endif + + PCMETER(cache->pchits++); + PCMETER(cache->setpchits++); + NATIVE_SET(cx, obj, shape, entry, strict, &rval); + break; + } + PCMETER(cache->setpcmisses++); + + name = origName; + } else { + JS_ASSERT(name); + } + + if (entry && JS_LIKELY(!obj->getOps()->setProperty)) { + uintN defineHow; + JSOp op = JSOp(*f.pc()); + if (op == JSOP_SETMETHOD) + defineHow = DNP_CACHE_RESULT | DNP_SET_METHOD; + else if (op == JSOP_SETNAME) + defineHow = DNP_CACHE_RESULT | DNP_UNQUALIFIED; + else + defineHow = DNP_CACHE_RESULT; + if (!SetPropertyHelper(cx, obj, name, defineHow, &rval, strict)) + THROW(); + } else { + if (!obj->setProperty(cx, name, &rval, strict)) + THROW(); + } + } while (0); + f.regs.sp[-2] = f.regs.sp[-1]; } template void JS_FASTCALL stubs::SetName(VMFrame &f, PropertyName *origName); template void JS_FASTCALL stubs::SetName(VMFrame &f, PropertyName *origName); +template +void JS_FASTCALL +stubs::SetPropNoCache(VMFrame &f, PropertyName *name) +{ + JSObject *obj = ValueToObject(f.cx, &f.regs.sp[-2]); + if (!obj) + THROW(); + Value rval = f.regs.sp[-1]; + + if (!obj->setProperty(f.cx, name, &f.regs.sp[-1], strict)) + THROW(); + f.regs.sp[-2] = rval; +} + +template void JS_FASTCALL stubs::SetPropNoCache(VMFrame &f, PropertyName *name); +template void JS_FASTCALL stubs::SetPropNoCache(VMFrame &f, PropertyName *name); + +template +void JS_FASTCALL +stubs::SetGlobalNameNoCache(VMFrame &f, PropertyName *name) +{ + JSContext *cx = f.cx; + + Value rval = f.regs.sp[-1]; + Value &lref = f.regs.sp[-2]; + JSObject *obj = ValueToObject(cx, &lref); + if (!obj || !obj->setProperty(cx, name, &rval, strict)) + THROW(); + + f.regs.sp[-2] = f.regs.sp[-1]; +} + +template void JS_FASTCALL stubs::SetGlobalNameNoCache(VMFrame &f, PropertyName *name); +template void JS_FASTCALL stubs::SetGlobalNameNoCache(VMFrame &f, PropertyName *name); + template void JS_FASTCALL stubs::SetGlobalName(VMFrame &f, PropertyName *name) @@ -123,13 +263,90 @@ stubs::SetGlobalName(VMFrame &f, PropertyName *name) template void JS_FASTCALL stubs::SetGlobalName(VMFrame &f, PropertyName *name); template void JS_FASTCALL stubs::SetGlobalName(VMFrame &f, PropertyName *name); +static inline void +PushImplicitThis(VMFrame &f, JSObject *obj, Value &rval) +{ + Value thisv; + + if (!ComputeImplicitThis(f.cx, obj, rval, &thisv)) + return; + *f.regs.sp++ = thisv; +} + +static JSObject * +NameOp(VMFrame &f, JSObject *obj, bool callname) +{ + JSContext *cx = f.cx; + + Value rval; + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, f.pc(), obj, obj2, entry, name); + if (!name) { + NATIVE_GET(cx, obj, obj2, entry->prop, JSGET_METHOD_BARRIER, &rval, return NULL); + JS_ASSERT(obj->isGlobal() || IsCacheableNonGlobalScope(obj)); + } else { + JSProperty *prop; + bool global = (js_CodeSpec[*f.pc()].format & JOF_GNAME); + if (!FindPropertyHelper(cx, name, true, global, &obj, &obj2, &prop)) + return NULL; + if (!prop) { + /* Kludge to allow (typeof foo == "undefined") tests. */ + JSOp op2 = JSOp(f.pc()[JSOP_NAME_LENGTH]); + if (op2 == JSOP_TYPEOF) { + f.regs.sp++; + f.regs.sp[-1].setUndefined(); + return obj; + } + ReportAtomNotDefined(cx, name); + return NULL; + } + + /* Take the slow path if prop was not found in a native object. */ + if (!obj->isNative() || !obj2->isNative()) { + if (!obj->getProperty(cx, name, &rval)) + return NULL; + } else { + Shape *shape = (Shape *)prop; + JSObject *normalized = obj; + if (normalized->isWith() && !shape->hasDefaultGetter()) + normalized = &normalized->asWith().object(); + NATIVE_GET(cx, normalized, obj2, shape, JSGET_METHOD_BARRIER, &rval, return NULL); + } + + /* + * If this is an incop, update the property's types themselves, + * to capture the type effect on the intermediate value. + */ + if (rval.isUndefined() && (js_CodeSpec[*f.pc()].format & (JOF_INC|JOF_DEC))) + AddTypePropertyId(cx, obj, ATOM_TO_JSID(name), Type::UndefinedType()); + } + + *f.regs.sp++ = rval; + + if (callname) + PushImplicitThis(f, obj, rval); + + return obj; +} + void JS_FASTCALL stubs::Name(VMFrame &f) { - if (!NameOperation(f.cx, f.pc(), &f.regs.sp[0])) + if (!NameOp(f, &f.fp()->scopeChain(), false)) THROW(); } +void JS_FASTCALL +stubs::GetGlobalName(VMFrame &f) +{ + JSObject *globalObj = &f.fp()->scopeChain().global(); + if (!NameOp(f, globalObj, false)) + THROW(); +} + void JS_FASTCALL stubs::GetElem(VMFrame &f) { @@ -160,8 +377,7 @@ stubs::GetElem(VMFrame &f) JS_ASSERT(!lref.isMagic(JS_LAZY_ARGUMENTS)); } - bool isObject = lref.isObject(); - JSObject *obj = ValueToObject(cx, lref); + JSObject *obj = ValueToObject(cx, &lref); if (!obj) THROW(); @@ -199,13 +415,6 @@ stubs::GetElem(VMFrame &f) } } } - -#if JS_HAS_NO_SUCH_METHOD - if (*f.pc() == JSOP_CALLELEM && JS_UNLIKELY(rval.isPrimitive()) && isObject) { - if (!OnUnknownMethod(cx, obj, rref, &rval)) - THROW(); - } -#endif } static inline bool @@ -219,6 +428,40 @@ FetchElementId(VMFrame &f, JSObject *obj, const Value &idval, jsid &id, Value *v return !!js_InternNonIntElementId(f.cx, obj, idval, &id, vp); } +void JS_FASTCALL +stubs::CallElem(VMFrame &f) +{ + JSContext *cx = f.cx; + FrameRegs ®s = f.regs; + + /* Find the object on which to look for |this|'s properties. */ + Value thisv = regs.sp[-2]; + JSObject *thisObj = ValuePropertyBearer(cx, thisv, -2); + if (!thisObj) + THROW(); + + /* Fetch index and convert it to id suitable for use with thisObj. */ + jsid id; + if (!FetchElementId(f, thisObj, regs.sp[-1], id, ®s.sp[-2])) + THROW(); + + /* Get or set the element. */ + if (!js_GetMethod(cx, thisObj, id, JSGET_NO_METHOD_BARRIER, ®s.sp[-2])) + THROW(); + +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(regs.sp[-2].isPrimitive()) && thisv.isObject()) { + regs.sp[-2] = regs.sp[-1]; + regs.sp[-1].setObject(*thisObj); + if (!OnUnknownMethod(cx, regs.sp - 2)) + THROW(); + } else +#endif + { + regs.sp[-1] = thisv; + } +} + template void JS_FASTCALL stubs::SetElem(VMFrame &f) @@ -233,7 +476,7 @@ stubs::SetElem(VMFrame &f) JSObject *obj; jsid id; - obj = ValueToObject(cx, objval); + obj = ValueToObject(cx, &objval); if (!obj) THROW(); @@ -280,7 +523,7 @@ stubs::ToId(VMFrame &f) Value &objval = f.regs.sp[-2]; Value &idval = f.regs.sp[-1]; - JSObject *obj = ValueToObject(f.cx, objval); + JSObject *obj = ValueToObject(f.cx, &objval); if (!obj) THROW(); @@ -293,15 +536,22 @@ stubs::ToId(VMFrame &f) } void JS_FASTCALL -stubs::ImplicitThis(VMFrame &f, PropertyName *name) +stubs::CallName(VMFrame &f) { - JSObject *obj, *obj2; - JSProperty *prop; - if (!FindPropertyHelper(f.cx, name, false, false, &obj, &obj2, &prop)) + JSObject *obj = NameOp(f, &f.fp()->scopeChain(), true); + if (!obj) THROW(); +} - if (!ComputeImplicitThis(f.cx, obj, &f.regs.sp[0])) - THROW(); +/* + * Push the implicit this value, with the assumption that the callee + * (which is on top of the stack) was read as a property from the + * global object. + */ +void JS_FASTCALL +stubs::PushImplicitThisForGlobal(VMFrame &f) +{ + return PushImplicitThis(f, &f.fp()->scopeChain().global(), f.regs.sp[-1]); } void JS_FASTCALL @@ -1199,38 +1449,150 @@ stubs::Lambda(VMFrame &f, JSFunction *fun) return obj; } -void JS_FASTCALL -stubs::GetProp(VMFrame &f, PropertyName *name) +static bool JS_ALWAYS_INLINE +InlineGetProp(VMFrame &f) { JSContext *cx = f.cx; FrameRegs ®s = f.regs; + Value *vp = &f.regs.sp[-1]; + + if (vp->isMagic(JS_LAZY_ARGUMENTS)) { + JS_ASSERT(JSOp(*f.pc()) == JSOP_LENGTH); + regs.sp[-1] = Int32Value(regs.fp()->numActualArgs()); + return true; + } + + JSObject *obj = ValueToObject(f.cx, vp); + if (!obj) + return false; + Value rval; - if (!GetPropertyOperation(cx, f.pc(), f.regs.sp[-1], &rval)) - THROW(); + do { + JSObject *aobj = js_GetProtoIfDenseArray(obj); + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, f.pc(), aobj, obj2, entry, name); + if (!name) { + NATIVE_GET(cx, obj, obj2, entry->prop, JSGET_METHOD_BARRIER, &rval, return false); + break; + } + + if (JS_LIKELY(!aobj->getOps()->getProperty) + ? !GetPropertyHelper(cx, obj, name, JSGET_CACHE_RESULT | JSGET_METHOD_BARRIER, &rval) + : !obj->getProperty(cx, name, &rval)) + { + return false; + } + } while (false); regs.sp[-1] = rval; + return true; +} + +void JS_FASTCALL +stubs::GetProp(VMFrame &f) +{ + if (!InlineGetProp(f)) + THROW(); } void JS_FASTCALL stubs::GetPropNoCache(VMFrame &f, PropertyName *name) { JSContext *cx = f.cx; - FrameRegs ®s = f.regs; - const Value &lval = f.regs.sp[-1]; - - // Uncached lookups are only used for .prototype accesses at the start of constructors. - JS_ASSERT(lval.isObject()); - JS_ASSERT(name == cx->runtime->atomState.classPrototypeAtom); - - JSObject *obj = &lval.toObject(); - - Value rval; - if (!obj->getProperty(cx, name, &rval)) + Value *vp = &f.regs.sp[-1]; + JSObject *obj = ValueToObject(cx, vp); + if (!obj) THROW(); - regs.sp[-1] = rval; + if (!obj->getProperty(cx, name, vp)) + THROW(); + + /* Don't check for undefined, this is only used for 'prototype'. See ic::GetProp. */ +} + +void JS_FASTCALL +stubs::CallProp(VMFrame &f, PropertyName *origName) +{ + JSContext *cx = f.cx; + FrameRegs ®s = f.regs; + + Value lval; + lval = regs.sp[-1]; + + Value objv; + if (lval.isObject()) { + objv = lval; + } else { + GlobalObject &global = f.fp()->scopeChain().global(); + JSObject *pobj; + if (lval.isString()) { + pobj = global.getOrCreateStringPrototype(cx); + } else if (lval.isNumber()) { + pobj = global.getOrCreateNumberPrototype(cx); + } else if (lval.isBoolean()) { + pobj = global.getOrCreateBooleanPrototype(cx); + } else { + JS_ASSERT(lval.isNull() || lval.isUndefined()); + js_ReportIsNullOrUndefined(cx, -1, lval, NULL); + THROW(); + } + if (!pobj) + THROW(); + objv.setObject(*pobj); + } + + JSObject *aobj = js_GetProtoIfDenseArray(&objv.toObject()); + Value rval; + + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name; + JS_PROPERTY_CACHE(cx).test(cx, f.pc(), aobj, obj2, entry, name); + if (!name) { + NATIVE_GET(cx, &objv.toObject(), obj2, entry->prop, JSGET_NO_METHOD_BARRIER, &rval, + THROW()); + regs.sp++; + regs.sp[-2] = rval; + regs.sp[-1] = lval; + } else { + /* Cache miss: use the name loaded for us under PropertyCache::test. */ + regs.sp++; + regs.sp[-1].setNull(); + if (lval.isObject()) { + if (!GetMethod(cx, &objv.toObject(), name, + JS_LIKELY(!aobj->getOps()->getProperty) + ? JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER + : JSGET_NO_METHOD_BARRIER, + &rval)) + { + THROW(); + } + regs.sp[-1] = objv; + regs.sp[-2] = rval; + } else { + JS_ASSERT(!objv.toObject().getOps()->getProperty); + if (!GetPropertyHelper(cx, &objv.toObject(), name, + JSGET_CACHE_RESULT | JSGET_NO_METHOD_BARRIER, + &rval)) + { + THROW(); + } + regs.sp[-1] = lval; + regs.sp[-2] = rval; + } + } +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(rval.isPrimitive()) && regs.sp[-1].isObject()) { + regs.sp[-2].setString(origName); + if (!OnUnknownMethod(cx, regs.sp - 2)) + THROW(); + } +#endif } void JS_FASTCALL @@ -1256,15 +1618,40 @@ InitPropOrMethod(VMFrame &f, PropertyName *name, JSOp op) JSObject *obj = ®s.sp[-2].toObject(); JS_ASSERT(obj->isNative()); - /* Get the immediate property name into id. */ - jsid id = ATOM_TO_JSID(name); + /* + * Probe the property cache. + * + * We can not assume that the object created by JSOP_NEWINIT is still + * single-threaded as the debugger can access it from other threads. + * So check first. + * + * On a hit, if the cached shape has a non-default setter, it must be + * __proto__. If shape->previous() != obj->lastProperty(), there must be a + * repeated property name. The fast path does not handle these two cases. + */ + PropertyCacheEntry *entry; + JSObject *obj2; + PropertyName *name2; + if (JS_PROPERTY_CACHE(cx).testForSet(cx, f.pc(), obj, &entry, &obj2, &name2) && + entry->prop->hasDefaultSetter() && + entry->isOwnPropertyHit()) + { + JS_ASSERT(obj == obj2); + /* Fast path. Property cache hit. */ + obj->nativeSetSlotWithType(cx, entry->prop, rval); + } else { + PCMETER(JS_PROPERTY_CACHE(cx).inipcmisses++); - uintN defineHow = (op == JSOP_INITMETHOD) ? DNP_SET_METHOD : 0; - if (JS_UNLIKELY(name == cx->runtime->atomState.protoAtom) - ? !js_SetPropertyHelper(cx, obj, id, defineHow, &rval, false) - : !DefineNativeProperty(cx, obj, id, rval, NULL, NULL, - JSPROP_ENUMERATE, 0, 0, defineHow)) { - THROW(); + uintN defineHow = (op == JSOP_INITMETHOD) + ? DNP_CACHE_RESULT | DNP_SET_METHOD + : DNP_CACHE_RESULT; + if (JS_UNLIKELY(name == cx->runtime->atomState.protoAtom) + ? !SetPropertyHelper(cx, obj, name, defineHow, &rval, false) + : !DefineNativeProperty(cx, obj, name, rval, NULL, NULL, + JSPROP_ENUMERATE, 0, 0, defineHow)) + { + THROW(); + } } } @@ -1641,7 +2028,7 @@ stubs::DelProp(VMFrame &f, PropertyName *name) { JSContext *cx = f.cx; - JSObject *obj = ValueToObject(cx, f.regs.sp[-1]); + JSObject *obj = ValueToObject(cx, &f.regs.sp[-1]); if (!obj) THROW(); @@ -1661,7 +2048,7 @@ stubs::DelElem(VMFrame &f) { JSContext *cx = f.cx; - JSObject *obj = ValueToObject(cx, f.regs.sp[-2]); + JSObject *obj = ValueToObject(cx, &f.regs.sp[-2]); if (!obj) THROW(); diff --git a/js/src/methodjit/StubCalls.h b/js/src/methodjit/StubCalls.h index 48954c223c6..82fb6dd1792 100644 --- a/js/src/methodjit/StubCalls.h +++ b/js/src/methodjit/StubCalls.h @@ -115,19 +115,26 @@ void JS_FASTCALL Throw(VMFrame &f); void * JS_FASTCALL LookupSwitch(VMFrame &f, jsbytecode *pc); void * JS_FASTCALL TableSwitch(VMFrame &f, jsbytecode *origPc); -void JS_FASTCALL BindName(VMFrame &f, PropertyName *name); +void JS_FASTCALL BindName(VMFrame &f); +void JS_FASTCALL BindNameNoCache(VMFrame &f, PropertyName *name); JSObject * JS_FASTCALL BindGlobalName(VMFrame &f); template void JS_FASTCALL SetName(VMFrame &f, PropertyName *name); +template void JS_FASTCALL SetPropNoCache(VMFrame &f, PropertyName *name); template void JS_FASTCALL SetGlobalName(VMFrame &f, PropertyName *name); +template void JS_FASTCALL SetGlobalNameNoCache(VMFrame &f, PropertyName *name); void JS_FASTCALL Name(VMFrame &f); -void JS_FASTCALL GetProp(VMFrame &f, PropertyName *name); +void JS_FASTCALL GetProp(VMFrame &f); void JS_FASTCALL GetPropNoCache(VMFrame &f, PropertyName *name); void JS_FASTCALL GetElem(VMFrame &f); +void JS_FASTCALL CallElem(VMFrame &f); template void JS_FASTCALL SetElem(VMFrame &f); void JS_FASTCALL ToId(VMFrame &f); -void JS_FASTCALL ImplicitThis(VMFrame &f, PropertyName *name); +void JS_FASTCALL CallName(VMFrame &f); +void JS_FASTCALL PushImplicitThisForGlobal(VMFrame &f); void JS_FASTCALL GetUpvar(VMFrame &f, uint32_t index); +void JS_FASTCALL GetGlobalName(VMFrame &f); +void JS_FASTCALL CallProp(VMFrame &f, PropertyName *name); template void JS_FASTCALL DelProp(VMFrame &f, PropertyName *name); template void JS_FASTCALL DelElem(VMFrame &f); void JS_FASTCALL DelName(VMFrame &f, PropertyName *name);