diff options
Diffstat (limited to 'js/src/jit')
-rw-r--r-- | js/src/jit/BaselineCacheIR.cpp | 52 | ||||
-rw-r--r-- | js/src/jit/BaselineIC.cpp | 269 | ||||
-rw-r--r-- | js/src/jit/BaselineIC.h | 10 | ||||
-rw-r--r-- | js/src/jit/BaselineInspector.cpp | 46 | ||||
-rw-r--r-- | js/src/jit/CacheIR.cpp | 72 | ||||
-rw-r--r-- | js/src/jit/CacheIR.h | 20 | ||||
-rw-r--r-- | js/src/jit/CodeGenerator.cpp | 32 | ||||
-rw-r--r-- | js/src/jit/IonBuilder.cpp | 183 | ||||
-rw-r--r-- | js/src/jit/IonBuilder.h | 13 | ||||
-rw-r--r-- | js/src/jit/IonCaches.cpp | 336 | ||||
-rw-r--r-- | js/src/jit/IonCaches.h | 12 | ||||
-rw-r--r-- | js/src/jit/JitOptions.cpp | 3 | ||||
-rw-r--r-- | js/src/jit/JitOptions.h | 3 | ||||
-rw-r--r-- | js/src/jit/MCallOptimize.cpp | 1 | ||||
-rw-r--r-- | js/src/jit/MIR.cpp | 9 | ||||
-rw-r--r-- | js/src/jit/MIR.h | 23 | ||||
-rw-r--r-- | js/src/jit/MacroAssembler.cpp | 274 | ||||
-rw-r--r-- | js/src/jit/MacroAssembler.h | 14 | ||||
-rw-r--r-- | js/src/jit/OptimizationTracking.cpp | 4 | ||||
-rw-r--r-- | js/src/jit/Recover.cpp | 36 | ||||
-rw-r--r-- | js/src/jit/ScalarReplacement.cpp | 33 | ||||
-rw-r--r-- | js/src/jit/SharedIC.cpp | 30 | ||||
-rw-r--r-- | js/src/jit/VMFunctions.cpp | 2 |
23 files changed, 1424 insertions, 53 deletions
diff --git a/js/src/jit/BaselineCacheIR.cpp b/js/src/jit/BaselineCacheIR.cpp index 67c80473b..7fb586811 100644 --- a/js/src/jit/BaselineCacheIR.cpp +++ b/js/src/jit/BaselineCacheIR.cpp @@ -16,7 +16,7 @@ using namespace js; using namespace js::jit; // OperandLocation represents the location of an OperandId. The operand is -// either in a register or on the stack. +// either in a register or on the stack, and is either boxed or unboxed. class OperandLocation { public: @@ -815,6 +815,36 @@ BaselineCacheIRCompiler::emitGuardSpecificObject() } bool +BaselineCacheIRCompiler::emitGuardNoUnboxedExpando() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando()); + masm.branchPtr(Assembler::NotEqual, expandoAddr, ImmWord(0), failure->label()); + return true; +} + +bool +BaselineCacheIRCompiler::emitGuardAndLoadUnboxedExpando() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + Register output = allocator.defineRegister(masm, reader.objOperandId()); + + FailurePath* failure; + if (!addFailurePath(&failure)) + return false; + + Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando()); + masm.loadPtr(expandoAddr, output); + masm.branchTestPtr(Assembler::Zero, output, output, failure->label()); + return true; +} + +bool BaselineCacheIRCompiler::emitLoadFixedSlotResult() { Register obj = allocator.useRegister(masm, reader.objOperandId()); @@ -841,6 +871,26 @@ BaselineCacheIRCompiler::emitLoadDynamicSlotResult() } bool +BaselineCacheIRCompiler::emitLoadUnboxedPropertyResult() +{ + Register obj = allocator.useRegister(masm, reader.objOperandId()); + AutoScratchRegister scratch(allocator, masm); + + JSValueType fieldType = reader.valueType(); + + Address fieldOffset(stubAddress(reader.stubOffset())); + masm.load32(fieldOffset, scratch); + masm.loadUnboxedProperty(BaseIndex(obj, scratch, TimesOne), fieldType, R0); + + if (fieldType == JSVAL_TYPE_OBJECT) + emitEnterTypeMonitorIC(); + else + emitReturnFromIC(); + + return true; +} + +bool BaselineCacheIRCompiler::emitGuardNoDetachedTypedObjects() { FailurePath* failure; diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index e65f10aac..1b98325b7 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -44,8 +44,8 @@ #include "jit/shared/Lowering-shared-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/Interpreter-inl.h" -#include "vm/NativeObject-inl.h" #include "vm/StringObject-inl.h" +#include "vm/UnboxedObject-inl.h" using mozilla::DebugOnly; @@ -741,6 +741,11 @@ LastPropertyForSetProp(JSObject* obj) if (obj->isNative()) return obj->as<NativeObject>().lastProperty(); + if (obj->is<UnboxedPlainObject>()) { + UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); + return expando ? expando->lastProperty() : nullptr; + } + return nullptr; } @@ -1157,6 +1162,56 @@ TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsb ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); + if (obj->is<UnboxedPlainObject>() && holder == obj) { + const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); + + // Once unboxed objects support symbol-keys, we need to change the following accordingly + MOZ_ASSERT_IF(!keyVal.isString(), !property); + + if (property) { + if (!cx->runtime()->jitSupportsFloatingPoint) + return true; + + RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); + ICGetElemNativeCompiler<PropertyName*> compiler(cx, ICStub::GetElem_UnboxedPropertyName, + monitorStub, obj, holder, + name, + ICGetElemNativeStub::UnboxedProperty, + needsAtomize, property->offset + + UnboxedPlainObject::offsetOfData(), + property->type); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + stub->addNewStub(newStub); + *attached = true; + return true; + } + + Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, id); + if (!shape->hasDefaultGetter() || !shape->hasSlot()) + return true; + + bool isFixedSlot; + uint32_t offset; + GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); + + ICGetElemNativeStub::AccessType acctype = + isFixedSlot ? ICGetElemNativeStub::FixedSlot + : ICGetElemNativeStub::DynamicSlot; + ICGetElemNativeCompiler<T> compiler(cx, getGetElemStubKind<T>(ICStub::GetElem_NativeSlotName), + monitorStub, obj, holder, key, + acctype, needsAtomize, offset); + ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + stub->addNewStub(newStub); + *attached = true; + return true; + } + if (!holder->isNative()) return true; @@ -1404,7 +1459,7 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_ } // Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses. - if (obj->isNative()) { + if (obj->isNative() || obj->is<UnboxedPlainObject>()) { RootedScript rootedScript(cx, script); if (rhs.isString()) { if (!TryAttachNativeOrUnboxedGetValueElemStub<PropertyName*>(cx, rootedScript, pc, stub, @@ -1816,6 +1871,14 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm) Register holderReg; if (obj_ == holder_) { holderReg = objReg; + + if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) { + // The property will be loaded off the unboxed expando. + masm.push(R1.scratchReg()); + popR1 = true; + holderReg = R1.scratchReg(); + masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); + } } else { // Shape guard holder. if (regs.empty()) { @@ -1866,6 +1929,13 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm) if (popR1) masm.addToStackPtr(ImmWord(sizeof(size_t))); + } else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) { + masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()), + scratchReg); + masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_, + TypedOrValueRegister(R0)); + if (popR1) + masm.addToStackPtr(ImmWord(sizeof(size_t))); } else { MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter || acctype_ == ICGetElemNativeStub::ScriptedGetter); @@ -2618,6 +2688,18 @@ BaselineScript::noteArrayWriteHole(uint32_t pcOffset) // SetElem_DenseOrUnboxedArray // +template <typename T> +void +EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, T address, JSValueType type) +{ + if (type == JSVAL_TYPE_OBJECT) + EmitPreBarrier(masm, address, MIRType::Object); + else if (type == JSVAL_TYPE_STRING) + EmitPreBarrier(masm, address, MIRType::String); + else + MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type)); +} + bool ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) { @@ -4061,7 +4143,18 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC return true; if (!obj->isNative()) { - return true; + if (obj->is<UnboxedPlainObject>()) { + UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); + if (expando) { + shape = expando->lookup(cx, name); + if (!shape) + return true; + } else { + return true; + } + } else { + return true; + } } size_t chainDepth; @@ -4209,6 +4302,40 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc, } static bool +TryAttachUnboxedSetPropStub(JSContext* cx, HandleScript script, + ICSetProp_Fallback* stub, HandleId id, + HandleObject obj, HandleValue rhs, bool* attached) +{ + MOZ_ASSERT(!*attached); + + if (!cx->runtime()->jitSupportsFloatingPoint) + return true; + + if (!obj->is<UnboxedPlainObject>()) + return true; + + const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); + if (!property) + return true; + + ICSetProp_Unboxed::Compiler compiler(cx, obj->group(), + property->offset + UnboxedPlainObject::offsetOfData(), + property->type); + ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + if (compiler.needsUpdateStubs() && !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, HandleObject obj, HandleValue rhs, bool* attached) @@ -4291,6 +4418,12 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_ return false; RootedReceiverGuard oldGuard(cx, ReceiverGuard(obj)); + if (obj->is<UnboxedPlainObject>()) { + MOZ_ASSERT(!oldShape); + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) + oldShape = expando->lastProperty(); + } + bool attached = false; // There are some reasons we can fail to attach a stub that are temporary. // We want to avoid calling noteUnoptimizableAccess() if the reason we @@ -4363,6 +4496,15 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_ if (!attached && lhs.isObject() && + !TryAttachUnboxedSetPropStub(cx, script, stub, id, obj, rhs, &attached)) + { + return false; + } + if (attached) + return true; + + if (!attached && + lhs.isObject() && !TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached)) { return false; @@ -4445,7 +4587,20 @@ GuardGroupAndShapeMaybeUnboxedExpando(MacroAssembler& masm, JSObject* obj, // Guard against shape or expando shape. masm.loadPtr(Address(ICStubReg, offsetOfShape), scratch); - masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); + if (obj->is<UnboxedPlainObject>()) { + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + 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); + } } bool @@ -4484,7 +4639,13 @@ ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm) regs.takeUnchecked(objReg); Register holderReg; - if (isFixedSlot_) { + if (obj_->is<UnboxedPlainObject>()) { + // 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(); @@ -4621,17 +4782,31 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm) regs.add(R0); regs.takeUnchecked(objReg); - // Write the object's new shape. - Address shapeAddr(objReg, ShapedObject::offsetOfShape()); - EmitPreBarrier(masm, shapeAddr, MIRType::Shape); - masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); - masm.storePtr(scratch, shapeAddr); + if (obj_->is<UnboxedPlainObject>()) { + holderReg = regs.takeAny(); + masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); - if (isFixedSlot_) { - holderReg = objReg; + // Write the expando object's new shape. + Address shapeAddr(holderReg, ShapedObject::offsetOfShape()); + EmitPreBarrier(masm, shapeAddr, MIRType::Shape); + masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); + masm.storePtr(scratch, shapeAddr); + + if (!isFixedSlot_) + masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg); } else { - holderReg = regs.takeAny(); - masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg); + // Write the object's new shape. + Address shapeAddr(objReg, ShapedObject::offsetOfShape()); + EmitPreBarrier(masm, shapeAddr, MIRType::Shape); + masm.loadPtr(Address(ICStubReg, 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 @@ -4663,6 +4838,70 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm) } bool +ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm) +{ + MOZ_ASSERT(engine_ == Engine::Baseline); + + Label failure; + + // Guard input is an object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + + AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); + Register scratch = regs.takeAny(); + + // Unbox and group guard. + Register object = masm.extractObject(R0, ExtractTemp0); + masm.loadPtr(Address(ICStubReg, ICSetProp_Unboxed::offsetOfGroup()), scratch); + masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch, + &failure); + + if (needsUpdateStubs()) { + // Stow both R0 and R1 (object and value). + EmitStowICValues(masm, 2); + + // Move RHS into R0 for TypeUpdate check. + masm.moveValue(R1, R0); + + // Call the type update stub. + if (!callTypeUpdateIC(masm, sizeof(Value))) + return false; + + // Unstow R0 and R1 (object and key) + EmitUnstowICValues(masm, 2); + + // The TypeUpdate IC may have smashed object. Rederive it. + masm.unboxObject(R0, object); + + // Trigger post barriers here on the values being written. Fields which + // objects can be written to also need update stubs. + LiveGeneralRegisterSet saveRegs; + saveRegs.add(R0); + saveRegs.add(R1); + saveRegs.addUnchecked(object); + saveRegs.add(ICStubReg); + emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs); + } + + // Compute the address being written to. + masm.load32(Address(ICStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch); + BaseIndex address(object, scratch, TimesOne); + + EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_); + masm.storeUnboxedProperty(address, fieldType_, + ConstantOrRegister(TypedOrValueRegister(R1)), &failure); + + // The RHS has to be in R0. + masm.moveValue(R1, R0); + + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +bool ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); @@ -5421,7 +5660,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb if (!thisObject) return false; - if (thisObject->is<PlainObject>()) + if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>()) templateObject = thisObject; } diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index e1ad12559..a1291a3bb 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -22,6 +22,7 @@ #include "jit/SharedICRegisters.h" #include "js/GCVector.h" #include "vm/ArrayObject.h" +#include "vm/UnboxedObject.h" namespace js { namespace jit { @@ -1822,7 +1823,8 @@ class ICSetProp_Native : public ICUpdatedStub virtual int32_t getKey() const { return static_cast<int32_t>(engine_) | (static_cast<int32_t>(kind) << 1) | - (static_cast<int32_t>(isFixedSlot_) << 17); + (static_cast<int32_t>(isFixedSlot_) << 17) | + (static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18); } MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm); @@ -1927,6 +1929,7 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler return static_cast<int32_t>(engine_) | (static_cast<int32_t>(kind) << 1) | (static_cast<int32_t>(isFixedSlot_) << 17) | + (static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18) | (static_cast<int32_t>(protoChainDepth_) << 19); } @@ -1951,7 +1954,10 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler newGroup = nullptr; RootedShape newShape(cx); - newShape = obj_->as<NativeObject>().lastProperty(); + if (obj_->isNative()) + newShape = obj_->as<NativeObject>().lastProperty(); + else + newShape = obj_->as<UnboxedPlainObject>().maybeExpando()->lastProperty(); return newStub<ICSetProp_NativeAddImpl<ProtoChainDepth>>( space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_); diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index 3b852debf..bcb527516 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -104,11 +104,19 @@ AddReceiver(const ReceiverGuard& receiver, static bool GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* receiver) { - // We match: + // We match either: // // GuardIsObject 0 // GuardShape 0 // LoadFixedSlotResult 0 or LoadDynamicSlotResult 0 + // + // or + // + // GuardIsObject 0 + // GuardGroup 0 + // 1: GuardAndLoadUnboxedExpando 0 + // GuardShape 1 + // LoadFixedSlotResult 1 or LoadDynamicSlotResult 1 *receiver = ReceiverGuard(); CacheIRReader reader(stub->stubInfo()); @@ -117,6 +125,14 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re if (!reader.matchOp(CacheOp::GuardIsObject, objId)) return false; + if (reader.matchOp(CacheOp::GuardGroup, objId)) { + receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset()); + + if (!reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId)) + return false; + objId = reader.objOperandId(); + } + if (reader.matchOp(CacheOp::GuardShape, objId)) { receiver->shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset()); return reader.matchOpEither(CacheOp::LoadFixedSlotResult, CacheOp::LoadDynamicSlotResult); @@ -125,6 +141,29 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re return false; } +static bool +GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* receiver) +{ + // We match: + // + // GuardIsObject 0 + // GuardGroup 0 + // LoadUnboxedPropertyResult 0 .. + + *receiver = ReceiverGuard(); + CacheIRReader reader(stub->stubInfo()); + + ObjOperandId objId = ObjOperandId(0); + if (!reader.matchOp(CacheOp::GuardIsObject, objId)) + return false; + + if (!reader.matchOp(CacheOp::GuardGroup, objId)) + return false; + receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset()); + + return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId); +} + bool BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers) { @@ -143,7 +182,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv while (stub->next()) { ReceiverGuard receiver; if (stub->isCacheIR_Monitored()) { - if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver)) + if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver) && + !GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Monitored(), &receiver)) { receivers.clear(); return true; @@ -151,6 +191,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv } else if (stub->isSetProp_Native()) { receiver = ReceiverGuard(stub->toSetProp_Native()->group(), stub->toSetProp_Native()->shape()); + } else if (stub->isSetProp_Unboxed()) { + receiver = ReceiverGuard(stub->toSetProp_Unboxed()->group(), nullptr); } else { receivers.clear(); return true; diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index d184ea40c..6822a70af 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -10,7 +10,8 @@ #include "jit/IonCaches.h" #include "jsobjinlines.h" -#include "vm/NativeObject-inl.h" + +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; @@ -59,6 +60,10 @@ GetPropIRGenerator::tryAttachStub(Maybe<CacheIRWriter>& writer) return false; if (!emitted_ && !tryAttachNative(*writer, obj, objId)) return false; + if (!emitted_ && !tryAttachUnboxed(*writer, obj, objId)) + return false; + if (!emitted_ && !tryAttachUnboxedExpando(*writer, obj, objId)) + return false; if (!emitted_ && !tryAttachTypedObject(*writer, obj, objId)) return false; if (!emitted_ && !tryAttachModuleNamespace(*writer, obj, objId)) @@ -158,9 +163,19 @@ GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, } static void -TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId) +TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId, + Maybe<ObjOperandId>* expandoId) { - if (obj->is<TypedObject>()) { + if (obj->is<UnboxedPlainObject>()) { + writer.guardGroup(objId, obj->group()); + + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) { + expandoId->emplace(writer.guardAndLoadUnboxedExpando(objId)); + writer.guardShape(expandoId->ref(), expando->lastProperty()); + } else { + writer.guardNoUnboxedExpando(objId); + } + } else if (obj->is<TypedObject>()) { writer.guardGroup(objId, obj->group()); } else { Shape* shape = obj->maybeShape(); @@ -173,7 +188,8 @@ static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId) { - TestMatchingReceiver(writer, obj, shape, objId); + Maybe<ObjOperandId> expandoId; + TestMatchingReceiver(writer, obj, shape, objId, &expandoId); ObjOperandId holderId; if (obj != holder) { @@ -196,6 +212,9 @@ EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, lastObjId = protoId; } } + } else if (obj->is<UnboxedPlainObject>()) { + holder = obj->as<UnboxedPlainObject>().maybeExpando(); + holderId = *expandoId; } else { holderId = objId; } @@ -247,6 +266,51 @@ GetPropIRGenerator::tryAttachNative(CacheIRWriter& writer, HandleObject obj, Obj } bool +GetPropIRGenerator::tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) +{ + MOZ_ASSERT(!emitted_); + + if (!obj->is<UnboxedPlainObject>()) + return true; + + const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(name_); + if (!property) + return true; + + if (!cx_->runtime()->jitSupportsFloatingPoint) + return true; + + writer.guardGroup(objId, obj->group()); + writer.loadUnboxedPropertyResult(objId, property->type, + UnboxedPlainObject::offsetOfData() + property->offset); + emitted_ = true; + preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; + return true; +} + +bool +GetPropIRGenerator::tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) +{ + MOZ_ASSERT(!emitted_); + + if (!obj->is<UnboxedPlainObject>()) + return true; + + UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); + if (!expando) + return true; + + Shape* shape = expando->lookup(cx_, NameToId(name_)); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) + return true; + + emitted_ = true; + + EmitReadSlotResult(writer, obj, obj, shape, objId); + return true; +} + +bool GetPropIRGenerator::tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) { MOZ_ASSERT(!emitted_); diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h index ae55cfebb..4fd8575f0 100644 --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -87,10 +87,13 @@ class ObjOperandId : public OperandId _(GuardClass) \ _(GuardSpecificObject) \ _(GuardNoDetachedTypedObjects) \ + _(GuardNoUnboxedExpando) \ + _(GuardAndLoadUnboxedExpando) \ _(LoadObject) \ _(LoadProto) \ _(LoadFixedSlotResult) \ _(LoadDynamicSlotResult) \ + _(LoadUnboxedPropertyResult) \ _(LoadTypedObjectResult) \ _(LoadInt32ArrayLengthResult) \ _(LoadArgumentsObjectLengthResult) \ @@ -271,6 +274,15 @@ class MOZ_RAII CacheIRWriter void guardNoDetachedTypedObjects() { writeOp(CacheOp::GuardNoDetachedTypedObjects); } + void guardNoUnboxedExpando(ObjOperandId obj) { + writeOpWithOperandId(CacheOp::GuardNoUnboxedExpando, obj); + } + ObjOperandId guardAndLoadUnboxedExpando(ObjOperandId obj) { + ObjOperandId res(nextOperandId_++); + writeOpWithOperandId(CacheOp::GuardAndLoadUnboxedExpando, obj); + writeOperandId(res); + return res; + } ObjOperandId loadObject(JSObject* obj) { ObjOperandId res(nextOperandId_++); @@ -296,6 +308,11 @@ class MOZ_RAII CacheIRWriter writeOpWithOperandId(CacheOp::LoadDynamicSlotResult, obj); addStubWord(offset, StubField::GCType::NoGCThing); } + void loadUnboxedPropertyResult(ObjOperandId obj, JSValueType type, size_t offset) { + writeOpWithOperandId(CacheOp::LoadUnboxedPropertyResult, obj); + buffer_.writeByte(uint32_t(type)); + addStubWord(offset, StubField::GCType::NoGCThing); + } void loadTypedObjectResult(ObjOperandId obj, uint32_t offset, TypedThingLayout layout, uint32_t typeDescr) { MOZ_ASSERT(uint32_t(layout) <= UINT8_MAX); @@ -389,6 +406,9 @@ class MOZ_RAII GetPropIRGenerator PreliminaryObjectAction preliminaryObjectAction_; MOZ_MUST_USE bool tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId); + MOZ_MUST_USE bool tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId); + MOZ_MUST_USE bool tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, + ObjOperandId objId); MOZ_MUST_USE bool tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId); MOZ_MUST_USE bool tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj, diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 2b1c671d1..901e9ea93 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -25,7 +25,6 @@ #include "builtin/Eval.h" #include "builtin/TypedObject.h" #include "gc/Nursery.h" -#include "gc/StoreBuffer-inl.h" #include "irregexp/NativeRegExpMacroAssembler.h" #include "jit/AtomicOperations.h" #include "jit/BaselineCompiler.h" @@ -3029,7 +3028,7 @@ CodeGenerator::visitStoreSlotV(LStoreSlotV* lir) static void GuardReceiver(MacroAssembler& masm, const ReceiverGuard& guard, - Register obj, Register scratch, Label* miss) + Register obj, Register scratch, Label* miss, bool checkNullExpando) { if (guard.group) { masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, miss); @@ -3051,11 +3050,13 @@ CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Regis Label next; masm.comment("GuardReceiver"); - GuardReceiver(masm, receiver, obj, scratch, &next); + GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false); if (receiver.shape) { masm.comment("loadTypedOrValue"); - Register target = obj; + // If this is an unboxed expando access, GuardReceiver loaded the + // expando object into scratch. + Register target = receiver.group ? scratch : obj; Shape* shape = mir->shape(i); if (shape->slot() < shape->numFixedSlots()) { @@ -3121,10 +3122,12 @@ CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Regis ReceiverGuard receiver = mir->receiver(i); Label next; - GuardReceiver(masm, receiver, obj, scratch, &next); + GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false); if (receiver.shape) { - Register target = obj; + // If this is an unboxed expando access, GuardReceiver loaded the + // expando object into scratch. + Register target = receiver.group ? scratch : obj; Shape* shape = mir->shape(i); if (shape->slot() < shape->numFixedSlots()) { @@ -3290,7 +3293,7 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir) const ReceiverGuard& receiver = mir->receiver(i); Label next; - GuardReceiver(masm, receiver, obj, temp, &next); + GuardReceiver(masm, receiver, obj, temp, &next, /* checkNullExpando = */ true); if (i == mir->numReceivers() - 1) { bailoutFrom(&next, lir->snapshot()); @@ -8379,6 +8382,11 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir) } } +typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*); +static const VMFunction ConvertUnboxedPlainObjectToNativeInfo = + FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative, + "UnboxedPlainObject::convertToNative"); + typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue); static const VMFunction ArrayPopDenseInfo = FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense"); @@ -8671,11 +8679,11 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir) masm.loadPtr(Address(niTemp, offsetof(NativeIterator, guard_array)), temp2); // Compare object with the first receiver guard. The last iterator can only - // match for native objects. + // match for native objects and unboxed objects. { Address groupAddr(temp2, offsetof(ReceiverGuard, group)); Address shapeAddr(temp2, offsetof(ReceiverGuard, shape)); - Label guardDone, shapeMismatch; + Label guardDone, shapeMismatch, noExpando; masm.loadObjShape(obj, temp1); masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, &shapeMismatch); @@ -8687,6 +8695,12 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir) masm.bind(&shapeMismatch); masm.loadObjGroup(obj, temp1); masm.branchPtr(Assembler::NotEqual, groupAddr, temp1, ool->entry()); + masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), temp1); + masm.branchTestPtr(Assembler::Zero, temp1, temp1, &noExpando); + branchIfNotEmptyObjectElements(temp1, ool->entry()); + masm.loadObjShape(temp1, temp1); + masm.bind(&noExpando); + masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, ool->entry()); masm.bind(&guardDone); } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 1e12f5dbe..f00167d92 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -10940,6 +10940,63 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_ return slot; } +uint32_t +IonBuilder::getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, JSValueType* punboxedType) +{ + if (!types || types->unknownObject() || !types->objectOrSentinel()) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); + return UINT32_MAX; + } + + uint32_t offset = UINT32_MAX; + + for (size_t i = 0; i < types->getObjectCount(); i++) { + TypeSet::ObjectKey* key = types->getObject(i); + if (!key) + continue; + + if (key->unknownProperties()) { + trackOptimizationOutcome(TrackedOutcome::UnknownProperties); + return UINT32_MAX; + } + + if (key->isSingleton()) { + trackOptimizationOutcome(TrackedOutcome::Singleton); + return UINT32_MAX; + } + + UnboxedLayout* layout = key->group()->maybeUnboxedLayout(); + if (!layout) { + trackOptimizationOutcome(TrackedOutcome::NotUnboxed); + return UINT32_MAX; + } + + const UnboxedLayout::Property* property = layout->lookup(name); + if (!property) { + trackOptimizationOutcome(TrackedOutcome::StructNoField); + return UINT32_MAX; + } + + if (layout->nativeGroup()) { + trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative); + return UINT32_MAX; + } + + if (offset == UINT32_MAX) { + offset = property->offset; + *punboxedType = property->type; + } else if (offset != property->offset) { + trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset); + return UINT32_MAX; + } else if (*punboxedType != property->type) { + trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType); + return UINT32_MAX; + } + } + + return offset; +} + bool IonBuilder::jsop_runonce() { @@ -11906,6 +11963,72 @@ IonBuilder::getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyN return true; } +MInstruction* +IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, + BarrierKind barrier, TemporaryTypeSet* types) +{ + // loadUnboxedValue is designed to load any value as if it were contained in + // an array. Thus a property offset is converted to an index, when the + // object is reinterpreted as an array of properties of the same size. + size_t index = offset / UnboxedTypeSize(unboxedType); + MInstruction* indexConstant = MConstant::New(alloc(), Int32Value(index)); + current->add(indexConstant); + + return loadUnboxedValue(obj, UnboxedPlainObject::offsetOfData(), + indexConstant, unboxedType, barrier, types); +} + +MInstruction* +IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset, + MDefinition* index, JSValueType unboxedType, + BarrierKind barrier, TemporaryTypeSet* types) +{ + MInstruction* load; + switch (unboxedType) { + case JSVAL_TYPE_BOOLEAN: + load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Uint8, + DoesNotRequireMemoryBarrier, elementsOffset); + load->setResultType(MIRType::Boolean); + break; + + case JSVAL_TYPE_INT32: + load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Int32, + DoesNotRequireMemoryBarrier, elementsOffset); + load->setResultType(MIRType::Int32); + break; + + case JSVAL_TYPE_DOUBLE: + load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Float64, + DoesNotRequireMemoryBarrier, elementsOffset, + /* canonicalizeDoubles = */ false); + load->setResultType(MIRType::Double); + break; + + case JSVAL_TYPE_STRING: + load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset); + break; + + case JSVAL_TYPE_OBJECT: { + MLoadUnboxedObjectOrNull::NullBehavior nullBehavior; + if (types->hasType(TypeSet::NullType())) + nullBehavior = MLoadUnboxedObjectOrNull::HandleNull; + else if (barrier != BarrierKind::NoBarrier) + nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull; + else + nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible; + load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index, nullBehavior, + elementsOffset); + break; + } + + default: + MOZ_CRASH(); + } + + current->add(load); + return load; +} + MDefinition* IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape, const BaselineInspector::ReceiverVector& receivers, @@ -12729,6 +12852,66 @@ IonBuilder::setPropTryDefiniteSlot(bool* emitted, MDefinition* obj, return true; } +MInstruction* +IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, + MDefinition* value) +{ + size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType); + MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant)); + current->add(scaledOffset); + + return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(), + scaledOffset, unboxedType, value); +} + +MInstruction* +IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset, + MDefinition* scaledOffset, JSValueType unboxedType, + MDefinition* value, bool preBarrier /* = true */) +{ + MInstruction* store; + switch (unboxedType) { + case JSVAL_TYPE_BOOLEAN: + store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Uint8, + MStoreUnboxedScalar::DontTruncateInput, + DoesNotRequireMemoryBarrier, elementsOffset); + break; + + case JSVAL_TYPE_INT32: + store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Int32, + MStoreUnboxedScalar::DontTruncateInput, + DoesNotRequireMemoryBarrier, elementsOffset); + break; + + case JSVAL_TYPE_DOUBLE: + store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Float64, + MStoreUnboxedScalar::DontTruncateInput, + DoesNotRequireMemoryBarrier, elementsOffset); + break; + + case JSVAL_TYPE_STRING: + store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value, + elementsOffset, preBarrier); + break; + + case JSVAL_TYPE_OBJECT: + MOZ_ASSERT(value->type() == MIRType::Object || + value->type() == MIRType::Null || + value->type() == MIRType::Value); + MOZ_ASSERT(!value->mightBeType(MIRType::Undefined), + "MToObjectOrNull slow path is invalid for unboxed objects"); + store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, obj, + elementsOffset, preBarrier); + break; + + default: + MOZ_CRASH(); + } + + current->add(store); + return store; +} + bool IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name, MDefinition* value, diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 6a3b61232..78af0e412 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1050,6 +1050,19 @@ class IonBuilder ResultWithOOM<bool> testNotDefinedProperty(MDefinition* obj, jsid id); uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed); + uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, + JSValueType* punboxedType); + MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, + BarrierKind barrier, TemporaryTypeSet* types); + MInstruction* loadUnboxedValue(MDefinition* elements, size_t elementsOffset, + MDefinition* scaledOffset, JSValueType unboxedType, + BarrierKind barrier, TemporaryTypeSet* types); + MInstruction* storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, + MDefinition* value); + MInstruction* storeUnboxedValue(MDefinition* obj, + MDefinition* elements, int32_t elementsOffset, + MDefinition* scaledOffset, JSValueType unboxedType, + MDefinition* value, bool preBarrier = true); MOZ_MUST_USE bool checkPreliminaryGroups(MDefinition *obj); MOZ_MUST_USE bool freezePropTypeSets(TemporaryTypeSet* types, JSObject* foundProto, PropertyName* name); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index fb4291188..f5e4659c1 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -31,6 +31,7 @@ #include "jit/shared/Lowering-shared-inl.h" #include "vm/Interpreter-inl.h" #include "vm/Shape-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; @@ -619,7 +620,26 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher, Register object, JSObject* obj, Label* failure, bool alwaysCheckGroup = false) { - if (obj->is<TypedObject>()) { + if (obj->is<UnboxedPlainObject>()) { + MOZ_ASSERT(failure); + + masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().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); + } + } else if (obj->is<TypedObject>()) { attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), ImmGCPtr(obj->group()), failure); @@ -736,6 +756,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm, // jump directly. Otherwise, jump to the end of the stub, so there's a // common point to patch. bool multipleFailureJumps = (obj != holder) + || obj->is<UnboxedPlainObject>() || (checkTDZ && output.hasValue()) || (failures != nullptr && failures->used()); @@ -754,6 +775,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm, Register scratchReg = Register::FromCode(0); // Quell compiler warning. if (obj != holder || + obj->is<UnboxedPlainObject>() || !holder->as<NativeObject>().isFixedSlot(shape->slot())) { if (output.hasValue()) { @@ -814,6 +836,10 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm, holderReg = InvalidReg; } + } else if (obj->is<UnboxedPlainObject>()) { + holder = obj->as<UnboxedPlainObject>().maybeExpando(); + holderReg = scratchReg; + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg); } else { holderReg = object; } @@ -841,6 +867,30 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm, attacher.jumpNextStub(masm); } +static void +GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm, + IonCache::StubAttacher& attacher, JSObject* obj, + const UnboxedLayout::Property* property, + Register object, TypedOrValueRegister output, + Label* failures = nullptr) +{ + // Guard on the group of the object. + attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, + Address(object, JSObject::offsetOfGroup()), + ImmGCPtr(obj->group()), failures); + + Address address(object, UnboxedPlainObject::offsetOfData() + property->offset); + + masm.loadUnboxedProperty(address, property->type, output); + + attacher.jumpRejoin(masm); + + if (failures) { + masm.bind(failures); + attacher.jumpNextStub(masm); + } +} + static bool EmitGetterCall(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher, JSObject* obj, @@ -1448,6 +1498,67 @@ GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip } bool +GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandleId id, void* returnAddr, bool* emitted) +{ + MOZ_ASSERT(canAttachStub()); + MOZ_ASSERT(!*emitted); + MOZ_ASSERT(outerScript->ionScript() == ion); + + if (!obj->is<UnboxedPlainObject>()) + return true; + const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); + if (!property) + return true; + + *emitted = true; + + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + + Label failures; + emitIdGuard(masm, id, &failures); + Label* maybeFailures = failures.used() ? &failures : nullptr; + + StubAttacher attacher(*this); + GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures); + return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed", + JS::TrackedOutcome::ICGetPropStub_UnboxedRead); +} + +bool +GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandleId id, void* returnAddr, bool* emitted) +{ + MOZ_ASSERT(canAttachStub()); + MOZ_ASSERT(!*emitted); + MOZ_ASSERT(outerScript->ionScript() == ion); + + if (!obj->is<UnboxedPlainObject>()) + return true; + Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando()); + if (!expando) + return true; + + Shape* shape = expando->lookup(cx, id); + if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot()) + return true; + + *emitted = true; + + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + + Label failures; + emitIdGuard(masm, id, &failures); + Label* maybeFailures = failures.used() ? &failures : nullptr; + + StubAttacher attacher(*this); + GenerateReadSlot(cx, ion, masm, attacher, DontCheckTDZ, obj, obj, + shape, object(), output(), maybeFailures); + return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando", + JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando); +} + +bool GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandleId id, bool* emitted) { @@ -2016,6 +2127,12 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted)) return false; + if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted)) + return false; + + if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted)) + return false; + if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted)) return false; } @@ -2194,6 +2311,12 @@ GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att NativeObject::slotsSizeMustNotOverflow(); + if (obj->is<UnboxedPlainObject>()) { + obj = obj->as<UnboxedPlainObject>().maybeExpando(); + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg); + object = tempReg; + } + if (obj->as<NativeObject>().isFixedSlot(shape->slot())) { Address addr(object, NativeObject::getFixedSlotOffset(shape->slot())); @@ -2831,13 +2954,23 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures); if (obj->maybeShape()) { masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures); + } else { + MOZ_ASSERT(obj->is<UnboxedPlainObject>()); + + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); + masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures); + + masm.loadPtr(expandoAddress, tempReg); + masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures); } Shape* newShape = obj->maybeShape(); + if (!newShape) + newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty(); // Guard that the incoming value is in the type set for the property // if a type barrier is required. - if (newShape && checkTypeset) + if (checkTypeset) CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures); // Guard shapes along prototype chain. @@ -2858,7 +2991,9 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att } // Call a stub to (re)allocate dynamic slots, if necessary. - uint32_t newNumDynamicSlots = obj->as<NativeObject>().numDynamicSlots(); + uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>() + ? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots() + : obj->as<NativeObject>().numDynamicSlots(); if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) { AllocatableRegisterSet regs(RegisterSet::Volatile()); LiveRegisterSet save(regs.asLiveSet()); @@ -2869,6 +3004,12 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att Register temp1 = regs.takeAnyGeneral(); Register temp2 = regs.takeAnyGeneral(); + if (obj->is<UnboxedPlainObject>()) { + // Pass the expando object to the stub. + masm.Push(object); + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object); + } + masm.setupUnalignedABICall(temp1); masm.loadJSContext(temp1); masm.passABIArg(temp1); @@ -2885,16 +3026,27 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att masm.jump(&allocDone); masm.bind(&allocFailed); + if (obj->is<UnboxedPlainObject>()) + masm.Pop(object); masm.PopRegsInMask(save); masm.jump(failures); masm.bind(&allocDone); masm.setFramePushed(framePushedAfterCall); + if (obj->is<UnboxedPlainObject>()) + masm.Pop(object); masm.PopRegsInMask(save); } bool popObject = false; + if (obj->is<UnboxedPlainObject>()) { + masm.push(object); + popObject = true; + obj = obj->as<UnboxedPlainObject>().maybeExpando(); + masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object); + } + // Write the object or expando object's new shape. Address shapeAddr(object, ShapedObject::offsetOfShape()); if (cx->zone()->needsIncrementalBarrier()) @@ -2902,6 +3054,8 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att masm.storePtr(ImmGCPtr(newShape), shapeAddr); if (oldGroup != obj->group()) { + MOZ_ASSERT(!obj->is<UnboxedPlainObject>()); + // 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. @@ -3144,6 +3298,141 @@ CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, const Const return SetPropertyIC::CanAttachNone; } +static void +GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher, + JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType, + Register object, Register tempReg, const ConstantOrRegister& value, + bool checkTypeset, Label* failures) +{ + // Guard on the type of the object. + masm.branchPtr(Assembler::NotEqual, + Address(object, JSObject::offsetOfGroup()), + ImmGCPtr(obj->group()), failures); + + if (checkTypeset) + CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures); + + Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset); + + if (cx->zone()->needsIncrementalBarrier()) { + if (unboxedType == JSVAL_TYPE_OBJECT) + masm.callPreBarrier(address, MIRType::Object); + else if (unboxedType == JSVAL_TYPE_STRING) + masm.callPreBarrier(address, MIRType::String); + else + MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType)); + } + + masm.storeUnboxedProperty(address, unboxedType, value, failures); + + attacher.jumpRejoin(masm); + + masm.bind(failures); + attacher.jumpNextStub(masm); +} + +static bool +CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, const ConstantOrRegister& val, + bool needsTypeBarrier, bool* checkTypeset, + uint32_t* unboxedOffset, JSValueType* unboxedType) +{ + if (!obj->is<UnboxedPlainObject>()) + return false; + + const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); + if (property) { + *checkTypeset = false; + if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset)) + return false; + *unboxedOffset = property->offset; + *unboxedType = property->type; + return true; + } + + return false; +} + +static bool +CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id, + const ConstantOrRegister& val, + bool needsTypeBarrier, bool* checkTypeset, Shape** pshape) +{ + if (!obj->is<UnboxedPlainObject>()) + return false; + + Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando()); + if (!expando) + return false; + + Shape* shape = expando->lookupPure(id); + if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable()) + return false; + + *checkTypeset = 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, const ConstantOrRegister& val, + bool needsTypeBarrier, bool* checkTypeset) +{ + if (!obj->is<UnboxedPlainObject>()) + return false; + + Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando()); + if (!expando || expando->inDictionaryMode()) + return false; + + Shape* newShape = expando->lastProperty(); + if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape) + return false; + + MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable()); + + if (PrototypeChainShadowsPropertyAdd(cx, obj, id)) + return false; + + *checkTypeset = false; + if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset)) + return false; + + return true; +} + +bool +SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandleId id, bool* emitted) +{ + MOZ_ASSERT(!*emitted); + + bool checkTypeset = false; + uint32_t unboxedOffset; + JSValueType unboxedType; + if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset, + &unboxedOffset, &unboxedType)) + { + return true; + } + + *emitted = true; + + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + StubAttacher attacher(*this); + + Label failures; + emitIdGuard(masm, id, &failures); + + GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType, + object(), temp(), value(), checkTypeset, &failures); + return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed", + JS::TrackedOutcome::ICSetPropStub_SetUnboxed); +} + bool SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandleId id, bool* emitted) @@ -3225,6 +3514,26 @@ SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip } bool +SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandleId id, bool* emitted) +{ + MOZ_ASSERT(!*emitted); + + RootedShape shape(cx); + bool checkTypeset = false; + if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(), + &checkTypeset, shape.address())) + { + return true; + } + + if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset)) + return false; + *emitted = true; + return true; +} + +bool SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandleValue idval, HandleValue value, MutableHandleId id, bool* emitted, bool* tryNativeAddSlot) @@ -3249,6 +3558,12 @@ SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot)) return false; + + if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted)) + return false; + + if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted)) + return false; } if (idval.isInt32()) { @@ -3300,6 +3615,16 @@ SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScri return true; } + checkTypeset = false; + if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(), + &checkTypeset)) + { + if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset)) + return false; + *emitted = true; + return true; + } + return true; } @@ -3321,6 +3646,11 @@ SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex return false; oldShape = obj->maybeShape(); + if (obj->is<UnboxedPlainObject>()) { + MOZ_ASSERT(!oldShape); + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) + oldShape = expando->lastProperty(); + } } RootedId id(cx); diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index b00646538..173e06c6b 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -529,6 +529,18 @@ class GetPropertyIC : public IonCache HandleObject obj, HandleId id, void* returnAddr, bool* emitted); + MOZ_MUST_USE bool tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandleId id, void* returnAddr, + bool* emitted); + + MOZ_MUST_USE bool tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, + IonScript* ion, HandleObject obj, HandleId id, + void* returnAddr, bool* emitted); + + MOZ_MUST_USE bool tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, + IonScript* ion, HandleObject obj, HandleId id, + void* returnAddr, bool* emitted); + MOZ_MUST_USE bool tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandleId id, bool* emitted); diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index 3f9d9db88..b9a7c7b27 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -221,6 +221,9 @@ DefaultJitOptions::DefaultJitOptions() Warn(forcedRegisterAllocatorEnv, env); } + // Toggles whether unboxed plain objects can be created by the VM. + SET_DEFAULT(disableUnboxedObjects, true); + // Test whether Atomics are allowed in asm.js code. SET_DEFAULT(asmJSAtomicsEnable, false); diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h index 719ee14d9..076980b4e 100644 --- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -91,6 +91,9 @@ struct DefaultJitOptions mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold; mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator; + // The options below affect the rest of the VM, and not just the JIT. + bool disableUnboxedObjects; + DefaultJitOptions(); bool isSmallFunction(JSScript* script) const; void setEagerCompilation(); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 236354530..a1c336391 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -30,6 +30,7 @@ #include "jit/shared/Lowering-shared-inl.h" #include "vm/NativeObject-inl.h" #include "vm/StringObject-inl.h" +#include "vm/UnboxedObject-inl.h" using mozilla::ArrayLength; using mozilla::AssertedCast; diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 0cf31adb3..1e4ee170f 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -4783,14 +4783,15 @@ MCreateThisWithTemplate::canRecoverOnBailout() const MObjectState::MObjectState(MObjectState* state) : numSlots_(state->numSlots_), - numFixedSlots_(state->numFixedSlots_) + numFixedSlots_(state->numFixedSlots_), + operandIndex_(state->operandIndex_) { // This instruction is only used as a summary for bailout paths. setResultType(MIRType::Object); setRecoveredOnBailout(); } -MObjectState::MObjectState(JSObject* templateObject) +MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex) { // This instruction is only used as a summary for bailout paths. setResultType(MIRType::Object); @@ -4801,6 +4802,8 @@ MObjectState::MObjectState(JSObject* templateObject) NativeObject* nativeObject = &templateObject->as<NativeObject>(); numSlots_ = nativeObject->slotSpan(); numFixedSlots_ = nativeObject->numFixedSlots(); + + operandIndex_ = operandIndex; } JSObject* @@ -4860,7 +4863,7 @@ MObjectState::New(TempAllocator& alloc, MDefinition* obj) JSObject* templateObject = templateObjectOf(obj); MOZ_ASSERT(templateObject, "Unexpected object creation."); - MObjectState* res = new(alloc) MObjectState(templateObject); + MObjectState* res = new(alloc) MObjectState(templateObject, nullptr); if (!res || !res->init(alloc, obj)) return nullptr; return res; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 0c1e77f80..cafdbab71 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -375,7 +375,7 @@ class AliasSet { Element = 1 << 1, // A Value member of obj->elements or // a typed object. UnboxedElement = 1 << 2, // An unboxed scalar or reference member of - // typed object. + // typed object or unboxed object. DynamicSlot = 1 << 3, // A Value member of obj->slots. FixedSlot = 1 << 4, // A Value member of obj->fixedSlots(). DOMProperty = 1 << 5, // A DOM property @@ -3758,9 +3758,14 @@ class MObjectState { private: uint32_t numSlots_; - uint32_t numFixedSlots_; + uint32_t numFixedSlots_; // valid if isUnboxed() == false. + OperandIndexMap* operandIndex_; // valid if isUnboxed() == true. - MObjectState(JSObject *templateObject); + bool isUnboxed() const { + return operandIndex_ != nullptr; + } + + MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex); explicit MObjectState(MObjectState* state); MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj); @@ -3820,6 +3825,18 @@ class MObjectState setSlot(slot + numFixedSlots(), def); } + // Interface reserved for unboxed objects. + bool hasOffset(uint32_t offset) const { + MOZ_ASSERT(isUnboxed()); + return offset < operandIndex_->map.length() && operandIndex_->map[offset] != 0; + } + MDefinition* getOffset(uint32_t offset) const { + return getOperand(operandIndex_->map[offset]); + } + void setOffset(uint32_t offset, MDefinition* def) { + replaceOperand(operandIndex_->map[offset], def); + } + MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { return true; diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index a739b9325..e50f68722 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -126,14 +126,20 @@ MacroAssembler::guardTypeSetMightBeIncomplete(TypeSet* types, Register obj, Regi { // Type set guards might miss when an object's group changes. In this case // either its old group's properties will become unknown, or it will change - // to a native object. Jump to label if this might have happened for the - // input object. + // to a native object with an original unboxed group. Jump to label if this + // might have happened for the input object. if (types->unknownObject()) { jump(label); return; } + loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch); + load32(Address(scratch, ObjectGroup::offsetOfFlags()), scratch); + and32(Imm32(OBJECT_FLAG_ADDENDUM_MASK), scratch); + branch32(Assembler::Equal, + scratch, Imm32(ObjectGroup::addendumOriginalUnboxedGroupValue()), label); + for (size_t i = 0; i < types->getObjectCount(); i++) { if (JSObject* singleton = types->getSingletonNoBarrier(i)) { movePtr(ImmGCPtr(singleton), scratch); @@ -462,6 +468,243 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); +template <typename T> +void +MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output) +{ + switch (type) { + case JSVAL_TYPE_INT32: { + // Handle loading an int32 into a double reg. + if (output.type() == MIRType::Double) { + convertInt32ToDouble(address, output.typedReg().fpu()); + break; + } + MOZ_FALLTHROUGH; + } + + case JSVAL_TYPE_BOOLEAN: + case JSVAL_TYPE_STRING: { + Register outReg; + if (output.hasValue()) { + outReg = output.valueReg().scratchReg(); + } else { + MOZ_ASSERT(output.type() == MIRTypeFromValueType(type)); + outReg = output.typedReg().gpr(); + } + + switch (type) { + case JSVAL_TYPE_BOOLEAN: + load8ZeroExtend(address, outReg); + break; + case JSVAL_TYPE_INT32: + load32(address, outReg); + break; + case JSVAL_TYPE_STRING: + loadPtr(address, outReg); + break; + default: + MOZ_CRASH(); + } + + if (output.hasValue()) + tagValue(type, outReg, output.valueReg()); + break; + } + + case JSVAL_TYPE_OBJECT: + if (output.hasValue()) { + Register scratch = output.valueReg().scratchReg(); + loadPtr(address, scratch); + + Label notNull, done; + branchPtr(Assembler::NotEqual, scratch, ImmWord(0), ¬Null); + + moveValue(NullValue(), output.valueReg()); + jump(&done); + + bind(¬Null); + tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg()); + + bind(&done); + } else { + // Reading null can't be possible here, as otherwise the result + // would be a value (either because null has been read before or + // because there is a barrier). + Register reg = output.typedReg().gpr(); + loadPtr(address, reg); +#ifdef DEBUG + Label ok; + branchTestPtr(Assembler::NonZero, reg, reg, &ok); + assumeUnreachable("Null not possible"); + bind(&ok); +#endif + } + break; + + case JSVAL_TYPE_DOUBLE: + // Note: doubles in unboxed objects are not accessed through other + // views and do not need canonicalization. + if (output.hasValue()) + loadValue(address, output.valueReg()); + else + loadDouble(address, output.typedReg().fpu()); + break; + + default: + MOZ_CRASH(); + } +} + +template void +MacroAssembler::loadUnboxedProperty(Address address, JSValueType type, + TypedOrValueRegister output); + +template void +MacroAssembler::loadUnboxedProperty(BaseIndex address, JSValueType type, + TypedOrValueRegister output); + +static void +StoreUnboxedFailure(MacroAssembler& masm, Label* failure) +{ + // Storing a value to an unboxed property is a fallible operation and + // the caller must provide a failure label if a particular unboxed store + // might fail. Sometimes, however, a store that cannot succeed (such as + // storing a string to an int32 property) will be marked as infallible. + // This can only happen if the code involved is unreachable. + if (failure) + masm.jump(failure); + else + masm.assumeUnreachable("Incompatible write to unboxed property"); +} + +template <typename T> +void +MacroAssembler::storeUnboxedProperty(T address, JSValueType type, + const ConstantOrRegister& value, Label* failure) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: + if (value.constant()) { + if (value.value().isBoolean()) + store8(Imm32(value.value().toBoolean()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Boolean) + store8(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestBoolean(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 1); + } + break; + + case JSVAL_TYPE_INT32: + if (value.constant()) { + if (value.value().isInt32()) + store32(Imm32(value.value().toInt32()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Int32) + store32(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestInt32(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 4); + } + break; + + case JSVAL_TYPE_DOUBLE: + if (value.constant()) { + if (value.value().isNumber()) { + loadConstantDouble(value.value().toNumber(), ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + } else { + StoreUnboxedFailure(*this, failure); + } + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Int32) { + convertInt32ToDouble(value.reg().typedReg().gpr(), ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + } else if (value.reg().type() == MIRType::Double) { + storeDouble(value.reg().typedReg().fpu(), address); + } else { + StoreUnboxedFailure(*this, failure); + } + } else { + ValueOperand reg = value.reg().valueReg(); + Label notInt32, end; + branchTestInt32(Assembler::NotEqual, reg, ¬Int32); + int32ValueToDouble(reg, ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + jump(&end); + bind(¬Int32); + if (failure) + branchTestDouble(Assembler::NotEqual, reg, failure); + storeValue(reg, address); + bind(&end); + } + break; + + case JSVAL_TYPE_OBJECT: + if (value.constant()) { + if (value.value().isObjectOrNull()) + storePtr(ImmGCPtr(value.value().toObjectOrNull()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + MOZ_ASSERT(value.reg().type() != MIRType::Null); + if (value.reg().type() == MIRType::Object) + storePtr(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) { + Label ok; + branchTestNull(Assembler::Equal, value.reg().valueReg(), &ok); + branchTestObject(Assembler::NotEqual, value.reg().valueReg(), failure); + bind(&ok); + } + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t)); + } + break; + + case JSVAL_TYPE_STRING: + if (value.constant()) { + if (value.value().isString()) + storePtr(ImmGCPtr(value.value().toString()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::String) + storePtr(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestString(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t)); + } + break; + + default: + MOZ_CRASH(); + } +} + +template void +MacroAssembler::storeUnboxedProperty(Address address, JSValueType type, + const ConstantOrRegister& value, Label* failure); + +template void +MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type, + const ConstantOrRegister& value, Label* failure); + // Inlined version of gc::CheckAllocatorState that checks the bare essentials // and bails for anything that cannot be handled with our jit allocators. void @@ -1009,6 +1252,10 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj, nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t); offset += sizeof(uintptr_t); } + } else if (templateObj->is<UnboxedPlainObject>()) { + storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando())); + if (initContents) + initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>()); } else { MOZ_CRASH("Unknown object"); } @@ -1030,6 +1277,29 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj, } void +MacroAssembler::initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject) +{ + const UnboxedLayout& layout = templateObject->layoutDontCheckGeneration(); + + // Initialize reference fields of the object, per UnboxedPlainObject::create. + if (const int32_t* list = layout.traceList()) { + while (*list != -1) { + storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty), + Address(object, UnboxedPlainObject::offsetOfData() + *list)); + list++; + } + list++; + while (*list != -1) { + storePtr(ImmWord(0), + Address(object, UnboxedPlainObject::offsetOfData() + *list)); + list++; + } + // Unboxed objects don't have Values to initialize. + MOZ_ASSERT(*(list + 1) == -1); + } +} + +void MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result, Label* fail) { diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index d5cc95839..6ee989463 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -36,6 +36,7 @@ #include "vm/ProxyObject.h" #include "vm/Shape.h" #include "vm/TypedArrayObject.h" +#include "vm/UnboxedObject.h" using mozilla::FloatingPoint; @@ -1625,6 +1626,17 @@ class MacroAssembler : public MacroAssemblerSpecific void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, unsigned numElems = 0); + // Load a property from an UnboxedPlainObject. + template <typename T> + void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output); + + // Store a property to an UnboxedPlainObject, without triggering barriers. + // If failure is null, the value definitely has a type suitable for storing + // in the property. + template <typename T> + void storeUnboxedProperty(T address, JSValueType type, + const ConstantOrRegister& value, Label* failure); + Register extractString(const Address& address, Register scratch) { return extractObject(address, scratch); } @@ -1701,6 +1713,8 @@ class MacroAssembler : public MacroAssemblerSpecific LiveRegisterSet liveRegs, Label* fail, TypedArrayObject* templateObj, TypedArrayLength lengthKind); + void initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject); + void newGCString(Register result, Register temp, Label* fail); void newGCFatInlineString(Register result, Register temp, Label* fail); diff --git a/js/src/jit/OptimizationTracking.cpp b/js/src/jit/OptimizationTracking.cpp index 7d72795a0..b42634d43 100644 --- a/js/src/jit/OptimizationTracking.cpp +++ b/js/src/jit/OptimizationTracking.cpp @@ -15,9 +15,11 @@ #include "jit/JitcodeMap.h" #include "jit/JitSpewer.h" #include "js/TrackedOptimizationInfo.h" +#include "vm/UnboxedObject.h" #include "vm/ObjectGroup-inl.h" #include "vm/TypeInference-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; @@ -844,6 +846,8 @@ MaybeConstructorFromType(TypeSet::Type ty) return nullptr; ObjectGroup* obj = ty.group(); TypeNewScript* newScript = obj->newScript(); + if (!newScript && obj->maybeUnboxedLayout()) + newScript = obj->unboxedLayout().newScript(); return newScript ? newScript->function() : nullptr; } diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 793b631df..8fe6ee3fb 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -30,6 +30,7 @@ #include "vm/Interpreter-inl.h" #include "vm/NativeObject-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; @@ -1539,12 +1540,37 @@ RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const RootedObject object(cx, &iter.read().toObject()); RootedValue val(cx); - RootedNativeObject nativeObject(cx, &object->as<NativeObject>()); - MOZ_ASSERT(nativeObject->slotSpan() == numSlots()); + if (object->is<UnboxedPlainObject>()) { + const UnboxedLayout& layout = object->as<UnboxedPlainObject>().layout(); - for (size_t i = 0; i < numSlots(); i++) { - val = iter.read(); - nativeObject->setSlot(i, val); + RootedId id(cx); + RootedValue receiver(cx, ObjectValue(*object)); + const UnboxedLayout::PropertyVector& properties = layout.properties(); + for (size_t i = 0; i < properties.length(); i++) { + val = iter.read(); + + // This is the default placeholder value of MObjectState, when no + // properties are defined yet. + if (val.isUndefined()) + continue; + + id = NameToId(properties[i].name); + ObjectOpResult result; + + // SetProperty can only fail due to OOM. + if (!SetProperty(cx, object, id, val, receiver, result)) + return false; + if (!result) + return result.reportError(cx, object, id); + } + } else { + RootedNativeObject nativeObject(cx, &object->as<NativeObject>()); + MOZ_ASSERT(nativeObject->slotSpan() == numSlots()); + + for (size_t i = 0; i < numSlots(); i++) { + val = iter.read(); + nativeObject->setSlot(i, val); + } } val.setObject(*object); diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp index 97ba52349..be9ceee2e 100644 --- a/js/src/jit/ScalarReplacement.cpp +++ b/js/src/jit/ScalarReplacement.cpp @@ -285,6 +285,10 @@ class ObjectMemoryView : public MDefinitionVisitorDefaultNoop void visitGuardShape(MGuardShape* ins); void visitFunctionEnvironment(MFunctionEnvironment* ins); void visitLambda(MLambda* ins); + + private: + void storeOffset(MInstruction* ins, size_t offset, MDefinition* value); + void loadOffset(MInstruction* ins, size_t offset); }; const char* ObjectMemoryView::phaseName = "Scalar Replacement of Object"; @@ -626,6 +630,35 @@ ObjectMemoryView::visitLambda(MLambda* ins) ins->setIncompleteObject(); } +void +ObjectMemoryView::storeOffset(MInstruction* ins, size_t offset, MDefinition* value) +{ + // Clone the state and update the slot value. + MOZ_ASSERT(state_->hasOffset(offset)); + state_ = BlockState::Copy(alloc_, state_); + if (!state_) { + oom_ = true; + return; + } + + state_->setOffset(offset, value); + ins->block()->insertBefore(ins, state_); + + // Remove original instruction. + ins->block()->discard(ins); +} + +void +ObjectMemoryView::loadOffset(MInstruction* ins, size_t offset) +{ + // Replace load by the slot value. + MOZ_ASSERT(state_->hasOffset(offset)); + ins->replaceAllUsesWith(state_->getOffset(offset)); + + // Remove original instruction. + ins->block()->discard(ins); +} + static bool IndexOf(MDefinition* ins, int32_t* res) { diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index 05a95824f..313957462 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -2244,7 +2244,8 @@ IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy) if (!isDOMProxy && !obj->isNative()) { if (obj == holder) return false; - if (!obj->is<TypedObject>()) + if (!obj->is<UnboxedPlainObject>() && + !obj->is<TypedObject>()) { return false; } @@ -2572,6 +2573,9 @@ CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, PropertyName* name, } else if (curObj != obj) { // Non-native objects are only handled as the original receiver. return false; + } else if (curObj->is<UnboxedPlainObject>()) { + if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name))) + return false; } else if (curObj->is<TypedObject>()) { if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name))) return false; @@ -2836,15 +2840,34 @@ GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard, { Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup()); Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape()); + Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); if (guard.group) { masm.loadPtr(groupAddress, scratch); masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure); + + if (guard.group->clasp() == &UnboxedPlainObject::class_ && !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); - masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); + if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) { + // 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); + } } } @@ -4228,7 +4251,8 @@ DoNewObject(JSContext* cx, void* payload, ICNewObject_Fallback* stub, MutableHan return false; if (!stub->invalid() && - !templateObject->as<PlainObject>().hasDynamicSlots()) + (templateObject->is<UnboxedPlainObject>() || + !templateObject->as<PlainObject>().hasDynamicSlots())) { JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject); if (!code) diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 652c23bf1..1cb731de8 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -28,7 +28,7 @@ #include "vm/NativeObject-inl.h" #include "vm/StringObject-inl.h" #include "vm/TypeInference-inl.h" -#include "gc/StoreBuffer-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; |