From c62c6dfd2a06602293b9babc691ab4cd88e695c6 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Wed, 11 Feb 2015 14:42:01 +0100 Subject: [PATCH] Bug 1129382 - Add Ion ICs for scripted getters/setters. r=efaust,nbp,djvj --- .../tests/ion/scripted-getter-setter.js | 59 ++++++ .../tests/profiler/getter-setter-ic.js | 32 ++++ js/src/jit/BaselineBailouts.cpp | 12 +- js/src/jit/Ion.cpp | 4 + js/src/jit/IonCaches.cpp | 178 +++++++++++++++--- js/src/jit/JitFrameIterator.h | 9 +- js/src/jit/JitFrames.cpp | 80 ++++++-- js/src/jit/JitFrames.h | 15 ++ js/src/jit/arm/Trampoline-arm.cpp | 50 +++++ js/src/jit/mips/Trampoline-mips.cpp | 50 +++++ js/src/jit/x64/Trampoline-x64.cpp | 48 +++++ js/src/jit/x86/Trampoline-x86.cpp | 48 +++++ .../misc/getter-setter-outerize-this.js | 21 +++ 13 files changed, 552 insertions(+), 54 deletions(-) create mode 100644 js/src/jit-test/tests/ion/scripted-getter-setter.js create mode 100644 js/src/jit-test/tests/profiler/getter-setter-ic.js create mode 100644 js/src/tests/ecma_5/misc/getter-setter-outerize-this.js diff --git a/js/src/jit-test/tests/ion/scripted-getter-setter.js b/js/src/jit-test/tests/ion/scripted-getter-setter.js new file mode 100644 index 00000000000..4e8842cba5d --- /dev/null +++ b/js/src/jit-test/tests/ion/scripted-getter-setter.js @@ -0,0 +1,59 @@ +if (getJitCompilerOptions()["ion.warmup.trigger"] > 50) + setJitCompilerOption("ion.warmup.trigger", 50); + +function getObjects() { + var objs = []; + + // Own scripted getter/setter. + objs.push({x: 0, get prop() { + assertJitStackInvariants(); + return ++this.x; + }, set prop(v) { + assertJitStackInvariants(); + this.x += v; + }}); + + // Scripted getter/setter on prototype. Also verify extra formal args are + // handled correctly. + function getter(a, b, c) { + assertEq(arguments.length, 0); + assertEq(a, undefined); + assertEq(b, undefined); + assertEq(c, undefined); + assertJitStackInvariants(); + bailout(); + return ++this.y; + } + function setter1(a, b) { + assertEq(arguments.length, 1); + assertEq(b, undefined); + assertJitStackInvariants(); + this.y = a; + bailout(); + return "unused"; + } + var proto = {}; + Object.defineProperty(proto, "prop", {get: getter, set: setter1}); + objs.push(Object.create(proto)); + + function setter2() { + assertEq(arguments.length, 1); + assertJitStackInvariants(); + this.y = arguments[0]; + } + proto = {}; + Object.defineProperty(proto, "prop", {get: getter, set: setter2}); + objs.push(Object.create(proto)); + return objs; +} +function f() { + var objs = getObjects(); + var res = 0; + for (var i=0; i<200; i++) { + var o = objs[i % objs.length]; + o.prop = 2; + res += o.prop; + } + assertEq(res, 7233); +} +f(); diff --git a/js/src/jit-test/tests/profiler/getter-setter-ic.js b/js/src/jit-test/tests/profiler/getter-setter-ic.js new file mode 100644 index 00000000000..15b0ee41ec5 --- /dev/null +++ b/js/src/jit-test/tests/profiler/getter-setter-ic.js @@ -0,0 +1,32 @@ +// Ensure readSPSProfilingStack() doesn't crash with Ion +// getter/setter ICs on the stack. +function getObjects() { + var objs = []; + objs.push({x: 0, get prop() { + readSPSProfilingStack(); + return ++this.x; + }, set prop(v) { + readSPSProfilingStack(); + this.x = v + 2; + }}); + objs.push({x: 0, y: 0, get prop() { + readSPSProfilingStack(); + return this.y; + }, set prop(v) { + readSPSProfilingStack(); + this.y = v; + }}); + return objs; +} +function f() { + var objs = getObjects(); + var res = 0; + for (var i=0; i<100; i++) { + var o = objs[i % objs.length]; + res += o.prop; + o.prop = i; + } + assertEq(res, 4901); +} +enableSPSProfiling(); +f(); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 765fdee0f60..edf48e5aef3 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -329,10 +329,11 @@ struct BaselineStackBuilder BufferPointer topFrame = topFrameAddress(); FrameType type = topFrame->prevType(); - // For IonJS and Entry frames, the "saved" frame pointer in the baseline - // frame is meaningless, since Ion saves all registers before calling other ion - // frames, and the entry frame saves all registers too. - if (type == JitFrame_IonJS || type == JitFrame_Entry) + // For IonJS, IonAccessorIC and Entry frames, the "saved" frame pointer + // in the baseline frame is meaningless, since Ion saves all registers + // before calling other ion frames, and the entry frame saves all + // registers too. + if (type == JitFrame_IonJS || type == JitFrame_Entry || type == JitFrame_IonAccessorIC) return nullptr; // BaselineStub - Baseline calling into Ion. @@ -1348,7 +1349,8 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter MOZ_ASSERT(prevFrameType == JitFrame_IonJS || prevFrameType == JitFrame_BaselineStub || prevFrameType == JitFrame_Entry || - prevFrameType == JitFrame_Rectifier); + prevFrameType == JitFrame_Rectifier || + prevFrameType == JitFrame_IonAccessorIC); // All incoming frames are going to look like this: // diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 96b619925fe..4adc48413da 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2507,10 +2507,14 @@ InvalidateActivation(FreeOp *fop, const JitActivationIterator &activations, bool case JitFrame_Unwound_IonJS: case JitFrame_Unwound_BaselineJS: case JitFrame_Unwound_BaselineStub: + case JitFrame_Unwound_IonAccessorIC: MOZ_CRASH("invalid"); case JitFrame_Unwound_Rectifier: JitSpew(JitSpew_IonInvalidate, "#%d unwound rectifier frame @ %p", frameno, it.fp()); break; + case JitFrame_IonAccessorIC: + JitSpew(JitSpew_IonInvalidate, "#%d ion IC getter/setter frame @ %p", frameno, it.fp()); + break; case JitFrame_Entry: JitSpew(JitSpew_IonInvalidate, "#%d entry frame @ %p", frameno, it.fp()); break; diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 508664a910b..81e2dd307cc 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -652,6 +652,26 @@ IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, Shape *shape) return !obj->getClass()->ext.outerObject; } +static bool +IsCacheableGetPropCallScripted(JSObject *obj, JSObject *holder, Shape *shape) +{ + if (!shape || !IsCacheableProtoChainForIon(obj, holder)) + return false; + + if (!shape->hasGetterValue() || !shape->getterValue().isObject()) + return false; + + if (!shape->getterValue().toObject().is()) + return false; + + JSFunction& getter = shape->getterValue().toObject().as(); + if (!getter.hasJITCode()) + return false; + + // See IsCacheableGetPropCallNative. + return !obj->getClass()->ext.outerObject; +} + static bool IsCacheableGetPropCallPropertyOp(JSObject *obj, JSObject *holder, Shape *shape) { @@ -920,20 +940,15 @@ EmitGetterCall(JSContext *cx, MacroAssembler &masm, // 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. - Register scratchReg = regSet.takeGeneral(); - Register argJSContextReg = regSet.takeGeneral(); - Register argUintNReg = regSet.takeGeneral(); - Register argVpReg = regSet.takeGeneral(); + Register scratchReg = regSet.takeGeneral(); - // Shape has a getter function. - bool callNative = IsCacheableGetPropCallNative(obj, holder, shape); - MOZ_ASSERT_IF(!callNative, IsCacheableGetPropCallPropertyOp(obj, holder, shape)); + // Shape has a JSNative, PropertyOp or scripted getter function. + if (IsCacheableGetPropCallNative(obj, holder, shape)) { + Register argJSContextReg = regSet.takeGeneral(); + Register argUintNReg = regSet.takeGeneral(); + Register argVpReg = regSet.takeGeneral(); - if (callNative) { - MOZ_ASSERT(shape->hasGetterValue() && shape->getterValue().isObject() && - shape->getterValue().toObject().is()); JSFunction *target = &shape->getterValue().toObject().as(); - MOZ_ASSERT(target); MOZ_ASSERT(target->isNative()); @@ -977,7 +992,10 @@ EmitGetterCall(JSContext *cx, MacroAssembler &masm, // masm.leaveExitFrame & pop locals masm.adjustStack(IonOOLNativeExitFrameLayout::Size(0)); - } else { + } else if (IsCacheableGetPropCallPropertyOp(obj, holder, shape)) { + Register argJSContextReg = regSet.takeGeneral(); + Register argUintNReg = regSet.takeGeneral(); + Register argVpReg = regSet.takeGeneral(); Register argObjReg = argUintNReg; Register argIdReg = regSet.takeGeneral(); @@ -1023,6 +1041,51 @@ EmitGetterCall(JSContext *cx, MacroAssembler &masm, // masm.leaveExitFrame & pop locals. masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size()); + } else { + MOZ_ASSERT(IsCacheableGetPropCallScripted(obj, holder, shape)); + + JSFunction *target = &shape->getterValue().toObject().as(); + uint32_t framePushedBefore = masm.framePushed(); + + // Construct IonAccessorICFrameLayout. + uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS); + attacher.pushStubCodePointer(masm); + masm.Push(Imm32(descriptor)); + masm.Push(ImmPtr(returnAddr)); + + // The JitFrameLayout pushed below will be aligned to JitStackAlignment, + // so we just have to make sure the stack is aligned after we push the + // |this| + argument Values. + uint32_t argSize = (target->nargs() + 1) * sizeof(Value); + uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment); + MOZ_ASSERT(padding % sizeof(uintptr_t) == 0); + MOZ_ASSERT(padding < JitStackAlignment); + masm.reserveStack(padding); + + for (size_t i = 0; i < target->nargs(); i++) + masm.Push(UndefinedValue()); + masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object))); + + masm.movePtr(ImmMaybeNurseryPtr(target), scratchReg); + + descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC); + masm.Push(Imm32(0)); // argc + masm.Push(scratchReg); + masm.Push(Imm32(descriptor)); + + // Check stack alignment. Add sizeof(uintptr_t) for the return address. + MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0); + + // The getter has JIT code now and we will only discard the getter's JIT + // code when discarding all JIT code in the Zone, so we can assume it'll + // still have JIT code. + MOZ_ASSERT(target->hasJITCode()); + masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg); + masm.loadBaselineOrIonRaw(scratchReg, scratchReg, nullptr); + masm.callJit(scratchReg); + masm.storeCallResultValue(output); + + masm.freeStack(masm.framePushed() - framePushedBefore); } masm.icRestoreLive(liveRegs, aic); @@ -1240,12 +1303,13 @@ CanAttachNativeGetProp(typename GetPropCache::Context cx, const GetPropCache &ca // // Be careful when adding support for other getters here: for outer window // proxies, IonBuilder can innerize and pass us the inner window (the global), - // see IonBuilder::getPropTryInnerize. This is fine for native getters because - // IsCacheableGetPropCallNative checks they can handle both the inner and - // outer object, but scripted getters would need a similar mechanism. + // see IonBuilder::getPropTryInnerize. This is fine for native/scripted getters + // because IsCacheableGetPropCallNative and IsCacheableGetPropCallScripted + // handle this. if (cache.allowGetters() && (IsCacheableGetPropCallNative(obj, holder, shape) || - IsCacheableGetPropCallPropertyOp(obj, holder, shape))) + IsCacheableGetPropCallPropertyOp(obj, holder, shape) || + IsCacheableGetPropCallScripted(obj, holder, shape))) { // Don't enable getter call if cache is idempotent, since they can be // effectful. This is handled by allowGetters() @@ -1995,6 +2059,19 @@ IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape->setterObject()->as().isNative(); } +static bool +IsCacheableSetPropCallScripted(HandleObject obj, HandleObject holder, HandleShape shape) +{ + MOZ_ASSERT(obj->isNative()); + + if (!shape || !IsCacheableProtoChainForIon(obj, holder)) + return false; + + return shape->hasSetterValue() && shape->setterObject() && + shape->setterObject()->is() && + shape->setterObject()->as().hasJITCode(); +} + static bool IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape) { @@ -2238,14 +2315,12 @@ GenerateCallSetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, // // Be very careful not to use any of these before value is pushed, since they // might shadow. - Register scratchReg = regSet.takeGeneral(); - Register argJSContextReg = regSet.takeGeneral(); - Register argVpReg = regSet.takeGeneral(); + Register scratchReg = regSet.takeGeneral(); - bool callNative = IsCacheableSetPropCallNative(obj, holder, shape); - MOZ_ASSERT_IF(!callNative, IsCacheableSetPropCallPropertyOp(obj, holder, shape)); + if (IsCacheableSetPropCallNative(obj, holder, shape)) { + Register argJSContextReg = regSet.takeGeneral(); + Register argVpReg = regSet.takeGeneral(); - if (callNative) { MOZ_ASSERT(shape->hasSetterValue() && shape->setterObject() && shape->setterObject()->is()); JSFunction *target = &shape->setterObject()->as(); @@ -2290,7 +2365,9 @@ GenerateCallSetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, // masm.leaveExitFrame & pop locals. masm.adjustStack(IonOOLNativeExitFrameLayout::Size(1)); - } else { + } else if (IsCacheableSetPropCallPropertyOp(obj, holder, shape)) { + Register argJSContextReg = regSet.takeGeneral(); + Register argVpReg = regSet.takeGeneral(); Register argObjReg = regSet.takeGeneral(); Register argIdReg = regSet.takeGeneral(); Register argStrictReg = regSet.takeGeneral(); @@ -2338,6 +2415,52 @@ GenerateCallSetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, // masm.leaveExitFrame & pop locals. masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size()); + } else { + MOZ_ASSERT(IsCacheableSetPropCallScripted(obj, holder, shape)); + + JSFunction *target = &shape->setterValue().toObject().as(); + uint32_t framePushedBefore = masm.framePushed(); + + // Construct IonAccessorICFrameLayout. + uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonJS); + attacher.pushStubCodePointer(masm); + masm.Push(Imm32(descriptor)); + masm.Push(ImmPtr(returnAddr)); + + // The JitFrameLayout pushed below will be aligned to JitStackAlignment, + // so we just have to make sure the stack is aligned after we push the + // |this| + argument Values. + uint32_t numArgs = Max(size_t(1), target->nargs()); + uint32_t argSize = (numArgs + 1) * sizeof(Value); + uint32_t padding = ComputeByteAlignment(masm.framePushed() + argSize, JitStackAlignment); + MOZ_ASSERT(padding % sizeof(uintptr_t) == 0); + MOZ_ASSERT(padding < JitStackAlignment); + masm.reserveStack(padding); + + for (size_t i = 1; i < target->nargs(); i++) + masm.Push(UndefinedValue()); + masm.Push(value); + masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object))); + + masm.movePtr(ImmMaybeNurseryPtr(target), scratchReg); + + descriptor = MakeFrameDescriptor(argSize + padding, JitFrame_IonAccessorIC); + masm.Push(Imm32(1)); // argc + masm.Push(scratchReg); + masm.Push(Imm32(descriptor)); + + // Check stack alignment. Add sizeof(uintptr_t) for the return address. + MOZ_ASSERT(((masm.framePushed() + sizeof(uintptr_t)) % JitStackAlignment) == 0); + + // The setter has JIT code now and we will only discard the setter's JIT + // code when discarding all JIT code in the Zone, so we can assume it'll + // still have JIT code. + MOZ_ASSERT(target->hasJITCode()); + masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg); + masm.loadBaselineOrIonRaw(scratchReg, scratchReg, nullptr); + masm.callJit(scratchReg); + + masm.freeStack(masm.framePushed() - framePushedBefore); } masm.icRestoreLive(liveRegs, aic); @@ -2364,7 +2487,8 @@ IsCacheableDOMProxyUnshadowedSetterCall(JSContext *cx, HandleObject obj, HandleP return true; if (!IsCacheableSetPropCallNative(checkObj, holder, shape) && - !IsCacheableSetPropCallPropertyOp(checkObj, holder, shape)) + !IsCacheableSetPropCallPropertyOp(checkObj, holder, shape) && + !IsCacheableSetPropCallScripted(checkObj, holder, shape)) { return true; } @@ -2729,7 +2853,8 @@ CanAttachNativeSetProp(JSContext *cx, HandleObject obj, HandleId id, ConstantOrR return SetPropertyIC::MaybeCanAttachAddSlot; if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) || - IsCacheableSetPropCallNative(obj, holder, shape)) + IsCacheableSetPropCallNative(obj, holder, shape) || + IsCacheableSetPropCallScripted(obj, holder, shape)) { return SetPropertyIC::CanAttachCallSetter; } @@ -4095,7 +4220,8 @@ IsCacheableNameCallGetter(HandleObject scopeChain, HandleObject obj, HandleObjec return false; return IsCacheableGetPropCallNative(obj, holder, shape) || - IsCacheableGetPropCallPropertyOp(obj, holder, shape); + IsCacheableGetPropCallPropertyOp(obj, holder, shape) || + IsCacheableGetPropCallScripted(obj, holder, shape); } bool diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index a2c864e1d2d..9aa7111e36e 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -44,18 +44,17 @@ enum FrameType // mismatches in calls. JitFrame_Rectifier, + // Ion IC calling a scripted getter/setter. + JitFrame_IonAccessorIC, + // An unwound JS frame is a JS frame signalling that its callee frame has been // turned into an exit frame (see EnsureExitFrame). Used by Ion bailouts and // Baseline exception unwinding. JitFrame_Unwound_BaselineJS, JitFrame_Unwound_IonJS, - - // Like Unwound_IonJS, but the caller is a baseline stub frame. JitFrame_Unwound_BaselineStub, - - // An unwound rectifier frame is a rectifier frame signalling that its callee - // frame has been turned into an exit frame (see EnsureExitFrame). JitFrame_Unwound_Rectifier, + JitFrame_Unwound_IonAccessorIC, // An exit frame is necessary for transitioning from a JS frame into C++. // From within C++, an exit frame is always the last frame in any diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index dd9fa059b48..c7c9f190c01 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -249,6 +249,7 @@ SizeOfFramePrefix(FrameType type) case JitFrame_Unwound_IonJS: return JitFrameLayout::Size(); case JitFrame_BaselineStub: + case JitFrame_Unwound_BaselineStub: return BaselineStubFrameLayout::Size(); case JitFrame_Rectifier: return RectifierFrameLayout::Size(); @@ -256,9 +257,12 @@ SizeOfFramePrefix(FrameType type) return IonUnwoundRectifierFrameLayout::Size(); case JitFrame_Exit: return ExitFrameLayout::Size(); - default: - MOZ_CRASH("unknown frame type"); + case JitFrame_IonAccessorIC: + case JitFrame_Unwound_IonAccessorIC: + return IonAccessorICFrameLayout::Size(); } + + MOZ_CRASH("unknown frame type"); } uint8_t * @@ -302,6 +306,8 @@ JitFrameIterator::operator++() type_ = JitFrame_BaselineJS; else if (type_ == JitFrame_Unwound_BaselineStub) type_ = JitFrame_BaselineStub; + else if (type_ == JitFrame_Unwound_IonAccessorIC) + type_ = JitFrame_IonAccessorIC; returnAddressToFp_ = current()->returnAddress(); current_ = prev; @@ -889,43 +895,51 @@ HandleException(ResumeFromException *rfe) void EnsureExitFrame(CommonFrameLayout *frame) { - if (frame->prevType() == JitFrame_Unwound_IonJS || - frame->prevType() == JitFrame_Unwound_BaselineJS || - frame->prevType() == JitFrame_Unwound_BaselineStub || - frame->prevType() == JitFrame_Unwound_Rectifier) - { + switch (frame->prevType()) { + case JitFrame_Unwound_IonJS: + case JitFrame_Unwound_BaselineJS: + case JitFrame_Unwound_BaselineStub: + case JitFrame_Unwound_Rectifier: + case JitFrame_Unwound_IonAccessorIC: // Already an exit frame, nothing to do. return; - } - if (frame->prevType() == JitFrame_Entry) { + case JitFrame_Entry: // The previous frame type is the entry frame, so there's no actual // need for an exit frame. return; - } - if (frame->prevType() == JitFrame_Rectifier) { + case JitFrame_Rectifier: // The rectifier code uses the frame descriptor to discard its stack, // so modifying its descriptor size here would be dangerous. Instead, // we change the frame type, and teach the stack walking code how to // deal with this edge case. bug 717297 would obviate the need frame->changePrevType(JitFrame_Unwound_Rectifier); return; - } - if (frame->prevType() == JitFrame_BaselineStub) { + case JitFrame_BaselineStub: frame->changePrevType(JitFrame_Unwound_BaselineStub); return; - } - - if (frame->prevType() == JitFrame_BaselineJS) { + case JitFrame_BaselineJS: frame->changePrevType(JitFrame_Unwound_BaselineJS); return; + + case JitFrame_IonJS: + frame->changePrevType(JitFrame_Unwound_IonJS); + return; + + case JitFrame_IonAccessorIC: + frame->changePrevType(JitFrame_Unwound_IonAccessorIC); + return; + + case JitFrame_Exit: + case JitFrame_Bailout: + // Fall-through to MOZ_CRASH below. + break; } - MOZ_ASSERT(frame->prevType() == JitFrame_IonJS); - frame->changePrevType(JitFrame_Unwound_IonJS); + MOZ_CRASH("Unexpected frame type"); } CalleeToken @@ -1169,6 +1183,14 @@ MarkBaselineStubFrame(JSTracer *trc, const JitFrameIterator &frame) } } +static void +MarkIonAccessorICFrame(JSTracer *trc, const JitFrameIterator &frame) +{ + MOZ_ASSERT(frame.type() == JitFrame_IonAccessorIC); + IonAccessorICFrameLayout *layout = (IonAccessorICFrameLayout *)frame.fp(); + gc::MarkJitCodeRoot(trc, layout->stubCode(), "ion-ic-accessor-code"); +} + void JitActivationIterator::jitStackRange(uintptr_t *&min, uintptr_t *&end) { @@ -1441,12 +1463,17 @@ MarkJitActivation(JSTracer *trc, const JitActivationIterator &activations) break; case JitFrame_Unwound_IonJS: case JitFrame_Unwound_BaselineJS: + case JitFrame_Unwound_BaselineStub: + case JitFrame_Unwound_IonAccessorIC: MOZ_CRASH("invalid"); case JitFrame_Rectifier: MarkRectifierFrame(trc, frames); break; case JitFrame_Unwound_Rectifier: break; + case JitFrame_IonAccessorIC: + MarkIonAccessorICFrame(trc, frames); + break; default: MOZ_CRASH("unexpected frame type"); } @@ -2660,6 +2687,11 @@ JitFrameIterator::dump() const fprintf(stderr, " Rectifier frame\n"); fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize())); break; + case JitFrame_IonAccessorIC: + case JitFrame_Unwound_IonAccessorIC: + fprintf(stderr, " Ion scripted accessor IC\n"); + fprintf(stderr, " Frame size: %u\n", unsigned(current()->prevFrameLocalSize())); + break; case JitFrame_Unwound_IonJS: case JitFrame_Unwound_BaselineJS: fprintf(stderr, "Warning! Unwound JS frames are not observable.\n"); @@ -3012,6 +3044,18 @@ JitProfilingFrameIterator::operator++() MOZ_CRASH("Bad frame type prior to rectifier frame."); } + if (prevType == JitFrame_IonAccessorIC || prevType == JitFrame_Unwound_IonAccessorIC) { + IonAccessorICFrameLayout *accessorFrame = + GetPreviousRawFrame(frame); + + MOZ_ASSERT(accessorFrame->prevType() == JitFrame_IonJS); + + returnAddressToFp_ = accessorFrame->returnAddress(); + fp_ = GetPreviousRawFrame(accessorFrame); + type_ = JitFrame_IonJS; + return; + } + if (prevType == JitFrame_Entry) { // No previous frame, set to null to indicate that JitFrameIterator is done() returnAddressToFp_ = nullptr; diff --git a/js/src/jit/JitFrames.h b/js/src/jit/JitFrames.h index f5e274d493e..120a853ec93 100644 --- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -428,6 +428,21 @@ class RectifierFrameLayout : public JitFrameLayout } }; +class IonAccessorICFrameLayout : public CommonFrameLayout +{ + protected: + // Pointer to root the stub's JitCode. + JitCode *stubCode_; + + public: + JitCode **stubCode() { + return &stubCode_; + } + static size_t Size() { + return sizeof(IonAccessorICFrameLayout); + } +}; + // The callee token is now dead. class IonUnwoundRectifierFrameLayout : public RectifierFrameLayout { diff --git a/js/src/jit/arm/Trampoline-arm.cpp b/js/src/jit/arm/Trampoline-arm.cpp index 2f2261b9d0d..181994006ae 100644 --- a/js/src/jit/arm/Trampoline-arm.cpp +++ b/js/src/jit/arm/Trampoline-arm.cpp @@ -1130,6 +1130,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) Label handle_IonJS; Label handle_BaselineStub; Label handle_Rectifier; + Label handle_IonAccessorIC; Label handle_Entry; Label end; @@ -1137,6 +1138,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonAccessorIC), &handle_IonAccessorIC); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); @@ -1307,6 +1309,54 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.ret(); } + // JitFrame_IonAccessorIC + // + // The caller is always an IonJS frame. + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- AccFrame-Descriptor.Size + // StubCode | + // AccFrame-Descriptor |- IonAccessorICFrameLayout::Size() + // AccFrame-ReturnAddr | + // ... accessor frame data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + masm.bind(&handle_IonAccessorIC); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.ma_add(StackPointer, scratch1, scratch2); + masm.addPtr(Imm32(JitFrameLayout::Size()), scratch2); + + // scratch3 := AccFrame-Descriptor.Size + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfDescriptor()), scratch3); +#ifdef DEBUG + // Assert previous frame is an IonJS frame. + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch1); + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch1, Imm32(JitFrame_IonJS), &checkOk); + masm.assumeUnreachable("IonAccessorIC frame must be preceded by IonJS frame"); + masm.bind(&checkOk); + } +#endif + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch3); + + // lastProfilingCallSite := AccFrame-ReturnAddr + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfReturnAddress()), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + + // lastProfilingFrame := AccessorFrame + AccFrame-Descriptor.Size + + // IonAccessorICFrameLayout::Size() + masm.ma_add(scratch2, scratch3, scratch1); + masm.addPtr(Imm32(IonAccessorICFrameLayout::Size()), scratch1); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + // // JitFrame_Entry // diff --git a/js/src/jit/mips/Trampoline-mips.cpp b/js/src/jit/mips/Trampoline-mips.cpp index 9a6a0c387be..4b80e63e2b5 100644 --- a/js/src/jit/mips/Trampoline-mips.cpp +++ b/js/src/jit/mips/Trampoline-mips.cpp @@ -1125,6 +1125,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) Label handle_IonJS; Label handle_BaselineStub; Label handle_Rectifier; + Label handle_IonAccessorIC; Label handle_Entry; Label end; @@ -1132,6 +1133,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonAccessorIC), &handle_IonAccessorIC); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); @@ -1302,6 +1304,54 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.ret(); } + // JitFrame_IonAccessorIC + // + // The caller is always an IonJS frame. + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- AccFrame-Descriptor.Size + // StubCode | + // AccFrame-Descriptor |- IonAccessorICFrameLayout::Size() + // AccFrame-ReturnAddr | + // ... accessor frame data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + masm.bind(&handle_IonAccessorIC); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.ma_addu(StackPointer, scratch1, scratch2); + masm.addPtr(Imm32(JitFrameLayout::Size()), scratch2); + + // scratch3 := AccFrame-Descriptor.Size + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfDescriptor()), scratch3); +#ifdef DEBUG + // Assert previous frame is an IonJS frame. + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch1); + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch1, Imm32(JitFrame_IonJS), &checkOk); + masm.assumeUnreachable("IonAccessorIC frame must be preceded by IonJS frame"); + masm.bind(&checkOk); + } +#endif + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch3); + + // lastProfilingCallSite := AccFrame-ReturnAddr + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfReturnAddress()), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + + // lastProfilingFrame := AccessorFrame + AccFrame-Descriptor.Size + + // IonAccessorICFrameLayout::Size() + masm.ma_addu(scratch2, scratch3, scratch1); + masm.addPtr(Imm32(IonAccessorICFrameLayout::Size()), scratch1); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + // // JitFrame_Entry // diff --git a/js/src/jit/x64/Trampoline-x64.cpp b/js/src/jit/x64/Trampoline-x64.cpp index 33484dbfdff..5261e2faf59 100644 --- a/js/src/jit/x64/Trampoline-x64.cpp +++ b/js/src/jit/x64/Trampoline-x64.cpp @@ -981,6 +981,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) Label handle_IonJS; Label handle_BaselineStub; Label handle_Rectifier; + Label handle_IonAccessorIC; Label handle_Entry; Label end; @@ -988,6 +989,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonAccessorIC), &handle_IonAccessorIC); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); @@ -1153,6 +1155,52 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.ret(); } + // JitFrame_IonAccessorIC + // + // The caller is always an IonJS frame. + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- AccFrame-Descriptor.Size + // StubCode | + // AccFrame-Descriptor |- IonAccessorICFrameLayout::Size() + // AccFrame-ReturnAddr | + // ... accessor frame data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + masm.bind(&handle_IonAccessorIC); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + + // scratch3 := AccFrame-Descriptor.Size + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfDescriptor()), scratch3); +#ifdef DEBUG + // Assert previous frame is an IonJS frame. + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch1); + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch1, Imm32(JitFrame_IonJS), &checkOk); + masm.assumeUnreachable("IonAccessorIC frame must be preceded by IonJS frame"); + masm.bind(&checkOk); + } +#endif + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch3); + + // lastProfilingCallSite := AccFrame-ReturnAddr + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfReturnAddress()), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + + // lastProfilingFrame := AccessorFrame + AccFrame-Descriptor.Size + + // IonAccessorICFrameLayout::Size() + masm.lea(Operand(scratch2, scratch3, TimesOne, IonAccessorICFrameLayout::Size()), scratch1); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + // // JitFrame_Entry // diff --git a/js/src/jit/x86/Trampoline-x86.cpp b/js/src/jit/x86/Trampoline-x86.cpp index bfe281e4ea5..165418ac61a 100644 --- a/js/src/jit/x86/Trampoline-x86.cpp +++ b/js/src/jit/x86/Trampoline-x86.cpp @@ -1008,6 +1008,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) Label handle_IonJS; Label handle_BaselineStub; Label handle_Rectifier; + Label handle_IonAccessorIC; Label handle_Entry; Label end; @@ -1015,6 +1016,7 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonAccessorIC), &handle_IonAccessorIC); masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); @@ -1182,6 +1184,52 @@ JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) masm.ret(); } + // JitFrame_IonAccessorIC + // + // The caller is always an IonJS frame. + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- AccFrame-Descriptor.Size + // StubCode | + // AccFrame-Descriptor |- IonAccessorICFrameLayout::Size() + // AccFrame-ReturnAddr | + // ... accessor frame data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + masm.bind(&handle_IonAccessorIC); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + + // scratch3 := AccFrame-Descriptor.Size + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfDescriptor()), scratch3); +#ifdef DEBUG + // Assert previous frame is an IonJS frame. + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch1); + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch1, Imm32(JitFrame_IonJS), &checkOk); + masm.assumeUnreachable("IonAccessorIC frame must be preceded by IonJS frame"); + masm.bind(&checkOk); + } +#endif + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch3); + + // lastProfilingCallSite := AccFrame-ReturnAddr + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfReturnAddress()), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + + // lastProfilingFrame := AccessorFrame + AccFrame-Descriptor.Size + + // IonAccessorICFrameLayout::Size() + masm.lea(Operand(scratch2, scratch3, TimesOne, IonAccessorICFrameLayout::Size()), scratch1); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + // // JitFrame_Entry // diff --git a/js/src/tests/ecma_5/misc/getter-setter-outerize-this.js b/js/src/tests/ecma_5/misc/getter-setter-outerize-this.js new file mode 100644 index 00000000000..46c61b889a5 --- /dev/null +++ b/js/src/tests/ecma_5/misc/getter-setter-outerize-this.js @@ -0,0 +1,21 @@ +if (typeof window === "undefined") { + // This test is meant to run in the browser, but it's easy to + // run it in the shell as well, even though it has no inner/outer + // windows. + window = this; +} + +var res = false; +Object.defineProperty(this, "foo", {configurable: true, + get: function() { return this === window; }, + set: function(v) { res = this === window; }}); + +(function() { + for (var i = 0; i < 3000; ++i) { + window.foo = i; + assertEq(res, true, "setter"); + assertEq(window.foo, true, "getter"); + } +})(); + +reportCompare(true, true);