summaryrefslogtreecommitdiffstats
path: root/js/src/jit
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@wolfbeast.com>2020-02-22 21:09:32 +0100
committerwolfbeast <mcwerewolf@wolfbeast.com>2020-02-22 21:09:32 +0100
commit5ee844f71001af821244696414bd92973f78fc09 (patch)
treee9d5ec0b5f06918c7ff842f1ac932ab2874138ff /js/src/jit
parentb1cd96989bb5107f83895c2320e0046d84b448ab (diff)
downloadUXP-5ee844f71001af821244696414bd92973f78fc09.tar
UXP-5ee844f71001af821244696414bd92973f78fc09.tar.gz
UXP-5ee844f71001af821244696414bd92973f78fc09.tar.lz
UXP-5ee844f71001af821244696414bd92973f78fc09.tar.xz
UXP-5ee844f71001af821244696414bd92973f78fc09.zip
Revert #1142 - Remove unboxed objects
- accounting for removal of watch()/unwatch()
Diffstat (limited to 'js/src/jit')
-rw-r--r--js/src/jit/BaselineCacheIR.cpp52
-rw-r--r--js/src/jit/BaselineIC.cpp269
-rw-r--r--js/src/jit/BaselineIC.h10
-rw-r--r--js/src/jit/BaselineInspector.cpp46
-rw-r--r--js/src/jit/CacheIR.cpp72
-rw-r--r--js/src/jit/CacheIR.h20
-rw-r--r--js/src/jit/CodeGenerator.cpp32
-rw-r--r--js/src/jit/IonBuilder.cpp183
-rw-r--r--js/src/jit/IonBuilder.h13
-rw-r--r--js/src/jit/IonCaches.cpp336
-rw-r--r--js/src/jit/IonCaches.h12
-rw-r--r--js/src/jit/JitOptions.cpp3
-rw-r--r--js/src/jit/JitOptions.h3
-rw-r--r--js/src/jit/MCallOptimize.cpp1
-rw-r--r--js/src/jit/MIR.cpp9
-rw-r--r--js/src/jit/MIR.h23
-rw-r--r--js/src/jit/MacroAssembler.cpp274
-rw-r--r--js/src/jit/MacroAssembler.h14
-rw-r--r--js/src/jit/OptimizationTracking.cpp4
-rw-r--r--js/src/jit/Recover.cpp36
-rw-r--r--js/src/jit/ScalarReplacement.cpp33
-rw-r--r--js/src/jit/SharedIC.cpp30
-rw-r--r--js/src/jit/VMFunctions.cpp2
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), &notNull);
+
+ moveValue(NullValue(), output.valueReg());
+ jump(&done);
+
+ bind(&notNull);
+ 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, &notInt32);
+ int32ValueToDouble(reg, ScratchDoubleReg);
+ storeDouble(ScratchDoubleReg, address);
+ jump(&end);
+ bind(&notInt32);
+ 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;