diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 53e3329736a..c515f5154de 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -1708,6 +1708,9 @@ GCMarker::processMarkStackTop(SliceBudget &budget) goto scan_unboxed; } if (clasp == &UnboxedPlainObject::class_) { + JSObject *expando = obj->as().maybeExpando(); + if (expando && mark(expando)) + repush(expando); const UnboxedLayout &layout = obj->as().layout(); unboxedTraceList = layout.traceList(); if (!unboxedTraceList) diff --git a/js/src/gc/Tracer.h b/js/src/gc/Tracer.h index c9cc177e91e..bf3a0137cc2 100644 --- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -320,7 +320,6 @@ class GCMarker : public JSTracer void markAndScanString(JSObject *source, JSString *str); void markAndScanSymbol(JSObject *source, JS::Symbol *sym); - bool markObject(JSObject *source, JSObject *obj); void appendGrayRoot(void *thing, JSGCTraceKind kind); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 9aa8a894b78..21159079112 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -168,7 +168,6 @@ ICStub::updateCode(JitCode *code) void ReceiverGuard::trace(JSTracer *trc) { - MOZ_ASSERT(!!shape_ != !!group_); if (shape_) MarkShape(trc, &shape_, "receiver_guard_shape"); else @@ -347,12 +346,12 @@ ICStub::trace(JSTracer *trc) } case ICStub::GetProp_Native: { ICGetProp_Native *propStub = toGetProp_Native(); - MarkShape(trc, &propStub->shape(), "baseline-getpropnative-stub-shape"); + propStub->receiverGuard().trace(trc); break; } case ICStub::GetProp_NativePrototype: { ICGetProp_NativePrototype *propStub = toGetProp_NativePrototype(); - propStub->guard().trace(trc); + propStub->receiverGuard().trace(trc); MarkObject(trc, &propStub->holder(), "baseline-getpropnativeproto-stub-holder"); MarkShape(trc, &propStub->holderShape(), "baseline-getpropnativeproto-stub-holdershape"); break; @@ -426,13 +425,12 @@ ICStub::trace(JSTracer *trc) } case ICStub::SetProp_Native: { ICSetProp_Native *propStub = toSetProp_Native(); - MarkShape(trc, &propStub->shape(), "baseline-setpropnative-stub-shape"); - MarkObjectGroup(trc, &propStub->group(), "baseline-setpropnative-stub-group"); + propStub->receiverGuard().trace(trc); break; } case ICStub::SetProp_NativeAdd: { ICSetProp_NativeAdd *propStub = toSetProp_NativeAdd(); - MarkObjectGroup(trc, &propStub->group(), "baseline-setpropnativeadd-stub-group"); + propStub->receiverGuard().trace(trc); MarkShape(trc, &propStub->newShape(), "baseline-setpropnativeadd-stub-newshape"); if (propStub->newGroup()) MarkObjectGroup(trc, &propStub->newGroup(), "baseline-setpropnativeadd-stub-new-group"); @@ -3448,16 +3446,22 @@ IsCacheableSetPropWriteSlot(JSObject *obj, Shape *oldShape, JSObject *holder, Sh } static bool -IsCacheableSetPropAddSlot(JSContext *cx, HandleObject obj, HandleShape oldShape, uint32_t oldSlots, - HandleId id, HandleObject holder, HandleShape shape, +IsCacheableSetPropAddSlot(JSContext *cx, JSObject *obj, Shape *oldShape, + jsid id, JSObject *holder, Shape *shape, size_t *protoChainDepth) { - if (!shape) + if (!shape || obj != holder) return false; // Property must be set directly on object, and be last added property of object. - if (!obj->isNative() || obj != holder || shape != obj->as().lastProperty()) - return false; + if (obj->isNative()) { + if (shape != obj->as().lastProperty()) + return false; + } else if (obj->is()) { + UnboxedExpandoObject *expando = obj->as().maybeExpando(); + if (!expando || shape != expando->lastProperty()) + return false; + } // Object must be extensible, oldShape must be immediate parent of curShape. if (!obj->nonProxyIsExtensible() || shape->previous() != oldShape) @@ -3497,7 +3501,7 @@ IsCacheableSetPropAddSlot(JSContext *cx, HandleObject obj, HandleShape oldShape, // Only add a IC entry if the dynamic slots didn't change when the shapes // changed. Need to ensure that a shape change for a subsequent object // won't involve reallocating the slot array. - if (obj->as().numDynamicSlots() != oldSlots) + if (NativeObject::dynamicSlotsCount(shape) != NativeObject::dynamicSlotsCount(oldShape)) return false; *protoChainDepth = chainDepth; @@ -5778,7 +5782,7 @@ UpdateExistingGetPropCallStubs(ICFallbackStub *fallbackStub, bool isOwnGetter = (holder == receiver); bool foundMatchingStub = false; - ReceiverGuard::Token receiverGuard = ReceiverGuard::objectToken(receiver); + ReceiverGuard::StackGuard receiverGuard(receiver); for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) { if (iter->kind() == kind) { ICGetPropCallGetter *getPropStub = static_cast(*iter); @@ -5804,10 +5808,12 @@ UpdateExistingGetPropCallStubs(ICFallbackStub *fallbackStub, // matches as protos with the old shape flow into it, but always // matches post-get, which is where we are now. getPropStub->holderShape() = holder->lastProperty(); + // Make sure to update the getter, since a shape change might // have changed which getter we want to use. getPropStub->getter() = getter; - if (receiverGuard == getPropStub->receiverGuard().token()) + + if (getPropStub->receiverGuard().matches(receiverGuard)) foundMatchingStub = true; } } @@ -5822,7 +5828,7 @@ static bool UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub, ICStub::Kind kind, NativeObject *holder, - ReceiverGuard::Token receiverGuard, + ReceiverGuard::StackGuard receiverGuard, JSFunction *setter) { MOZ_ASSERT(kind == ICStub::SetProp_CallScripted || @@ -5835,13 +5841,13 @@ UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub, // We want to update the holder shape to match the new one no // matter what, even if the receiver shape is different. MOZ_ASSERT(setPropStub->holderShape() != holder->lastProperty() || - setPropStub->guard().token() != receiverGuard, + !setPropStub->guard().matches(receiverGuard), "Why didn't we end up using this stub?"); setPropStub->holderShape() = holder->lastProperty(); // Make sure to update the setter, since a shape change might // have changed which setter we want to use. setPropStub->setter() = setter; - if (receiverGuard == setPropStub->guard().token()) + if (setPropStub->guard().matches(receiverGuard)) foundMatchingStub = true; } } @@ -6712,6 +6718,45 @@ TryAttachUnboxedGetPropStub(JSContext *cx, HandleScript script, return true; } +static bool +TryAttachUnboxedExpandoGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, + ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val, + bool *attached) +{ + MOZ_ASSERT(!*attached); + + if (!val.isObject() || !val.toObject().is()) + return true; + Rooted obj(cx, &val.toObject().as()); + + Rooted expando(cx, obj->maybeExpando()); + if (!expando) + return true; + + Shape *shape = expando->lookup(cx, name); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) + return true; + + bool isFixedSlot; + uint32_t offset; + GetFixedOrDynamicSlotOffset(expando, shape->slot(), &isFixedSlot, &offset); + + ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); + + bool isCallProp = (JSOp(*pc) == JSOP_CALLPROP); + ICGetPropNativeCompiler compiler(cx, ICStub::GetProp_Native, isCallProp, monitorStub, obj, obj, + name, isFixedSlot, offset); + ICGetPropNativeStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + StripPreliminaryObjectStubs(cx, stub); + + stub->addNewStub(newStub); + *attached = true; + return true; +} + static bool TryAttachTypedObjectGetPropStub(JSContext *cx, HandleScript script, ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val, @@ -6967,6 +7012,11 @@ DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub_ if (attached) return true; + if (!TryAttachUnboxedExpandoGetPropStub(cx, script, pc, stub, name, val, &attached)) + return false; + if (attached) + return true; + if (!TryAttachTypedObjectGetPropStub(cx, script, stub, name, val, &attached)) return false; if (attached) @@ -7145,16 +7195,16 @@ ICGetProp_Primitive::Compiler::generateStubCode(MacroAssembler &masm) ICGetPropNativeStub * ICGetPropNativeCompiler::getStub(ICStubSpace *space) { + ReceiverGuard::StackGuard guard(obj_); + switch (kind) { case ICStub::GetProp_Native: { MOZ_ASSERT(obj_ == holder_); - RootedShape shape(cx, obj_->as().lastProperty()); - return ICStub::New(space, getStubCode(), firstMonitorStub_, shape, offset_); + return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, offset_); } case ICStub::GetProp_NativePrototype: { MOZ_ASSERT(obj_ != holder_); - ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_); Shape *holderShape = holder_->as().lastProperty(); return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, offset_, holder_, holderShape); @@ -7166,21 +7216,40 @@ ICGetPropNativeCompiler::getStub(ICStubSpace *space) } static void -GuardNativeOrUnboxedReceiver(MacroAssembler &masm, JSObject *obj, +GuardNativeOrUnboxedReceiver(MacroAssembler &masm, ReceiverGuard::StackGuard guard, Register object, Register scratch, size_t receiverGuardOffset, Label *failure) { - if (obj->isNative()) { - masm.loadPtr(Address(BaselineStubReg, - receiverGuardOffset + ReceiverGuard::offsetOfShape()), - scratch); - masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); - } else { - MOZ_ASSERT(obj->is()); - masm.loadPtr(Address(BaselineStubReg, - receiverGuardOffset + ReceiverGuard::offsetOfGroup()), - scratch); + Address groupAddress(BaselineStubReg, receiverGuardOffset + ReceiverGuard::offsetOfGroup()); + Address shapeAddress(BaselineStubReg, receiverGuardOffset + ReceiverGuard::offsetOfShape()); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + + if (guard.group) { + masm.loadPtr(groupAddress, scratch); masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure); + + if (guard.group->maybeUnboxedLayout() && !guard.shape) { + // Guard the unboxed object has no expando object. + masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure); + } + } + + if (guard.shape) { + masm.loadPtr(shapeAddress, scratch); + if (guard.group && guard.group->maybeUnboxedLayout()) { + // Guard the unboxed object has a matching expando object. + masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure); + Label done; + masm.push(object); + masm.loadPtr(expandoAddress, object); + masm.branchTestObjShape(Assembler::Equal, object, scratch, &done); + masm.pop(object); + masm.jump(failure); + masm.bind(&done); + masm.pop(object); + } else { + masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); + } } } @@ -7204,14 +7273,18 @@ ICGetPropNativeCompiler::generateStubCode(MacroAssembler &masm) Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); // Shape/group guard. - MOZ_ASSERT(ICGetProp_Native::offsetOfShape() == - ICGetProp_NativePrototype::offsetOfGuard() + ReceiverGuard::offsetOfShape()); - GuardNativeOrUnboxedReceiver(masm, obj_, objReg, scratch, - ICGetProp_NativePrototype::offsetOfGuard(), &failure); + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_), objReg, scratch, + ICGetPropNativeStub::offsetOfReceiverGuard(), &failure); Register holderReg; if (obj_ == holder_) { - holderReg = objReg; + if (obj_->is()) { + // We are loading off the expando object, so use that for the holder. + holderReg = regs.takeAny(); + masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); + } else { + holderReg = objReg; + } } else { // Shape guard holder. holderReg = regs.takeAny(); @@ -7348,7 +7421,7 @@ ICGetPropNativeDoesNotExistCompiler::generateStubCode(MacroAssembler &masm) // Unbox and guard against old shape/group. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardNativeOrUnboxedReceiver(masm, obj_, objReg, scratch, + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_), objReg, scratch, ICGetProp_NativeDoesNotExist::offsetOfGuard(), &failure); Register protoReg = regs.takeAny(); @@ -7387,7 +7460,7 @@ ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) // Unbox and shape guard. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardNativeOrUnboxedReceiver(masm, receiver_, objReg, scratch, + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(receiver_), objReg, scratch, ICGetProp_CallScripted::offsetOfReceiverGuard(), &failure); if (receiver_ != holder_) { @@ -7495,7 +7568,7 @@ ICGetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) Register scratch = regs.takeAnyExcluding(BaselineTailCallReg); // Shape guard. - GuardNativeOrUnboxedReceiver(masm, receiver_, objReg, scratch, + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(receiver_), objReg, scratch, ICGetProp_CallNative::offsetOfReceiverGuard(), &failure); if (receiver_ != holder_ ) { @@ -7985,21 +8058,34 @@ BaselineScript::noteAccessedGetter(uint32_t pcOffset) // value property. static bool TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub, - HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, uint32_t oldSlots, + HandleObject obj, const ReceiverGuard::RootedStackGuard &oldGuard, HandlePropertyName name, HandleId id, HandleValue rhs, bool *attached) { MOZ_ASSERT(!*attached); - if (!obj->isNative() || obj->watched()) + if (obj->watched()) return true; RootedShape shape(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape)) return false; + if (!holder) + return true; + + if (holder->is()) { + UnboxedExpandoObject *expando = holder->as().maybeExpando(); + if (expando) { + shape = expando->lookup(cx, name); + if (!shape) + return true; + } else { + return true; + } + } size_t chainDepth; - if (IsCacheableSetPropAddSlot(cx, obj, oldShape, oldSlots, id, holder, shape, &chainDepth)) { + if (IsCacheableSetPropAddSlot(cx, obj, oldGuard.shape, id, holder, shape, &chainDepth)) { // Don't attach if proto chain depth is too high. if (chainDepth > ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH) return true; @@ -8008,18 +8094,22 @@ TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, IC // script properties analysis hasn't been performed for yet, as there // may be a shape change required here afterwards. Pretend we attached // a stub, though, so the access is not marked as unoptimizable. - if (oldGroup->newScript() && !oldGroup->newScript()->analyzed()) { + if (oldGuard.group->newScript() && !oldGuard.group->newScript()->analyzed()) { *attached = true; return true; } bool isFixedSlot; uint32_t offset; - GetFixedOrDynamicSlotOffset(&obj->as(), shape->slot(), &isFixedSlot, &offset); + if (obj->is()) { + GetFixedOrDynamicSlotOffset(&obj->as(), shape->slot(), &isFixedSlot, &offset); + } else { + GetFixedOrDynamicSlotOffset(obj->as().maybeExpando(), + shape->slot(), &isFixedSlot, &offset); + } JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObject.ADD) stub"); - ICSetPropNativeAddCompiler compiler(cx, obj, oldShape, oldGroup, - chainDepth, isFixedSlot, offset); + ICSetPropNativeAddCompiler compiler(cx, obj, oldGuard, chainDepth, isFixedSlot, offset); ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; @@ -8031,7 +8121,10 @@ TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, IC return true; } - if (IsCacheableSetPropWriteSlot(obj, oldShape, holder, shape)) { + if (!obj->isNative()) + return true; + + if (IsCacheableSetPropWriteSlot(obj, oldGuard.shape, holder, shape)) { // For some property writes, such as the initial overwrite of global // properties, TI will not mark the property as having been // overwritten. Don't attach a stub in this case, so that we don't @@ -8047,7 +8140,7 @@ TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, IC GetFixedOrDynamicSlotOffset(&obj->as(), shape->slot(), &isFixedSlot, &offset); JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObject.PROP) stub"); - MOZ_ASSERT(obj->as().lastProperty() == oldShape, + MOZ_ASSERT(obj->as().lastProperty() == oldGuard.shape, "Should this really be a SetPropWriteSlot?"); ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset); ICSetProp_Native *newStub = compiler.getStub(compiler.getStubSpace(script)); @@ -8072,8 +8165,10 @@ TryAttachSetValuePropStub(JSContext *cx, HandleScript script, jsbytecode *pc, IC // Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on // an accessor property. static bool -TryAttachSetAccessorPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICSetProp_Fallback *stub, - HandleObject obj, ReceiverGuard::Token receiverGuard, HandlePropertyName name, +TryAttachSetAccessorPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, + ICSetProp_Fallback *stub, + HandleObject obj, const ReceiverGuard::RootedStackGuard &receiverGuard, + HandlePropertyName name, HandleId id, HandleValue rhs, bool *attached, bool *isTemporarilyUnoptimizable) { @@ -8179,6 +8274,41 @@ TryAttachUnboxedSetPropStub(JSContext *cx, HandleScript script, return true; } +static bool +TryAttachUnboxedExpandoSetPropStub(JSContext *cx, HandleScript script, + ICSetProp_Fallback *stub, HandleId id, + HandleObject obj, Shape *oldShape, HandleValue rhs, bool *attached) +{ + MOZ_ASSERT(!*attached); + + if (!obj->is()) + return true; + + UnboxedExpandoObject *expando = obj->as().maybeExpando(); + if (!expando || expando->lastProperty() != oldShape) + return true; + + Shape *shape = expando->lookup(cx, id); + if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable()) + return true; + + bool isFixedSlot; + uint32_t offset; + GetFixedOrDynamicSlotOffset(expando, shape->slot(), &isFixedSlot, &offset); + + ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset); + ICSetProp_Native *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub || !newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) + return false; + + stub->addNewStub(newStub); + + StripPreliminaryObjectStubs(cx, stub); + + *attached = true; + return true; +} + static bool TryAttachTypedObjectSetPropStub(JSContext *cx, HandleScript script, ICSetProp_Fallback *stub, HandleId id, @@ -8253,14 +8383,9 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_ RootedId id(cx, NameToId(name)); RootedObject obj(cx, ToObjectFromStack(cx, lhs)); - if (!obj) + if (!obj || !obj->getGroup(cx)) return false; - ReceiverGuard::Token oldGuard = ReceiverGuard::objectToken(obj); - RootedShape oldShape(cx, obj->maybeShape()); - RootedObjectGroup oldGroup(cx, obj->getGroup(cx)); - if (!oldGroup) - return false; - uint32_t oldSlots = obj->isNative() ? obj->as().numDynamicSlots() : 0; + ReceiverGuard::RootedStackGuard oldGuard(cx, ReceiverGuard::StackGuard(obj, true)); bool attached = false; // There are some reasons we can fail to attach a stub that are temporary. @@ -8313,8 +8438,8 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_ if (!attached && lhs.isObject() && - !TryAttachSetValuePropStub(cx, script, pc, stub, obj, oldShape, - oldGroup, oldSlots, name, id, rhs, &attached)) + !TryAttachSetValuePropStub(cx, script, pc, stub, obj, oldGuard, + name, id, rhs, &attached)) { return false; } @@ -8330,6 +8455,15 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_ if (attached) return true; + if (!attached && + lhs.isObject() && + !TryAttachUnboxedExpandoSetPropStub(cx, script, stub, id, obj, oldGuard.shape, rhs, &attached)) + { + return false; + } + if (attached) + return true; + if (!attached && lhs.isObject() && !TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached)) @@ -8402,23 +8536,17 @@ ICSetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler &masm, Handle< bool ICSetProp_Native::Compiler::generateStubCode(MacroAssembler &masm) { - Label failure; + Label failure, failurePopObject; // Guard input is an object. masm.branchTestObject(Assembler::NotEqual, R0, &failure); + Register objReg = masm.extractObject(R0, ExtractTemp0); GeneralRegisterSet regs(availableGeneralRegs(2)); Register scratch = regs.takeAny(); - // Unbox and shape guard. - Register objReg = masm.extractObject(R0, ExtractTemp0); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_Native::offsetOfShape()), scratch); - masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); - - // Guard that the object group matches. - masm.loadPtr(Address(BaselineStubReg, ICSetProp_Native::offsetOfGroup()), scratch); - masm.branchPtr(Assembler::NotEqual, Address(objReg, JSObject::offsetOfGroup()), scratch, - &failure); + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_, true), objReg, scratch, + ICSetProp_Native::offsetOfReceiverGuard(), &failure); // Stow both R0 and R1 (object and value). EmitStowICValues(masm, 2); @@ -8437,7 +8565,13 @@ ICSetProp_Native::Compiler::generateStubCode(MacroAssembler &masm) regs.takeUnchecked(objReg); Register holderReg; - if (isFixedSlot_) { + if (obj_->is()) { + // We are loading off the expando object, so use that for the holder. + holderReg = regs.takeAny(); + masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); + if (!isFixedSlot_) + masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg); + } else if (isFixedSlot_) { holderReg = objReg; } else { holderReg = regs.takeAny(); @@ -8462,6 +8596,11 @@ ICSetProp_Native::Compiler::generateStubCode(MacroAssembler &masm) masm.moveValue(R1, R0); EmitReturnFromIC(masm); + if (failurePopObject.used()) { + masm.bind(&failurePopObject); + masm.pop(objReg); + } + // Failure case - jump to next stub masm.bind(&failure); EmitStubGuardFailure(masm); @@ -8472,8 +8611,6 @@ ICUpdatedStub * ICSetPropNativeAddCompiler::getStub(ICStubSpace *space) { AutoShapeVector shapes(cx); - if (!shapes.append(oldShape_)) - return nullptr; if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) return nullptr; @@ -8497,8 +8634,7 @@ ICSetPropNativeAddCompiler::getStub(ICStubSpace *space) bool ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) { - Label failure; - Label failureUnstow; + Label failure, failurePopObject, failureUnstow; // Guard input is an object. masm.branchTestObject(Assembler::NotEqual, R0, &failure); @@ -8506,15 +8642,10 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) GeneralRegisterSet regs(availableGeneralRegs(2)); Register scratch = regs.takeAny(); - // Unbox and guard against old shape. + // Unbox and guard that the object group matches. Register objReg = masm.extractObject(R0, ExtractTemp0); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(0)), scratch); - masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); - - // Guard that the object group matches. - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfGroup()), scratch); - masm.branchPtr(Assembler::NotEqual, Address(objReg, JSObject::offsetOfGroup()), scratch, - &failure); + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_, true), objReg, scratch, + ICSetProp_NativeAdd::offsetOfReceiverGuard(), &failure); // Stow both R0 and R1 (object and value). EmitStowICValues(masm, 2); @@ -8526,7 +8657,7 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) for (size_t i = 0; i < protoChainDepth_; i++) { masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg); masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(i + 1)), + masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAddImpl<1>::offsetOfShape(i)), scratch); masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failureUnstow); } @@ -8546,44 +8677,60 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) regs = availableGeneralRegs(2); scratch = regs.takeAny(); - // Changing object shape. Write the object's new shape. - Address shapeAddr(objReg, JSObject::offsetOfShape()); - EmitPreBarrier(masm, shapeAddr, MIRType_Shape); - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); - masm.storePtr(scratch, shapeAddr); + if (obj_->is()) { + // Try to change the object's group. + Label noGroupChange; - // Try to change the object's group. - Label noGroupChange; + // Check if the cache has a new group to change to. + masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); + masm.branchTestPtr(Assembler::Zero, scratch, scratch, &noGroupChange); - // Check if the cache has a new group to change to. - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); - masm.branchTestPtr(Assembler::Zero, scratch, scratch, &noGroupChange); + // Check if the old group still has a newScript. + masm.loadPtr(Address(objReg, JSObject::offsetOfGroup()), scratch); + masm.branchPtr(Assembler::Equal, + Address(scratch, ObjectGroup::offsetOfAddendum()), + ImmWord(0), + &noGroupChange); - // Check if the old group still has a newScript. - masm.loadPtr(Address(objReg, JSObject::offsetOfGroup()), scratch); - masm.branchPtr(Assembler::Equal, - Address(scratch, ObjectGroup::offsetOfAddendum()), - ImmWord(0), - &noGroupChange); + // Reload the new group from the cache. + masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); - // Reload the new group from the cache. - masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); + // Change the object's group. + Address groupAddr(objReg, JSObject::offsetOfGroup()); + EmitPreBarrier(masm, groupAddr, MIRType_ObjectGroup); + masm.storePtr(scratch, groupAddr); - // Change the object's group. - Address groupAddr(objReg, JSObject::offsetOfGroup()); - EmitPreBarrier(masm, groupAddr, MIRType_ObjectGroup); - masm.storePtr(scratch, groupAddr); - - masm.bind(&noGroupChange); + masm.bind(&noGroupChange); + } Register holderReg; regs.add(R0); regs.takeUnchecked(objReg); - if (isFixedSlot_) { - holderReg = objReg; - } else { + if (obj_->is()) { holderReg = regs.takeAny(); - masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg); + masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); + + // Write the expando object's new shape. + Address shapeAddr(holderReg, JSObject::offsetOfShape()); + EmitPreBarrier(masm, shapeAddr, MIRType_Shape); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); + masm.storePtr(scratch, shapeAddr); + + if (!isFixedSlot_) + masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg); + } else { + // Write the object's new shape. + Address shapeAddr(objReg, JSObject::offsetOfShape()); + EmitPreBarrier(masm, shapeAddr, MIRType_Shape); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); + masm.storePtr(scratch, shapeAddr); + + if (isFixedSlot_) { + holderReg = objReg; + } else { + holderReg = regs.takeAny(); + masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg); + } } // Perform the store. No write barrier required since this is a new @@ -8605,6 +8752,12 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) masm.moveValue(R1, R0); EmitReturnFromIC(masm); + if (failurePopObject.used()) { + masm.bind(&failurePopObject); + masm.pop(objReg); + masm.jump(&failure); + } + // Failure case - jump to next stub masm.bind(&failureUnstow); EmitUnstowICValues(masm, 2); @@ -8823,7 +8976,7 @@ ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) // Unbox and shape guard. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardNativeOrUnboxedReceiver(masm, obj_, objReg, scratch, + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_), objReg, scratch, ICSetProp_CallScripted::offsetOfGuard(), &failureUnstow); Register holderReg = regs.takeAny(); @@ -8942,7 +9095,7 @@ ICSetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) // Unbox and shape guard. Register objReg = masm.extractObject(R0, ExtractTemp0); - GuardNativeOrUnboxedReceiver(masm, obj_, objReg, scratch, + GuardNativeOrUnboxedReceiver(masm, ReceiverGuard::StackGuard(obj_), objReg, scratch, ICSetProp_CallNative::offsetOfGuard(), &failureUnstow); Register holderReg = regs.takeAny(); @@ -11765,8 +11918,10 @@ ICGetProp_Primitive::ICGetProp_Primitive(JitCode *stubCode, ICStub *firstMonitor { } ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode *stubCode, - ICStub *firstMonitorStub, uint32_t offset) + ICStub *firstMonitorStub, + ReceiverGuard::StackGuard guard, uint32_t offset) : ICMonitoredStub(kind, stubCode, firstMonitorStub), + receiverGuard_(guard), offset_(offset) { } @@ -11774,15 +11929,14 @@ ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode *stubCode, ICGetProp_Native::Clone(ICStubSpace *space, ICStub *firstMonitorStub, ICGetProp_Native &other) { - return New(space, other.jitCode(), firstMonitorStub, other.shape(), + return New(space, other.jitCode(), firstMonitorStub, other.receiverGuard(), other.offset()); } ICGetProp_NativePrototype::ICGetProp_NativePrototype(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token guard, uint32_t offset, + ReceiverGuard::StackGuard guard, uint32_t offset, JSObject *holder, Shape *holderShape) - : ICGetPropNativeStub(GetProp_NativePrototype, stubCode, firstMonitorStub, offset), - guard_(guard), + : ICGetPropNativeStub(GetProp_NativePrototype, stubCode, firstMonitorStub, guard, offset), holder_(holder), holderShape_(holderShape) { } @@ -11792,12 +11946,13 @@ ICGetProp_NativePrototype::Clone(ICStubSpace *space, ICStub *firstMonitorStub, ICGetProp_NativePrototype &other) { return New(space, other.jitCode(), firstMonitorStub, - other.guard().token(), - other.offset(), other.holder_, other.holderShape_); + other.receiverGuard(), other.offset(), + other.holder_, other.holderShape_); } ICGetProp_NativeDoesNotExist::ICGetProp_NativeDoesNotExist( - JitCode *stubCode, ICStub *firstMonitorStub, ReceiverGuard::Token guard, size_t protoChainDepth) + JitCode *stubCode, ICStub *firstMonitorStub, ReceiverGuard::StackGuard guard, + size_t protoChainDepth) : ICMonitoredStub(GetProp_NativeDoesNotExist, stubCode, firstMonitorStub), guard_(guard) { @@ -11816,7 +11971,7 @@ ICGetProp_NativeDoesNotExist::offsetOfShape(size_t idx) template ICGetProp_NativeDoesNotExistImpl::ICGetProp_NativeDoesNotExistImpl( - JitCode *stubCode, ICStub *firstMonitorStub, ReceiverGuard::Token guard, + JitCode *stubCode, ICStub *firstMonitorStub, ReceiverGuard::StackGuard guard, const AutoShapeVector *shapes) : ICGetProp_NativeDoesNotExist(stubCode, firstMonitorStub, guard, ProtoChainDepth) { @@ -11838,7 +11993,7 @@ ICGetPropNativeDoesNotExistCompiler::ICGetPropNativeDoesNotExistCompiler( } ICGetPropCallGetter::ICGetPropCallGetter(Kind kind, JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token receiverGuard, JSObject *holder, + ReceiverGuard::StackGuard receiverGuard, JSObject *holder, Shape *holderShape, JSFunction *getter, uint32_t pcOffset) : ICMonitoredStub(kind, stubCode, firstMonitorStub), @@ -11867,7 +12022,7 @@ ICGetProp_CallScripted::Clone(ICStubSpace *space, ICStub *firstMonitorStub, ICGetProp_CallScripted &other) { return New(space, other.jitCode(), firstMonitorStub, - other.receiverGuard().token(), + other.receiverGuard(), other.holder_, other.holderShape_, other.getter_, other.pcOffset_); } @@ -11877,39 +12032,34 @@ ICGetProp_CallNative::Clone(ICStubSpace *space, ICStub *firstMonitorStub, ICGetProp_CallNative &other) { return New(space, other.jitCode(), firstMonitorStub, - other.receiverGuard().token(), other.holder_, + other.receiverGuard(), other.holder_, other.holderShape_, other.getter_, other.pcOffset_); } -ICSetProp_Native::ICSetProp_Native(JitCode *stubCode, ObjectGroup *group, Shape *shape, +ICSetProp_Native::ICSetProp_Native(JitCode *stubCode, ReceiverGuard::StackGuard guard, uint32_t offset) : ICUpdatedStub(SetProp_Native, stubCode), - group_(group), - shape_(shape), + receiverGuard_(guard), offset_(offset) { } ICSetProp_Native * ICSetProp_Native::Compiler::getStub(ICStubSpace *space) { - RootedObjectGroup group(cx, obj_->getGroup(cx)); - if (!group) - return nullptr; - - RootedShape shape(cx, obj_->as().lastProperty()); - ICSetProp_Native *stub = ICStub::New(space, getStubCode(), group, shape, offset_); + ReceiverGuard::StackGuard guard(obj_, true); + ICSetProp_Native *stub = ICStub::New(space, getStubCode(), guard, offset_); if (!stub || !stub->initUpdatingChain(cx, space)) return nullptr; return stub; } -ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode *stubCode, ObjectGroup *group, +ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode *stubCode, ReceiverGuard::StackGuard guard, size_t protoChainDepth, Shape *newShape, ObjectGroup *newGroup, uint32_t offset) : ICUpdatedStub(SetProp_NativeAdd, stubCode), - group_(group), + receiverGuard_(guard), newShape_(newShape), newGroup_(newGroup), offset_(offset) @@ -11920,28 +12070,26 @@ ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode *stubCode, ObjectGroup *group, template ICSetProp_NativeAddImpl::ICSetProp_NativeAddImpl(JitCode *stubCode, - ObjectGroup *group, + ReceiverGuard::StackGuard guard, const AutoShapeVector *shapes, Shape *newShape, ObjectGroup *newGroup, uint32_t offset) - : ICSetProp_NativeAdd(stubCode, group, ProtoChainDepth, newShape, newGroup, offset) + : ICSetProp_NativeAdd(stubCode, guard, ProtoChainDepth, newShape, newGroup, offset) { MOZ_ASSERT(shapes->length() == NumShapes); - for (size_t i = 0; i < NumShapes; i++) + for (int i = 0; i < int(NumShapes); i++) // Use an int here to avoid compiler warnings. shapes_[i].init((*shapes)[i]); } ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext *cx, HandleObject obj, - HandleShape oldShape, - HandleObjectGroup oldGroup, + ReceiverGuard::StackGuard oldGuard, size_t protoChainDepth, bool isFixedSlot, uint32_t offset) : ICStubCompiler(cx, ICStub::SetProp_NativeAdd), obj_(cx, obj), - oldShape_(cx, oldShape), - oldGroup_(cx, oldGroup), + oldGuard_(cx, oldGuard), protoChainDepth_(protoChainDepth), isFixedSlot_(isFixedSlot), offset_(offset) @@ -11949,7 +12097,7 @@ ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext *cx, HandleObje MOZ_ASSERT(protoChainDepth_ <= ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH); } -ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode *stubCode, ReceiverGuard::Token guard, +ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode *stubCode, ReceiverGuard::StackGuard guard, JSObject *holder, Shape *holderShape, JSFunction *setter, uint32_t pcOffset) : ICStub(kind, stubCode), @@ -11965,14 +12113,14 @@ ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode *stubCode, ReceiverG /* static */ ICSetProp_CallScripted * ICSetProp_CallScripted::Clone(ICStubSpace *space, ICStub *, ICSetProp_CallScripted &other) { - return New(space, other.jitCode(), other.guard().token(), other.holder_, + return New(space, other.jitCode(), other.guard(), other.holder_, other.holderShape_, other.setter_, other.pcOffset_); } /* static */ ICSetProp_CallNative * ICSetProp_CallNative::Clone(ICStubSpace *space, ICStub *, ICSetProp_CallNative &other) { - return New(space, other.jitCode(), other.guard().token(), other.holder_, + return New(space, other.jitCode(), other.guard(), other.holder_, other.holderShape_, other.setter_, other.pcOffset_); } @@ -12082,8 +12230,8 @@ ICGetPropCallDOMProxyNativeStub::ICGetPropCallDOMProxyNativeStub(Kind kind, JitC Shape *holderShape, JSFunction *getter, uint32_t pcOffset) -: ICGetPropCallGetter(kind, stubCode, firstMonitorStub, ReceiverGuard::shapeToken(shape), - holder, holderShape, getter, pcOffset), + : ICGetPropCallGetter(kind, stubCode, firstMonitorStub, ReceiverGuard::StackGuard(shape), + holder, holderShape, getter, pcOffset), expandoShape_(expandoShape) { } diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index c7c7105b02e..f778874f0b6 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -3841,26 +3841,147 @@ class ICGetProp_StringLength : public ICStub }; }; +// Structure encapsulating the guarding that needs to be done on an object +// before it can be accessed or modified. +class ReceiverGuard +{ + // Group to guard on, or null. If the object is not unboxed and the IC does + // not require the object to have a specific group, this is null. + // Otherwise, this is the object's group. + HeapPtrObjectGroup group_; + + // Shape to guard on, or null. If the object is not unboxed then this is + // the object's shape. If the object is unboxed, then this is the shape of + // the object's expando, null if the object has no expando. + HeapPtrShape shape_; + + public: + struct StackGuard; + + struct RootedStackGuard + { + RootedObjectGroup group; + RootedShape shape; + + RootedStackGuard(JSContext *cx, const StackGuard &guard) + : group(cx, guard.group), shape(cx, guard.shape) + {} + }; + + struct StackGuard + { + ObjectGroup *group; + Shape *shape; + + MOZ_IMPLICIT StackGuard(const ReceiverGuard &guard) + : group(guard.group_), shape(guard.shape_) + {} + + MOZ_IMPLICIT StackGuard(const RootedStackGuard &guard) + : group(guard.group), shape(guard.shape) + {} + + explicit StackGuard(JSObject *obj, bool guardGroup = false) + : group(nullptr), shape(nullptr) + { + if (obj) { + shape = obj->maybeShape(); + if (shape) { + if (guardGroup) + group = obj->group(); + } else { + group = obj->group(); + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) + shape = expando->lastProperty(); + } + } + } + + explicit StackGuard(Shape *shape) + : group(nullptr), shape(shape) + {} + + Shape *ownShape() const { + // Get a shape belonging to the object itself, rather than an unboxed expando. + if (!group || !group->maybeUnboxedLayout()) + return shape; + return nullptr; + } + }; + + explicit ReceiverGuard(const StackGuard &guard) + : group_(guard.group), shape_(guard.shape) + {} + + bool matches(const StackGuard &guard) { + return group_ == guard.group && shape_ == guard.shape; + } + + void update(const StackGuard &other) { + group_ = other.group; + shape_ = other.shape; + } + + void trace(JSTracer *trc); + + Shape *shape() const { + return shape_; + } + ObjectGroup *group() const { + return group_; + } + + Shape *ownShape() const { + return StackGuard(*this).ownShape(); + } + + static size_t offsetOfShape() { + return offsetof(ReceiverGuard, shape_); + } + static size_t offsetOfGroup() { + return offsetof(ReceiverGuard, group_); + } + + // Bits to munge into IC compiler keys when that IC has a ReceiverGuard. + // This uses at two bits for data. + static int32_t keyBits(JSObject *obj) { + if (obj->maybeShape()) + return 0; + return obj->as().maybeExpando() ? 1 : 2; + } +}; + // Base class for native GetProp stubs. class ICGetPropNativeStub : public ICMonitoredStub { + // Object shape/group. + ReceiverGuard receiverGuard_; + // Fixed or dynamic slot offset. uint32_t offset_; protected: ICGetPropNativeStub(ICStub::Kind kind, JitCode *stubCode, ICStub *firstMonitorStub, - uint32_t offset); + ReceiverGuard::StackGuard guard, uint32_t offset); public: + ReceiverGuard &receiverGuard() { + return receiverGuard_; + } uint32_t offset() const { return offset_; } + void notePreliminaryObject() { extra_ = 1; } bool hasPreliminaryObject() const { return extra_; } + + static size_t offsetOfReceiverGuard() { + return offsetof(ICGetPropNativeStub, receiverGuard_); + } static size_t offsetOfOffset() { return offsetof(ICGetPropNativeStub, offset_); } @@ -3871,103 +3992,16 @@ class ICGetProp_Native : public ICGetPropNativeStub { friend class ICStubSpace; - // Object shape (lastProperty). - HeapPtrShape shape_; - - ICGetProp_Native(JitCode *stubCode, ICStub *firstMonitorStub, Shape *shape, + ICGetProp_Native(JitCode *stubCode, ICStub *firstMonitorStub, ReceiverGuard::StackGuard guard, uint32_t offset) - : ICGetPropNativeStub(GetProp_Native, stubCode, firstMonitorStub, offset), - shape_(shape) + : ICGetPropNativeStub(GetProp_Native, stubCode, firstMonitorStub, guard, offset) {} public: - HeapPtrShape &shape() { - return shape_; - } - static size_t offsetOfShape() { - return offsetof(ICGetProp_Native, shape_); - } - static ICGetProp_Native *Clone(ICStubSpace *space, ICStub *firstMonitorStub, ICGetProp_Native &other); }; -// Structure encapsulating the guarding that needs to be done on an object -// which might be either native or unboxed. In the former case, only the -// object's shape needs to be guarded. In the latter case, only the object's -// group needs to be guarded. -class ReceiverGuard -{ - HeapPtrShape shape_; - HeapPtrObjectGroup group_; - - public: - typedef uintptr_t Token; - - static Token shapeToken(Shape *shape) { - return reinterpret_cast(shape) | 1; - } - - static Token groupToken(ObjectGroup *group) { - return reinterpret_cast(group); - } - - static Shape *tokenShape(Token token) { - if (token & 1) - return reinterpret_cast(token & ~1); - return nullptr; - } - - static ObjectGroup *tokenGroup(Token token) { - if (!(token & 1)) - return reinterpret_cast(token); - return nullptr; - } - - static Token objectToken(JSObject *obj) { - if (obj->is()) - return groupToken(obj->group()); - return shapeToken(obj->maybeShape()); - } - - explicit ReceiverGuard(Token token) - : shape_(tokenShape(token)), group_(tokenGroup(token)) - { - MOZ_ASSERT(shape_ || group_); - } - - void update(Token token) { - MOZ_ASSERT(!!shape_ == !!tokenShape(token)); - MOZ_ASSERT(!!group_ == !!tokenGroup(token)); - shape_ = tokenShape(token); - group_ = tokenGroup(token); - MOZ_ASSERT(shape_ || group_); - } - - void trace(JSTracer *trc); - - Token token() { - MOZ_ASSERT(!!shape_ != !!group_); - if (shape_) - return shapeToken(shape_); - return groupToken(group_); - } - - Shape *shape() const { - return shape_; - } - ObjectGroup *group() const { - return group_; - } - - static size_t offsetOfShape() { - return offsetof(ReceiverGuard, shape_); - } - static size_t offsetOfGroup() { - return offsetof(ReceiverGuard, group_); - } -}; - // Stub for accessing a property on the native prototype of a native or unboxed // object. Note that due to the shape teleporting optimization, we only have to // guard on the object's shape/group and the holder's shape. @@ -3976,15 +4010,12 @@ class ICGetProp_NativePrototype : public ICGetPropNativeStub friend class ICStubSpace; protected: - // Object shape/group. - ReceiverGuard guard_; - // Holder and its shape. HeapPtrObject holder_; HeapPtrShape holderShape_; ICGetProp_NativePrototype(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token guard, + ReceiverGuard::StackGuard guard, uint32_t offset, JSObject *holder, Shape *holderShape); public: @@ -3993,18 +4024,12 @@ class ICGetProp_NativePrototype : public ICGetPropNativeStub ICGetProp_NativePrototype &other); public: - ReceiverGuard &guard() { - return guard_; - } HeapPtrObject &holder() { return holder_; } HeapPtrShape &holderShape() { return holderShape_; } - static size_t offsetOfGuard() { - return offsetof(ICGetProp_NativePrototype, guard_); - } static size_t offsetOfHolder() { return offsetof(ICGetProp_NativePrototype, holder_); } @@ -4033,7 +4058,7 @@ class ICGetPropNativeCompiler : public ICStubCompiler (static_cast(isCallProp_) << 16) | (static_cast(isFixedSlot_) << 17) | (static_cast(inputDefinitelyObject_) << 18) | - (static_cast(obj_->isNative()) << 19); + (ReceiverGuard::keyBits(obj_) << 19); } public: @@ -4067,7 +4092,7 @@ class ICGetProp_NativeDoesNotExist : public ICMonitoredStub protected: ICGetProp_NativeDoesNotExist(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token guard, + ReceiverGuard::StackGuard guard, size_t protoChainDepth); public: @@ -4105,7 +4130,7 @@ class ICGetProp_NativeDoesNotExistImpl : public ICGetProp_NativeDoesNotExist mozilla::Array shapes_; ICGetProp_NativeDoesNotExistImpl(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token guard, + ReceiverGuard::StackGuard guard, const AutoShapeVector *shapes); public: @@ -4129,8 +4154,8 @@ class ICGetPropNativeDoesNotExistCompiler : public ICStubCompiler protected: virtual int32_t getKey() const { return static_cast(kind) | - (static_cast(obj_->isNative()) << 16) | - (static_cast(protoChainDepth_) << 17); + (ReceiverGuard::keyBits(obj_) << 16) | + (static_cast(protoChainDepth_) << 18); } bool generateStubCode(MacroAssembler &masm); @@ -4141,8 +4166,9 @@ class ICGetPropNativeDoesNotExistCompiler : public ICStubCompiler template ICStub *getStubSpecific(ICStubSpace *space, const AutoShapeVector *shapes) { + ReceiverGuard::StackGuard guard(obj_); return ICStub::New > - (space, getStubCode(), firstMonitorStub_, ReceiverGuard::objectToken(obj_), shapes); + (space, getStubCode(), firstMonitorStub_, guard, shapes); } ICStub *getStub(ICStubSpace *space); @@ -4299,7 +4325,7 @@ class ICGetPropCallGetter : public ICMonitoredStub uint32_t pcOffset_; ICGetPropCallGetter(Kind kind, JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token receiverGuard, JSObject *holder, + ReceiverGuard::StackGuard receiverGuard, JSObject *holder, Shape *holderShape, JSFunction *getter, uint32_t pcOffset); public: @@ -4351,9 +4377,9 @@ class ICGetPropCallGetter : public ICMonitoredStub // ICGetProp_CallNative::Compiler::getKey adds more bits to our // return value, so be careful when making changes here. return static_cast(kind) | - (static_cast(receiver_->isNative()) << 16) | - (static_cast(!!outerClass_) << 17) | - (static_cast(receiver_ != holder_) << 18); + (ReceiverGuard::keyBits(receiver_) << 16) | + (static_cast(!!outerClass_) << 18) | + (static_cast(receiver_ != holder_) << 19); } public: @@ -4382,7 +4408,7 @@ class ICGetProp_CallScripted : public ICGetPropCallGetter protected: ICGetProp_CallScripted(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token receiverGuard, + ReceiverGuard::StackGuard receiverGuard, JSObject *holder, Shape *holderShape, JSFunction *getter, uint32_t pcOffset) : ICGetPropCallGetter(GetProp_CallScripted, stubCode, firstMonitorStub, @@ -4406,7 +4432,7 @@ class ICGetProp_CallScripted : public ICGetPropCallGetter {} ICStub *getStub(ICStubSpace *space) { - ReceiverGuard::Token guard = ReceiverGuard::objectToken(receiver_); + ReceiverGuard::StackGuard guard(receiver_); Shape *holderShape = holder_->as().lastProperty(); return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, holder_, holderShape, getter_, @@ -4423,7 +4449,8 @@ class ICGetProp_CallNative : public ICGetPropCallGetter protected: ICGetProp_CallNative(JitCode *stubCode, ICStub *firstMonitorStub, - ReceiverGuard::Token receiverGuard, JSObject *holder, Shape *holderShape, + ReceiverGuard::StackGuard receiverGuard, + JSObject *holder, Shape *holderShape, JSFunction *getter, uint32_t pcOffset) : ICGetPropCallGetter(GetProp_CallNative, stubCode, firstMonitorStub, receiverGuard, holder, holderShape, getter, pcOffset) @@ -4441,8 +4468,8 @@ class ICGetProp_CallNative : public ICGetPropCallGetter virtual int32_t getKey() const { int32_t baseKey = ICGetPropCallGetter::Compiler::getKey(); - MOZ_ASSERT((baseKey >> 19) == 0); - return baseKey | (static_cast(inputDefinitelyObject_) << 19); + MOZ_ASSERT((baseKey >> 20) == 0); + return baseKey | (static_cast(inputDefinitelyObject_) << 20); } public: @@ -4456,7 +4483,7 @@ class ICGetProp_CallNative : public ICGetPropCallGetter {} ICStub *getStub(ICStubSpace *space) { - ReceiverGuard::Token guard = ReceiverGuard::objectToken(receiver_); + ReceiverGuard::StackGuard guard(receiver_); Shape *holderShape = holder_->as().lastProperty(); return ICStub::New(space, getStubCode(), firstMonitorStub_, guard, holder_, holderShape, @@ -4736,18 +4763,14 @@ class ICSetProp_Native : public ICUpdatedStub friend class ICStubSpace; protected: // Protected to silence Clang warning. - HeapPtrObjectGroup group_; - HeapPtrShape shape_; + ReceiverGuard receiverGuard_; uint32_t offset_; - ICSetProp_Native(JitCode *stubCode, ObjectGroup *group, Shape *shape, uint32_t offset); + ICSetProp_Native(JitCode *stubCode, ReceiverGuard::StackGuard guard, uint32_t offset); public: - HeapPtrObjectGroup &group() { - return group_; - } - HeapPtrShape &shape() { - return shape_; + ReceiverGuard &receiverGuard() { + return receiverGuard_; } void notePreliminaryObject() { extra_ = 1; @@ -4755,11 +4778,8 @@ class ICSetProp_Native : public ICUpdatedStub bool hasPreliminaryObject() const { return extra_; } - static size_t offsetOfGroup() { - return offsetof(ICSetProp_Native, group_); - } - static size_t offsetOfShape() { - return offsetof(ICSetProp_Native, shape_); + static size_t offsetOfReceiverGuard() { + return offsetof(ICSetProp_Native, receiverGuard_); } static size_t offsetOfOffset() { return offsetof(ICSetProp_Native, offset_); @@ -4772,7 +4792,9 @@ class ICSetProp_Native : public ICUpdatedStub protected: virtual int32_t getKey() const { - return static_cast(kind) | (static_cast(isFixedSlot_) << 16); + return static_cast(kind) | + (static_cast(isFixedSlot_) << 16) | + (ReceiverGuard::keyBits(obj_) << 17); } bool generateStubCode(MacroAssembler &masm); @@ -4798,20 +4820,20 @@ class ICSetProp_NativeAdd : public ICUpdatedStub static const size_t MAX_PROTO_CHAIN_DEPTH = 4; protected: // Protected to silence Clang warning. - HeapPtrObjectGroup group_; + ReceiverGuard receiverGuard_; HeapPtrShape newShape_; HeapPtrObjectGroup newGroup_; uint32_t offset_; - ICSetProp_NativeAdd(JitCode *stubCode, ObjectGroup *group, size_t protoChainDepth, + ICSetProp_NativeAdd(JitCode *stubCode, ReceiverGuard::StackGuard guard, size_t protoChainDepth, Shape *newShape, ObjectGroup *newGroup, uint32_t offset); public: size_t protoChainDepth() const { return extra_; } - HeapPtrObjectGroup &group() { - return group_; + ReceiverGuard &receiverGuard() { + return receiverGuard_; } HeapPtrShape &newShape() { return newShape_; @@ -4826,8 +4848,8 @@ class ICSetProp_NativeAdd : public ICUpdatedStub return static_cast *>(this); } - static size_t offsetOfGroup() { - return offsetof(ICSetProp_NativeAdd, group_); + static size_t offsetOfReceiverGuard() { + return offsetof(ICSetProp_NativeAdd, receiverGuard_); } static size_t offsetOfNewShape() { return offsetof(ICSetProp_NativeAdd, newShape_); @@ -4845,20 +4867,23 @@ class ICSetProp_NativeAddImpl : public ICSetProp_NativeAdd { friend class ICStubSpace; - static const size_t NumShapes = ProtoChainDepth + 1; + static const size_t NumShapes = ProtoChainDepth; mozilla::Array shapes_; - ICSetProp_NativeAddImpl(JitCode *stubCode, ObjectGroup *group, + ICSetProp_NativeAddImpl(JitCode *stubCode, ReceiverGuard::StackGuard guard, const AutoShapeVector *shapes, Shape *newShape, ObjectGroup *newGroup, uint32_t offset); public: void traceShapes(JSTracer *trc) { - for (size_t i = 0; i < NumShapes; i++) + // Note: using int32_t here to avoid gcc warning. + for (int32_t i = 0; i < int32_t(NumShapes); i++) MarkShape(trc, &shapes_[i], "baseline-setpropnativeadd-stub-shape"); } static size_t offsetOfShape(size_t idx) { + // The shape array might be aligned differently if its length is zero. + JS_STATIC_ASSERT(NumShapes != 0); return offsetof(ICSetProp_NativeAddImpl, shapes_) + (idx * sizeof(HeapPtrShape)); } }; @@ -4866,23 +4891,24 @@ class ICSetProp_NativeAddImpl : public ICSetProp_NativeAdd class ICSetPropNativeAddCompiler : public ICStubCompiler { RootedObject obj_; - RootedShape oldShape_; - RootedObjectGroup oldGroup_; + ReceiverGuard::RootedStackGuard oldGuard_; size_t protoChainDepth_; bool isFixedSlot_; uint32_t offset_; protected: virtual int32_t getKey() const { - return static_cast(kind) | (static_cast(isFixedSlot_) << 16) | - (static_cast(protoChainDepth_) << 20); + return static_cast(kind) | + (static_cast(isFixedSlot_) << 16) | + (static_cast(ReceiverGuard::keyBits(obj_)) << 17) | + (static_cast(protoChainDepth_) << 19); } bool generateStubCode(MacroAssembler &masm); public: ICSetPropNativeAddCompiler(JSContext *cx, HandleObject obj, - HandleShape oldShape, HandleObjectGroup oldGroup, + ReceiverGuard::StackGuard oldGuard, size_t protoChainDepth, bool isFixedSlot, uint32_t offset); template @@ -4895,13 +4921,17 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler // Only specify newGroup when the object's group changes due to the // object becoming fully initialized per the acquired properties // analysis. - if (newGroup == oldGroup_) + if (newGroup == oldGuard_.group) newGroup = nullptr; - RootedShape newShape(cx, obj_->as().lastProperty()); + RootedShape newShape(cx); + if (obj_->is()) + newShape = obj_->as().maybeExpando()->lastProperty(); + else + newShape = obj_->as().lastProperty(); return ICStub::New>( - space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_); + space, getStubCode(), oldGuard_, shapes, newShape, newGroup, offset_); } ICUpdatedStub *getStub(ICStubSpace *space); @@ -5076,8 +5106,9 @@ class ICSetPropCallSetter : public ICStub // PC of call, for profiler uint32_t pcOffset_; - ICSetPropCallSetter(Kind kind, JitCode *stubCode, ReceiverGuard::Token guard, JSObject *holder, - Shape *holderShape, JSFunction *setter, uint32_t pcOffset); + ICSetPropCallSetter(Kind kind, JitCode *stubCode, ReceiverGuard::StackGuard guard, + JSObject *holder, Shape *holderShape, JSFunction *setter, + uint32_t pcOffset); public: ReceiverGuard &guard() { @@ -5118,7 +5149,7 @@ class ICSetPropCallSetter : public ICStub virtual int32_t getKey() const { return static_cast(kind) | - (static_cast(obj_->isNative()) << 16); + (ReceiverGuard::keyBits(obj_) << 16); } public: @@ -5141,7 +5172,7 @@ class ICSetProp_CallScripted : public ICSetPropCallSetter friend class ICStubSpace; protected: - ICSetProp_CallScripted(JitCode *stubCode, ReceiverGuard::Token guard, JSObject *holder, + ICSetProp_CallScripted(JitCode *stubCode, ReceiverGuard::StackGuard guard, JSObject *holder, Shape *holderShape, JSFunction *setter, uint32_t pcOffset) : ICSetPropCallSetter(SetProp_CallScripted, stubCode, guard, holder, holderShape, setter, pcOffset) @@ -5163,7 +5194,7 @@ class ICSetProp_CallScripted : public ICSetPropCallSetter {} ICStub *getStub(ICStubSpace *space) { - ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_); + ReceiverGuard::StackGuard guard(obj_); Shape *holderShape = holder_->as().lastProperty(); return ICStub::New(space, getStubCode(), guard, holder_, holderShape, setter_, pcOffset_); @@ -5177,7 +5208,7 @@ class ICSetProp_CallNative : public ICSetPropCallSetter friend class ICStubSpace; protected: - ICSetProp_CallNative(JitCode *stubCode, ReceiverGuard::Token guard, JSObject *holder, + ICSetProp_CallNative(JitCode *stubCode, ReceiverGuard::StackGuard guard, JSObject *holder, Shape *holderShape, JSFunction *setter, uint32_t pcOffset) : ICSetPropCallSetter(SetProp_CallNative, stubCode, guard, holder, holderShape, setter, pcOffset) @@ -5199,7 +5230,7 @@ class ICSetProp_CallNative : public ICSetPropCallSetter {} ICStub *getStub(ICStubSpace *space) { - ReceiverGuard::Token guard = ReceiverGuard::objectToken(obj_); + ReceiverGuard::StackGuard guard(obj_); Shape *holderShape = holder_->as().lastProperty(); return ICStub::New(space, getStubCode(), guard, holder_, holderShape, setter_, pcOffset_); diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index 35f29df88f2..9146b190347 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -116,14 +116,16 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode *pc, Shape *shape = nullptr; ObjectGroup *group = nullptr; if (stub->isGetProp_Native()) { - shape = stub->toGetProp_Native()->shape(); + shape = stub->toGetProp_Native()->receiverGuard().ownShape(); } else if (stub->isSetProp_Native()) { - shape = stub->toSetProp_Native()->shape(); + shape = stub->toSetProp_Native()->receiverGuard().ownShape(); } else if (stub->isGetProp_Unboxed()) { group = stub->toGetProp_Unboxed()->group(); } else if (stub->isSetProp_Unboxed()) { group = stub->toSetProp_Unboxed()->group(); - } else { + } + + if (!shape && !group) { nativeShapes.clear(); unboxedGroups.clear(); return true; @@ -589,12 +591,17 @@ GlobalShapeForGetPropFunction(ICStub *stub) static bool AddReceiver(BaselineInspector::ShapeVector &nativeShapes, BaselineInspector::ObjectGroupVector &unboxedGroups, - ReceiverGuard::Token receiver) + ReceiverGuard::StackGuard receiver) { - if (Shape *shape = ReceiverGuard::tokenShape(receiver)) + if (Shape *shape = receiver.ownShape()) return VectorAppendNoDuplicate(nativeShapes, shape); - ObjectGroup *group = ReceiverGuard::tokenGroup(receiver); - return VectorAppendNoDuplicate(unboxedGroups, group); + + // Only unboxed objects with no expandos are handled by the common + // getprop/setprop optimizations. + if (!receiver.shape) + return VectorAppendNoDuplicate(unboxedGroups, receiver.group); + + return false; } static bool @@ -605,13 +612,7 @@ AddReceiverForGetPropFunction(BaselineInspector::ShapeVector &nativeShapes, if (stub->isOwnGetter()) return true; - ReceiverGuard::Token token; - if (stub->isGetProp_CallScripted()) - token = stub->toGetProp_CallScripted()->receiverGuard().token(); - else - token = stub->toGetProp_CallNative()->receiverGuard().token(); - - return AddReceiver(nativeShapes, unboxedGroups, token); + return AddReceiver(nativeShapes, unboxedGroups, stub->receiverGuard()); } bool @@ -690,7 +691,7 @@ BaselineInspector::commonSetPropFunction(jsbytecode *pc, JSObject **holder, Shap for (ICStub *stub = entry.firstStub(); stub; stub = stub->next()) { if (stub->isSetProp_CallScripted() || stub->isSetProp_CallNative()) { ICSetPropCallSetter *nstub = static_cast(stub); - if (!AddReceiver(nativeShapes, unboxedGroups, nstub->guard().token())) + if (!AddReceiver(nativeShapes, unboxedGroups, nstub->guard())) return false; if (!*holder) { diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 46ca1266b90..caa0eb2e858 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2564,6 +2564,10 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic *lir) } if (mir->numUnboxedGroups()) { + // The guard requires that unboxed objects not have expandos. + bailoutCmpPtr(Assembler::NotEqual, Address(obj, JSObject::offsetOfShape()), + ImmWord(0), lir->snapshot()); + masm.loadObjGroup(obj, temp); for (size_t i = 0; i < mir->numUnboxedGroups(); i++) { diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index a720f4e102f..ba38f92ab63 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -10521,7 +10521,7 @@ IonBuilder::addShapeGuardsForGetterSetter(MDefinition *obj, JSObject *holder, Sh MDefinition *holderDef = constantMaybeNursery(holder); addShapeGuard(holderDef, holderShape, Bailout_ShapeGuard); - return addShapeGuardPolymorphic(obj, receiverShapes, receiverUnboxedGroups); + return addGuardReceiverPolymorphic(obj, receiverShapes, receiverUnboxedGroups); } bool @@ -10787,7 +10787,7 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, MDefinition *obj, PropertyName return false; if (sameSlot && unboxedGroups.empty()) { - obj = addShapeGuardPolymorphic(obj, nativeShapes, unboxedGroups); + obj = addGuardReceiverPolymorphic(obj, nativeShapes, unboxedGroups); if (!obj) return false; @@ -11467,7 +11467,7 @@ IonBuilder::setPropTryInlineAccess(bool *emitted, MDefinition *obj, return false; if (sameSlot && unboxedGroups.empty()) { - obj = addShapeGuardPolymorphic(obj, nativeShapes, unboxedGroups); + obj = addGuardReceiverPolymorphic(obj, nativeShapes, unboxedGroups); if (!obj) return false; @@ -12379,11 +12379,12 @@ IonBuilder::addShapeGuard(MDefinition *obj, Shape *const shape, BailoutKind bail } MInstruction * -IonBuilder::addGroupGuard(MDefinition *obj, ObjectGroup *group, BailoutKind bailoutKind) +IonBuilder::addGroupGuard(MDefinition *obj, ObjectGroup *group, BailoutKind bailoutKind, + bool checkUnboxedExpando) { MGuardObjectGroup *guard = MGuardObjectGroup::New(alloc(), obj, group, /* bailOnEquality = */ false, - bailoutKind); + bailoutKind, checkUnboxedExpando); current->add(guard); // If a shape guard failed in the past, don't optimize group guards. @@ -12398,15 +12399,19 @@ IonBuilder::addGroupGuard(MDefinition *obj, ObjectGroup *group, BailoutKind bail } MInstruction * -IonBuilder::addShapeGuardPolymorphic(MDefinition *obj, - const BaselineInspector::ShapeVector &shapes, - const BaselineInspector::ObjectGroupVector &unboxedGroups) +IonBuilder::addGuardReceiverPolymorphic(MDefinition *obj, + const BaselineInspector::ShapeVector &shapes, + const BaselineInspector::ObjectGroupVector &unboxedGroups) { if (shapes.length() == 1 && unboxedGroups.empty()) return addShapeGuard(obj, shapes[0], Bailout_ShapeGuard); - if (shapes.empty() && unboxedGroups.length() == 1) - return addGroupGuard(obj, unboxedGroups[0], Bailout_ShapeGuard); + if (shapes.empty() && unboxedGroups.length() == 1) { + // The guard requires that unboxed objects not have expando objects. + // An inline cache will be used in these cases. + return addGroupGuard(obj, unboxedGroups[0], Bailout_ShapeGuard, + /* checkUnboxedExpando = */ true); + } MOZ_ASSERT(shapes.length() + unboxedGroups.length() > 1); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 8a22437228c..d7d0601f4be 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -398,12 +398,13 @@ class IonBuilder MDefinition *addMaybeCopyElementsForWrite(MDefinition *object); MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length); MInstruction *addShapeGuard(MDefinition *obj, Shape *const shape, BailoutKind bailoutKind); - MInstruction *addGroupGuard(MDefinition *obj, ObjectGroup *group, BailoutKind bailoutKind); + MInstruction *addGroupGuard(MDefinition *obj, ObjectGroup *group, BailoutKind bailoutKind, + bool checkUnboxedExpando = false); MInstruction * - addShapeGuardPolymorphic(MDefinition *obj, - const BaselineInspector::ShapeVector &shapes, - const BaselineInspector::ObjectGroupVector &unboxedGroups); + addGuardReceiverPolymorphic(MDefinition *obj, + const BaselineInspector::ShapeVector &shapes, + const BaselineInspector::ObjectGroupVector &unboxedGroups); MDefinition *convertShiftToMaskForStaticTypedArray(MDefinition *id, Scalar::Type viewType); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index e83ab9faff6..781487a6847 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -699,6 +699,41 @@ IsCacheableGetPropCallPropertyOp(JSObject *obj, JSObject *holder, Shape *shape) return true; } +static void +TestMatchingReceiver(MacroAssembler &masm, IonCache::StubAttacher &attacher, + Register object, JSObject *obj, Label *failure, + bool alwaysCheckGroup = false) +{ + if (Shape *shape = obj->maybeShape()) { + attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, + Address(object, JSObject::offsetOfShape()), + ImmGCPtr(shape), failure); + + if (alwaysCheckGroup) + masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure); + } else { + MOZ_ASSERT(obj->is()); + MOZ_ASSERT(failure); + + masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) { + masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure); + Label success; + masm.push(object); + masm.loadPtr(expandoAddress, object); + masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(), + &success); + masm.pop(object); + masm.jump(failure); + masm.bind(&success); + masm.pop(object); + } else { + masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure); + } + } +} + static inline void EmitLoadSlot(MacroAssembler &masm, NativeObject *holder, Shape *shape, Register holderReg, TypedOrValueRegister output, Register scratchReg) @@ -791,14 +826,16 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext *cx, MacroAssembler &masm, JSObject static void GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, - IonCache::StubAttacher &attacher, JSObject *obj, NativeObject *holder, + IonCache::StubAttacher &attacher, JSObject *obj, JSObject *holder, Shape *shape, Register object, TypedOrValueRegister output, Label *failures = nullptr) { // 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 // common point to patch. - bool multipleFailureJumps = (obj != holder) || (failures != nullptr && failures->used()); + bool multipleFailureJumps = (obj != holder) + || obj->is() + || (failures != nullptr && failures->used()); // If we have multiple failure jumps but didn't get a label from the // outside, make one ourselves. @@ -806,18 +843,7 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, if (multipleFailureJumps && !failures) failures = &failures_; - // Guard on the shape or type of the object, depending on whether it is native. - if (obj->isNative()) { - attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, - Address(object, JSObject::offsetOfShape()), - ImmGCPtr(obj->as().lastProperty()), - failures); - } else { - attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, - Address(object, JSObject::offsetOfGroup()), - ImmGCPtr(obj->group()), - failures); - } + TestMatchingReceiver(masm, attacher, object, obj, failures); // If we need a scratch register, use either an output register or the // object register. After this point, we cannot jump directly to @@ -825,7 +851,10 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, bool restoreScratch = false; Register scratchReg = Register::FromCode(0); // Quell compiler warning. - if (obj != holder || !holder->isFixedSlot(shape->slot())) { + if (obj != holder || + obj->is() || + !holder->as().isFixedSlot(shape->slot())) + { if (output.hasValue()) { scratchReg = output.valueReg().scratchReg(); } else if (output.type() == MIRType_Double) { @@ -839,7 +868,7 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, // Fast path: single failure jump, no prototype guards. if (!multipleFailureJumps) { - EmitLoadSlot(masm, holder, shape, object, output, scratchReg); + EmitLoadSlot(masm, &holder->as(), shape, object, output, scratchReg); if (restoreScratch) masm.pop(scratchReg); attacher.jumpRejoin(masm); @@ -860,7 +889,7 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, masm.movePtr(ImmMaybeNurseryPtr(holder), holderReg); masm.branchPtr(Assembler::NotEqual, Address(holderReg, JSObject::offsetOfShape()), - ImmGCPtr(holder->lastProperty()), + ImmGCPtr(holder->as().lastProperty()), &prototypeFailures); } else { // The property does not exist. Guard on everything in the @@ -883,13 +912,17 @@ GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm, holderReg = InvalidReg; } + } else if (obj->is()) { + holder = obj->as().maybeExpando(); + holderReg = scratchReg; + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg); } else { holderReg = object; } // Slot access. if (holder) - EmitLoadSlot(masm, holder, shape, holderReg, output, scratchReg); + EmitLoadSlot(masm, &holder->as(), shape, holderReg, output, scratchReg); else masm.moveValue(UndefinedValue(), output.valueReg()); @@ -1098,15 +1131,6 @@ EmitGetterCall(JSContext *cx, MacroAssembler &masm, return true; } -static void -TestMatchingReceiver(MacroAssembler &masm, Register object, JSObject *obj, Label *failure) -{ - if (Shape *shape = obj->maybeShape()) - masm.branchTestObjShape(Assembler::NotEqual, object, shape, failure); - else - masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure); -} - static bool GenerateCallGetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, IonCache::StubAttacher &attacher, JSObject *obj, PropertyName *name, @@ -1119,7 +1143,7 @@ GenerateCallGetter(JSContext *cx, IonScript *ion, MacroAssembler &masm, Label stubFailure; failures = failures ? failures : &stubFailure; - TestMatchingReceiver(masm, object, obj, failures); + TestMatchingReceiver(masm, attacher, object, obj, failures); Register scratchReg = output.valueReg().scratchReg(); bool spillObjReg = scratchReg == object; @@ -1428,6 +1452,35 @@ GetPropertyIC::tryAttachUnboxed(JSContext *cx, HandleScript outerScript, IonScri return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed"); } +bool +GetPropertyIC::tryAttachUnboxedExpando(JSContext *cx, HandleScript outerScript, IonScript *ion, + HandleObject obj, HandlePropertyName name, + void *returnAddr, bool *emitted) +{ + MOZ_ASSERT(canAttachStub()); + MOZ_ASSERT(!*emitted); + MOZ_ASSERT(outerScript->ionScript() == ion); + + if (!obj->is()) + return true; + Rooted expando(cx, obj->as().maybeExpando()); + if (!expando) + return true; + + Shape *shape = expando->lookup(cx, name); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) + return true; + + *emitted = true; + + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + + RepatchStubAppender attacher(*this); + GenerateReadSlot(cx, ion, masm, attacher, obj, obj, + shape, object(), output()); + return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando"); +} + bool GetPropertyIC::tryAttachTypedArrayLength(JSContext *cx, HandleScript outerScript, IonScript *ion, HandleObject obj, HandlePropertyName name, bool *emitted) @@ -1867,6 +1920,9 @@ GetPropertyIC::tryAttachStub(JSContext *cx, HandleScript outerScript, IonScript if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, name, returnAddr, emitted)) return false; + if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, name, returnAddr, emitted)) + return false; + if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, name, emitted)) return false; @@ -1997,37 +2053,33 @@ CheckTypeSetForWrite(MacroAssembler &masm, JSObject *obj, jsid id, static void GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, - NativeObject *obj, Shape *shape, Register object, ConstantOrRegister value, + JSObject *obj, Shape *shape, Register object, ConstantOrRegister value, bool needsTypeBarrier, bool checkTypeset) { - MOZ_ASSERT(obj->isNative()); + Label failures, failurePopObject; - Label failures, barrierFailure; - masm.branchPtr(Assembler::NotEqual, - Address(object, JSObject::offsetOfShape()), - ImmGCPtr(obj->lastProperty()), &failures); + TestMatchingReceiver(masm, attacher, object, obj, &failures, needsTypeBarrier); // Guard that the incoming value is in the type set for the property // if a type barrier is required. if (needsTypeBarrier) { // We can't do anything that would change the HeapTypeSet, so // just guard that it's already there. - - // Obtain and guard on the ObjectGroup of the object. - ObjectGroup *group = obj->group(); - masm.branchPtr(Assembler::NotEqual, - Address(object, JSObject::offsetOfGroup()), - ImmGCPtr(group), &failures); - if (checkTypeset) { masm.push(object); - CheckTypeSetForWrite(masm, obj, shape->propid(), object, value, &barrierFailure); + CheckTypeSetForWrite(masm, obj, shape->propid(), object, value, &failurePopObject); masm.pop(object); } } NativeObject::slotsSizeMustNotOverflow(); - if (obj->isFixedSlot(shape->slot())) { + + if (obj->is()) { + obj = obj->as().maybeExpando(); + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object); + } + + if (obj->as().isFixedSlot(shape->slot())) { Address addr(object, NativeObject::getFixedSlotOffset(shape->slot())); if (cx->zone()->needsIncrementalBarrier()) @@ -2038,7 +2090,7 @@ GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att Register slotsReg = object; masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), slotsReg); - Address addr(slotsReg, obj->dynamicSlotIndex(shape->slot()) * sizeof(Value)); + Address addr(slotsReg, obj->as().dynamicSlotIndex(shape->slot()) * sizeof(Value)); if (cx->zone()->needsIncrementalBarrier()) masm.callPreBarrier(addr, MIRType_Value); @@ -2048,8 +2100,8 @@ GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att attacher.jumpRejoin(masm); - if (barrierFailure.used()) { - masm.bind(&barrierFailure); + if (failurePopObject.used()) { + masm.bind(&failurePopObject); masm.pop(object); } @@ -2059,7 +2111,7 @@ GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att bool SetPropertyIC::attachSetSlot(JSContext *cx, HandleScript outerScript, IonScript *ion, - HandleNativeObject obj, HandleShape shape, bool checkTypeset) + HandleObject obj, HandleShape shape, bool checkTypeset) { MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); RepatchStubAppender attacher(*this); @@ -2663,7 +2715,7 @@ SetPropertyIC::attachCallSetter(JSContext *cx, HandleScript outerScript, IonScri RepatchStubAppender attacher(*this); Label failure; - TestMatchingReceiver(masm, object(), obj, &failure); + TestMatchingReceiver(masm, attacher, object(), obj, &failure); if (!GenerateCallSetter(cx, ion, masm, attacher, obj, holder, shape, strict(), object(), value(), &failure, liveRegs_, returnAddr)) @@ -2683,31 +2735,42 @@ SetPropertyIC::attachCallSetter(JSContext *cx, HandleScript outerScript, IonScri static void GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher, - NativeObject *obj, Shape *oldShape, ObjectGroup *oldGroup, + JSObject *obj, Shape *oldShape, ObjectGroup *oldGroup, Register object, ConstantOrRegister value, bool checkTypeset) { - MOZ_ASSERT(obj->isNative()); + Label failures, failuresPopObject; - Label failures; + // Use a modified version of TestMatchingReceiver that uses the old shape and group. + masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, &failures); + if (obj->maybeShape()) { + masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures); + } else { + MOZ_ASSERT(obj->is()); - // Guard the type of the object - masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), - ImmGCPtr(oldGroup), &failures); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), &failures); - // Guard shapes along prototype chain. - masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures); + masm.push(object); + masm.loadPtr(expandoAddress, object); + masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failuresPopObject); + masm.pop(object); + } + + Shape *newShape = obj->maybeShape(); + if (!newShape) + newShape = obj->as().maybeExpando()->lastProperty(); - Label failuresPopObject; masm.push(object); // save object reg because we clobber it // Guard that the incoming value is in the type set for the property // if a type barrier is required. if (checkTypeset) { - CheckTypeSetForWrite(masm, obj, obj->lastProperty()->propid(), object, value, &failuresPopObject); + CheckTypeSetForWrite(masm, obj, newShape->propid(), object, value, &failuresPopObject); masm.loadPtr(Address(StackPointer, 0), object); } + // Guard shapes along prototype chain. JSObject *proto = obj->getProto(); Register protoReg = object; while (proto) { @@ -2724,14 +2787,19 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att masm.pop(object); // restore object reg - // Changing object shape. Write the object's new shape. - Shape *newShape = obj->lastProperty(); + // Write the object or expando object's new shape. + if (obj->is()) { + obj = obj->as().maybeExpando(); + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object); + } Address shapeAddr(object, JSObject::offsetOfShape()); if (cx->zone()->needsIncrementalBarrier()) masm.callPreBarrier(shapeAddr, MIRType_Shape); masm.storePtr(ImmGCPtr(newShape), shapeAddr); if (oldGroup != obj->group()) { + MOZ_ASSERT(!obj->is()); + // Changing object's group from a partially to fully initialized group, // per the acquired properties analysis. Only change the group if the // old group still has a newScript. @@ -2759,7 +2827,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att // Set the value on the object. Since this is an add, obj->lastProperty() // must be the shape of the property we are adding. NativeObject::slotsSizeMustNotOverflow(); - if (obj->isFixedSlot(newShape->slot())) { + if (obj->as().isFixedSlot(newShape->slot())) { Address addr(object, NativeObject::getFixedSlotOffset(newShape->slot())); masm.storeConstantOrRegister(value, addr); } else { @@ -2767,7 +2835,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att masm.loadPtr(Address(object, NativeObject::offsetOfSlots()), slotsReg); - Address addr(slotsReg, obj->dynamicSlotIndex(newShape->slot()) * sizeof(Value)); + Address addr(slotsReg, obj->as().dynamicSlotIndex(newShape->slot()) * sizeof(Value)); masm.storeConstantOrRegister(value, addr); } @@ -2784,7 +2852,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att bool SetPropertyIC::attachAddSlot(JSContext *cx, HandleScript outerScript, IonScript *ion, - HandleNativeObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, + HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, bool checkTypeset) { MOZ_ASSERT_IF(!needsTypeBarrier(), !checkTypeset); @@ -2860,7 +2928,33 @@ IsPropertySetInlineable(NativeObject *obj, HandleId id, MutableHandleShape pshap } static bool -IsPropertyAddInlineable(NativeObject *obj, HandleId id, ConstantOrRegister val, uint32_t oldSlots, +PrototypeChainShadowsPropertyAdd(JSObject *obj, jsid id) +{ + // Walk up the object prototype chain and ensure that all prototypes + // are native, and that all prototypes have no getter or setter + // defined on the property + for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) { + // If prototype is non-native, don't optimize + if (!proto->isNative()) + return true; + + // If prototype defines this property in a non-plain way, don't optimize + Shape *protoShape = proto->as().lookupPure(id); + if (protoShape && !protoShape->hasDefaultSetter()) + return true; + + // Otherwise, if there's no such property, watch out for a resolve + // hook that would need to be invoked and thus prevent inlining of + // property addition. + if (proto->getClass()->resolve) + return true; + } + + return false; +} + +static bool +IsPropertyAddInlineable(NativeObject *obj, HandleId id, ConstantOrRegister val, HandleShape oldShape, bool needsTypeBarrier, bool *checkTypeset) { // If the shape of the object did not change, then this was not an add. @@ -2886,35 +2980,18 @@ IsPropertyAddInlineable(NativeObject *obj, HandleId id, ConstantOrRegister val, if (!obj->nonProxyIsExtensible() || !shape->writable()) return false; - // Walk up the object prototype chain and ensure that all prototypes - // are native, and that all prototypes have no getter or setter - // defined on the property - for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) { - // If prototype is non-native, don't optimize - if (!proto->isNative()) - return false; - - // If prototype defines this property in a non-plain way, don't optimize - Shape *protoShape = proto->as().lookupPure(id); - if (protoShape && !protoShape->hasDefaultSetter()) - return false; - - // Otherwise, if there's no such property, watch out for a resolve - // hook that would need to be invoked and thus prevent inlining of - // property addition. - if (proto->getClass()->resolve) - return false; - } + if (PrototypeChainShadowsPropertyAdd(obj, id)) + return false; // Only add a IC entry if the dynamic slots didn't change when the shapes // changed. Need to ensure that a shape change for a subsequent object // won't involve reallocating the slot array. - if (obj->numDynamicSlots() != oldSlots) + if (obj->numDynamicSlots() != NativeObject::dynamicSlotsCount(oldShape)) return false; // Don't attach if we are adding a property to an object which the new // script properties analysis hasn't been performed for yet, as there - // may be a shape change required here afterwards. + // may be a group change required here afterwards. if (obj->group()->newScript() && !obj->group()->newScript()->analyzed()) return false; @@ -2946,7 +3023,7 @@ CanAttachNativeSetProp(JSContext *cx, HandleObject obj, HandleId id, ConstantOrR // a stub to add the property until we do the VM call to add. If the // property exists as a data property on the prototype, we should add // a new, shadowing property. - if (!shape || (obj != holder && shape->hasDefaultSetter() && shape->hasSlot())) + if (obj->isNative() && (!shape || (obj != holder && shape->hasDefaultSetter() && shape->hasSlot()))) return SetPropertyIC::MaybeCanAttachAddSlot; if (IsImplicitNonNativeProperty(shape)) @@ -3041,6 +3118,58 @@ CanAttachSetUnboxed(JSContext *cx, HandleObject obj, HandleId id, ConstantOrRegi return false; } +static bool +CanAttachSetUnboxedExpando(JSContext *cx, HandleObject obj, HandleId id, ConstantOrRegister val, + bool needsTypeBarrier, bool *checkTypeset, Shape **pshape) +{ + if (!obj->is()) + return false; + + Rooted expando(cx, obj->as().maybeExpando()); + if (!expando) + return false; + + Shape *shape = expando->lookupPure(id); + if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable()) + return false; + + if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset)) + return false; + + *pshape = shape; + return true; +} + +static bool +CanAttachAddUnboxedExpando(JSContext *cx, HandleObject obj, HandleShape oldShape, + HandleId id, ConstantOrRegister val, + bool needsTypeBarrier, bool *checkTypeset) +{ + if (!obj->is()) + return false; + + Rooted expando(cx, obj->as().maybeExpando()); + if (!expando || expando->inDictionaryMode()) + return false; + + Shape *newShape = expando->lastProperty(); + if (newShape->propid() != id || newShape->previous() != oldShape) + return false; + + MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable()); + + if (PrototypeChainShadowsPropertyAdd(obj, id)) + return false; + + if (NativeObject::dynamicSlotsCount(oldShape) != NativeObject::dynamicSlotsCount(newShape)) + return false; + + if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset)) + return false; + + return true; +} + bool SetPropertyIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex, HandleObject obj, HandleValue value) @@ -3109,9 +3238,9 @@ SetPropertyIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex checkTypeset = false; uint32_t unboxedOffset; JSValueType unboxedType; - if (!addedSetterStub && CanAttachSetUnboxed(cx, obj, id, cache.value(), - cache.needsTypeBarrier(), - &checkTypeset, &unboxedOffset, &unboxedType)) + if (!addedSetterStub && + CanAttachSetUnboxed(cx, obj, id, cache.value(), cache.needsTypeBarrier(), + &checkTypeset, &unboxedOffset, &unboxedType)) { if (!cache.attachSetUnboxed(cx, outerScript, ion, obj, id, unboxedOffset, unboxedType, checkTypeset)) @@ -3120,10 +3249,23 @@ SetPropertyIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex } addedSetterStub = true; } + + checkTypeset = false; + if (!addedSetterStub && + CanAttachSetUnboxedExpando(cx, obj, id, cache.value(), cache.needsTypeBarrier(), + &checkTypeset, shape.address())) + { + if (!cache.attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset)) + return false; + addedSetterStub = true; + } } - uint32_t oldSlots = obj->is() ? obj->as().numDynamicSlots() : 0; RootedShape oldShape(cx, obj->maybeShape()); + if (!oldShape) { + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) + oldShape = expando->lastProperty(); + } // Set/Add the property on the object, the inlined cache are setup for the next execution. if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc())) @@ -3132,12 +3274,20 @@ SetPropertyIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex // The property did not exist before, now we can try to inline the property add. bool checkTypeset; if (!addedSetterStub && canCache == MaybeCanAttachAddSlot && - IsPropertyAddInlineable(&obj->as(), id, - cache.value(), oldSlots, oldShape, cache.needsTypeBarrier(), - &checkTypeset)) + IsPropertyAddInlineable(&obj->as(), id, cache.value(), oldShape, + cache.needsTypeBarrier(), &checkTypeset)) { - RootedNativeObject nobj(cx, &obj->as()); - if (!cache.attachAddSlot(cx, outerScript, ion, nobj, oldShape, oldGroup, checkTypeset)) + if (!cache.attachAddSlot(cx, outerScript, ion, obj, oldShape, oldGroup, checkTypeset)) + return false; + addedSetterStub = true; + } + + checkTypeset = false; + if (!addedSetterStub && cache.canAttachStub() && + CanAttachAddUnboxedExpando(cx, obj, oldShape, id, cache.value(), + cache.needsTypeBarrier(), &checkTypeset)) + { + if (!cache.attachAddSlot(cx, outerScript, ion, obj, oldShape, oldGroup, checkTypeset)) return false; addedSetterStub = true; } diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index 3bb73ff9804..94202e377b9 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -670,6 +670,10 @@ class GetPropertyIC : public RepatchIonCache HandleObject obj, HandlePropertyName name, void *returnAddr, bool *emitted); + bool tryAttachUnboxedExpando(JSContext *cx, HandleScript outerScript, IonScript *ion, + HandleObject obj, HandlePropertyName name, + void *returnAddr, bool *emitted); + bool tryAttachTypedArrayLength(JSContext *cx, HandleScript outerScript, IonScript *ion, HandleObject obj, HandlePropertyName name, bool *emitted); @@ -739,14 +743,14 @@ class SetPropertyIC : public RepatchIonCache }; bool attachSetSlot(JSContext *cx, HandleScript outerScript, IonScript *ion, - HandleNativeObject obj, HandleShape shape, bool checkTypeset); + HandleObject obj, HandleShape shape, bool checkTypeset); bool attachCallSetter(JSContext *cx, HandleScript outerScript, IonScript *ion, HandleObject obj, HandleObject holder, HandleShape shape, void *returnAddr); bool attachAddSlot(JSContext *cx, HandleScript outerScript, IonScript *ion, - HandleNativeObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, + HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, bool checkTypeset); bool attachSetUnboxed(JSContext *cx, HandleScript outerScript, IonScript *ion, diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 1ba0cc59fd5..4cc2cf2c3ba 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -4909,7 +4909,8 @@ AddGroupGuard(TempAllocator &alloc, MBasicBlock *current, MDefinition *obj, if (key->isGroup()) { guard = MGuardObjectGroup::New(alloc, obj, key->group(), bailOnEquality, - Bailout_ObjectIdentityOrTypeGuard); + Bailout_ObjectIdentityOrTypeGuard, + /* checkUnboxedExpando = */ false); } else { MConstant *singletonConst = MConstant::NewConstraintlessObject(alloc, key->singleton()); current->add(singletonConst); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 542e07f50b2..430b45d8603 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -10157,13 +10157,15 @@ class MGuardObjectGroup AlwaysTenured group_; bool bailOnEquality_; BailoutKind bailoutKind_; + bool checkUnboxedExpando_; MGuardObjectGroup(MDefinition *obj, ObjectGroup *group, bool bailOnEquality, - BailoutKind bailoutKind) + BailoutKind bailoutKind, bool checkUnboxedExpando) : MUnaryInstruction(obj), group_(group), bailOnEquality_(bailOnEquality), - bailoutKind_(bailoutKind) + bailoutKind_(bailoutKind), + checkUnboxedExpando_(checkUnboxedExpando) { setGuard(); setMovable(); @@ -10178,8 +10180,10 @@ class MGuardObjectGroup INSTRUCTION_HEADER(GuardObjectGroup) static MGuardObjectGroup *New(TempAllocator &alloc, MDefinition *obj, ObjectGroup *group, - bool bailOnEquality, BailoutKind bailoutKind) { - return new(alloc) MGuardObjectGroup(obj, group, bailOnEquality, bailoutKind); + bool bailOnEquality, BailoutKind bailoutKind, + bool checkUnboxedExpando) { + return new(alloc) MGuardObjectGroup(obj, group, bailOnEquality, bailoutKind, + checkUnboxedExpando); } MDefinition *obj() const { @@ -10194,6 +10198,9 @@ class MGuardObjectGroup BailoutKind bailoutKind() const { return bailoutKind_; } + bool checkUnboxedExpando() const { + return checkUnboxedExpando_; + } bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE { if (!ins->isGuardObjectGroup()) return false; diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index f1cbe392db5..d31249b0bbf 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1281,8 +1281,7 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject *templateObj, offset += sizeof(uintptr_t); } } else if (templateObj->is()) { - storePtr(ImmWord(0), Address(obj, JSObject::offsetOfShape())); - + storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando())); if (initContents) initUnboxedObjectContents(obj, &templateObj->as()); } else { diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index 8c5c4ad9e55..7bf19742496 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1662,6 +1662,13 @@ CodeGeneratorARM::visitGuardObjectGroup(LGuardObjectGroup *guard) { Register obj = ToRegister(guard->input()); Register tmp = ToRegister(guard->tempInt()); + MOZ_ASSERT(obj != tmp); + + if (guard->mir()->checkUnboxedExpando()) { + masm.ma_ldr(DTRAddr(obj, DtrOffImm(UnboxedPlainObject::offsetOfExpando())), tmp); + masm.ma_cmp(tmp, ImmWord(0)); + bailoutIf(Assembler::NotEqual, guard->snapshot()); + } masm.ma_ldr(DTRAddr(obj, DtrOffImm(JSObject::offsetOfGroup())), tmp); masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->group())); diff --git a/js/src/jit/mips/CodeGenerator-mips.cpp b/js/src/jit/mips/CodeGenerator-mips.cpp index 441cb2cf6b3..2fda5aff94d 100644 --- a/js/src/jit/mips/CodeGenerator-mips.cpp +++ b/js/src/jit/mips/CodeGenerator-mips.cpp @@ -1751,6 +1751,12 @@ CodeGeneratorMIPS::visitGuardObjectGroup(LGuardObjectGroup *guard) { Register obj = ToRegister(guard->input()); Register tmp = ToRegister(guard->tempInt()); + MOZ_ASSERT(obj != tmp); + + if (guard->mir()->checkUnboxedExpando()) { + masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), tmp); + bailoutCmpPtr(Assembler::NotEqual, tmp, ImmWord(0), guard->snapshot()); + } masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), tmp); Assembler::Condition cond = guard->mir()->bailOnEquality() diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 7f14e8aa817..edba0fecd0e 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -2070,6 +2070,12 @@ void CodeGeneratorX86Shared::visitGuardObjectGroup(LGuardObjectGroup *guard) { Register obj = ToRegister(guard->input()); + + if (guard->mir()->checkUnboxedExpando()) { + masm.cmpPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), ImmWord(0)); + bailoutIf(Assembler::NotEqual, guard->snapshot()); + } + masm.cmpPtr(Operand(obj, JSObject::offsetOfGroup()), ImmGCPtr(guard->mir()->group())); Assembler::Condition cond = diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index febdd6084da..f4efe761f70 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2931,7 +2931,7 @@ js::LookupPropertyPure(ExclusiveContext *cx, JSObject *obj, jsid id, JSObject ** // are not handled here. if (!obj->is()) return false; - if (obj->as().layout().lookup(id)) { + if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) { *objp = obj; MarkNonNativePropertyFound(propp); return true; diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 6d768f29dc7..89e81ea9e74 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -626,6 +626,12 @@ class NativeObject : public JSObject return lookup(cx, shape->propid()) == shape; } + bool containsShapeOrElement(ExclusiveContext *cx, jsid id) { + if (JSID_IS_INT(id) && containsDenseElement(JSID_TO_INT(id))) + return true; + return contains(cx, id); + } + /* Contextless; can be called from other pure code. */ Shape *lookupPure(jsid id); Shape *lookupPure(PropertyName *name) { @@ -874,6 +880,9 @@ class NativeObject : public JSObject * array is kept in sync with this count. */ static uint32_t dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class *clasp); + static uint32_t dynamicSlotsCount(Shape *shape) { + return dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), shape->getObjectClass()); + } /* Elements accessors. */ diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index b0fef7ad27b..0cc677700a6 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -428,7 +428,8 @@ class ObjectGroup : public gc::TenuredCell * objects, property types account for plain data properties (those with a * slot and no getter or setter hook) and dense elements. In typed objects * and unboxed objects, property types account for object and value - * properties and elements in the object. + * properties and elements in the object, and expando properties in unboxed + * objects. * * For accesses on these properties, the correspondence is as follows: * diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index 98d7a14e301..9a4294c9f62 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -346,6 +346,11 @@ UnboxedPlainObject::getValue(const UnboxedLayout::Property &property) void UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj) { + if (obj->as().expando_) { + MarkObjectUnbarriered(trc, reinterpret_cast(&obj->as().expando_), + "unboxed_expando"); + } + const UnboxedLayout &layout = obj->as().layoutDontCheckGeneration(); const int32_t *list = layout.traceList(); if (!list) @@ -369,6 +374,39 @@ UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj) MOZ_ASSERT(*(list + 1) == -1); } +/* static */ UnboxedExpandoObject * +UnboxedPlainObject::ensureExpando(JSContext *cx, Handle obj) +{ + if (obj->expando_) + return obj->expando_; + + UnboxedExpandoObject *expando = NewObjectWithGivenProto(cx, NullPtr()); + if (!expando) + return nullptr; + + // As with setValue(), we need to manually trigger post barriers on the + // whole object. If we treat the field as a HeapPtrObject and later convert + // the object to its native representation, we will end up with a corrupted + // store buffer entry. + if (IsInsideNursery(expando) && !IsInsideNursery(obj)) + cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(obj); + + obj->expando_ = expando; + return expando; +} + +bool +UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext *cx, jsid id) const +{ + if (layout().lookup(id)) + return true; + + if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id)) + return true; + + return false; +} + /* static */ bool UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group) { @@ -471,6 +509,7 @@ UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group) UnboxedPlainObject::convertToNative(JSContext *cx, JSObject *obj) { const UnboxedLayout &layout = obj->as().layout(); + UnboxedExpandoObject *expando = obj->as().maybeExpando(); if (!layout.nativeGroup()) { if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) @@ -493,6 +532,41 @@ UnboxedPlainObject::convertToNative(JSContext *cx, JSObject *obj) for (size_t i = 0; i < values.length(); i++) obj->as().initSlotUnchecked(i, values[i]); + if (expando) { + // Add properties from the expando object to the object, in order. + // Suppress GC here, so that callers don't need to worry about this + // method collecting. The stuff below can only fail due to OOM, in + // which case the object will not have been completely filled back in. + gc::AutoSuppressGC suppress(cx); + + Vector ids(cx); + for (Shape::Range r(expando->lastProperty()); !r.empty(); r.popFront()) { + if (!ids.append(r.front().propid())) + return false; + } + for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) { + if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) { + if (!ids.append(INT_TO_JSID(i))) + return false; + } + } + ::Reverse(ids.begin(), ids.end()); + + RootedPlainObject nobj(cx, &obj->as()); + Rooted nexpando(cx, expando); + RootedId id(cx); + Rooted desc(cx); + for (size_t i = 0; i < ids.length(); i++) { + id = ids[i]; + if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc)) + return false; + ObjectOpResult result; + if (!StandardDefineProperty(cx, nobj, id, desc, result)) + return false; + MOZ_ASSERT(result.ok()); + } + } + return true; } @@ -508,8 +582,8 @@ UnboxedPlainObject::create(ExclusiveContext *cx, HandleObjectGroup group, NewObj if (!res) return nullptr; - // Avoid spurious shape guard hits. - res->dummy_ = nullptr; + // Overwrite the dummy shape which was written to the object's expando field. + res->initExpando(); // Initialize reference fields of the object. All fields in the object will // be overwritten shortly, but references need to be safe for the GC. @@ -585,7 +659,7 @@ UnboxedPlainObject::obj_lookupProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp, MutableHandleShape propp) { - if (obj->as().layout().lookup(id)) { + if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) { MarkNonNativePropertyFound(propp); objp.set(obj); return true; @@ -606,16 +680,38 @@ UnboxedPlainObject::obj_defineProperty(JSContext *cx, HandleObject obj, HandleId GetterOp getter, SetterOp setter, unsigned attrs, ObjectOpResult &result) { - if (!convertToNative(cx, obj)) + const UnboxedLayout &layout = obj->as().layout(); + + if (const UnboxedLayout::Property *property = layout.lookup(id)) { + if (!getter && !setter && attrs == JSPROP_ENUMERATE) { + // This define is equivalent to setting an existing property. + if (obj->as().setValue(cx, *property, v)) + return true; + } + + // Trying to incompatibly redefine an existing property requires the + // object to be converted to a native object. + if (!convertToNative(cx, obj)) + return false; + + return DefineProperty(cx, obj, id, v, getter, setter, attrs); + } + + // Define the property on the expando object. + Rooted expando(cx, ensureExpando(cx, obj.as())); + if (!expando) return false; - return DefineProperty(cx, obj, id, v, getter, setter, attrs, result); + // Update property types on the unboxed object as well. + AddTypePropertyId(cx, obj, id, v); + + return DefineProperty(cx, expando, id, v, getter, setter, attrs, result); } /* static */ bool UnboxedPlainObject::obj_hasProperty(JSContext *cx, HandleObject obj, HandleId id, bool *foundp) { - if (obj->as().layout().lookup(id)) { + if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) { *foundp = true; return true; } @@ -640,6 +736,14 @@ UnboxedPlainObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObjec return true; } + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + RootedObject nexpando(cx, expando); + RootedObject nreceiver(cx, (obj == receiver) ? expando : receiver.get()); + return GetProperty(cx, nexpando, nreceiver, id, vp); + } + } + RootedObject proto(cx, obj->getProto()); if (!proto) { vp.setUndefined(); @@ -668,6 +772,17 @@ UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandleObjec return SetPropertyByDefining(cx, obj, receiver, id, vp, false, result); } + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + // Update property types on the unboxed object as well. + AddTypePropertyId(cx, obj, id, vp); + + RootedObject nexpando(cx, expando); + RootedObject nreceiver(cx, (obj == receiver) ? expando : receiver.get()); + return SetProperty(cx, nexpando, nreceiver, id, vp, result); + } + } + return SetPropertyOnProto(cx, obj, receiver, id, vp, result); } @@ -684,6 +799,13 @@ UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj return true; } + if (UnboxedExpandoObject *expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + RootedObject nexpando(cx, expando); + return GetOwnPropertyDescriptor(cx, nexpando, id, desc); + } + } + desc.object().set(nullptr); return true; } @@ -708,16 +830,45 @@ UnboxedPlainObject::obj_watch(JSContext *cx, HandleObject obj, HandleId id, Hand /* static */ bool UnboxedPlainObject::obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties) { + UnboxedExpandoObject *expando = obj->as().maybeExpando(); + + // Add dense elements in the expando first, for consistency with plain objects. + if (expando) { + for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) { + if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) { + if (!properties.append(INT_TO_JSID(i))) + return false; + } + } + } + const UnboxedLayout::PropertyVector &unboxed = obj->as().layout().properties(); for (size_t i = 0; i < unboxed.length(); i++) { if (!properties.append(NameToId(unboxed[i].name))) return false; } + + if (expando) { + Vector ids(cx); + for (Shape::Range r(expando->lastProperty()); !r.empty(); r.popFront()) { + if (!ids.append(r.front().propid())) + return false; + } + ::Reverse(ids.begin(), ids.end()); + if (!properties.append(ids.begin(), ids.length())) + return false; + } + return true; } +const Class UnboxedExpandoObject::class_ = { + "UnboxedExpandoObject", + JSCLASS_IMPLEMENTS_BARRIERS +}; + const Class UnboxedPlainObject::class_ = { - "Object", + js_Object_str, Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS, nullptr, /* addProperty */ nullptr, /* delProperty */ @@ -1000,6 +1151,7 @@ js::TryConvertToUnboxedLayout(ExclusiveContext *cx, Shape *templateShape, if (!objects->get(i)) continue; UnboxedPlainObject *obj = &objects->get(i)->as(); + obj->initExpando(); memset(obj->data(), 0, layout->size()); for (size_t j = 0; j < templateShape->slotSpan(); j++) { Value v = values[valueCursor++]; diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h index 9b1c4a3a29f..84f0992e20e 100644 --- a/js/src/vm/UnboxedObject.h +++ b/js/src/vm/UnboxedObject.h @@ -162,14 +162,25 @@ class UnboxedLayout : public mozilla::LinkedListElement static bool makeConstructorCode(JSContext *cx, HandleObjectGroup group); }; +// Class for expando objects holding extra properties given to an unboxed plain +// object. These objects behave identically to normal native plain objects, and +// have a separate Class to distinguish them for memory usage reporting. +class UnboxedExpandoObject : public NativeObject +{ + public: + static const Class class_; +}; + // Class for a plain object using an unboxed representation. The physical // layout of these objects is identical to that of an InlineTypedObject, though // these objects use an UnboxedLayout instead of a TypeDescr to keep track of // how their properties are stored. class UnboxedPlainObject : public JSObject { - // Placeholder for extra properties. See bug 1137180. - void *dummy_; + // Optional object which stores extra properties on this object. This is + // not automatically barriered to avoid problems if the object is converted + // to a native. See ensureExpando(). + UnboxedExpandoObject *expando_; // Start of the inline data, which immediately follows the group and extra properties. uint8_t data_[1]; @@ -214,6 +225,18 @@ class UnboxedPlainObject : public JSObject return &data_[0]; } + UnboxedExpandoObject *maybeExpando() const { + return expando_; + } + + void initExpando() { + expando_ = nullptr; + } + + bool containsUnboxedOrExpandoProperty(ExclusiveContext *cx, jsid id) const; + + static UnboxedExpandoObject *ensureExpando(JSContext *cx, Handle obj); + bool setValue(ExclusiveContext *cx, const UnboxedLayout::Property &property, const Value &v); Value getValue(const UnboxedLayout::Property &property); @@ -225,6 +248,10 @@ class UnboxedPlainObject : public JSObject static void trace(JSTracer *trc, JSObject *object); + static size_t offsetOfExpando() { + return offsetof(UnboxedPlainObject, expando_); + } + static size_t offsetOfData() { return offsetof(UnboxedPlainObject, data_[0]); }