From a95c44ef3467c1de22718c313d0c0aa3a55cca73 Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Wed, 3 Oct 2012 22:55:24 -0400 Subject: [PATCH] Bug 795803 - Enable ICing of JSNative and PropertyOp getters. (r=jandem) --- js/src/ion/CodeGenerator.cpp | 15 +- js/src/ion/IonBuilder.cpp | 13 +- js/src/ion/IonCaches.cpp | 496 ++++++++++++++++-- js/src/ion/IonCaches.h | 13 +- js/src/ion/IonMacroAssembler.h | 35 +- js/src/ion/MIR.h | 8 + js/src/ion/RegisterSets.h | 9 + js/src/ion/TypeOracle.cpp | 6 + js/src/ion/TypeOracle.h | 4 + js/src/ion/arm/MacroAssembler-arm.cpp | 15 + js/src/ion/arm/MacroAssembler-arm.h | 10 + js/src/ion/shared/MacroAssembler-x86-shared.h | 7 + js/src/ion/x64/MacroAssembler-x64.h | 5 + js/src/ion/x86/MacroAssembler-x86.h | 6 + 14 files changed, 580 insertions(+), 62 deletions(-) diff --git a/js/src/ion/CodeGenerator.cpp b/js/src/ion/CodeGenerator.cpp index 2a940f570a9..224d9190888 100644 --- a/js/src/ion/CodeGenerator.cpp +++ b/js/src/ion/CodeGenerator.cpp @@ -674,7 +674,7 @@ CodeGenerator::visitCallDOMNative(LCallDOMNative *call) uint32 safepointOffset; if (!masm.buildFakeExitFrame(argJSContext, &safepointOffset)) return false; - masm.enterFakeDOMFrame(ION_FRAME_DOMMETHOD); + masm.enterFakeExitFrame(ION_FRAME_DOMMETHOD); if (!markSafepointAt(safepointOffset, call)) return false; @@ -3222,7 +3222,7 @@ CodeGenerator::visitOutOfLineCacheGetProperty(OutOfLineCache *ool) RegisterSet liveRegs = ool->cache()->safepoint()->liveRegs(); LInstruction *ins = ool->cache(); - const MInstruction *mir = ins->mirRaw()->toInstruction(); + MInstruction *mir = ins->mirRaw()->toInstruction(); TypedOrValueRegister output; @@ -3233,6 +3233,7 @@ CodeGenerator::visitOutOfLineCacheGetProperty(OutOfLineCache *ool) // Note: because all registers are saved, the output register should be // a def register, else the result will be overriden by restoreLive(ins) PropertyName *name = NULL; + bool allowGetters = false; switch (ins->op()) { case LInstruction::LOp_InstanceOfO: case LInstruction::LOp_InstanceOfV: @@ -3244,11 +3245,15 @@ CodeGenerator::visitOutOfLineCacheGetProperty(OutOfLineCache *ool) name = ((LGetPropertyCacheT *) ins)->mir()->name(); objReg = ToRegister(ins->getOperand(0)); output = TypedOrValueRegister(mir->type(), ToAnyRegister(ins->getDef(0))); + JS_ASSERT(mir->isGetPropertyCache()); + allowGetters = mir->toGetPropertyCache()->allowGetters(); break; case LInstruction::LOp_GetPropertyCacheV: name = ((LGetPropertyCacheV *) ins)->mir()->name(); objReg = ToRegister(ins->getOperand(0)); output = TypedOrValueRegister(GetValueOutput(ins)); + JS_ASSERT(mir->isGetPropertyCache()); + allowGetters = mir->toGetPropertyCache()->allowGetters(); break; default: JS_NOT_REACHED("Bad instruction"); @@ -3257,7 +3262,7 @@ CodeGenerator::visitOutOfLineCacheGetProperty(OutOfLineCache *ool) IonCacheGetProperty cache(ool->getInlineJump(), ool->getInlineLabel(), masm.labelForPatch(), liveRegs, - objReg, name, output); + objReg, name, output, allowGetters); if (mir->resumePoint()) cache.setScriptedLocation(mir->block()->info().script(), mir->resumePoint()->pc()); @@ -4039,7 +4044,7 @@ CodeGenerator::visitGetDOMProperty(LGetDOMProperty *ins) uint32 safepointOffset; if (!masm.buildFakeExitFrame(JSContextReg, &safepointOffset)) return false; - masm.enterFakeDOMFrame(ION_FRAME_DOMGETTER); + masm.enterFakeExitFrame(ION_FRAME_DOMGETTER); if (!markSafepointAt(safepointOffset, ins)) return false; @@ -4105,7 +4110,7 @@ CodeGenerator::visitSetDOMProperty(LSetDOMProperty *ins) uint32 safepointOffset; if (!masm.buildFakeExitFrame(JSContextReg, &safepointOffset)) return false; - masm.enterFakeDOMFrame(ION_FRAME_DOMSETTER); + masm.enterFakeExitFrame(ION_FRAME_DOMSETTER); if (!markSafepointAt(safepointOffset, ins)) return false; diff --git a/js/src/ion/IonBuilder.cpp b/js/src/ion/IonBuilder.cpp index 4605b61792f..cdb29630ba4 100644 --- a/js/src/ion/IonBuilder.cpp +++ b/js/src/ion/IonBuilder.cpp @@ -5878,6 +5878,7 @@ IonBuilder::jsop_getprop(HandlePropertyName name) return makeCallBarrier(getter, 0, false, types, barrier); } + bool accessGetter = oracle->propertyReadAccessGetter(script_, pc); if (unary.ival == MIRType_Object) { MIRType rvalType = MIRType_Value; if (!barrier && !IsNullOrUndefined(unary.rval)) @@ -5918,11 +5919,19 @@ IonBuilder::jsop_getprop(HandlePropertyName name) load->setIdempotent(); } - ins = load; if (JSOp(*pc) == JSOP_CALLPROP) { if (!annotateGetPropertyCache(cx, obj, load, unaryTypes.inTypes, types)) return false; } + + // If the cache is known to access getters, then enable generation of + // getter stubs and set its result type to value. + if (accessGetter) { + load->setResultType(MIRType_Value); + load->setAllowGetters(); + } + + ins = load; } else { ins = MCallGetProperty::New(obj, name); } @@ -5933,7 +5942,7 @@ IonBuilder::jsop_getprop(HandlePropertyName name) if (ins->isEffectful() && !resumeAfter(ins)) return false; - if (ins->isCallGetProperty()) + if (ins->isCallGetProperty() || accessGetter) monitorResult(ins, types); return pushTypeBarrier(ins, types, barrier); } diff --git a/js/src/ion/IonCaches.cpp b/js/src/ion/IonCaches.cpp index 7059df38cb6..52daefb54a4 100644 --- a/js/src/ion/IonCaches.cpp +++ b/js/src/ion/IonCaches.cpp @@ -80,6 +80,23 @@ CodeOffsetJump::fixup(MacroAssembler *masm) static const size_t MAX_STUBS = 16; +static bool +IsCacheableListBase(JSObject *obj) +{ + if (!obj->isProxy()) + return false; + + BaseProxyHandler *handler = GetProxyHandler(obj); + + if (handler->family() != GetListBaseHandlerFamily()) + return false; + + if (obj->numFixedSlots() <= GetListBaseExpandoSlot()) + return false; + + return true; +} + static void GeneratePrototypeGuards(JSContext *cx, MacroAssembler &masm, JSObject *obj, JSObject *holder, Register objectReg, Register scratchReg, Label *failures) @@ -94,7 +111,10 @@ GeneratePrototypeGuards(JSContext *cx, MacroAssembler &masm, JSObject *obj, JSOb masm.branchPtr(Assembler::NotEqual, proto, ImmGCPtr(obj->getProto()), failures); } - JSObject *pobj = obj->getProto(); + JSObject *pobj = IsCacheableListBase(obj) + ? obj->getTaggedProto().toObjectOrNull() + : obj->getProto(); + JS_ASSERT(pobj); while (pobj != holder) { if (pobj->hasUncacheableProto()) { if (pobj->hasSingletonType()) { @@ -112,14 +132,71 @@ GeneratePrototypeGuards(JSContext *cx, MacroAssembler &masm, JSObject *obj, JSOb } } +static bool +IsCacheableProtoChain(JSObject *obj, JSObject *holder) +{ + while (obj != holder) { + /* + * We cannot assume that we find the holder object on the prototype + * chain and must check for null proto. The prototype chain can be + * altered during the lookupProperty call. + */ + JSObject *proto = IsCacheableListBase(obj) + ? obj->getTaggedProto().toObjectOrNull() + : obj->getProto(); + if (!proto || !proto->isNative()) + return false; + obj = proto; + } + return true; +} + +static bool +IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, const Shape *shape) +{ + if (!shape || !IsCacheableProtoChain(obj, holder)) + return false; + + if (!shape->hasSlot() || !shape->hasDefaultGetter()) + return false; + + return true; +} + +static bool +IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, const Shape *shape) +{ + if (!shape || !IsCacheableProtoChain(obj, holder)) + return false; + + if (!shape->hasGetterValue() || !shape->getterValue().isObject()) + return false; + + return shape->getterValue().toObject().isFunction() && + shape->getterValue().toObject().toFunction()->isNative(); +} + +static bool +IsCacheableGetPropCallPropertyOp(JSObject *obj, JSObject *holder, const Shape *shape) +{ + if (!shape || !IsCacheableProtoChain(obj, holder)) + return false; + + if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter()) + return false; + + return true; +} + struct GetNativePropertyStub { CodeOffsetJump exitOffset; CodeOffsetJump rejoinOffset; + CodeOffsetLabel stubCodePatchOffset; - void generate(JSContext *cx, MacroAssembler &masm, JSObject *obj, JSObject *holder, - const Shape *shape, Register object, TypedOrValueRegister output, - RepatchLabel *failures, Label *nonRepatchFailures = NULL) + void generateReadSlot(JSContext *cx, MacroAssembler &masm, JSObject *obj, PropertyName *propName, + JSObject *holder, const Shape *shape, Register object, TypedOrValueRegister output, + RepatchLabel *failures, Label *nonRepatchFailures = NULL) { // If there's a single jump to |failures|, we can patch the shape guard // jump directly. Otherwise, jump to the end of the stub, so there's a @@ -149,6 +226,7 @@ struct GetNativePropertyStub } } + // Generate prototype guards. Register holderReg; if (obj != holder) { // Note: this may clobber the object register if it's used as scratch. @@ -165,6 +243,7 @@ struct GetNativePropertyStub holderReg = object; } + // Slot access. if (holder->isFixedSlot(shape->slot())) { Address addr(holderReg, JSObject::getFixedSlotOffset(shape->slot())); masm.loadTypedOrValue(addr, output); @@ -196,17 +275,284 @@ struct GetNativePropertyStub masm.bind(failures); } } + + bool generateCallGetter(JSContext *cx, MacroAssembler &masm, JSObject *obj, + PropertyName *propName, JSObject *holder, const Shape *shape, + RegisterSet &liveRegs, Register object, TypedOrValueRegister output, + types::TypeSet *outputTypes, void *returnAddr, jsbytecode *pc, + RepatchLabel *failures, Label *nonRepatchFailures = NULL) + { + // Initial shape check. + Label stubFailure; + masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfShape()), + ImmGCPtr(obj->lastProperty()), &stubFailure); + + // If this is a stub for a ListBase object, guard the following: + // 1. The object is a ListBase. + // 2. The object does not have expando properties, or has an expando + // which is known to not have the desired property. + if (IsCacheableListBase(obj)) { + Address handlerAddr(object, JSObject::getFixedSlotOffset(JSSLOT_PROXY_HANDLER)); + Address expandoAddr(object, JSObject::getFixedSlotOffset(GetListBaseExpandoSlot())); + + // Check that object is a ListBase. + masm.branchPrivatePtr(Assembler::NotEqual, handlerAddr, ImmWord(GetProxyHandler(obj)), &stubFailure); + + // For the remaining code, we need to reserve some registers to load a value. + // This is ugly, but unvaoidable. + RegisterSet listBaseRegSet(RegisterSet::All()); + listBaseRegSet.take(AnyRegister(object)); + ValueOperand tempVal = listBaseRegSet.takeValueOperand(); + masm.pushValue(tempVal); + + Label failListBaseCheck; + Label listBaseOk; + + Value expandoVal = obj->getFixedSlot(GetListBaseExpandoSlot()); + JSObject *expando = expandoVal.isObject() ? &(expandoVal.toObject()) : NULL; + JS_ASSERT_IF(expando, expando->isNative() && expando->getProto() == NULL); + + masm.loadValue(expandoAddr, tempVal); + if (expando && expando->nativeLookupNoAllocation(propName)) { + // Reference object has an expando that doesn't define the name. + // Check incoming object's expando and make sure it's an object. + + // If checkExpando is true, we'll temporarily use register(s) for a ValueOperand. + // If we do that, we save the register(s) on stack before use and pop them + // on both exit paths. + + masm.branchTestObject(Assembler::NotEqual, tempVal, &failListBaseCheck); + masm.extractObject(tempVal, tempVal.scratchReg()); + masm.branchPtr(Assembler::Equal, + Address(tempVal.scratchReg(), JSObject::offsetOfShape()), + ImmGCPtr(expando->lastProperty()), + &listBaseOk); + } else { + // Reference object has no expando. Check incoming object and ensure + // it has no expando. + masm.branchTestUndefined(Assembler::Equal, tempVal, &listBaseOk); + } + + // Failure case: restore the tempVal registers and jump to failures. + masm.bind(&failListBaseCheck); + masm.popValue(tempVal); + masm.jump(&stubFailure); + + // Success case: restore the tempval and proceed. + masm.bind(&listBaseOk); + masm.popValue(tempVal); + } + + // Reserve scratch register for prototype guards. + bool restoreScratch = false; + Register scratchReg = Register::FromCode(0); // Quell compiler warning. + + // If we need a scratch register, use either an output register or the object + // register (and restore it afterwards). After this point, we cannot jump + // directly to |stubFailure| since we may still have to pop the object register. + Label prototypeFailures; + JS_ASSERT(output.hasValue()); + scratchReg = output.valueReg().scratchReg(); + + // Note: this may clobber the object register if it's used as scratch. + if (obj != holder) + GeneratePrototypeGuards(cx, masm, obj, holder, object, scratchReg, &prototypeFailures); + + // Guard on the holder's shape. + Register holderReg = scratchReg; + masm.movePtr(ImmGCPtr(holder), holderReg); + masm.branchPtr(Assembler::NotEqual, + Address(holderReg, JSObject::offsetOfShape()), + ImmGCPtr(holder->lastProperty()), + &prototypeFailures); + + if (restoreScratch) + masm.pop(scratchReg); + + // Now we're good to go to invoke the native call. + + // saveLive() + masm.PushRegsInMask(liveRegs); + + // Remaining registers should basically be free, but we need to use |object| still + // so leave it alone. + RegisterSet regSet(RegisterSet::All()); + regSet.take(AnyRegister(object)); + + // This is a slower stub path, and we're going to be doing a call anyway. Don't need + // to try so hard to not use the stack. Scratch regs are just taken from the register + // set not including the input, current value saved on the stack, and restored when + // we're done with it. + scratchReg = regSet.takeGeneral(); + Register argJSContextReg = regSet.takeGeneral(); + Register argUintNReg = regSet.takeGeneral(); + Register argVpReg = regSet.takeGeneral(); + + // Shape has a getter function. + bool callNative = IsCacheableGetPropCallNative(obj, holder, shape); + JS_ASSERT_IF(!callNative, IsCacheableGetPropCallPropertyOp(obj, holder, shape)); + + // TODO: ensure stack is aligned? + DebugOnly initialStack = masm.framePushed(); + masm.checkStackAlignment(); + + Label success, exception; + + // Push the IonCode pointer for the stub we're generating. + // WARNING: + // WARNING: If IonCode ever becomes relocatable, the following code is incorrect. + // WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr. + // WARNING: This is not a marking issue since the stub IonCode won't be collected + // WARNING: between the time it's called and when we get here, but it would fail + // WARNING: if the IonCode object ever moved, since we'd be rooting a nonsense + // WARNING: value here. + // WARNING: + stubCodePatchOffset = masm.pushWithPatch(ImmWord(uintptr_t(-1))); + // Manually adjust framePushed to account for this push which is not otherwise + // accounted for. + masm.setFramePushed(masm.framePushed() + sizeof(uintptr_t)); + + if (callNative) { + JS_ASSERT(shape->hasGetterValue() && shape->getterValue().isObject() && + shape->getterValue().toObject().isFunction()); + JSFunction *target = shape->getterValue().toObject().toFunction(); + + JS_ASSERT(target); + JS_ASSERT(target->isNative()); + + // Native functions have the signature: + // bool (*)(JSContext *, unsigned, Value *vp) + // Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward + // are the function arguments. + + // Construct vp array: + // Push object value for |this| + masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object))); + // Push callee/outparam. + masm.Push(ObjectValue(*target)); + + // Preload arguments into registers. + masm.loadJSContext(argJSContextReg); + masm.move32(Imm32(0), argUintNReg); + masm.movePtr(StackPointer, argVpReg); + + if (!masm.buildOOLFakeExitFrame(returnAddr)) + return false; + masm.enterFakeExitFrame(ION_FRAME_OOL_NATIVE_GETTER); + + // Construct and execute call. + masm.setupUnalignedABICall(3, scratchReg); + masm.passABIArg(argJSContextReg); + masm.passABIArg(argUintNReg); + masm.passABIArg(argVpReg); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->native())); + + // Test for failure. + masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception); + + // Load the outparam vp[0] into output register(s). + masm.loadValue( + Address(StackPointer, IonOOLNativeGetterExitFrameLayout::offsetOfResult()), + JSReturnOperand); + } else { + Register argObjReg = argUintNReg; + Register argIdReg = regSet.takeGeneral(); + + PropertyOp target = shape->getterOp(); + JS_ASSERT(target); + // JSPropertyOp: JSBool fn(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp) + + // Push args on stack first so we can take pointers to make handles. + masm.Push(UndefinedValue()); + masm.movePtr(StackPointer, argVpReg); + + // push canonical jsid from shape instead of propertyname. + jsid propId; + if (!shape->getUserId(cx, &propId)) + return false; + masm.Push(propId, scratchReg); + masm.movePtr(StackPointer, argIdReg); + + masm.Push(object); + masm.movePtr(StackPointer, argObjReg); + + masm.loadJSContext(argJSContextReg); + + if (!masm.buildOOLFakeExitFrame(returnAddr)) + return false; + masm.enterFakeExitFrame(ION_FRAME_OOL_PROPERTY_OP); + + // Make the call. + masm.setupUnalignedABICall(4, scratchReg); + masm.passABIArg(argJSContextReg); + masm.passABIArg(argObjReg); + masm.passABIArg(argIdReg); + masm.passABIArg(argVpReg); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target)); + + // Test for failure. + masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception); + + // Load the outparam vp[0] into output register(s). + masm.loadValue( + Address(StackPointer, IonOOLPropertyOpExitFrameLayout::offsetOfResult()), + JSReturnOperand); + } + + // If generating getter call stubs, then return type MUST have been generalized + // to MIRType_Value. + masm.jump(&success); + + // Handle exception case. + masm.bind(&exception); + masm.handleException(); + + // Handle success case. + masm.bind(&success); + masm.storeCallResultValue(output); + + // The next instruction is removing the footer of the exit frame, so there + // is no need for leaveFakeExitFrame. + + // Move the StackPointer back to its original location, unwinding the native exit frame. + if (callNative) + masm.adjustStack(IonOOLNativeGetterExitFrameLayout::Size()); + else + masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size()); + JS_ASSERT(masm.framePushed() == initialStack); + + // restoreLive() + masm.PopRegsInMask(liveRegs); + + // Rejoin jump. + RepatchLabel rejoin_; + rejoinOffset = masm.jumpWithPatch(&rejoin_); + masm.bind(&rejoin_); + + // Exit jump. + masm.bind(&prototypeFailures); + if (restoreScratch) + masm.pop(scratchReg); + masm.bind(&stubFailure); + if (nonRepatchFailures) + masm.bind(nonRepatchFailures); + RepatchLabel exit_; + exitOffset = masm.jumpWithPatch(&exit_); + masm.bind(&exit_); + + return true; + } }; bool -IonCacheGetProperty::attachNative(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, - const Shape *shape) +IonCacheGetProperty::attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, + const Shape *shape) { MacroAssembler masm; RepatchLabel failures; GetNativePropertyStub getprop; - getprop.generate(cx, masm, obj, holder, shape, object(), output(), &failures); + getprop.generateReadSlot(cx, masm, obj, name(), holder, shape, object(), output(), &failures); Linker linker(masm); IonCode *code = linker.newCode(cx); @@ -233,54 +579,106 @@ IonCacheGetProperty::attachNative(JSContext *cx, IonScript *ion, JSObject *obj, return true; } -static bool -IsCacheableProtoChain(JSObject *obj, JSObject *holder) +bool +IonCacheGetProperty::attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, + JSObject *holder, const Shape *shape, + const SafepointIndex *safepointIndex, void *returnAddr) { - while (obj != holder) { - /* - * We cannot assume that we find the holder object on the prototype - * chain and must check for null proto. The prototype chain can be - * altered during the lookupProperty call. - */ - JSObject *proto = obj->getProto(); - if (!proto || !proto->isNative()) - return false; - obj = proto; - } - return true; -} + MacroAssembler masm; + RepatchLabel failures; -static bool -IsCacheableGetProp(JSObject *obj, JSObject *holder, const Shape *shape) -{ - return (shape && - IsCacheableProtoChain(obj, holder) && - shape->hasSlot() && - shape->hasDefaultGetter()); + JS_ASSERT(!idempotent()); + JS_ASSERT(allowGetters()); + + // Need to set correct framePushed on the masm so that exit frame descriptors are + // properly constructed. + masm.setFramePushed(script->ionScript()->frameSize()); + + // Generating a call getter may need the pushed typeset. + types::StackTypeSet *outputTypes = script->analysis()->pushedTypes(pc, 0); + + GetNativePropertyStub getprop; + if (!getprop.generateCallGetter(cx, masm, obj, name(), holder, shape, liveRegs, + object(), output(), outputTypes, returnAddr, pc, + &failures)) + { + return false; + } + + Linker linker(masm); + IonCode *code = linker.newCode(cx); + if (!code) + return false; + + getprop.rejoinOffset.fixup(&masm); + getprop.exitOffset.fixup(&masm); + getprop.stubCodePatchOffset.fixup(&masm); + + if (ion->invalidated()) + return true; + + Assembler::patchDataWithValueCheck(CodeLocationLabel(code, getprop.stubCodePatchOffset), + ImmWord(uintptr_t(code)), ImmWord(uintptr_t(-1))); + + CodeLocationJump rejoinJump(code, getprop.rejoinOffset); + CodeLocationJump exitJump(code, getprop.exitOffset); + CodeLocationJump lastJump_ = lastJump(); + PatchJump(lastJump_, CodeLocationLabel(code)); + PatchJump(rejoinJump, rejoinLabel()); + PatchJump(exitJump, cacheLabel()); + updateLastJump(exitJump); + + IonSpew(IonSpew_InlineCaches, "Generated native GETPROP stub at %p %s", code->raw(), + idempotent() ? "(idempotent)" : "(not idempotent)"); + + return true; } static bool TryAttachNativeStub(JSContext *cx, IonScript *ion, IonCacheGetProperty &cache, HandleObject obj, - HandlePropertyName name, bool *isCacheableNative) + HandlePropertyName name, const SafepointIndex *safepointIndex, + void *returnAddr, bool *isCacheable) { - JS_ASSERT(!*isCacheableNative); + JS_ASSERT(!*isCacheable); - if (!obj->isNative()) + RootedObject checkObj(cx, obj); + bool isListBase = IsCacheableListBase(obj); + if (isListBase) + checkObj = obj->getTaggedProto().toObjectOrNull(); + + if (!checkObj || !checkObj->isNative()) return true; // If the cache is idempotent, watch out for resolve hooks or non-native // objects on the proto chain. We check this before calling lookupProperty, // to make sure no effectful lookup hooks or resolve hooks are called. - if (cache.idempotent() && !obj->hasIdempotentProtoChain()) + if (cache.idempotent() && !checkObj->hasIdempotentProtoChain()) return true; RootedShape shape(cx); RootedObject holder(cx); - if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape)) + if (!JSObject::lookupProperty(cx, checkObj, name, &holder, &shape)) return false; - if (!IsCacheableGetProp(obj, holder, shape)) + // Check what kind of cache stub we can emit: either a slot read, + // or a getter call. + bool readSlot = false; + bool callGetter = false; + + if (IsCacheableGetPropReadSlot(checkObj, holder, shape)) { + readSlot = true; + } else if (IsCacheableGetPropCallNative(checkObj, holder, shape) || + IsCacheableGetPropCallPropertyOp(checkObj, holder, shape)) + { + // Don't enable getter call if cache is idempotent, since + // they can be effectful. + if (!cache.idempotent() && cache.allowGetters()) + callGetter = true; + } + + // Only continue if one of the cache methods is viable. + if (!readSlot && !callGetter) return true; // TI infers the possible types of native object properties. There's one @@ -295,13 +693,19 @@ TryAttachNativeStub(JSContext *cx, IonScript *ion, return true; } - *isCacheableNative = true; + *isCacheable = true; + + // readSlot and callGetter are mutually exclusive + JS_ASSERT_IF(readSlot, !callGetter); + JS_ASSERT_IF(callGetter, !readSlot); if (cache.stubCount() < MAX_STUBS) { cache.incrementStubCount(); - if (!cache.attachNative(cx, ion, obj, holder, shape)) - return false; + if (readSlot) + return cache.attachReadSlot(cx, ion, obj, holder, shape); + else + return cache.attachCallGetter(cx, ion, obj, holder, shape, safepointIndex, returnAddr); } return true; @@ -311,7 +715,9 @@ bool js::ion::GetPropertyCache(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp) { AutoFlushCache afc ("GetPropertyCache"); - JSScript *topScript = GetTopIonJSScript(cx); + const SafepointIndex *safepointIndex; + void *returnAddr; + JSScript *topScript = GetTopIonJSScript(cx, &safepointIndex, &returnAddr); IonScript *ion = topScript->ionScript(); IonCacheGetProperty &cache = ion->getCache(cacheIndex).toGetProperty(); @@ -331,11 +737,11 @@ js::ion::GetPropertyCache(JSContext *cx, size_t cacheIndex, HandleObject obj, Mu // For now, just stop generating new stubs once we hit the stub count // limit. Once we can make calls from within generated stubs, a new call // stub will be generated instead and the previous stubs unlinked. - bool isCacheableNative = false; - if (!TryAttachNativeStub(cx, ion, cache, obj, name, &isCacheableNative)) + bool isCacheable = false; + if (!TryAttachNativeStub(cx, ion, cache, obj, name, safepointIndex, returnAddr, &isCacheable)) return false; - if (cache.idempotent() && !isCacheableNative) { + if (cache.idempotent() && !isCacheable) { // Invalidate the cache if the property was not found, or was found on // a non-native object. This ensures: // 1) The property read has no observable side-effects. @@ -683,7 +1089,7 @@ IonCacheGetElement::attachGetProp(JSContext *cx, IonScript *ion, HandleObject ob if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape)) return false; - if (!IsCacheableGetProp(obj, holder, shape)) { + if (!IsCacheableGetPropReadSlot(obj, holder, shape)) { IonSpew(IonSpew_InlineCaches, "GETELEM uncacheable property"); return true; } @@ -699,7 +1105,7 @@ IonCacheGetElement::attachGetProp(JSContext *cx, IonScript *ion, HandleObject ob masm.branchTestValue(Assembler::NotEqual, val, idval, &nonRepatchFailures); GetNativePropertyStub getprop; - getprop.generate(cx, masm, obj, holder, shape, object(), output(), &failures, &nonRepatchFailures); + getprop.generateReadSlot(cx, masm, obj, name, holder, shape, object(), output(), &failures, &nonRepatchFailures); Linker linker(masm); IonCode *code = linker.newCode(cx); @@ -1148,7 +1554,7 @@ IsCacheableName(JSContext *cx, HandleObject scopeChain, HandleObject obj, Handle if (obj->isGlobal()) { // Support only simple property lookups. - if (!IsCacheableGetProp(obj, holder, shape)) + if (!IsCacheableGetPropReadSlot(obj, holder, shape)) return false; } else if (obj->isCall()) { if (!shape->hasDefaultGetter()) diff --git a/js/src/ion/IonCaches.h b/js/src/ion/IonCaches.h index d753493d3f4..411e6c8c2e8 100644 --- a/js/src/ion/IonCaches.h +++ b/js/src/ion/IonCaches.h @@ -111,6 +111,7 @@ class IonCache Register object; PropertyName *name; TypedOrValueRegisterSpace output; + bool allowGetters; } getprop; struct { Register object; @@ -256,20 +257,26 @@ class IonCacheGetProperty : public IonCache CodeOffsetLabel cacheLabel, RegisterSet liveRegs, Register object, PropertyName *name, - TypedOrValueRegister output) + TypedOrValueRegister output, + bool allowGetters) { init(GetProperty, liveRegs, initialJump, rejoinLabel, cacheLabel); u.getprop.object = object; u.getprop.name = name; u.getprop.output.data() = output; + u.getprop.allowGetters = allowGetters; } Register object() const { return u.getprop.object; } PropertyName *name() const { return u.getprop.name; } TypedOrValueRegister output() const { return u.getprop.output.data(); } + bool allowGetters() const { return u.getprop.allowGetters; } - bool attachNative(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, - const Shape *shape); + bool attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, + const Shape *shape); + bool attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder, + const Shape *shape, + const SafepointIndex *safepointIndex, void *returnAddr); }; class IonCacheSetProperty : public IonCache diff --git a/js/src/ion/IonMacroAssembler.h b/js/src/ion/IonMacroAssembler.h index 2e9656d85f6..547de393401 100644 --- a/js/src/ion/IonMacroAssembler.h +++ b/js/src/ion/IonMacroAssembler.h @@ -271,6 +271,33 @@ class MacroAssembler : public MacroAssemblerSpecific using MacroAssemblerSpecific::Push; + void Push(jsid id, Register scratchReg) { + if (JSID_IS_GCTHING(id)) { + // If we're pushing a gcthing, then we can't just push the tagged jsid + // value since the GC won't have any idea that the push instruction + // carries a reference to a gcthing. Need to unpack the pointer, + // push it using ImmGCPtr, and then rematerialize the id at runtime. + + // double-checking this here to ensure we don't lose sync + // with implementation of JSID_IS_GCTHING. + if (JSID_IS_OBJECT(id)) { + JSObject *obj = JSID_TO_OBJECT(id); + movePtr(ImmGCPtr(obj), scratchReg); + JS_ASSERT(((size_t)obj & JSID_TYPE_MASK) == 0); + orPtr(Imm32(JSID_TYPE_OBJECT), scratchReg); + Push(scratchReg); + } else { + JSString *str = JSID_TO_STRING(id); + JS_ASSERT(((size_t)str & JSID_TYPE_MASK) == 0); + JS_ASSERT(JSID_TYPE_STRING == 0x0); + Push(ImmGCPtr(str)); + } + } else { + size_t idbits = JSID_BITS(id); + Push(ImmWord(idbits)); + } + } + void Push(TypedOrValueRegister v) { if (v.hasValue()) Push(v.valueReg()); @@ -447,13 +474,7 @@ class MacroAssembler : public MacroAssemblerSpecific // Push VMFunction pointer, to mark arguments. Push(ImmWord(f)); } - void enterFakeExitFrame() { - linkExitFrame(); - Push(ImmWord(uintptr_t(NULL))); - Push(ImmWord(uintptr_t(NULL))); - } - - void enterFakeDOMFrame(void *codeVal) { + void enterFakeExitFrame(void *codeVal = NULL) { linkExitFrame(); Push(ImmWord(uintptr_t(codeVal))); Push(ImmWord(uintptr_t(NULL))); diff --git a/js/src/ion/MIR.h b/js/src/ion/MIR.h index 548fcb70d3f..f9607e16508 100644 --- a/js/src/ion/MIR.h +++ b/js/src/ion/MIR.h @@ -4078,6 +4078,7 @@ class MGetPropertyCache { CompilerRootPropertyName name_; bool idempotent_; + bool allowGetters_; InlinePropertyTable *inlinePropertyTable_; @@ -4085,6 +4086,7 @@ class MGetPropertyCache : MUnaryInstruction(obj), name_(name), idempotent_(false), + allowGetters_(false), inlinePropertyTable_(NULL) { setResultType(MIRType_Value); @@ -4129,6 +4131,12 @@ class MGetPropertyCache idempotent_ = true; setMovable(); } + bool allowGetters() const { + return allowGetters_; + } + void setAllowGetters() { + allowGetters_ = true; + } TypePolicy *typePolicy() { return this; } bool congruentTo(MDefinition * const &ins) const { diff --git a/js/src/ion/RegisterSets.h b/js/src/ion/RegisterSets.h index c511d0c6fef..b82f09f70f3 100644 --- a/js/src/ion/RegisterSets.h +++ b/js/src/ion/RegisterSets.h @@ -449,6 +449,15 @@ class RegisterSet { Register takeGeneral() { return gpr_.takeAny(); } + ValueOperand takeValueOperand() { +#if defined(JS_NUNBOX32) + return ValueOperand(takeGeneral(), takeGeneral()); +#elif defined(JS_PUNBOX64) + return ValueOperand(takeGeneral()); +#else +#error "Bad architecture" +#endif + } void take(const AnyRegister ®) { if (reg.isFloat()) fpu_.take(reg.fpu()); diff --git a/js/src/ion/TypeOracle.cpp b/js/src/ion/TypeOracle.cpp index 3260a61a236..6d63ec91fb3 100644 --- a/js/src/ion/TypeOracle.cpp +++ b/js/src/ion/TypeOracle.cpp @@ -280,6 +280,12 @@ TypeInferenceOracle::propertyReadIdempotent(JSScript *script, jsbytecode *pc, Ha return true; } +bool +TypeInferenceOracle::propertyReadAccessGetter(JSScript *script, jsbytecode *pc) +{ + return script->analysis()->getCode(pc).accessGetter; +} + bool TypeInferenceOracle::elementReadIsDenseArray(JSScript *script, jsbytecode *pc) { diff --git a/js/src/ion/TypeOracle.h b/js/src/ion/TypeOracle.h index 1e8819d3989..20d1a359af7 100644 --- a/js/src/ion/TypeOracle.h +++ b/js/src/ion/TypeOracle.h @@ -84,6 +84,9 @@ class TypeOracle virtual bool propertyReadIdempotent(JSScript *script, jsbytecode *pc, HandleId id) { return false; } + virtual bool propertyReadAccessGetter(JSScript *script, jsbytecode *pc) { + return false; + } virtual types::HeapTypeSet *globalPropertyWrite(JSScript *script, jsbytecode *pc, jsid id, bool *canSpecialize) { *canSpecialize = true; @@ -234,6 +237,7 @@ class TypeInferenceOracle : public TypeOracle types::StackTypeSet *propertyRead(JSScript *script, jsbytecode *pc); types::StackTypeSet *propertyReadBarrier(JSScript *script, jsbytecode *pc); bool propertyReadIdempotent(JSScript *script, jsbytecode *pc, HandleId id); + bool propertyReadAccessGetter(JSScript *script, jsbytecode *pc); types::HeapTypeSet *globalPropertyWrite(JSScript *script, jsbytecode *pc, jsid id, bool *canSpecialize); types::StackTypeSet *returnTypeSet(JSScript *script, jsbytecode *pc, types::StackTypeSet **barrier); types::StackTypeSet *getCallTarget(JSScript *caller, uint32 argc, jsbytecode *pc); diff --git a/js/src/ion/arm/MacroAssembler-arm.cpp b/js/src/ion/arm/MacroAssembler-arm.cpp index 40ab3ba4224..0abf3666e24 100644 --- a/js/src/ion/arm/MacroAssembler-arm.cpp +++ b/js/src/ion/arm/MacroAssembler-arm.cpp @@ -1343,6 +1343,21 @@ MacroAssemblerARMCompat::buildFakeExitFrame(const Register &scratch, uint32 *off return true; } +bool +MacroAssemblerARMCompat::buildOOLFakeExitFrame(void *fakeReturnAddr) +{ + DebugOnly initialDepth = framePushed(); + uint32 descriptor = MakeFrameDescriptor(framePushed(), IonFrame_JS); + + Push(Imm32(descriptor)); // descriptor_ + + enterNoPool(); + push(Imm32((uint32) fakeReturnAddr)); + leaveNoPool(); + + return true; +} + void MacroAssemblerARMCompat::callWithExitFrame(IonCode *target) { diff --git a/js/src/ion/arm/MacroAssembler-arm.h b/js/src/ion/arm/MacroAssembler-arm.h index f52f1262b05..0de0a1f4c84 100644 --- a/js/src/ion/arm/MacroAssembler-arm.h +++ b/js/src/ion/arm/MacroAssembler-arm.h @@ -602,6 +602,15 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void branchPtr(Condition cond, const Address &lhs, Register rhs, Label *label) { branch32(cond, lhs, rhs, label); } + + void branchPrivatePtr(Condition cond, const Address &lhs, ImmWord ptr, Label *label) { + branchPtr(cond, lhs, ptr, label); + } + + void branchPrivatePtr(Condition cond, Register lhs, ImmWord ptr, Label *label) { + branchPtr(cond, lhs, ptr, label); + } + template void branchTestDouble(Condition cond, const T & t, Label *label) { Condition c = testDouble(cond, t); @@ -870,6 +879,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM // Builds an exit frame on the stack, with a return address to an internal // non-function. Returns offset to be passed to markSafepointAt(). bool buildFakeExitFrame(const Register &scratch, uint32 *offset); + bool buildOOLFakeExitFrame(void *fakeReturnAddr); void callWithExitFrame(IonCode *target); void callWithExitFrame(IonCode *target, Register dynStack); diff --git a/js/src/ion/shared/MacroAssembler-x86-shared.h b/js/src/ion/shared/MacroAssembler-x86-shared.h index 7e7a2df969e..18941fc9381 100644 --- a/js/src/ion/shared/MacroAssembler-x86-shared.h +++ b/js/src/ion/shared/MacroAssembler-x86-shared.h @@ -375,6 +375,13 @@ class MacroAssemblerX86Shared : public Assembler return true; } + bool buildOOLFakeExitFrame(void *fakeReturnAddr) { + uint32 descriptor = MakeFrameDescriptor(framePushed(), IonFrame_JS); + Push(Imm32(descriptor)); + Push(ImmWord(fakeReturnAddr)); + return true; + } + void callWithExitFrame(IonCode *target) { uint32 descriptor = MakeFrameDescriptor(framePushed(), IonFrame_JS); Push(Imm32(descriptor)); diff --git a/js/src/ion/x64/MacroAssembler-x64.h b/js/src/ion/x64/MacroAssembler-x64.h index 1d5e0d658c4..2be025e7a6a 100644 --- a/js/src/ion/x64/MacroAssembler-x64.h +++ b/js/src/ion/x64/MacroAssembler-x64.h @@ -416,6 +416,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared branchPtr(cond, Operand(ScratchReg, 0x0), ptr, label); } + template + void branchPrivatePtr(Condition cond, T lhs, ImmWord ptr, Label *label) { + branchPtr(cond, lhs, ImmWord(ptr.value >> 1), label); + } + template void branchPtr(Condition cond, T lhs, S ptr, Label *label) { cmpPtr(Operand(lhs), ptr); diff --git a/js/src/ion/x86/MacroAssembler-x86.h b/js/src/ion/x86/MacroAssembler-x86.h index 706d1869f08..c45ba54f331 100644 --- a/js/src/ion/x86/MacroAssembler-x86.h +++ b/js/src/ion/x86/MacroAssembler-x86.h @@ -396,6 +396,12 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmpl(Operand(lhs), ptr); j(cond, label); } + + template + void branchPrivatePtr(Condition cond, T lhs, ImmWord ptr, Label *label) { + branchPtr(cond, lhs, ptr, label); + } + template void branchPtr(Condition cond, T lhs, S ptr, RepatchLabel *label) { cmpl(Operand(lhs), ptr);