summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--js/src/gc/Marking.cpp46
-rw-r--r--js/src/gc/Tracer.cpp69
-rw-r--r--js/src/jit/AliasAnalysisShared.cpp2
-rw-r--r--js/src/jit/BaselineInspector.cpp31
-rw-r--r--js/src/jit/BaselineInspector.h9
-rw-r--r--js/src/jit/CodeGenerator.cpp61
-rw-r--r--js/src/jit/CodeGenerator.h3
-rw-r--r--js/src/jit/IonAnalysis.cpp2
-rw-r--r--js/src/jit/IonBuilder.cpp272
-rw-r--r--js/src/jit/IonBuilder.h10
-rw-r--r--js/src/jit/Lowering.cpp26
-rw-r--r--js/src/jit/Lowering.h3
-rw-r--r--js/src/jit/MCallOptimize.cpp8
-rw-r--r--js/src/jit/MIR.cpp195
-rw-r--r--js/src/jit/MIR.h131
-rw-r--r--js/src/jit/MOpcodes.h3
-rw-r--r--js/src/jit/OptimizationTracking.cpp2
-rw-r--r--js/src/jit/ScalarReplacement.cpp112
-rw-r--r--js/src/jit/SharedIC.cpp1
-rw-r--r--js/src/jit/shared/LIR-shared.h48
-rw-r--r--js/src/jit/shared/LOpcodes-shared.h3
-rw-r--r--js/src/jsapi.cpp3
-rw-r--r--js/src/jsapi.h25
-rw-r--r--js/src/jsgc.cpp6
-rw-r--r--js/src/jsiter.cpp42
-rw-r--r--js/src/jsobj.cpp76
-rw-r--r--js/src/shell/js.cpp3
-rw-r--r--js/src/vm/Interpreter-inl.h13
-rw-r--r--js/src/vm/Interpreter.cpp7
-rw-r--r--js/src/vm/ReceiverGuard.cpp1
-rw-r--r--js/src/vm/ReceiverGuard.h5
-rw-r--r--js/src/vm/TypeInference.cpp16
-rw-r--r--js/src/vm/TypeInference.h1
-rw-r--r--js/src/vm/UnboxedObject.cpp400
-rw-r--r--js/src/vm/UnboxedObject.h7
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp4
-rw-r--r--modules/libpref/init/all.js1
37 files changed, 1545 insertions, 102 deletions
diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp
index a206a8762..43e325394 100644
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -18,7 +18,6 @@
#include "builtin/ModuleObject.h"
#include "gc/GCInternals.h"
#include "gc/Policy.h"
-#include "gc/StoreBuffer-inl.h"
#include "jit/IonCode.h"
#include "js/SliceBudget.h"
#include "vm/ArgumentsObject.h"
@@ -29,6 +28,7 @@
#include "vm/Shape.h"
#include "vm/Symbol.h"
#include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
#include "wasm/WasmJS.h"
#include "jscompartmentinlines.h"
@@ -37,6 +37,7 @@
#include "gc/Nursery-inl.h"
#include "vm/String-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::gc;
@@ -1394,6 +1395,14 @@ js::ObjectGroup::traceChildren(JSTracer* trc)
if (maybePreliminaryObjects())
maybePreliminaryObjects()->trace(trc);
+ if (maybeUnboxedLayout())
+ unboxedLayout().trace(trc);
+
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) {
+ TraceManuallyBarrieredEdge(trc, &unboxedGroup, "group_original_unboxed_group");
+ setOriginalUnboxedGroup(unboxedGroup);
+ }
+
if (JSObject* descr = maybeTypeDescr()) {
TraceManuallyBarrieredEdge(trc, &descr, "group_type_descr");
setTypeDescr(&descr->as<TypeDescr>());
@@ -1427,6 +1436,12 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group)
if (group->maybePreliminaryObjects())
group->maybePreliminaryObjects()->trace(this);
+ if (group->maybeUnboxedLayout())
+ group->unboxedLayout().trace(this);
+
+ if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
+ traverseEdge(group, unboxedGroup);
+
if (TypeDescr* descr = group->maybeTypeDescr())
traverseEdge(group, static_cast<JSObject*>(descr));
@@ -1469,6 +1484,23 @@ CallTraceHook(Functor f, JSTracer* trc, JSObject* obj, CheckGeneration check, Ar
return nullptr;
}
+ if (clasp == &UnboxedPlainObject::class_) {
+ JSObject** pexpando = obj->as<UnboxedPlainObject>().addressOfExpando();
+ if (*pexpando)
+ f(pexpando, mozilla::Forward<Args>(args)...);
+
+ UnboxedPlainObject& unboxed = obj->as<UnboxedPlainObject>();
+ const UnboxedLayout& layout = check == CheckGeneration::DoChecks
+ ? unboxed.layout()
+ : unboxed.layoutDontCheckGeneration();
+ if (layout.traceList()) {
+ VisitTraceList(f, layout.traceList(), unboxed.data(),
+ mozilla::Forward<Args>(args)...);
+ }
+
+ return nullptr;
+ }
+
clasp->doTrace(trc, obj);
if (!clasp->isNative())
@@ -2261,6 +2293,18 @@ static inline void
TraceWholeCell(TenuringTracer& mover, JSObject* object)
{
mover.traceObject(object);
+
+ // Additionally trace the expando object attached to any unboxed plain
+ // objects. Baseline and Ion can write properties to the expando while
+ // only adding a post barrier to the owning unboxed object. Note that
+ // it isn't possible for a nursery unboxed object to have a tenured
+ // expando, so that adding a post barrier on the original object will
+ // capture any tenured->nursery edges in the expando as well.
+
+ if (object->is<UnboxedPlainObject>()) {
+ if (UnboxedExpandoObject* expando = object->as<UnboxedPlainObject>().maybeExpando())
+ expando->traceChildren(&mover);
+ }
}
static inline void
diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp
index 3416464dd..63cd9b08a 100644
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -201,15 +201,80 @@ gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, Shape* shape)
} while (shape);
}
+// Object groups can point to other object groups via an UnboxedLayout or the
+// the original unboxed group link. There can potentially be deep or cyclic
+// chains of such groups to trace through without going through a thing that
+// participates in cycle collection. These need to be handled iteratively to
+// avoid blowing the stack when running the cycle collector's callback tracer.
+struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer
+{
+ explicit ObjectGroupCycleCollectorTracer(JS::CallbackTracer* innerTracer)
+ : JS::CallbackTracer(innerTracer->runtime(), DoNotTraceWeakMaps),
+ innerTracer(innerTracer)
+ {}
+
+ void onChild(const JS::GCCellPtr& thing) override;
+
+ JS::CallbackTracer* innerTracer;
+ Vector<ObjectGroup*, 4, SystemAllocPolicy> seen, worklist;
+};
+
+void
+ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing)
+{
+ if (thing.is<BaseShape>()) {
+ // The CC does not care about BaseShapes, and no additional GC things
+ // will be reached by following this edge.
+ return;
+ }
+
+ if (thing.is<JSObject>() || thing.is<JSScript>()) {
+ // Invoke the inner cycle collector callback on this child. It will not
+ // recurse back into TraceChildren.
+ innerTracer->onChild(thing);
+ return;
+ }
+
+ if (thing.is<ObjectGroup>()) {
+ // If this group is required to be in an ObjectGroup chain, trace it
+ // via the provided worklist rather than continuing to recurse.
+ ObjectGroup& group = thing.as<ObjectGroup>();
+ if (group.maybeUnboxedLayout()) {
+ for (size_t i = 0; i < seen.length(); i++) {
+ if (seen[i] == &group)
+ return;
+ }
+ if (seen.append(&group) && worklist.append(&group)) {
+ return;
+ } else {
+ // If append fails, keep tracing normally. The worst that will
+ // happen is we end up overrecursing.
+ }
+ }
+ }
+
+ TraceChildren(this, thing.asCell(), thing.kind());
+}
+
void
gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, ObjectGroup* group)
{
MOZ_ASSERT(trc->isCallbackTracer());
- group->traceChildren(trc);
-}
+ // Early return if this group is not required to be in an ObjectGroup chain.
+ if (!group->maybeUnboxedLayout())
+ return group->traceChildren(trc);
+
+ ObjectGroupCycleCollectorTracer groupTracer(trc->asCallbackTracer());
+ group->traceChildren(&groupTracer);
+ while (!groupTracer.worklist.empty()) {
+ ObjectGroup* innerGroup = groupTracer.worklist.popCopy();
+ innerGroup->traceChildren(&groupTracer);
+ }
+}
+
/*** Traced Edge Printer *************************************************************************/
static size_t
diff --git a/js/src/jit/AliasAnalysisShared.cpp b/js/src/jit/AliasAnalysisShared.cpp
index 1a643698f..99c23d2a3 100644
--- a/js/src/jit/AliasAnalysisShared.cpp
+++ b/js/src/jit/AliasAnalysisShared.cpp
@@ -112,6 +112,8 @@ GetObject(const MDefinition* ins)
case MDefinition::Op_GuardObjectGroup:
case MDefinition::Op_GuardObjectIdentity:
case MDefinition::Op_GuardClass:
+ case MDefinition::Op_GuardUnboxedExpando:
+ case MDefinition::Op_LoadUnboxedExpando:
case MDefinition::Op_LoadSlot:
case MDefinition::Op_StoreSlot:
case MDefinition::Op_InArray:
diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp
index 9c7b88fb2..c9e09bed7 100644
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -96,8 +96,13 @@ VectorAppendNoDuplicate(S& list, T value)
static bool
AddReceiver(const ReceiverGuard& receiver,
- BaselineInspector::ReceiverVector& receivers)
+ BaselineInspector::ReceiverVector& receivers,
+ BaselineInspector::ObjectGroupVector& convertUnboxedGroups)
{
+ if (receiver.group && receiver.group->maybeUnboxedLayout()) {
+ if (receiver.group->unboxedLayout().nativeGroup())
+ return VectorAppendNoDuplicate(convertUnboxedGroups, receiver.group);
+ }
return VectorAppendNoDuplicate(receivers, receiver);
}
@@ -165,12 +170,16 @@ GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* r
}
bool
-BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers)
+BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
// Return a list of the receivers seen by the baseline IC for the current
// op. Empty lists indicate no receivers are known, or there was an
- // uncacheable access.
+ // uncacheable access. convertUnboxedGroups is used for unboxed object
+ // groups which have been seen, but have had instances converted to native
+ // objects and should be eagerly converted by Ion.
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
if (!hasBaselineScript())
return true;
@@ -198,7 +207,7 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
return true;
}
- if (!AddReceiver(receiver, receivers))
+ if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
return false;
stub = stub->next();
@@ -691,12 +700,14 @@ bool
BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonGetter, Shape** globalShape,
bool* isOwnProperty,
- ReceiverVector& receivers)
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
if (!hasBaselineScript())
return false;
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
*holder = nullptr;
const ICEntry& entry = icEntryFromPC(pc);
@@ -708,7 +719,7 @@ BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shap
{
ICGetPropCallGetter* nstub = static_cast<ICGetPropCallGetter*>(stub);
bool isOwn = nstub->isOwnGetter();
- if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers))
+ if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers, convertUnboxedGroups))
return false;
if (!*holder) {
@@ -740,19 +751,21 @@ BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shap
if (!*holder)
return false;
- MOZ_ASSERT(*isOwnProperty == (receivers.empty()));
+ MOZ_ASSERT(*isOwnProperty == (receivers.empty() && convertUnboxedGroups.empty()));
return true;
}
bool
BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonSetter, bool* isOwnProperty,
- ReceiverVector& receivers)
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
if (!hasBaselineScript())
return false;
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
*holder = nullptr;
const ICEntry& entry = icEntryFromPC(pc);
@@ -761,7 +774,7 @@ BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shap
if (stub->isSetProp_CallScripted() || stub->isSetProp_CallNative()) {
ICSetPropCallSetter* nstub = static_cast<ICSetPropCallSetter*>(stub);
bool isOwn = nstub->isOwnSetter();
- if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers))
+ if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers, convertUnboxedGroups))
return false;
if (!*holder) {
diff --git a/js/src/jit/BaselineInspector.h b/js/src/jit/BaselineInspector.h
index 4a1791798..961df18aa 100644
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -95,7 +95,8 @@ class BaselineInspector
public:
typedef Vector<ReceiverGuard, 4, JitAllocPolicy> ReceiverVector;
typedef Vector<ObjectGroup*, 4, JitAllocPolicy> ObjectGroupVector;
- MOZ_MUST_USE bool maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers);
+ MOZ_MUST_USE bool maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
SetElemICInspector setElemICInspector(jsbytecode* pc) {
return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
@@ -130,10 +131,12 @@ class BaselineInspector
MOZ_MUST_USE bool commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonGetter, Shape** globalShape,
- bool* isOwnProperty, ReceiverVector& receivers);
+ bool* isOwnProperty, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
MOZ_MUST_USE bool commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonSetter, bool* isOwnProperty,
- ReceiverVector& receivers);
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
MOZ_MUST_USE bool instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot,
JSObject** prototypeObject);
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index c3e242991..bb12b09c8 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3032,6 +3032,15 @@ GuardReceiver(MacroAssembler& masm, const ReceiverGuard& guard,
{
if (guard.group) {
masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, miss);
+
+ Address expandoAddress(obj, UnboxedPlainObject::offsetOfExpando());
+ if (guard.shape) {
+ masm.loadPtr(expandoAddress, scratch);
+ masm.branchPtr(Assembler::Equal, scratch, ImmWord(0), miss);
+ masm.branchTestObjShape(Assembler::NotEqual, scratch, guard.shape, miss);
+ } else if (checkNullExpando) {
+ masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), miss);
+ }
} else {
masm.branchTestObjShape(Assembler::NotEqual, obj, guard.shape, miss);
}
@@ -3069,6 +3078,13 @@ CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
masm.loadPtr(Address(target, NativeObject::offsetOfSlots()), scratch);
masm.loadTypedOrValue(Address(scratch, offset), output);
}
+ } else {
+ masm.comment("loadUnboxedProperty");
+ const UnboxedLayout::Property* property =
+ receiver.group->unboxedLayout().lookup(mir->name());
+ Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ masm.loadUnboxedProperty(propertyAddr, property->type, output);
}
if (i == mir->numReceivers() - 1) {
@@ -3109,6 +3125,8 @@ EmitUnboxedPreBarrier(MacroAssembler &masm, T address, JSValueType type)
masm.patchableCallPreBarrier(address, MIRType::Object);
else if (type == JSVAL_TYPE_STRING)
masm.patchableCallPreBarrier(address, MIRType::String);
+ else
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
}
void
@@ -3144,6 +3162,13 @@ CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
emitPreBarrier(addr);
masm.storeConstantOrRegister(value, addr);
}
+ } else {
+ const UnboxedLayout::Property* property =
+ receiver.group->unboxedLayout().lookup(mir->name());
+ Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ EmitUnboxedPreBarrier(masm, propertyAddr, property->type);
+ masm.storeUnboxedProperty(propertyAddr, property->type, value, nullptr);
}
if (i == mir->numReceivers() - 1) {
@@ -3309,6 +3334,27 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir)
}
void
+CodeGenerator::visitGuardUnboxedExpando(LGuardUnboxedExpando* lir)
+{
+ Label miss;
+
+ Register obj = ToRegister(lir->object());
+ masm.branchPtr(lir->mir()->requireExpando() ? Assembler::Equal : Assembler::NotEqual,
+ Address(obj, UnboxedPlainObject::offsetOfExpando()), ImmWord(0), &miss);
+
+ bailoutFrom(&miss, lir->snapshot());
+}
+
+void
+CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir)
+{
+ Register obj = ToRegister(lir->object());
+ Register result = ToRegister(lir->getDef(0));
+
+ masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), result);
+}
+
+void
CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir)
{
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
@@ -8530,6 +8576,21 @@ static const VMFunction ConvertUnboxedArrayObjectToNativeInfo =
FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedArrayObject::convertToNative,
"UnboxedArrayObject::convertToNative");
+void
+CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir)
+{
+ Register object = ToRegister(lir->getOperand(0));
+
+ OutOfLineCode* ool = oolCallVM(lir->mir()->group()->unboxedLayoutDontCheckGeneration().isArray()
+ ? ConvertUnboxedArrayObjectToNativeInfo
+ : ConvertUnboxedPlainObjectToNativeInfo,
+ lir, ArgList(object), StoreNothing());
+
+ masm.branchPtr(Assembler::Equal, Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(lir->mir()->group()), ool->entry());
+ masm.bind(ool->rejoin());
+}
+
typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction ArrayPopDenseInfo =
FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h
index bc8fcccea..6a5c7f34f 100644
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -148,6 +148,8 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite* lir);
void visitGuardObjectIdentity(LGuardObjectIdentity* guard);
void visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir);
+ void visitGuardUnboxedExpando(LGuardUnboxedExpando* lir);
+ void visitLoadUnboxedExpando(LLoadUnboxedExpando* lir);
void visitTypeBarrierV(LTypeBarrierV* lir);
void visitTypeBarrierO(LTypeBarrierO* lir);
void visitMonitorTypes(LMonitorTypes* lir);
@@ -309,6 +311,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitFallibleStoreElementV(LFallibleStoreElementV* lir);
void visitFallibleStoreElementT(LFallibleStoreElementT* lir);
void visitStoreUnboxedPointer(LStoreUnboxedPointer* lir);
+ void visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir);
void emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, Register obj,
Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
void visitArrayPopShiftV(LArrayPopShiftV* lir);
diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp
index 38604612c..3c0f2c4b3 100644
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3515,6 +3515,8 @@ PassthroughOperand(MDefinition* def)
return def->toConvertElementsToDoubles()->elements();
if (def->isMaybeCopyElementsForWrite())
return def->toMaybeCopyElementsForWrite()->object();
+ if (def->isConvertUnboxedObjectToNative())
+ return def->toConvertUnboxedObjectToNative()->object();
return nullptr;
}
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index a5991cc7b..f08baf865 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -32,6 +32,7 @@
#include "vm/EnvironmentObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -6400,7 +6401,7 @@ IonBuilder::createThisScriptedSingleton(JSFunction* target, MDefinition* callee)
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject)
return nullptr;
- if (!templateObject->is<PlainObject>())
+ if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
return nullptr;
if (templateObject->staticPrototype() != proto)
return nullptr;
@@ -6437,7 +6438,7 @@ IonBuilder::createThisScriptedBaseline(MDefinition* callee)
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject)
return nullptr;
- if (!templateObject->is<PlainObject>())
+ if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
return nullptr;
Shape* shape = target->lookupPure(compartment->runtime()->names().prototype);
@@ -7734,6 +7735,8 @@ IonBuilder::jsop_initprop(PropertyName* name)
if (templateObject->is<PlainObject>()) {
if (!templateObject->as<PlainObject>().containsPure(name))
useSlowPath = true;
+ } else {
+ MOZ_ASSERT(templateObject->as<UnboxedPlainObject>().layout().lookup(name));
}
} else {
useSlowPath = true;
@@ -8192,7 +8195,8 @@ IonBuilder::maybeMarkEmpty(MDefinition* ins)
static bool
ClassHasEffectlessLookup(const Class* clasp)
{
- return (clasp == &UnboxedArrayObject::class_) ||
+ return (clasp == &UnboxedPlainObject::class_) ||
+ (clasp == &UnboxedArrayObject::class_) ||
IsTypedObjectClass(clasp) ||
(clasp->isNative() && !clasp->getOpsLookupProperty());
}
@@ -9023,6 +9027,8 @@ IonBuilder::jsop_getelem()
}
obj = maybeUnboxForPropertyAccess(obj);
+ if (obj->type() == MIRType::Object)
+ obj = convertUnboxedObjects(obj);
bool emitted = false;
@@ -10146,7 +10152,7 @@ IonBuilder::jsop_setelem()
MDefinition* value = current->pop();
MDefinition* index = current->pop();
- MDefinition* object = current->pop();
+ MDefinition* object = convertUnboxedObjects(current->pop());
trackTypeInfo(TrackedTypeSite::Receiver, object->type(), object->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());
@@ -10981,8 +10987,11 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_
}
// Definite slots will always be fixed slots when they are in the
- // allowable range for fixed slots.
+ // allowable range for fixed slots, except for objects which were
+ // converted from unboxed objects and have a smaller allocation size.
size_t nfixed = NativeObject::MAX_FIXED_SLOTS;
+ if (ObjectGroup* group = key->group()->maybeOriginalUnboxedGroup())
+ nfixed = gc::GetGCKindSlots(group->unboxedLayout().getAllocKind());
uint32_t propertySlot = property.maybeTypes()->definiteSlot();
if (slot == UINT32_MAX) {
@@ -11039,6 +11048,8 @@ IonBuilder::getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, JSValu
return UINT32_MAX;
}
+ key->watchStateChangeForUnboxedConvertedToNative(constraints());
+
if (offset == UINT32_MAX) {
offset = property->offset;
*punboxedType = property->type;
@@ -11500,6 +11511,8 @@ IonBuilder::jsop_getprop(PropertyName* name)
}
obj = maybeUnboxForPropertyAccess(obj);
+ if (obj->type() == MIRType::Object)
+ obj = convertUnboxedObjects(obj);
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
obj, name, types);
@@ -11571,6 +11584,11 @@ IonBuilder::jsop_getprop(PropertyName* name)
if (!getPropTryDefiniteSlot(&emitted, obj, name, barrier, types) || emitted)
return emitted;
+ // Try to emit loads from unboxed objects.
+ trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
+ if (!getPropTryUnboxed(&emitted, obj, name, barrier, types) || emitted)
+ return emitted;
+
// Try to inline a common property getter, or make a call.
trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
if (!getPropTryCommonGetter(&emitted, obj, name, types) || emitted)
@@ -11936,6 +11954,49 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool* emitted,
fieldPrediction, fieldTypeObj);
}
+MDefinition*
+IonBuilder::convertUnboxedObjects(MDefinition* obj)
+{
+ // If obj might be in any particular unboxed group which should be
+ // converted to a native representation, perform that conversion. This does
+ // not guarantee the object will not have such a group afterwards, if the
+ // object's possible groups are not precisely known.
+ TemporaryTypeSet* types = obj->resultTypeSet();
+ if (!types || types->unknownObject() || !types->objectOrSentinel())
+ return obj;
+
+ BaselineInspector::ObjectGroupVector list(alloc());
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = obj->resultTypeSet()->getObject(i);
+ if (!key || !key->isGroup())
+ continue;
+
+ if (UnboxedLayout* layout = key->group()->maybeUnboxedLayout()) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (layout->nativeGroup() && !list.append(key->group()))
+ oomUnsafe.crash("IonBuilder::convertUnboxedObjects");
+ }
+ }
+
+ return convertUnboxedObjects(obj, list);
+}
+
+MDefinition*
+IonBuilder::convertUnboxedObjects(MDefinition* obj,
+ const BaselineInspector::ObjectGroupVector& list)
+{
+ for (size_t i = 0; i < list.length(); i++) {
+ ObjectGroup* group = list[i];
+ if (TemporaryTypeSet* types = obj->resultTypeSet()) {
+ if (!types->hasType(TypeSet::ObjectType(group)))
+ continue;
+ }
+ obj = MConvertUnboxedObjectToNative::New(alloc(), obj, group);
+ current->add(obj->toInstruction());
+ }
+ return obj;
+}
+
bool
IonBuilder::getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
BarrierKind barrier, TemporaryTypeSet* types)
@@ -12086,14 +12147,45 @@ IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
return load;
}
+bool
+IonBuilder::getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
+ BarrierKind barrier, TemporaryTypeSet* types)
+{
+ MOZ_ASSERT(*emitted == false);
+
+ JSValueType unboxedType;
+ uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+ if (offset == UINT32_MAX)
+ return true;
+
+ if (obj->type() != MIRType::Object) {
+ MGuardObject* guard = MGuardObject::New(alloc(), obj);
+ current->add(guard);
+ obj = guard;
+ }
+
+ MInstruction* load = loadUnboxedProperty(obj, offset, unboxedType, barrier, types);
+ current->push(load);
+
+ if (!pushTypeBarrier(load, types, barrier))
+ return false;
+
+ trackOptimizationSuccess();
+ *emitted = true;
+ return true;
+}
+
MDefinition*
IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
+ const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
bool isOwnProperty)
{
MOZ_ASSERT(holder);
MOZ_ASSERT(holderShape);
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
if (isOwnProperty) {
MOZ_ASSERT(receivers.empty());
return addShapeGuard(obj, holderShape, Bailout_ShapeGuard);
@@ -12117,8 +12209,10 @@ IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName
JSObject* foundProto = nullptr;
bool isOwnProperty = false;
BaselineInspector::ReceiverVector receivers(alloc());
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->commonGetPropFunction(pc, &foundProto, &lastProperty, &commonGetter,
- &globalShape, &isOwnProperty, receivers))
+ &globalShape, &isOwnProperty,
+ receivers, convertUnboxedGroups))
{
return true;
}
@@ -12134,7 +12228,8 @@ IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName
// If type information is bad, we can still optimize the getter if we
// shape guard.
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
- receivers, isOwnProperty);
+ receivers, convertUnboxedGroups,
+ isOwnProperty);
if (!obj)
return false;
}
@@ -12302,12 +12397,15 @@ IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName
MOZ_ASSERT(*emitted == false);
BaselineInspector::ReceiverVector receivers(alloc());
- if (!inspector->maybeInfoForPropertyOp(pc, receivers))
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+ if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(receivers))
return true;
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
rvalType = MIRType::Value;
@@ -12330,6 +12428,45 @@ IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName
return true;
}
+ if (receivers[0].shape) {
+ // Monomorphic load from an unboxed object expando.
+ spew("Inlining monomorphic unboxed expando GETPROP");
+
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ true, Bailout_ShapeGuard);
+
+ MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
+ current->add(expando);
+
+ expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
+
+ Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
+ MOZ_ASSERT(shape);
+
+ if (!loadSlot(expando, shape, rvalType, barrier, types))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
+ return true;
+ }
+
+ // Monomorphic load from an unboxed object.
+ ObjectGroup* group = receivers[0].group;
+ if (obj->resultTypeSet() && !obj->resultTypeSet()->hasType(TypeSet::ObjectType(group)))
+ return true;
+
+ obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
+
+ const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name);
+ MInstruction* load = loadUnboxedProperty(obj, property->offset, property->type, barrier, types);
+ current->push(load);
+
+ if (!pushTypeBarrier(load, types, barrier))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
return true;
}
@@ -12571,7 +12708,7 @@ bool
IonBuilder::jsop_setprop(PropertyName* name)
{
MDefinition* value = current->pop();
- MDefinition* obj = current->pop();
+ MDefinition* obj = convertUnboxedObjects(current->pop());
bool emitted = false;
startTrackingOptimizations();
@@ -12604,6 +12741,13 @@ IonBuilder::jsop_setprop(PropertyName* name)
bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
/* canModify = */ true);
+ if (!forceInlineCaches()) {
+ // Try to emit stores to unboxed objects.
+ trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
+ if (!setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes) || emitted)
+ return emitted;
+ }
+
// Add post barrier if needed. The instructions above manage any post
// barriers they need directly.
if (NeedsPostBarrier(value))
@@ -12637,8 +12781,10 @@ IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
JSObject* foundProto = nullptr;
bool isOwnProperty;
BaselineInspector::ReceiverVector receivers(alloc());
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->commonSetPropFunction(pc, &foundProto, &lastProperty, &commonSetter,
- &isOwnProperty, receivers))
+ &isOwnProperty,
+ receivers, convertUnboxedGroups))
{
trackOptimizationOutcome(TrackedOutcome::NoProtoFound);
return true;
@@ -12653,7 +12799,8 @@ IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
// If type information is bad, we can still optimize the setter if we
// shape guard.
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
- receivers, isOwnProperty);
+ receivers, convertUnboxedGroups,
+ isOwnProperty);
if (!obj)
return false;
}
@@ -12970,6 +13117,40 @@ IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t e
}
bool
+IonBuilder::setPropTryUnboxed(bool* emitted, MDefinition* obj,
+ PropertyName* name, MDefinition* value,
+ bool barrier, TemporaryTypeSet* objTypes)
+{
+ MOZ_ASSERT(*emitted == false);
+
+ if (barrier) {
+ trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
+ return true;
+ }
+
+ JSValueType unboxedType;
+ uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+ if (offset == UINT32_MAX)
+ return true;
+
+ if (obj->type() != MIRType::Object) {
+ MGuardObject* guard = MGuardObject::New(alloc(), obj);
+ current->add(guard);
+ obj = guard;
+ }
+
+ MInstruction* store = storeUnboxedProperty(obj, offset, unboxedType, value);
+
+ current->push(value);
+
+ if (!resumeAfter(store))
+ return false;
+
+ *emitted = true;
+ return true;
+}
+
+bool
IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes)
@@ -12982,12 +13163,15 @@ IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
}
BaselineInspector::ReceiverVector receivers(alloc());
- if (!inspector->maybeInfoForPropertyOp(pc, receivers))
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+ if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(receivers))
return true;
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
if (receivers.length() == 1) {
if (!receivers[0].group) {
// Monomorphic store to a native object.
@@ -13007,6 +13191,46 @@ IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
return true;
}
+ if (receivers[0].shape) {
+ // Monomorphic store to an unboxed object expando.
+ spew("Inlining monomorphic unboxed expando SETPROP");
+
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ true, Bailout_ShapeGuard);
+
+ MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
+ current->add(expando);
+
+ expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
+
+ Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
+ MOZ_ASSERT(shape);
+
+ bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
+ if (!storeSlot(expando, shape, value, needsBarrier))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
+ return true;
+ }
+
+ // Monomorphic store to an unboxed object.
+ spew("Inlining monomorphic unboxed SETPROP");
+
+ ObjectGroup* group = receivers[0].group;
+ if (!objTypes->hasType(TypeSet::ObjectType(group)))
+ return true;
+
+ obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
+
+ const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name);
+ storeUnboxedProperty(obj, property->offset, property->type, value);
+
+ current->push(value);
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
return true;
}
@@ -13705,7 +13929,7 @@ IonBuilder::jsop_setaliasedvar(EnvironmentCoordinate ec)
bool
IonBuilder::jsop_in()
{
- MDefinition* obj = current->pop();
+ MDefinition* obj = convertUnboxedObjects(current->pop());
MDefinition* id = current->pop();
bool emitted = false;
@@ -14062,6 +14286,19 @@ IonBuilder::addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bail
}
MInstruction*
+IonBuilder::addUnboxedExpandoGuard(MDefinition* obj, bool hasExpando, BailoutKind bailoutKind)
+{
+ MGuardUnboxedExpando* guard = MGuardUnboxedExpando::New(alloc(), obj, hasExpando, bailoutKind);
+ current->add(guard);
+
+ // If a shape guard failed in the past, don't optimize group guards.
+ if (failedShapeGuard_)
+ guard->setNotMovable();
+
+ return guard;
+}
+
+MInstruction*
IonBuilder::addGuardReceiverPolymorphic(MDefinition* obj,
const BaselineInspector::ReceiverVector& receivers)
{
@@ -14070,6 +14307,15 @@ IonBuilder::addGuardReceiverPolymorphic(MDefinition* obj,
// Monomorphic guard on a native object.
return addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard);
}
+
+ if (!receivers[0].shape) {
+ // Guard on an unboxed object that does not have an expando.
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ return addUnboxedExpandoGuard(obj, /* hasExpando = */ false, Bailout_ShapeGuard);
+ }
+
+ // Monomorphic receiver guards are not yet supported when the receiver
+ // is an unboxed object with an expando.
}
MGuardReceiverPolymorphic* guard = MGuardReceiverPolymorphic::New(alloc(), obj);
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index 1f84b45df..f359c764f 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -401,6 +401,7 @@ class IonBuilder
MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length);
MInstruction* addShapeGuard(MDefinition* obj, Shape* const shape, BailoutKind bailoutKind);
MInstruction* addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bailoutKind);
+ MInstruction* addUnboxedExpandoGuard(MDefinition* obj, bool hasExpando, BailoutKind bailoutKind);
MInstruction* addSharedTypedArrayGuard(MDefinition* obj);
MInstruction*
@@ -440,6 +441,8 @@ class IonBuilder
BarrierKind barrier, TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name,
BarrierKind barrier, TemporaryTypeSet* types);
+ MOZ_MUST_USE bool getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
+ BarrierKind barrier, TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
@@ -472,6 +475,9 @@ class IonBuilder
MOZ_MUST_USE bool setPropTryDefiniteSlot(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes);
+ MOZ_MUST_USE bool setPropTryUnboxed(bool* emitted, MDefinition* obj,
+ PropertyName* name, MDefinition* value,
+ bool barrier, TemporaryTypeSet* objTypes);
MOZ_MUST_USE bool setPropTryInlineAccess(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes);
@@ -1037,6 +1043,7 @@ class IonBuilder
MDefinition*
addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
+ const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
bool isOwnProperty);
MOZ_MUST_USE bool annotateGetPropertyCache(MDefinition* obj, PropertyName* name,
@@ -1054,6 +1061,9 @@ class IonBuilder
ResultWithOOM<bool> testNotDefinedProperty(MDefinition* obj, jsid id);
uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed);
+ MDefinition* convertUnboxedObjects(MDefinition* obj);
+ MDefinition* convertUnboxedObjects(MDefinition* obj,
+ const BaselineInspector::ObjectGroupVector& list);
uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
JSValueType* punboxedType);
MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp
index c3bd47744..19266bae8 100644
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3258,6 +3258,14 @@ LIRGenerator::visitStoreUnboxedString(MStoreUnboxedString* ins)
}
void
+LIRGenerator::visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins)
+{
+ LInstruction* check = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()));
+ add(check, ins);
+ assignSafepoint(check, ins);
+}
+
+void
LIRGenerator::visitEffectiveAddress(MEffectiveAddress* ins)
{
define(new(alloc()) LEffectiveAddress(useRegister(ins->base()), useRegister(ins->index())), ins);
@@ -3775,6 +3783,24 @@ LIRGenerator::visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins)
}
void
+LIRGenerator::visitGuardUnboxedExpando(MGuardUnboxedExpando* ins)
+{
+ LGuardUnboxedExpando* guard =
+ new(alloc()) LGuardUnboxedExpando(useRegister(ins->object()));
+ assignSnapshot(guard, ins->bailoutKind());
+ add(guard, ins);
+ redefine(ins, ins->object());
+}
+
+void
+LIRGenerator::visitLoadUnboxedExpando(MLoadUnboxedExpando* ins)
+{
+ LLoadUnboxedExpando* lir =
+ new(alloc()) LLoadUnboxedExpando(useRegisterAtStart(ins->object()));
+ define(lir, ins);
+}
+
+void
LIRGenerator::visitAssertRange(MAssertRange* ins)
{
MDefinition* input = ins->input();
diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h
index bb06baa29..de66f175b 100644
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -233,6 +233,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitFallibleStoreElement(MFallibleStoreElement* ins);
void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins);
void visitStoreUnboxedString(MStoreUnboxedString* ins);
+ void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins);
void visitEffectiveAddress(MEffectiveAddress* ins);
void visitArrayPopShift(MArrayPopShift* ins);
void visitArrayPush(MArrayPush* ins);
@@ -256,6 +257,8 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitGuardObject(MGuardObject* ins);
void visitGuardString(MGuardString* ins);
void visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins);
+ void visitGuardUnboxedExpando(MGuardUnboxedExpando* ins);
+ void visitLoadUnboxedExpando(MLoadUnboxedExpando* ins);
void visitPolyInlineGuard(MPolyInlineGuard* ins);
void visitAssertRange(MAssertRange* ins);
void visitCallGetProperty(MCallGetProperty* ins);
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index 276b5eba5..f2071dc6a 100644
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -611,7 +611,7 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
OBJECT_FLAG_LENGTH_OVERFLOW |
OBJECT_FLAG_ITERATED;
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
TemporaryTypeSet* thisTypes = obj->resultTypeSet();
if (!thisTypes)
return InliningStatus_NotInlined;
@@ -700,7 +700,7 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
MDefinition* value = callInfo.getArg(0);
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
&obj, nullptr, &value, /* canModify = */ false))
@@ -773,7 +773,7 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
// Ensure |this| and result are objects.
if (getInlineReturnType() != MIRType::Object)
@@ -2097,7 +2097,7 @@ IonBuilder::inlineDefineDataProperty(CallInfo& callInfo)
if (callInfo.argc() != 3)
return InliningStatus_NotInlined;
- MDefinition* obj = callInfo.getArg(0);
+ MDefinition* obj = convertUnboxedObjects(callInfo.getArg(0));
MDefinition* id = callInfo.getArg(1);
MDefinition* value = callInfo.getArg(2);
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp
index 403e70c02..b9e5e8d61 100644
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2630,6 +2630,40 @@ jit::EqualTypes(MIRType type1, TemporaryTypeSet* typeset1,
return typeset1->equals(typeset2);
}
+// Tests whether input/inputTypes can always be stored to an unboxed
+// object/array property with the given unboxed type.
+bool
+jit::CanStoreUnboxedType(TempAllocator& alloc,
+ JSValueType unboxedType, MIRType input, TypeSet* inputTypes)
+{
+ TemporaryTypeSet types;
+
+ switch (unboxedType) {
+ case JSVAL_TYPE_BOOLEAN:
+ case JSVAL_TYPE_INT32:
+ case JSVAL_TYPE_DOUBLE:
+ case JSVAL_TYPE_STRING:
+ types.addType(TypeSet::PrimitiveType(unboxedType), alloc.lifoAlloc());
+ break;
+
+ case JSVAL_TYPE_OBJECT:
+ types.addType(TypeSet::AnyObjectType(), alloc.lifoAlloc());
+ types.addType(TypeSet::NullType(), alloc.lifoAlloc());
+ break;
+
+ default:
+ MOZ_CRASH("Bad unboxed type");
+ }
+
+ return TypeSetIncludes(&types, input, inputTypes);
+}
+
+static bool
+CanStoreUnboxedType(TempAllocator& alloc, JSValueType unboxedType, MDefinition* value)
+{
+ return CanStoreUnboxedType(alloc, unboxedType, value->type(), value->resultTypeSet());
+}
+
bool
MPhi::specializeType(TempAllocator& alloc)
{
@@ -4776,8 +4810,35 @@ MBeta::printOpcode(GenericPrinter& out) const
bool
MCreateThisWithTemplate::canRecoverOnBailout() const
{
- MOZ_ASSERT(templateObject()->is<PlainObject>());
- MOZ_ASSERT(!templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>());
+ MOZ_ASSERT_IF(templateObject()->is<PlainObject>(),
+ !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
+ return true;
+}
+
+bool
+OperandIndexMap::init(TempAllocator& alloc, JSObject* templateObject)
+{
+ const UnboxedLayout& layout =
+ templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+
+ const UnboxedLayout::PropertyVector& properties = layout.properties();
+ MOZ_ASSERT(properties.length() < 255);
+
+ // Allocate an array of indexes, where the top of each field correspond to
+ // the index of the operand in the MObjectState instance.
+ if (!map.init(alloc, layout.size()))
+ return false;
+
+ // Reset all indexes to 0, which is an error code.
+ for (size_t i = 0; i < map.length(); i++)
+ map[i] = 0;
+
+ // Map the property offsets to the indexes of MObjectState operands.
+ uint8_t index = 1;
+ for (size_t i = 0; i < properties.length(); i++, index++)
+ map[properties[i].offset] = index;
+
return true;
}
@@ -4797,11 +4858,17 @@ MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandInd
setResultType(MIRType::Object);
setRecoveredOnBailout();
- MOZ_ASSERT(templateObject->is<NativeObject>());
-
- NativeObject* nativeObject = &templateObject->as<NativeObject>();
- numSlots_ = nativeObject->slotSpan();
- numFixedSlots_ = nativeObject->numFixedSlots();
+ if (templateObject->is<NativeObject>()) {
+ NativeObject* nativeObject = &templateObject->as<NativeObject>();
+ numSlots_ = nativeObject->slotSpan();
+ numFixedSlots_ = nativeObject->numFixedSlots();
+ } else {
+ const UnboxedLayout& layout =
+ templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+ // Same as UnboxedLayout::makeNativeGroup
+ numSlots_ = layout.properties().length();
+ numFixedSlots_ = gc::GetGCKindSlots(layout.getAllocKind());
+ }
operandIndex_ = operandIndex;
}
@@ -4838,21 +4905,39 @@ MObjectState::initFromTemplateObject(TempAllocator& alloc, MDefinition* undefine
// the template object. This is needed to account values which are baked in
// the template objects and not visible in IonMonkey, such as the
// uninitialized-lexical magic value of call objects.
- NativeObject& nativeObject = templateObject->as<NativeObject>();
- MOZ_ASSERT(nativeObject.slotSpan() == numSlots());
-
- MOZ_ASSERT(templateObject->is<NativeObject>());
- for (size_t i = 0; i < numSlots(); i++) {
- Value val = nativeObject.getSlot(i);
- MDefinition *def = undefinedVal;
- if (!val.isUndefined()) {
- MConstant* ins = val.isObject() ?
- MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
- MConstant::New(alloc, val);
- block()->insertBefore(this, ins);
- def = ins;
+ if (templateObject->is<UnboxedPlainObject>()) {
+ UnboxedPlainObject& unboxedObject = templateObject->as<UnboxedPlainObject>();
+ const UnboxedLayout& layout = unboxedObject.layoutDontCheckGeneration();
+ const UnboxedLayout::PropertyVector& properties = layout.properties();
+
+ for (size_t i = 0; i < properties.length(); i++) {
+ Value val = unboxedObject.getValue(properties[i], /* maybeUninitialized = */ true);
+ MDefinition *def = undefinedVal;
+ if (!val.isUndefined()) {
+ MConstant* ins = val.isObject() ?
+ MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
+ MConstant::New(alloc, val);
+ block()->insertBefore(this, ins);
+ def = ins;
+ }
+ initSlot(i, def);
+ }
+ } else {
+ NativeObject& nativeObject = templateObject->as<NativeObject>();
+ MOZ_ASSERT(nativeObject.slotSpan() == numSlots());
+
+ for (size_t i = 0; i < numSlots(); i++) {
+ Value val = nativeObject.getSlot(i);
+ MDefinition *def = undefinedVal;
+ if (!val.isUndefined()) {
+ MConstant* ins = val.isObject() ?
+ MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
+ MConstant::New(alloc, val);
+ block()->insertBefore(this, ins);
+ def = ins;
+ }
+ initSlot(i, def);
}
- initSlot(i, def);
}
return true;
}
@@ -4863,7 +4948,14 @@ MObjectState::New(TempAllocator& alloc, MDefinition* obj)
JSObject* templateObject = templateObjectOf(obj);
MOZ_ASSERT(templateObject, "Unexpected object creation.");
- MObjectState* res = new(alloc) MObjectState(templateObject, nullptr);
+ OperandIndexMap* operandIndex = nullptr;
+ if (templateObject->is<UnboxedPlainObject>()) {
+ operandIndex = new(alloc) OperandIndexMap;
+ if (!operandIndex || !operandIndex->init(alloc, templateObject))
+ return nullptr;
+ }
+
+ MObjectState* res = new(alloc) MObjectState(templateObject, operandIndex);
if (!res || !res->init(alloc, obj))
return nullptr;
return res;
@@ -5770,6 +5862,35 @@ MGetFirstDollarIndex::foldsTo(TempAllocator& alloc)
return MConstant::New(alloc, Int32Value(index));
}
+MConvertUnboxedObjectToNative*
+MConvertUnboxedObjectToNative::New(TempAllocator& alloc, MDefinition* obj, ObjectGroup* group)
+{
+ MConvertUnboxedObjectToNative* res = new(alloc) MConvertUnboxedObjectToNative(obj, group);
+
+ ObjectGroup* nativeGroup = group->unboxedLayout().nativeGroup();
+
+ // Make a new type set for the result of this instruction which replaces
+ // the input group with the native group we will convert it to.
+ TemporaryTypeSet* types = obj->resultTypeSet();
+ if (types && !types->unknownObject()) {
+ TemporaryTypeSet* newTypes = types->cloneWithoutObjects(alloc.lifoAlloc());
+ if (newTypes) {
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (!key)
+ continue;
+ if (key->unknownProperties() || !key->isGroup() || key->group() != group)
+ newTypes->addType(TypeSet::ObjectType(key), alloc.lifoAlloc());
+ else
+ newTypes->addType(TypeSet::ObjectType(nativeGroup), alloc.lifoAlloc());
+ }
+ res->setResultTypeSet(newTypes);
+ }
+ }
+
+ return res;
+}
+
bool
jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id)
@@ -5824,6 +5945,8 @@ jit::UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* o
elementType = layout.elementType();
else
return JSVAL_TYPE_MAGIC;
+
+ key->watchStateChangeForUnboxedConvertedToNative(constraints);
}
return elementType;
@@ -6447,6 +6570,23 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
}
}
+ // Perform additional filtering to make sure that any unboxed property
+ // being written can accommodate the value.
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (key && key->isGroup() && key->group()->maybeUnboxedLayout()) {
+ const UnboxedLayout& layout = key->group()->unboxedLayout();
+ if (name) {
+ const UnboxedLayout::Property* property = layout.lookup(name);
+ if (property && !CanStoreUnboxedType(alloc, property->type, *pvalue))
+ return true;
+ } else {
+ if (layout.isArray() && !CanStoreUnboxedType(alloc, layout.elementType(), *pvalue))
+ return true;
+ }
+ }
+ }
+
if (success)
return false;
@@ -6477,6 +6617,17 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
MOZ_ASSERT(excluded);
+ // If the excluded object is a group with an unboxed layout, make sure it
+ // does not have a corresponding native group. Objects with the native
+ // group might appear even though they are not in the type set.
+ if (excluded->isGroup()) {
+ if (UnboxedLayout* layout = excluded->group()->maybeUnboxedLayout()) {
+ if (layout->nativeGroup())
+ return true;
+ excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
+ }
+ }
+
*pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
return false;
}
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
index ebc98a4f8..af0abc695 100644
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -30,6 +30,7 @@
#include "vm/EnvironmentObject.h"
#include "vm/SharedMem.h"
#include "vm/TypedArrayCommon.h"
+#include "vm/UnboxedObject.h"
// Undo windows.h damage on Win64
#undef MemoryBarrier
@@ -9749,6 +9750,59 @@ class MStoreUnboxedString
ALLOW_CLONE(MStoreUnboxedString)
};
+// Passes through an object, after ensuring it is converted from an unboxed
+// object to a native representation.
+class MConvertUnboxedObjectToNative
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerObjectGroup group_;
+
+ explicit MConvertUnboxedObjectToNative(MDefinition* obj, ObjectGroup* group)
+ : MUnaryInstruction(obj),
+ group_(group)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ConvertUnboxedObjectToNative)
+ NAMED_OPERANDS((0, object))
+
+ static MConvertUnboxedObjectToNative* New(TempAllocator& alloc, MDefinition* obj,
+ ObjectGroup* group);
+
+ ObjectGroup* group() const {
+ return group_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ return ins->toConvertUnboxedObjectToNative()->group() == group();
+ }
+ AliasSet getAliasSet() const override {
+ // This instruction can read and write to all parts of the object, but
+ // is marked as non-effectful so it can be consolidated by LICM and GVN
+ // and avoid inhibiting other optimizations.
+ //
+ // This is valid to do because when unboxed objects might have a native
+ // group they can be converted to, we do not optimize accesses to the
+ // unboxed objects and do not guard on their group or shape (other than
+ // in this opcode).
+ //
+ // Later accesses can assume the object has a native representation
+ // and optimize accordingly. Those accesses cannot be reordered before
+ // this instruction, however. This is prevented by chaining this
+ // instruction with the object itself, in the same way as MBoundsCheck.
+ return AliasSet::None();
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(group_);
+ }
+};
+
// Array.prototype.pop or Array.prototype.shift on a dense array.
class MArrayPopShift
: public MUnaryInstruction,
@@ -11128,6 +11182,11 @@ class MGuardShape
setMovable();
setResultType(MIRType::Object);
setResultTypeSet(obj->resultTypeSet());
+
+ // Disallow guarding on unboxed object shapes. The group is better to
+ // guard on, and guarding on the shape can interact badly with
+ // MConvertUnboxedObjectToNative.
+ MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_);
}
public:
@@ -11222,6 +11281,11 @@ class MGuardObjectGroup
setGuard();
setMovable();
setResultType(MIRType::Object);
+
+ // Unboxed groups which might be converted to natives can't be guarded
+ // on, due to MConvertUnboxedObjectToNative.
+ MOZ_ASSERT_IF(group->maybeUnboxedLayoutDontCheckGeneration(),
+ !group->unboxedLayoutDontCheckGeneration().nativeGroup());
}
public:
@@ -11330,6 +11394,73 @@ class MGuardClass
ALLOW_CLONE(MGuardClass)
};
+// Guard on the presence or absence of an unboxed object's expando.
+class MGuardUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool requireExpando_;
+ BailoutKind bailoutKind_;
+
+ MGuardUnboxedExpando(MDefinition* obj, bool requireExpando, BailoutKind bailoutKind)
+ : MUnaryInstruction(obj),
+ requireExpando_(requireExpando),
+ bailoutKind_(bailoutKind)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool requireExpando() const {
+ return requireExpando_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ if (requireExpando() != ins->toGuardUnboxedExpando()->requireExpando())
+ return false;
+ return true;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Load an unboxed plain object's expando.
+class MLoadUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ private:
+ explicit MLoadUnboxedExpando(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
// Load from vp[slot] (slots that are not inline in an object).
class MLoadSlot
: public MUnaryInstruction,
diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h
index f5f59dee6..2f67f8039 100644
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -188,6 +188,8 @@ namespace jit {
_(GuardObjectGroup) \
_(GuardObjectIdentity) \
_(GuardClass) \
+ _(GuardUnboxedExpando) \
+ _(LoadUnboxedExpando) \
_(ArrayLength) \
_(SetArrayLength) \
_(GetNextEntryForIterator) \
@@ -218,6 +220,7 @@ namespace jit {
_(StoreUnboxedScalar) \
_(StoreUnboxedObjectOrNull) \
_(StoreUnboxedString) \
+ _(ConvertUnboxedObjectToNative) \
_(ArrayPopShift) \
_(ArrayPush) \
_(ArraySlice) \
diff --git a/js/src/jit/OptimizationTracking.cpp b/js/src/jit/OptimizationTracking.cpp
index b42634d43..308def041 100644
--- a/js/src/jit/OptimizationTracking.cpp
+++ b/js/src/jit/OptimizationTracking.cpp
@@ -15,11 +15,9 @@
#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;
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
index 2065c0371..4614b2162 100644
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -13,6 +13,7 @@
#include "jit/MIR.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
@@ -182,6 +183,25 @@ IsObjectEscaped(MInstruction* ins, JSObject* objDefault)
JitSpewDef(JitSpew_Escape, "is escaped by\n", def);
return true;
+ case MDefinition::Op_LoadUnboxedScalar:
+ case MDefinition::Op_StoreUnboxedScalar:
+ case MDefinition::Op_LoadUnboxedObjectOrNull:
+ case MDefinition::Op_StoreUnboxedObjectOrNull:
+ case MDefinition::Op_LoadUnboxedString:
+ case MDefinition::Op_StoreUnboxedString:
+ // Not escaped if it is the first argument.
+ if (def->indexOf(*i) != 0) {
+ JitSpewDef(JitSpew_Escape, "is escaped by\n", def);
+ return true;
+ }
+
+ if (!def->getOperand(1)->isConstant()) {
+ JitSpewDef(JitSpew_Escape, "is addressed with unknown index\n", def);
+ return true;
+ }
+
+ break;
+
case MDefinition::Op_PostWriteBarrier:
break;
@@ -285,6 +305,12 @@ class ObjectMemoryView : public MDefinitionVisitorDefaultNoop
void visitGuardShape(MGuardShape* ins);
void visitFunctionEnvironment(MFunctionEnvironment* ins);
void visitLambda(MLambda* ins);
+ void visitStoreUnboxedScalar(MStoreUnboxedScalar* ins);
+ void visitLoadUnboxedScalar(MLoadUnboxedScalar* ins);
+ void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins);
+ void visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins);
+ void visitStoreUnboxedString(MStoreUnboxedString* ins);
+ void visitLoadUnboxedString(MLoadUnboxedString* ins);
private:
void storeOffset(MInstruction* ins, size_t offset, MDefinition* value);
@@ -630,6 +656,21 @@ ObjectMemoryView::visitLambda(MLambda* ins)
ins->setIncompleteObject();
}
+static size_t
+GetOffsetOf(MDefinition* index, size_t width, int32_t baseOffset)
+{
+ int32_t idx = index->toConstant()->toInt32();
+ MOZ_ASSERT(idx >= 0);
+ MOZ_ASSERT(baseOffset >= 0 && size_t(baseOffset) >= UnboxedPlainObject::offsetOfData());
+ return idx * width + baseOffset - UnboxedPlainObject::offsetOfData();
+}
+
+static size_t
+GetOffsetOf(MDefinition* index, Scalar::Type type, int32_t baseOffset)
+{
+ return GetOffsetOf(index, Scalar::byteSize(type), baseOffset);
+}
+
void
ObjectMemoryView::storeOffset(MInstruction* ins, size_t offset, MDefinition* value)
{
@@ -659,6 +700,77 @@ ObjectMemoryView::loadOffset(MInstruction* ins, size_t offset)
ins->block()->discard(ins);
}
+void
+ObjectMemoryView::visitStoreUnboxedScalar(MStoreUnboxedScalar* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ size_t offset = GetOffsetOf(ins->index(), ins->storageType(), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedScalar(MLoadUnboxedScalar* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), ins->storageType(), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
+void
+ObjectMemoryView::visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Clone the state and update the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
+void
+ObjectMemoryView::visitStoreUnboxedString(MStoreUnboxedString* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Clone the state and update the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedString(MLoadUnboxedString* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
static bool
IndexOf(MDefinition* ins, int32_t* res)
{
diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp
index 2475dfb22..767cff661 100644
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -27,7 +27,6 @@
#endif
#include "jit/VMFunctions.h"
#include "vm/Interpreter.h"
-#include "vm/NativeObject-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/Interpreter-inl.h"
diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h
index 49879eedb..e6aab6ba3 100644
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -5892,6 +5892,22 @@ class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0>
}
};
+// If necessary, convert an unboxed object in a particular group to its native
+// representation.
+class LConvertUnboxedObjectToNative : public LInstructionHelper<0, 1, 0>
+{
+ public:
+ LIR_HEADER(ConvertUnboxedObjectToNative)
+
+ explicit LConvertUnboxedObjectToNative(const LAllocation& object) {
+ setOperand(0, object);
+ }
+
+ MConvertUnboxedObjectToNative* mir() {
+ return mir_->toConvertUnboxedObjectToNative();
+ }
+};
+
class LArrayPopShiftV : public LInstructionHelper<BOX_PIECES, 1, 2>
{
public:
@@ -7414,6 +7430,38 @@ class LGuardReceiverPolymorphic : public LInstructionHelper<0, 1, 1>
}
};
+class LGuardUnboxedExpando : public LInstructionHelper<0, 1, 0>
+{
+ public:
+ LIR_HEADER(GuardUnboxedExpando)
+
+ explicit LGuardUnboxedExpando(const LAllocation& in) {
+ setOperand(0, in);
+ }
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+ const MGuardUnboxedExpando* mir() const {
+ return mir_->toGuardUnboxedExpando();
+ }
+};
+
+class LLoadUnboxedExpando : public LInstructionHelper<1, 1, 0>
+{
+ public:
+ LIR_HEADER(LoadUnboxedExpando)
+
+ explicit LLoadUnboxedExpando(const LAllocation& in) {
+ setOperand(0, in);
+ }
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+ const MLoadUnboxedExpando* mir() const {
+ return mir_->toLoadUnboxedExpando();
+ }
+};
+
// Guard that a value is in a TypeSet.
class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 1>
{
diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h
index 505c0ea03..ea185e1b8 100644
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -257,6 +257,8 @@
_(GuardObjectGroup) \
_(GuardObjectIdentity) \
_(GuardClass) \
+ _(GuardUnboxedExpando) \
+ _(LoadUnboxedExpando) \
_(TypeBarrierV) \
_(TypeBarrierO) \
_(MonitorTypes) \
@@ -284,6 +286,7 @@
_(StoreElementT) \
_(StoreUnboxedScalar) \
_(StoreUnboxedPointer) \
+ _(ConvertUnboxedObjectToNative) \
_(ArrayPopShiftV) \
_(ArrayPopShiftT) \
_(ArrayPushV) \
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 6483641f3..cb3945152 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6588,6 +6588,9 @@ JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t v
}
jit::JitOptions.jumpThreshold = value;
break;
+ case JSJITCOMPILER_UNBOXED_OBJECTS:
+ jit::JitOptions.disableUnboxedObjects = !value;
+ break;
case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE:
jit::JitOptions.asmJSAtomicsEnable = !!value;
break;
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 0e2eeebaa..9138a4a92 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5896,19 +5896,20 @@ JS_SetParallelParsingEnabled(JSContext* cx, bool enabled);
extern JS_PUBLIC_API(void)
JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled);
-#define JIT_COMPILER_OPTIONS(Register) \
- Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger") \
- Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger") \
- Register(ION_GVN_ENABLE, "ion.gvn.enable") \
- Register(ION_FORCE_IC, "ion.forceinlineCaches") \
- Register(ION_ENABLE, "ion.enable") \
+#define JIT_COMPILER_OPTIONS(Register) \
+ Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger") \
+ Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger") \
+ Register(ION_GVN_ENABLE, "ion.gvn.enable") \
+ Register(ION_FORCE_IC, "ion.forceinlineCaches") \
+ Register(ION_ENABLE, "ion.enable") \
Register(ION_INTERRUPT_WITHOUT_SIGNAL, "ion.interrupt-without-signals") \
- Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis") \
- Register(BASELINE_ENABLE, "baseline.enable") \
- Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
- Register(JUMP_THRESHOLD, "jump-threshold") \
- Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \
- Register(WASM_TEST_MODE, "wasm.test-mode") \
+ Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis") \
+ Register(BASELINE_ENABLE, "baseline.enable") \
+ Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
+ Register(JUMP_THRESHOLD, "jump-threshold") \
+ Register(UNBOXED_OBJECTS, "unboxed_objects") \
+ Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \
+ Register(WASM_TEST_MODE, "wasm.test-mode") \
Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets")
typedef enum JSJitCompilerOption {
diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp
index 3ad526f74..64573b55a 100644
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6176,6 +6176,12 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
for (auto group = source->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
group->setGeneration(target->zone()->types.generation);
group->compartment_ = target;
+
+ // Remove any unboxed layouts from the list in the off thread
+ // compartment. These do not need to be reinserted in the target
+ // compartment's list, as the list is not required to be complete.
+ if (UnboxedLayout* layout = group->maybeUnboxedLayoutDontCheckGeneration())
+ layout->detachFromCompartment();
}
// Fixup zone pointers in source's zone to refer to target's zone.
diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp
index c58f32382..8b1436b68 100644
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -158,11 +158,8 @@ SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp)
}
static bool
-EnumerateNativeProperties(JSContext* cx,
- HandleNativeObject pobj,
- unsigned flags,
- Maybe<IdSet>& ht,
- AutoIdVector* props)
+EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
+ AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
{
bool enumerateSymbols;
if (flags & JSITER_SYMBOLSONLY) {
@@ -224,6 +221,16 @@ EnumerateNativeProperties(JSContext* cx,
return false;
}
+ if (unboxed) {
+ // If |unboxed| is set then |pobj| is the expando for an unboxed
+ // plain object we are enumerating. Add the unboxed properties
+ // themselves here since they are all property names that were
+ // given to the object before any of the expando's properties.
+ MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
+ if (!EnumerateExtraProperties(cx, unboxed, flags, ht, props))
+ return false;
+ }
+
size_t initialLength = props->length();
/* Collect all unique property names from this object's shape. */
@@ -349,12 +356,22 @@ Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
do {
if (pobj->getOpsEnumerate()) {
- if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
- return false;
-
- if (pobj->isNative()) {
- if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
+ if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
+ // Special case unboxed objects with an expando object.
+ RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
+ if (!EnumerateNativeProperties(cx, expando, flags, ht, props,
+ pobj.as<UnboxedPlainObject>()))
+ {
+ return false;
+ }
+ } else {
+ if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
return false;
+
+ if (pobj->isNative()) {
+ if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
+ return false;
+ }
}
} else if (pobj->isNative()) {
// Give the object a chance to resolve all lazy properties
@@ -769,6 +786,11 @@ CanCompareIterableObjectToCache(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().hasEmptyElements();
+ if (obj->is<UnboxedPlainObject>()) {
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ return expando->hasEmptyElements();
+ return true;
+ }
return false;
}
diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp
index 901a9f505..d4379bd7d 100644
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -53,7 +53,6 @@
#include "vm/RegExpStaticsObject.h"
#include "vm/Shape.h"
#include "vm/TypedArrayCommon.h"
-#include "vm/UnboxedObject-inl.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
@@ -869,6 +868,9 @@ static inline JSObject*
CreateThisForFunctionWithGroup(JSContext* cx, HandleObjectGroup group,
NewObjectKind newKind)
{
+ if (group->maybeUnboxedLayout() && newKind != SingletonObject)
+ return UnboxedPlainObject::create(cx, group, newKind);
+
if (TypeNewScript* newScript = group->newScript()) {
if (newScript->analyzed()) {
// The definite properties analysis has been performed for this
@@ -1157,27 +1159,46 @@ static bool
GetScriptPlainObjectProperties(JSContext* cx, HandleObject obj,
MutableHandle<IdValueVector> properties)
{
- MOZ_ASSERT(obj->is<PlainObject>());
- PlainObject* nobj = &obj->as<PlainObject>();
+ if (obj->is<PlainObject>()) {
+ PlainObject* nobj = &obj->as<PlainObject>();
- if (!properties.appendN(IdValuePair(), nobj->slotSpan()))
- return false;
+ if (!properties.appendN(IdValuePair(), nobj->slotSpan()))
+ return false;
- for (Shape::Range<NoGC> r(nobj->lastProperty()); !r.empty(); r.popFront()) {
- Shape& shape = r.front();
- MOZ_ASSERT(shape.isDataDescriptor());
- uint32_t slot = shape.slot();
- properties[slot].get().id = shape.propid();
- properties[slot].get().value = nobj->getSlot(slot);
+ for (Shape::Range<NoGC> r(nobj->lastProperty()); !r.empty(); r.popFront()) {
+ Shape& shape = r.front();
+ MOZ_ASSERT(shape.isDataDescriptor());
+ uint32_t slot = shape.slot();
+ properties[slot].get().id = shape.propid();
+ properties[slot].get().value = nobj->getSlot(slot);
+ }
+
+ for (size_t i = 0; i < nobj->getDenseInitializedLength(); i++) {
+ Value v = nobj->getDenseElement(i);
+ if (!v.isMagic(JS_ELEMENTS_HOLE) && !properties.append(IdValuePair(INT_TO_JSID(i), v)))
+ return false;
+ }
+
+ return true;
}
- for (size_t i = 0; i < nobj->getDenseInitializedLength(); i++) {
- Value v = nobj->getDenseElement(i);
- if (!v.isMagic(JS_ELEMENTS_HOLE) && !properties.append(IdValuePair(INT_TO_JSID(i), v)))
+ if (obj->is<UnboxedPlainObject>()) {
+ UnboxedPlainObject* nobj = &obj->as<UnboxedPlainObject>();
+
+ const UnboxedLayout& layout = nobj->layout();
+ if (!properties.appendN(IdValuePair(), layout.properties().length()))
return false;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ properties[i].get().id = NameToId(property.name);
+ properties[i].get().value = nobj->getValue(property);
+ }
+
+ return true;
}
- return true;
+ MOZ_CRASH("Bad object kind");
}
static bool
@@ -1199,9 +1220,8 @@ js::DeepCloneObjectLiteral(JSContext* cx, HandleObject obj, NewObjectKind newKin
/* NB: Keep this in sync with XDRObjectLiteral. */
MOZ_ASSERT_IF(obj->isSingleton(),
cx->compartment()->behaviors().getSingletonsAsTemplates());
- MOZ_ASSERT(obj->is<PlainObject>() ||
- obj->is<ArrayObject>() ||
- obj->is<UnboxedArrayObject>());
+ MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() ||
+ obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
MOZ_ASSERT(newKind != SingletonObject);
if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
@@ -1320,6 +1340,7 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
{
if (mode == XDR_ENCODE) {
MOZ_ASSERT(obj->is<PlainObject>() ||
+ obj->is<UnboxedPlainObject>() ||
obj->is<ArrayObject>() ||
obj->is<UnboxedArrayObject>());
isArray = (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) ? 1 : 0;
@@ -2315,6 +2336,11 @@ js::LookupOwnPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Shape**
// us the resolve hook won't define a property with this id.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
return false;
+ } else if (obj->is<UnboxedPlainObject>()) {
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ MarkNonNativePropertyFound<NoGC>(propp);
+ return true;
+ }
} else if (obj->is<UnboxedArrayObject>()) {
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
MarkNonNativePropertyFound<NoGC>(propp);
@@ -2576,6 +2602,11 @@ js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto, JS::Object
break;
}
+ // Convert unboxed objects to their native representations before changing
+ // their prototype/group, as they depend on the group for their layout.
+ if (!MaybeConvertUnboxedObjectToNative(cx, obj))
+ return false;
+
Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto));
if (!SetClassAndProto(cx, obj, obj->getClass(), taggedProto))
return false;
@@ -2599,6 +2630,9 @@ js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result, I
if (!obj->nonProxyIsExtensible())
return result.succeed();
+ if (!MaybeConvertUnboxedObjectToNative(cx, obj))
+ return false;
+
// Force lazy properties to be resolved.
AutoIdVector props(cx);
if (!js::GetPropertyKeys(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
@@ -3611,6 +3645,12 @@ JSObject::allocKindForTenure(const js::Nursery& nursery) const
if (IsProxy(this))
return as<ProxyObject>().allocKindForTenure();
+ // Unboxed plain objects are sized according to the data they store.
+ if (is<UnboxedPlainObject>()) {
+ size_t nbytes = as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
+ return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
+ }
+
// Unboxed arrays use inline data if their size is small enough.
if (is<UnboxedArrayObject>()) {
const UnboxedArrayObject* nobj = &as<UnboxedArrayObject>();
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
index 35a527c5e..8cd821b31 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7279,6 +7279,9 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
if (op.getBoolOption("wasm-check-bce"))
jit::JitOptions.wasmAlwaysCheckBounds = true;
+ if (op.getBoolOption("no-unboxed-objects"))
+ jit::JitOptions.disableUnboxedObjects = true;
+
if (const char* str = op.getStringOption("cache-ir-stubs")) {
if (strcmp(str, "on") == 0)
jit::JitOptions.disableCacheIR = false;
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
index adefa6e93..0e81dfef4 100644
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -22,6 +22,7 @@
#include "vm/EnvironmentObject-inl.h"
#include "vm/Stack-inl.h"
#include "vm/String-inl.h"
+#include "vm/UnboxedObject-inl.h"
namespace js {
@@ -336,10 +337,14 @@ InitGlobalLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvAr
inline bool
InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
{
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
- unsigned propAttrs = GetInitDataPropAttrs(op);
- return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs,
- nullptr, nullptr, propAttrs);
+ if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
+ unsigned propAttrs = GetInitDataPropAttrs(op);
+ return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
+ propAttrs);
+ }
+
+ MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
+ return PutProperty(cx, obj, id, rhs, false);
}
inline bool
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 834084c4d..274392335 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -4157,7 +4157,7 @@ CASE(JSOP_INITHOMEOBJECT)
/* Load the home object */
ReservedRooted<JSObject*> obj(&rootObject0);
obj = &REGS.sp[int(-2 - skipOver)].toObject();
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
+ MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() || obj->is<JSFunction>());
func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj));
}
@@ -4973,10 +4973,15 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
return nullptr;
if (group->maybePreliminaryObjects()) {
group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout())
+ group->maybeUnboxedLayout()->setAllocationSite(script, pc);
}
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedPlainObject::create(cx, group, newKind);
}
RootedObject obj(cx);
diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp
index 11c2d0727..97df908c3 100644
--- a/js/src/vm/ReceiverGuard.cpp
+++ b/js/src/vm/ReceiverGuard.cpp
@@ -7,6 +7,7 @@
#include "vm/ReceiverGuard.h"
#include "builtin/TypedObject.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
using namespace js;
diff --git a/js/src/vm/ReceiverGuard.h b/js/src/vm/ReceiverGuard.h
index c14f0d83b..459cc0012 100644
--- a/js/src/vm/ReceiverGuard.h
+++ b/js/src/vm/ReceiverGuard.h
@@ -28,6 +28,11 @@ namespace js {
// TypedObject: The structure of a typed object is determined by its group.
// All typed objects with the same group have the same class, prototype, and
// own properties.
+//
+// UnboxedPlainObject: The structure of an unboxed plain object is determined
+// by its group and its expando object's shape, if there is one. All unboxed
+// plain objects with the same group and expando shape have the same
+// properties except those stored in the expando's dense elements.
class HeapReceiverGuard;
class RootedReceiverGuard;
diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp
index ee5bbef5d..438188a36 100644
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1999,6 +1999,17 @@ TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* co
ConstraintDataFreezeObjectForTypedArrayData(tarray)));
}
+void
+TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints)
+{
+ HeapTypeSetKey objectProperty = property(JSID_EMPTY);
+ LifoAlloc* alloc = constraints->alloc();
+
+ typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForUnboxedConvertedToNative> T;
+ constraints->add(alloc->new_<T>(alloc, objectProperty,
+ ConstraintDataFreezeObjectForUnboxedConvertedToNative()));
+}
+
static void
ObjectStateChange(ExclusiveContext* cxArg, ObjectGroup* group, bool markingUnknown)
{
@@ -3573,6 +3584,7 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro
}
}
+ TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects);
if (group->maybeUnboxedLayout())
return;
@@ -3901,6 +3913,10 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
PodCopy(initializerList, initializerVector.begin(), initializerVector.length());
}
+ // Try to use an unboxed representation for the group.
+ if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects))
+ return false;
+
js_delete(preliminaryObjects);
preliminaryObjects = nullptr;
diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h
index 537baa21f..9ec1bf927 100644
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -262,6 +262,7 @@ class TypeSet
bool hasStableClassAndProto(CompilerConstraintList* constraints);
void watchStateChangeForInlinedCall(CompilerConstraintList* constraints);
void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints);
+ void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints);
HeapTypeSetKey property(jsid id);
void ensureTrackedProperty(JSContext* cx, jsid id);
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp
index 059293a2d..d8c9c774a 100644
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -1635,12 +1635,227 @@ const Class UnboxedArrayObject::class_ = {
// API
/////////////////////////////////////////////////////////////////////
+static bool
+UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
+{
+ if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32)
+ return true;
+ if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL)
+ return true;
+ return false;
+}
+
+static bool
+CombineUnboxedTypes(const Value& value, JSValueType* existing)
+{
+ JSValueType type = value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType();
+
+ if (*existing == JSVAL_TYPE_MAGIC || *existing == type || UnboxedTypeIncludes(type, *existing)) {
+ *existing = type;
+ return true;
+ }
+ if (UnboxedTypeIncludes(*existing, type))
+ return true;
+ return false;
+}
+
+// Return whether the property names and types in layout are a subset of the
+// specified vector.
+static bool
+PropertiesAreSuperset(const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout)
+{
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& layoutProperty = layout->properties()[i];
+ bool found = false;
+ for (size_t j = 0; j < properties.length(); j++) {
+ if (layoutProperty.name == properties[j].name) {
+ found = (layoutProperty.type == properties[j].type);
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+static bool
+CombinePlainObjectProperties(PlainObject* obj, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // All preliminary objects must have been created with enough space to
+ // fill in their unboxed data inline. This is ensured either by using
+ // the largest allocation kind (which limits the maximum size of an
+ // unboxed object), or by using an allocation kind that covers all
+ // properties in the template, as the space used by unboxed properties
+ // is less than or equal to that used by boxed properties.
+ MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
+ Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
+
+ if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) {
+ // Only use an unboxed representation if all created objects match
+ // the template shape exactly.
+ return false;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ Value val = obj->getSlot(i);
+
+ JSValueType& existing = properties[i].type;
+ if (!CombineUnboxedTypes(val, &existing))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+CombineArrayObjectElements(ExclusiveContext* cx, ArrayObject* obj, JSValueType* elementType)
+{
+ if (obj->inDictionaryMode() ||
+ obj->lastProperty()->propid() != AtomToId(cx->names().length) ||
+ !obj->lastProperty()->previous()->isEmptyShape())
+ {
+ // Only use an unboxed representation if the object has no properties.
+ return false;
+ }
+
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ Value val = obj->getDenseElement(i);
+
+ // For now, unboxed arrays cannot have holes.
+ if (val.isMagic(JS_ELEMENTS_HOLE))
+ return false;
+
+ if (!CombineUnboxedTypes(val, elementType))
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+ComputePlainObjectLayout(ExclusiveContext* cx, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // Fill in the names for all the object's properties.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ size_t slot = r.front().slot();
+ MOZ_ASSERT(!properties[slot].name);
+ properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
+ }
+
+ // Fill in all the unboxed object's property offsets.
+ uint32_t offset = 0;
+
+ // Search for an existing unboxed layout which is a subset of this one.
+ // If there are multiple such layouts, use the largest one. If we're able
+ // to find such a layout, use the same property offsets for the shared
+ // properties, which will allow us to generate better code if the objects
+ // have a subtype/supertype relation and are accessed at common sites.
+ UnboxedLayout* bestExisting = nullptr;
+ for (UnboxedLayout* existing : cx->compartment()->unboxedLayouts) {
+ if (PropertiesAreSuperset(properties, existing)) {
+ if (!bestExisting ||
+ existing->properties().length() > bestExisting->properties().length())
+ {
+ bestExisting = existing;
+ }
+ }
+ }
+ if (bestExisting) {
+ for (size_t i = 0; i < bestExisting->properties().length(); i++) {
+ const UnboxedLayout::Property& existingProperty = bestExisting->properties()[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (existingProperty.name == properties[j].name) {
+ MOZ_ASSERT(existingProperty.type == properties[j].type);
+ properties[j].offset = existingProperty.offset;
+ }
+ }
+ }
+ offset = bestExisting->size();
+ }
+
+ // Order remaining properties from the largest down for the best space
+ // utilization.
+ static const size_t typeSizes[] = { 8, 4, 1 };
+
+ for (size_t i = 0; i < ArrayLength(typeSizes); i++) {
+ size_t size = typeSizes[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (properties[j].offset != UINT32_MAX)
+ continue;
+ JSValueType type = properties[j].type;
+ if (UnboxedTypeSize(type) == size) {
+ offset = JS_ROUNDUP(offset, size);
+ properties[j].offset = offset;
+ offset += size;
+ }
+ }
+ }
+
+ // The final offset is the amount of data needed by the object.
+ return offset;
+}
+
+static bool
+SetLayoutTraceList(ExclusiveContext* cx, UnboxedLayout* layout)
+{
+ // Figure out the offsets of any objects or string properties.
+ Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout->properties()[i];
+ MOZ_ASSERT(property.offset != UINT32_MAX);
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ if (!objectOffsets.append(property.offset))
+ return false;
+ } else if (property.type == JSVAL_TYPE_STRING) {
+ if (!stringOffsets.append(property.offset))
+ return false;
+ }
+ }
+
+ // Construct the layout's trace list.
+ if (!objectOffsets.empty() || !stringOffsets.empty()) {
+ Vector<int32_t, 8, SystemAllocPolicy> entries;
+ if (!entries.appendAll(stringOffsets) ||
+ !entries.append(-1) ||
+ !entries.appendAll(objectOffsets) ||
+ !entries.append(-1) ||
+ !entries.append(-1))
+ {
+ return false;
+ }
+ int32_t* traceList = cx->zone()->pod_malloc<int32_t>(entries.length());
+ if (!traceList)
+ return false;
+ PodCopy(traceList, entries.begin(), entries.length());
+ layout->setTraceList(traceList);
+ }
+
+ return true;
+}
+
static inline Value
NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
{
return values[(*valueCursor)++];
}
+static bool
+GetValuesFromPreliminaryArrayObject(ArrayObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ if (!values.append(Int32Value(obj->length())))
+ return false;
+ if (!values.append(Int32Value(obj->getDenseInitializedLength())))
+ return false;
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ if (!values.append(obj->getDenseElement(i)))
+ return false;
+ }
+ return true;
+}
+
void
UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
Handle<GCVector<Value>> values, size_t* valueCursor)
@@ -1666,6 +1881,16 @@ UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor)));
}
+static bool
+GetValuesFromPreliminaryPlainObject(PlainObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < obj->slotSpan(); i++) {
+ if (!values.append(obj->getSlot(i)))
+ return false;
+ }
+ return true;
+}
+
void
UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
Handle<GCVector<Value>> values, size_t* valueCursor)
@@ -1676,6 +1901,181 @@ UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
}
+bool
+js::TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects)
+{
+ bool isArray = !templateShape;
+
+ // Unboxed arrays are nightly only for now. The getenv() call will be
+ // removed when they are on by default. See bug 1153266.
+ if (isArray) {
+#ifdef NIGHTLY_BUILD
+ if (!getenv("JS_OPTION_USE_UNBOXED_ARRAYS")) {
+ if (!cx->options().unboxedArrays())
+ return true;
+ }
+#else
+ return true;
+#endif
+ } else {
+ if (jit::JitOptions.disableUnboxedObjects)
+ return true;
+ }
+
+ MOZ_ASSERT_IF(templateShape, !templateShape->getObjectFlags());
+
+ if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global()))
+ return true;
+
+ if (!isArray && templateShape->slotSpan() == 0)
+ return true;
+
+ UnboxedLayout::PropertyVector properties;
+ if (!isArray) {
+ if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
+ return false;
+ }
+ JSValueType elementType = JSVAL_TYPE_MAGIC;
+
+ size_t objectCount = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (obj->isSingleton() || obj->group() != group)
+ return true;
+
+ objectCount++;
+
+ if (isArray) {
+ if (!CombineArrayObjectElements(cx, &obj->as<ArrayObject>(), &elementType))
+ return true;
+ } else {
+ if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), templateShape, properties))
+ return true;
+ }
+ }
+
+ size_t layoutSize = 0;
+ if (isArray) {
+ // Don't use an unboxed representation if we couldn't determine an
+ // element type for the objects.
+ if (UnboxedTypeSize(elementType) == 0)
+ return true;
+ } else {
+ if (objectCount <= 1) {
+ // If only one of the objects has been created, it is more likely
+ // to have new properties added later. This heuristic is not used
+ // for array objects, where we might want an unboxed representation
+ // even if there is only one large array.
+ return true;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ // We can't use an unboxed representation if e.g. all the objects have
+ // a null value for one of the properties, as we can't decide what type
+ // it is supposed to have.
+ if (UnboxedTypeSize(properties[i].type) == 0)
+ return true;
+ }
+
+ // Make sure that all properties on the template shape are property
+ // names, and not indexes.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ jsid id = r.front().propid();
+ uint32_t dummy;
+ if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
+ return true;
+ }
+
+ layoutSize = ComputePlainObjectLayout(cx, templateShape, properties);
+
+ // The entire object must be allocatable inline.
+ if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE)
+ return true;
+ }
+
+ UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
+ MOZ_ASSERT(!layout);
+ layout = group->zone()->make_unique<UnboxedLayout>();
+ if (!layout)
+ return false;
+
+ if (isArray) {
+ layout->initArray(elementType);
+ } else {
+ if (!layout->initProperties(properties, layoutSize))
+ return false;
+
+ // The unboxedLayouts list only tracks layouts for plain objects.
+ cx->compartment()->unboxedLayouts.insertFront(layout.get());
+
+ if (!SetLayoutTraceList(cx, layout.get()))
+ return false;
+ }
+
+ // We've determined that all the preliminary objects can use the new layout
+ // just constructed, so convert the existing group to use the unboxed class,
+ // and update the preliminary objects to use the new layout. Do the
+ // fallible stuff first before modifying any objects.
+
+ // Get an empty shape which we can use for the preliminary objects.
+ const Class* clasp = isArray ? &UnboxedArrayObject::class_ : &UnboxedPlainObject::class_;
+ Shape* newShape = EmptyShape::getInitialShape(cx, clasp, group->proto(), 0);
+ if (!newShape) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+
+ // Accumulate a list of all the values in each preliminary object, and
+ // update their shapes.
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ bool ok;
+ if (isArray)
+ ok = GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), &values);
+ else
+ ok = GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), &values);
+
+ if (!ok) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+ }
+
+ if (TypeNewScript* newScript = group->newScript())
+ layout->setNewScript(newScript);
+
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ if (JSObject* obj = objects->get(i))
+ obj->as<NativeObject>().setLastPropertyMakeNonNative(newShape);
+ }
+
+ group->setClasp(clasp);
+ group->setUnboxedLayout(layout.release());
+
+ size_t valueCursor = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (isArray)
+ obj->as<UnboxedArrayObject>().fillAfterConvert(cx, values, &valueCursor);
+ else
+ obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
+ }
+
+ MOZ_ASSERT(valueCursor == values.length());
+ return true;
+}
+
DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements,
ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t,
ShouldUpdateTypes);
diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h
index 779dd14c7..ecff8be5b 100644
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -317,6 +317,13 @@ class UnboxedPlainObject : public JSObject
}
};
+// Try to construct an UnboxedLayout for each of the preliminary objects,
+// provided they all match the template shape. If successful, converts the
+// preliminary objects and their group to the new unboxed representation.
+bool
+TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects);
+
inline gc::AllocKind
UnboxedLayout::getAllocKind() const
{
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index 0243d80e3..bde949a96 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1427,6 +1427,8 @@ ReloadPrefsCallback(const char* pref, void* data)
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
+ bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
+
sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
#ifdef DEBUG
@@ -1455,6 +1457,8 @@ ReloadPrefsCallback(const char* pref, void* data)
useBaselineEager ? 0 : -1);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
useIonEager ? 0 : -1);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_UNBOXED_OBJECTS,
+ unboxedObjects);
}
XPCJSContext::~XPCJSContext()
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 6aa0111ef..a2b00391f 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1260,6 +1260,7 @@ pref("javascript.options.strict", false);
#ifdef DEBUG
pref("javascript.options.strict.debug", false);
#endif
+pref("javascript.options.unboxed_objects", false);
pref("javascript.options.baselinejit", true);
pref("javascript.options.ion", true);
pref("javascript.options.asmjs", true);