summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js49
-rw-r--r--js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js47
-rw-r--r--js/src/jit-test/tests/basic/unboxed-object-getelem.js20
-rw-r--r--js/src/jit-test/tests/basic/unboxed-object-set-property.js31
-rw-r--r--js/src/jit-test/tests/basic/unboxed-property-enumeration.js24
-rw-r--r--js/src/jit-test/tests/ion/unboxed-objects-invalidate.js16
-rw-r--r--js/src/jit/BaselineCacheIR.cpp52
-rw-r--r--js/src/jit/BaselineIC.cpp269
-rw-r--r--js/src/jit/BaselineIC.h10
-rw-r--r--js/src/jit/BaselineInspector.cpp46
-rw-r--r--js/src/jit/CacheIR.cpp72
-rw-r--r--js/src/jit/CacheIR.h20
-rw-r--r--js/src/jit/CodeGenerator.cpp32
-rw-r--r--js/src/jit/IonBuilder.cpp183
-rw-r--r--js/src/jit/IonBuilder.h13
-rw-r--r--js/src/jit/IonCaches.cpp336
-rw-r--r--js/src/jit/IonCaches.h12
-rw-r--r--js/src/jit/JitOptions.cpp3
-rw-r--r--js/src/jit/JitOptions.h3
-rw-r--r--js/src/jit/MCallOptimize.cpp1
-rw-r--r--js/src/jit/MIR.cpp9
-rw-r--r--js/src/jit/MIR.h23
-rw-r--r--js/src/jit/MacroAssembler.cpp274
-rw-r--r--js/src/jit/MacroAssembler.h14
-rw-r--r--js/src/jit/OptimizationTracking.cpp4
-rw-r--r--js/src/jit/Recover.cpp36
-rw-r--r--js/src/jit/ScalarReplacement.cpp33
-rw-r--r--js/src/jit/SharedIC.cpp30
-rw-r--r--js/src/jit/VMFunctions.cpp2
-rw-r--r--js/src/jsarray.cpp1
-rw-r--r--js/src/jscompartment.h3
-rw-r--r--js/src/jsfriendapi.cpp2
-rw-r--r--js/src/jsobj.cpp2
-rw-r--r--js/src/jsobjinlines.h15
-rw-r--r--js/src/moz.build1
-rw-r--r--js/src/shell/js.cpp2
-rw-r--r--js/src/vm/Interpreter.cpp5
-rw-r--r--js/src/vm/NativeObject.cpp27
-rw-r--r--js/src/vm/NativeObject.h5
-rw-r--r--js/src/vm/ObjectGroup-inl.h14
-rw-r--r--js/src/vm/ObjectGroup.cpp55
-rw-r--r--js/src/vm/ObjectGroup.h62
-rw-r--r--js/src/vm/ReceiverGuard.cpp14
-rw-r--r--js/src/vm/TypeInference-inl.h5
-rw-r--r--js/src/vm/TypeInference.cpp156
-rw-r--r--js/src/vm/UnboxedObject-inl.h177
-rw-r--r--js/src/vm/UnboxedObject.cpp946
-rw-r--r--js/src/vm/UnboxedObject.h319
48 files changed, 3400 insertions, 75 deletions
diff --git a/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js b/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js
new file mode 100644
index 000000000..f55456222
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js
@@ -0,0 +1,49 @@
+
+function Foo(a, b) {
+ this.a = a;
+ this.b = b;
+}
+
+function invalidate_foo() {
+ var a = [];
+ var counter = 0;
+ for (var i = 0; i < 50; i++)
+ a.push(new Foo(i, i + 1));
+ Object.defineProperty(Foo.prototype, "a", {configurable: true, set: function() { counter++; }});
+ for (var i = 0; i < 50; i++)
+ a.push(new Foo(i, i + 1));
+ delete Foo.prototype.a;
+ var total = 0;
+ for (var i = 0; i < a.length; i++) {
+ assertEq('a' in a[i], i < 50);
+ total += a[i].b;
+ }
+ assertEq(total, 2550);
+ assertEq(counter, 50);
+}
+invalidate_foo();
+
+function Bar(a, b, fn) {
+ this.a = a;
+ if (b == 30)
+ Object.defineProperty(Bar.prototype, "b", {configurable: true, set: fn});
+ this.b = b;
+}
+
+function invalidate_bar() {
+ var a = [];
+ var counter = 0;
+ function fn() { counter++; }
+ for (var i = 0; i < 50; i++)
+ a.push(new Bar(i, i + 1, fn));
+ delete Bar.prototype.b;
+ var total = 0;
+ for (var i = 0; i < a.length; i++) {
+ assertEq('a' in a[i], true);
+ assertEq('b' in a[i], i < 29);
+ total += a[i].a;
+ }
+ assertEq(total, 1225);
+ assertEq(counter, 21);
+}
+invalidate_bar();
diff --git a/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js b/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js
new file mode 100644
index 000000000..691fe166c
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js
@@ -0,0 +1,47 @@
+
+// Test various ways of converting an unboxed object to native.
+
+function Foo(a, b) {
+ this.a = a;
+ this.b = b;
+}
+
+var proxyObj = {
+ get: function(recipient, name) {
+ return recipient[name] + 2;
+ }
+};
+
+function f() {
+ var a = [];
+ for (var i = 0; i < 50; i++)
+ a.push(new Foo(i, i + 1));
+
+ var prop = "a";
+
+ i = 0;
+ for (; i < 5; i++)
+ a[i].c = i;
+ for (; i < 10; i++)
+ Object.defineProperty(a[i], 'c', {value: i});
+ for (; i < 15; i++)
+ a[i] = new Proxy(a[i], proxyObj);
+ for (; i < 20; i++)
+ a[i].a = 3.5;
+ for (; i < 25; i++)
+ delete a[i].b;
+ for (; i < 30; i++)
+ a[prop] = 4;
+
+ var total = 0;
+ for (i = 0; i < a.length; i++) {
+ if ('a' in a[i])
+ total += a[i].a;
+ if ('b' in a[i])
+ total += a[i].b;
+ if ('c' in a[i])
+ total += a[i].c;
+ }
+ assertEq(total, 2382.5);
+}
+f();
diff --git a/js/src/jit-test/tests/basic/unboxed-object-getelem.js b/js/src/jit-test/tests/basic/unboxed-object-getelem.js
new file mode 100644
index 000000000..b30b8325a
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-getelem.js
@@ -0,0 +1,20 @@
+
+function f() {
+ var propNames = ["a","b","c","d","e","f","g","h","i","j","x","y"];
+ var arr = [];
+ for (var i=0; i<64; i++)
+ arr.push({x:1, y:2});
+ for (var i=0; i<64; i++) {
+ // Make sure there are expandos with dynamic slots for each object.
+ for (var j = 0; j < propNames.length; j++)
+ arr[i][propNames[j]] = j;
+ }
+ var res = 0;
+ for (var i=0; i<100000; i++) {
+ var o = arr[i % 64];
+ var p = propNames[i % propNames.length];
+ res += o[p];
+ }
+ assertEq(res, 549984);
+}
+f();
diff --git a/js/src/jit-test/tests/basic/unboxed-object-set-property.js b/js/src/jit-test/tests/basic/unboxed-object-set-property.js
new file mode 100644
index 000000000..fdcfcf6b2
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-set-property.js
@@ -0,0 +1,31 @@
+
+// Use the correct receiver when non-native objects are prototypes of other objects.
+
+function Thing(a, b) {
+ this.a = a;
+ this.b = b;
+}
+
+function foo() {
+ var array = [];
+ for (var i = 0; i < 10000; i++)
+ array.push(new Thing(i, i + 1));
+
+ var proto = new Thing(1, 2);
+ var obj = Object.create(proto);
+
+ Object.defineProperty(Thing.prototype, "c", {set:function() { this.d = 0; }});
+ obj.c = 3;
+ assertEq(obj.c, undefined);
+ assertEq(obj.d, 0);
+ assertEq(obj.hasOwnProperty("d"), true);
+ assertEq(proto.d, undefined);
+ assertEq(proto.hasOwnProperty("d"), false);
+
+ obj.a = 3;
+ assertEq(obj.a, 3);
+ assertEq(proto.a, 1);
+ assertEq(obj.hasOwnProperty("a"), true);
+}
+
+foo();
diff --git a/js/src/jit-test/tests/basic/unboxed-property-enumeration.js b/js/src/jit-test/tests/basic/unboxed-property-enumeration.js
new file mode 100644
index 000000000..142d932dd
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-property-enumeration.js
@@ -0,0 +1,24 @@
+function O() {
+ this.x = 1;
+ this.y = 2;
+}
+function testUnboxed() {
+ var arr = [];
+ for (var i=0; i<100; i++)
+ arr.push(new O);
+
+ var o = arr[arr.length-1];
+ o[0] = 0;
+ o[2] = 2;
+ var sym = Symbol();
+ o[sym] = 1;
+ o.z = 3;
+ Object.defineProperty(o, '3', {value:1,enumerable:false,configurable:false,writable:false});
+ o[4] = 4;
+
+ var props = Reflect.ownKeys(o);
+ assertEq(props[props.length-1], sym);
+
+ assertEq(Object.getOwnPropertyNames(o).join(""), "0234xyz");
+}
+testUnboxed();
diff --git a/js/src/jit-test/tests/ion/unboxed-objects-invalidate.js b/js/src/jit-test/tests/ion/unboxed-objects-invalidate.js
new file mode 100644
index 000000000..02e27614f
--- /dev/null
+++ b/js/src/jit-test/tests/ion/unboxed-objects-invalidate.js
@@ -0,0 +1,16 @@
+
+var a = [];
+for (var i = 0; i < 2000; i++)
+ a.push({f:i});
+
+function f() {
+ var total = 0;
+ for (var i = 0; i < a.length; i++)
+ total += a[i].f;
+ return total;
+}
+assertEq(f(), 1999000);
+
+var sub = Object.create(a[0]);
+
+assertEq(f(), 1999000);
diff --git a/js/src/jit/BaselineCacheIR.cpp b/js/src/jit/BaselineCacheIR.cpp
index 67c80473b..7fb586811 100644
--- a/js/src/jit/BaselineCacheIR.cpp
+++ b/js/src/jit/BaselineCacheIR.cpp
@@ -16,7 +16,7 @@ using namespace js;
using namespace js::jit;
// OperandLocation represents the location of an OperandId. The operand is
-// either in a register or on the stack.
+// either in a register or on the stack, and is either boxed or unboxed.
class OperandLocation
{
public:
@@ -815,6 +815,36 @@ BaselineCacheIRCompiler::emitGuardSpecificObject()
}
bool
+BaselineCacheIRCompiler::emitGuardNoUnboxedExpando()
+{
+ Register obj = allocator.useRegister(masm, reader.objOperandId());
+
+ FailurePath* failure;
+ if (!addFailurePath(&failure))
+ return false;
+
+ Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando());
+ masm.branchPtr(Assembler::NotEqual, expandoAddr, ImmWord(0), failure->label());
+ return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitGuardAndLoadUnboxedExpando()
+{
+ Register obj = allocator.useRegister(masm, reader.objOperandId());
+ Register output = allocator.defineRegister(masm, reader.objOperandId());
+
+ FailurePath* failure;
+ if (!addFailurePath(&failure))
+ return false;
+
+ Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando());
+ masm.loadPtr(expandoAddr, output);
+ masm.branchTestPtr(Assembler::Zero, output, output, failure->label());
+ return true;
+}
+
+bool
BaselineCacheIRCompiler::emitLoadFixedSlotResult()
{
Register obj = allocator.useRegister(masm, reader.objOperandId());
@@ -841,6 +871,26 @@ BaselineCacheIRCompiler::emitLoadDynamicSlotResult()
}
bool
+BaselineCacheIRCompiler::emitLoadUnboxedPropertyResult()
+{
+ Register obj = allocator.useRegister(masm, reader.objOperandId());
+ AutoScratchRegister scratch(allocator, masm);
+
+ JSValueType fieldType = reader.valueType();
+
+ Address fieldOffset(stubAddress(reader.stubOffset()));
+ masm.load32(fieldOffset, scratch);
+ masm.loadUnboxedProperty(BaseIndex(obj, scratch, TimesOne), fieldType, R0);
+
+ if (fieldType == JSVAL_TYPE_OBJECT)
+ emitEnterTypeMonitorIC();
+ else
+ emitReturnFromIC();
+
+ return true;
+}
+
+bool
BaselineCacheIRCompiler::emitGuardNoDetachedTypedObjects()
{
FailurePath* failure;
diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp
index e65f10aac..1b98325b7 100644
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -44,8 +44,8 @@
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/Interpreter-inl.h"
-#include "vm/NativeObject-inl.h"
#include "vm/StringObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using mozilla::DebugOnly;
@@ -741,6 +741,11 @@ LastPropertyForSetProp(JSObject* obj)
if (obj->isNative())
return obj->as<NativeObject>().lastProperty();
+ if (obj->is<UnboxedPlainObject>()) {
+ UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+ return expando ? expando->lastProperty() : nullptr;
+ }
+
return nullptr;
}
@@ -1157,6 +1162,56 @@ TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsb
ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
+ if (obj->is<UnboxedPlainObject>() && holder == obj) {
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+
+ // Once unboxed objects support symbol-keys, we need to change the following accordingly
+ MOZ_ASSERT_IF(!keyVal.isString(), !property);
+
+ if (property) {
+ if (!cx->runtime()->jitSupportsFloatingPoint)
+ return true;
+
+ RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
+ ICGetElemNativeCompiler<PropertyName*> compiler(cx, ICStub::GetElem_UnboxedPropertyName,
+ monitorStub, obj, holder,
+ name,
+ ICGetElemNativeStub::UnboxedProperty,
+ needsAtomize, property->offset +
+ UnboxedPlainObject::offsetOfData(),
+ property->type);
+ ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+ if (!newStub)
+ return false;
+
+ stub->addNewStub(newStub);
+ *attached = true;
+ return true;
+ }
+
+ Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, id);
+ if (!shape->hasDefaultGetter() || !shape->hasSlot())
+ return true;
+
+ bool isFixedSlot;
+ uint32_t offset;
+ GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
+
+ ICGetElemNativeStub::AccessType acctype =
+ isFixedSlot ? ICGetElemNativeStub::FixedSlot
+ : ICGetElemNativeStub::DynamicSlot;
+ ICGetElemNativeCompiler<T> compiler(cx, getGetElemStubKind<T>(ICStub::GetElem_NativeSlotName),
+ monitorStub, obj, holder, key,
+ acctype, needsAtomize, offset);
+ ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+ if (!newStub)
+ return false;
+
+ stub->addNewStub(newStub);
+ *attached = true;
+ return true;
+ }
+
if (!holder->isNative())
return true;
@@ -1404,7 +1459,7 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
}
// Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses.
- if (obj->isNative()) {
+ if (obj->isNative() || obj->is<UnboxedPlainObject>()) {
RootedScript rootedScript(cx, script);
if (rhs.isString()) {
if (!TryAttachNativeOrUnboxedGetValueElemStub<PropertyName*>(cx, rootedScript, pc, stub,
@@ -1816,6 +1871,14 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm)
Register holderReg;
if (obj_ == holder_) {
holderReg = objReg;
+
+ if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) {
+ // The property will be loaded off the unboxed expando.
+ masm.push(R1.scratchReg());
+ popR1 = true;
+ holderReg = R1.scratchReg();
+ masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
+ }
} else {
// Shape guard holder.
if (regs.empty()) {
@@ -1866,6 +1929,13 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm)
if (popR1)
masm.addToStackPtr(ImmWord(sizeof(size_t)));
+ } else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) {
+ masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()),
+ scratchReg);
+ masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_,
+ TypedOrValueRegister(R0));
+ if (popR1)
+ masm.addToStackPtr(ImmWord(sizeof(size_t)));
} else {
MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter ||
acctype_ == ICGetElemNativeStub::ScriptedGetter);
@@ -2618,6 +2688,18 @@ BaselineScript::noteArrayWriteHole(uint32_t pcOffset)
// SetElem_DenseOrUnboxedArray
//
+template <typename T>
+void
+EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, T address, JSValueType type)
+{
+ if (type == JSVAL_TYPE_OBJECT)
+ EmitPreBarrier(masm, address, MIRType::Object);
+ else if (type == JSVAL_TYPE_STRING)
+ EmitPreBarrier(masm, address, MIRType::String);
+ else
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
+}
+
bool
ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
{
@@ -4061,7 +4143,18 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC
return true;
if (!obj->isNative()) {
- return true;
+ if (obj->is<UnboxedPlainObject>()) {
+ UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+ if (expando) {
+ shape = expando->lookup(cx, name);
+ if (!shape)
+ return true;
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
}
size_t chainDepth;
@@ -4209,6 +4302,40 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
}
static bool
+TryAttachUnboxedSetPropStub(JSContext* cx, HandleScript script,
+ ICSetProp_Fallback* stub, HandleId id,
+ HandleObject obj, HandleValue rhs, bool* attached)
+{
+ MOZ_ASSERT(!*attached);
+
+ if (!cx->runtime()->jitSupportsFloatingPoint)
+ return true;
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+ if (!property)
+ return true;
+
+ ICSetProp_Unboxed::Compiler compiler(cx, obj->group(),
+ property->offset + UnboxedPlainObject::offsetOfData(),
+ property->type);
+ ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+ if (!newStub)
+ return false;
+ if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
+ return false;
+
+ stub->addNewStub(newStub);
+
+ StripPreliminaryObjectStubs(cx, stub);
+
+ *attached = true;
+ return true;
+}
+
+static bool
TryAttachTypedObjectSetPropStub(JSContext* cx, HandleScript script,
ICSetProp_Fallback* stub, HandleId id,
HandleObject obj, HandleValue rhs, bool* attached)
@@ -4291,6 +4418,12 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_
return false;
RootedReceiverGuard oldGuard(cx, ReceiverGuard(obj));
+ if (obj->is<UnboxedPlainObject>()) {
+ MOZ_ASSERT(!oldShape);
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ oldShape = expando->lastProperty();
+ }
+
bool attached = false;
// There are some reasons we can fail to attach a stub that are temporary.
// We want to avoid calling noteUnoptimizableAccess() if the reason we
@@ -4363,6 +4496,15 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_
if (!attached &&
lhs.isObject() &&
+ !TryAttachUnboxedSetPropStub(cx, script, stub, id, obj, rhs, &attached))
+ {
+ return false;
+ }
+ if (attached)
+ return true;
+
+ if (!attached &&
+ lhs.isObject() &&
!TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached))
{
return false;
@@ -4445,7 +4587,20 @@ GuardGroupAndShapeMaybeUnboxedExpando(MacroAssembler& masm, JSObject* obj,
// Guard against shape or expando shape.
masm.loadPtr(Address(ICStubReg, offsetOfShape), scratch);
- masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
+ if (obj->is<UnboxedPlainObject>()) {
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
+ Label done;
+ masm.push(object);
+ masm.loadPtr(expandoAddress, object);
+ masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
+ masm.pop(object);
+ masm.jump(failure);
+ masm.bind(&done);
+ masm.pop(object);
+ } else {
+ masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
+ }
}
bool
@@ -4484,7 +4639,13 @@ ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm)
regs.takeUnchecked(objReg);
Register holderReg;
- if (isFixedSlot_) {
+ if (obj_->is<UnboxedPlainObject>()) {
+ // We are loading off the expando object, so use that for the holder.
+ holderReg = regs.takeAny();
+ masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
+ if (!isFixedSlot_)
+ masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
+ } else if (isFixedSlot_) {
holderReg = objReg;
} else {
holderReg = regs.takeAny();
@@ -4621,17 +4782,31 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm)
regs.add(R0);
regs.takeUnchecked(objReg);
- // Write the object's new shape.
- Address shapeAddr(objReg, ShapedObject::offsetOfShape());
- EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
- masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
- masm.storePtr(scratch, shapeAddr);
+ if (obj_->is<UnboxedPlainObject>()) {
+ holderReg = regs.takeAny();
+ masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
- if (isFixedSlot_) {
- holderReg = objReg;
+ // Write the expando object's new shape.
+ Address shapeAddr(holderReg, ShapedObject::offsetOfShape());
+ EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
+ masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
+ masm.storePtr(scratch, shapeAddr);
+
+ if (!isFixedSlot_)
+ masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
} else {
- holderReg = regs.takeAny();
- masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
+ // Write the object's new shape.
+ Address shapeAddr(objReg, ShapedObject::offsetOfShape());
+ EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
+ masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
+ masm.storePtr(scratch, shapeAddr);
+
+ if (isFixedSlot_) {
+ holderReg = objReg;
+ } else {
+ holderReg = regs.takeAny();
+ masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
+ }
}
// Perform the store. No write barrier required since this is a new
@@ -4663,6 +4838,70 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm)
}
bool
+ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm)
+{
+ MOZ_ASSERT(engine_ == Engine::Baseline);
+
+ Label failure;
+
+ // Guard input is an object.
+ masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+
+ AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
+ Register scratch = regs.takeAny();
+
+ // Unbox and group guard.
+ Register object = masm.extractObject(R0, ExtractTemp0);
+ masm.loadPtr(Address(ICStubReg, ICSetProp_Unboxed::offsetOfGroup()), scratch);
+ masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch,
+ &failure);
+
+ if (needsUpdateStubs()) {
+ // Stow both R0 and R1 (object and value).
+ EmitStowICValues(masm, 2);
+
+ // Move RHS into R0 for TypeUpdate check.
+ masm.moveValue(R1, R0);
+
+ // Call the type update stub.
+ if (!callTypeUpdateIC(masm, sizeof(Value)))
+ return false;
+
+ // Unstow R0 and R1 (object and key)
+ EmitUnstowICValues(masm, 2);
+
+ // The TypeUpdate IC may have smashed object. Rederive it.
+ masm.unboxObject(R0, object);
+
+ // Trigger post barriers here on the values being written. Fields which
+ // objects can be written to also need update stubs.
+ LiveGeneralRegisterSet saveRegs;
+ saveRegs.add(R0);
+ saveRegs.add(R1);
+ saveRegs.addUnchecked(object);
+ saveRegs.add(ICStubReg);
+ emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
+ }
+
+ // Compute the address being written to.
+ masm.load32(Address(ICStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch);
+ BaseIndex address(object, scratch, TimesOne);
+
+ EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_);
+ masm.storeUnboxedProperty(address, fieldType_,
+ ConstantOrRegister(TypedOrValueRegister(R1)), &failure);
+
+ // The RHS has to be in R0.
+ masm.moveValue(R1, R0);
+
+ EmitReturnFromIC(masm);
+
+ masm.bind(&failure);
+ EmitStubGuardFailure(masm);
+ return true;
+}
+
+bool
ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
@@ -5421,7 +5660,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
if (!thisObject)
return false;
- if (thisObject->is<PlainObject>())
+ if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>())
templateObject = thisObject;
}
diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h
index e1ad12559..a1291a3bb 100644
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -22,6 +22,7 @@
#include "jit/SharedICRegisters.h"
#include "js/GCVector.h"
#include "vm/ArrayObject.h"
+#include "vm/UnboxedObject.h"
namespace js {
namespace jit {
@@ -1822,7 +1823,8 @@ class ICSetProp_Native : public ICUpdatedStub
virtual int32_t getKey() const {
return static_cast<int32_t>(engine_) |
(static_cast<int32_t>(kind) << 1) |
- (static_cast<int32_t>(isFixedSlot_) << 17);
+ (static_cast<int32_t>(isFixedSlot_) << 17) |
+ (static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18);
}
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
@@ -1927,6 +1929,7 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler
return static_cast<int32_t>(engine_) |
(static_cast<int32_t>(kind) << 1) |
(static_cast<int32_t>(isFixedSlot_) << 17) |
+ (static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18) |
(static_cast<int32_t>(protoChainDepth_) << 19);
}
@@ -1951,7 +1954,10 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler
newGroup = nullptr;
RootedShape newShape(cx);
- newShape = obj_->as<NativeObject>().lastProperty();
+ if (obj_->isNative())
+ newShape = obj_->as<NativeObject>().lastProperty();
+ else
+ newShape = obj_->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
return newStub<ICSetProp_NativeAddImpl<ProtoChainDepth>>(
space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_);
diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp
index 3b852debf..bcb527516 100644
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -104,11 +104,19 @@ AddReceiver(const ReceiverGuard& receiver,
static bool
GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
{
- // We match:
+ // We match either:
//
// GuardIsObject 0
// GuardShape 0
// LoadFixedSlotResult 0 or LoadDynamicSlotResult 0
+ //
+ // or
+ //
+ // GuardIsObject 0
+ // GuardGroup 0
+ // 1: GuardAndLoadUnboxedExpando 0
+ // GuardShape 1
+ // LoadFixedSlotResult 1 or LoadDynamicSlotResult 1
*receiver = ReceiverGuard();
CacheIRReader reader(stub->stubInfo());
@@ -117,6 +125,14 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re
if (!reader.matchOp(CacheOp::GuardIsObject, objId))
return false;
+ if (reader.matchOp(CacheOp::GuardGroup, objId)) {
+ receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
+
+ if (!reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
+ return false;
+ objId = reader.objOperandId();
+ }
+
if (reader.matchOp(CacheOp::GuardShape, objId)) {
receiver->shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
return reader.matchOpEither(CacheOp::LoadFixedSlotResult, CacheOp::LoadDynamicSlotResult);
@@ -125,6 +141,29 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re
return false;
}
+static bool
+GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
+{
+ // We match:
+ //
+ // GuardIsObject 0
+ // GuardGroup 0
+ // LoadUnboxedPropertyResult 0 ..
+
+ *receiver = ReceiverGuard();
+ CacheIRReader reader(stub->stubInfo());
+
+ ObjOperandId objId = ObjOperandId(0);
+ if (!reader.matchOp(CacheOp::GuardIsObject, objId))
+ return false;
+
+ if (!reader.matchOp(CacheOp::GuardGroup, objId))
+ return false;
+ receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
+
+ return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId);
+}
+
bool
BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers)
{
@@ -143,7 +182,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
while (stub->next()) {
ReceiverGuard receiver;
if (stub->isCacheIR_Monitored()) {
- if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver))
+ if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver) &&
+ !GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Monitored(), &receiver))
{
receivers.clear();
return true;
@@ -151,6 +191,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
} else if (stub->isSetProp_Native()) {
receiver = ReceiverGuard(stub->toSetProp_Native()->group(),
stub->toSetProp_Native()->shape());
+ } else if (stub->isSetProp_Unboxed()) {
+ receiver = ReceiverGuard(stub->toSetProp_Unboxed()->group(), nullptr);
} else {
receivers.clear();
return true;
diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp
index d184ea40c..6822a70af 100644
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -10,7 +10,8 @@
#include "jit/IonCaches.h"
#include "jsobjinlines.h"
-#include "vm/NativeObject-inl.h"
+
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -59,6 +60,10 @@ GetPropIRGenerator::tryAttachStub(Maybe<CacheIRWriter>& writer)
return false;
if (!emitted_ && !tryAttachNative(*writer, obj, objId))
return false;
+ if (!emitted_ && !tryAttachUnboxed(*writer, obj, objId))
+ return false;
+ if (!emitted_ && !tryAttachUnboxedExpando(*writer, obj, objId))
+ return false;
if (!emitted_ && !tryAttachTypedObject(*writer, obj, objId))
return false;
if (!emitted_ && !tryAttachModuleNamespace(*writer, obj, objId))
@@ -158,9 +163,19 @@ GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
}
static void
-TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId)
+TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId,
+ Maybe<ObjOperandId>* expandoId)
{
- if (obj->is<TypedObject>()) {
+ if (obj->is<UnboxedPlainObject>()) {
+ writer.guardGroup(objId, obj->group());
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ expandoId->emplace(writer.guardAndLoadUnboxedExpando(objId));
+ writer.guardShape(expandoId->ref(), expando->lastProperty());
+ } else {
+ writer.guardNoUnboxedExpando(objId);
+ }
+ } else if (obj->is<TypedObject>()) {
writer.guardGroup(objId, obj->group());
} else {
Shape* shape = obj->maybeShape();
@@ -173,7 +188,8 @@ static void
EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
Shape* shape, ObjOperandId objId)
{
- TestMatchingReceiver(writer, obj, shape, objId);
+ Maybe<ObjOperandId> expandoId;
+ TestMatchingReceiver(writer, obj, shape, objId, &expandoId);
ObjOperandId holderId;
if (obj != holder) {
@@ -196,6 +212,9 @@ EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
lastObjId = protoId;
}
}
+ } else if (obj->is<UnboxedPlainObject>()) {
+ holder = obj->as<UnboxedPlainObject>().maybeExpando();
+ holderId = *expandoId;
} else {
holderId = objId;
}
@@ -247,6 +266,51 @@ GetPropIRGenerator::tryAttachNative(CacheIRWriter& writer, HandleObject obj, Obj
}
bool
+GetPropIRGenerator::tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
+{
+ MOZ_ASSERT(!emitted_);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(name_);
+ if (!property)
+ return true;
+
+ if (!cx_->runtime()->jitSupportsFloatingPoint)
+ return true;
+
+ writer.guardGroup(objId, obj->group());
+ writer.loadUnboxedPropertyResult(objId, property->type,
+ UnboxedPlainObject::offsetOfData() + property->offset);
+ emitted_ = true;
+ preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
+ return true;
+}
+
+bool
+GetPropIRGenerator::tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
+{
+ MOZ_ASSERT(!emitted_);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+
+ UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+ if (!expando)
+ return true;
+
+ Shape* shape = expando->lookup(cx_, NameToId(name_));
+ if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
+ return true;
+
+ emitted_ = true;
+
+ EmitReadSlotResult(writer, obj, obj, shape, objId);
+ return true;
+}
+
+bool
GetPropIRGenerator::tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
{
MOZ_ASSERT(!emitted_);
diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h
index ae55cfebb..4fd8575f0 100644
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -87,10 +87,13 @@ class ObjOperandId : public OperandId
_(GuardClass) \
_(GuardSpecificObject) \
_(GuardNoDetachedTypedObjects) \
+ _(GuardNoUnboxedExpando) \
+ _(GuardAndLoadUnboxedExpando) \
_(LoadObject) \
_(LoadProto) \
_(LoadFixedSlotResult) \
_(LoadDynamicSlotResult) \
+ _(LoadUnboxedPropertyResult) \
_(LoadTypedObjectResult) \
_(LoadInt32ArrayLengthResult) \
_(LoadArgumentsObjectLengthResult) \
@@ -271,6 +274,15 @@ class MOZ_RAII CacheIRWriter
void guardNoDetachedTypedObjects() {
writeOp(CacheOp::GuardNoDetachedTypedObjects);
}
+ void guardNoUnboxedExpando(ObjOperandId obj) {
+ writeOpWithOperandId(CacheOp::GuardNoUnboxedExpando, obj);
+ }
+ ObjOperandId guardAndLoadUnboxedExpando(ObjOperandId obj) {
+ ObjOperandId res(nextOperandId_++);
+ writeOpWithOperandId(CacheOp::GuardAndLoadUnboxedExpando, obj);
+ writeOperandId(res);
+ return res;
+ }
ObjOperandId loadObject(JSObject* obj) {
ObjOperandId res(nextOperandId_++);
@@ -296,6 +308,11 @@ class MOZ_RAII CacheIRWriter
writeOpWithOperandId(CacheOp::LoadDynamicSlotResult, obj);
addStubWord(offset, StubField::GCType::NoGCThing);
}
+ void loadUnboxedPropertyResult(ObjOperandId obj, JSValueType type, size_t offset) {
+ writeOpWithOperandId(CacheOp::LoadUnboxedPropertyResult, obj);
+ buffer_.writeByte(uint32_t(type));
+ addStubWord(offset, StubField::GCType::NoGCThing);
+ }
void loadTypedObjectResult(ObjOperandId obj, uint32_t offset, TypedThingLayout layout,
uint32_t typeDescr) {
MOZ_ASSERT(uint32_t(layout) <= UINT8_MAX);
@@ -389,6 +406,9 @@ class MOZ_RAII GetPropIRGenerator
PreliminaryObjectAction preliminaryObjectAction_;
MOZ_MUST_USE bool tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
+ MOZ_MUST_USE bool tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
+ MOZ_MUST_USE bool tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj,
+ ObjOperandId objId);
MOZ_MUST_USE bool tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj,
ObjOperandId objId);
MOZ_MUST_USE bool tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj,
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index 2b1c671d1..901e9ea93 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -25,7 +25,6 @@
#include "builtin/Eval.h"
#include "builtin/TypedObject.h"
#include "gc/Nursery.h"
-#include "gc/StoreBuffer-inl.h"
#include "irregexp/NativeRegExpMacroAssembler.h"
#include "jit/AtomicOperations.h"
#include "jit/BaselineCompiler.h"
@@ -3029,7 +3028,7 @@ CodeGenerator::visitStoreSlotV(LStoreSlotV* lir)
static void
GuardReceiver(MacroAssembler& masm, const ReceiverGuard& guard,
- Register obj, Register scratch, Label* miss)
+ Register obj, Register scratch, Label* miss, bool checkNullExpando)
{
if (guard.group) {
masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, miss);
@@ -3051,11 +3050,13 @@ CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
Label next;
masm.comment("GuardReceiver");
- GuardReceiver(masm, receiver, obj, scratch, &next);
+ GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
if (receiver.shape) {
masm.comment("loadTypedOrValue");
- Register target = obj;
+ // If this is an unboxed expando access, GuardReceiver loaded the
+ // expando object into scratch.
+ Register target = receiver.group ? scratch : obj;
Shape* shape = mir->shape(i);
if (shape->slot() < shape->numFixedSlots()) {
@@ -3121,10 +3122,12 @@ CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
ReceiverGuard receiver = mir->receiver(i);
Label next;
- GuardReceiver(masm, receiver, obj, scratch, &next);
+ GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
if (receiver.shape) {
- Register target = obj;
+ // If this is an unboxed expando access, GuardReceiver loaded the
+ // expando object into scratch.
+ Register target = receiver.group ? scratch : obj;
Shape* shape = mir->shape(i);
if (shape->slot() < shape->numFixedSlots()) {
@@ -3290,7 +3293,7 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir)
const ReceiverGuard& receiver = mir->receiver(i);
Label next;
- GuardReceiver(masm, receiver, obj, temp, &next);
+ GuardReceiver(masm, receiver, obj, temp, &next, /* checkNullExpando = */ true);
if (i == mir->numReceivers() - 1) {
bailoutFrom(&next, lir->snapshot());
@@ -8379,6 +8382,11 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir)
}
}
+typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
+static const VMFunction ConvertUnboxedPlainObjectToNativeInfo =
+ FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative,
+ "UnboxedPlainObject::convertToNative");
+
typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction ArrayPopDenseInfo =
FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
@@ -8671,11 +8679,11 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir)
masm.loadPtr(Address(niTemp, offsetof(NativeIterator, guard_array)), temp2);
// Compare object with the first receiver guard. The last iterator can only
- // match for native objects.
+ // match for native objects and unboxed objects.
{
Address groupAddr(temp2, offsetof(ReceiverGuard, group));
Address shapeAddr(temp2, offsetof(ReceiverGuard, shape));
- Label guardDone, shapeMismatch;
+ Label guardDone, shapeMismatch, noExpando;
masm.loadObjShape(obj, temp1);
masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, &shapeMismatch);
@@ -8687,6 +8695,12 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir)
masm.bind(&shapeMismatch);
masm.loadObjGroup(obj, temp1);
masm.branchPtr(Assembler::NotEqual, groupAddr, temp1, ool->entry());
+ masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), temp1);
+ masm.branchTestPtr(Assembler::Zero, temp1, temp1, &noExpando);
+ branchIfNotEmptyObjectElements(temp1, ool->entry());
+ masm.loadObjShape(temp1, temp1);
+ masm.bind(&noExpando);
+ masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, ool->entry());
masm.bind(&guardDone);
}
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index 1e12f5dbe..f00167d92 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -10940,6 +10940,63 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_
return slot;
}
+uint32_t
+IonBuilder::getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, JSValueType* punboxedType)
+{
+ if (!types || types->unknownObject() || !types->objectOrSentinel()) {
+ trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
+ return UINT32_MAX;
+ }
+
+ uint32_t offset = UINT32_MAX;
+
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (!key)
+ continue;
+
+ if (key->unknownProperties()) {
+ trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
+ return UINT32_MAX;
+ }
+
+ if (key->isSingleton()) {
+ trackOptimizationOutcome(TrackedOutcome::Singleton);
+ return UINT32_MAX;
+ }
+
+ UnboxedLayout* layout = key->group()->maybeUnboxedLayout();
+ if (!layout) {
+ trackOptimizationOutcome(TrackedOutcome::NotUnboxed);
+ return UINT32_MAX;
+ }
+
+ const UnboxedLayout::Property* property = layout->lookup(name);
+ if (!property) {
+ trackOptimizationOutcome(TrackedOutcome::StructNoField);
+ return UINT32_MAX;
+ }
+
+ if (layout->nativeGroup()) {
+ trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
+ return UINT32_MAX;
+ }
+
+ if (offset == UINT32_MAX) {
+ offset = property->offset;
+ *punboxedType = property->type;
+ } else if (offset != property->offset) {
+ trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
+ return UINT32_MAX;
+ } else if (*punboxedType != property->type) {
+ trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
+ return UINT32_MAX;
+ }
+ }
+
+ return offset;
+}
+
bool
IonBuilder::jsop_runonce()
{
@@ -11906,6 +11963,72 @@ IonBuilder::getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyN
return true;
}
+MInstruction*
+IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
+ BarrierKind barrier, TemporaryTypeSet* types)
+{
+ // loadUnboxedValue is designed to load any value as if it were contained in
+ // an array. Thus a property offset is converted to an index, when the
+ // object is reinterpreted as an array of properties of the same size.
+ size_t index = offset / UnboxedTypeSize(unboxedType);
+ MInstruction* indexConstant = MConstant::New(alloc(), Int32Value(index));
+ current->add(indexConstant);
+
+ return loadUnboxedValue(obj, UnboxedPlainObject::offsetOfData(),
+ indexConstant, unboxedType, barrier, types);
+}
+
+MInstruction*
+IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
+ MDefinition* index, JSValueType unboxedType,
+ BarrierKind barrier, TemporaryTypeSet* types)
+{
+ MInstruction* load;
+ switch (unboxedType) {
+ case JSVAL_TYPE_BOOLEAN:
+ load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Uint8,
+ DoesNotRequireMemoryBarrier, elementsOffset);
+ load->setResultType(MIRType::Boolean);
+ break;
+
+ case JSVAL_TYPE_INT32:
+ load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Int32,
+ DoesNotRequireMemoryBarrier, elementsOffset);
+ load->setResultType(MIRType::Int32);
+ break;
+
+ case JSVAL_TYPE_DOUBLE:
+ load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Float64,
+ DoesNotRequireMemoryBarrier, elementsOffset,
+ /* canonicalizeDoubles = */ false);
+ load->setResultType(MIRType::Double);
+ break;
+
+ case JSVAL_TYPE_STRING:
+ load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset);
+ break;
+
+ case JSVAL_TYPE_OBJECT: {
+ MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
+ if (types->hasType(TypeSet::NullType()))
+ nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
+ else if (barrier != BarrierKind::NoBarrier)
+ nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
+ else
+ nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
+ load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index, nullBehavior,
+ elementsOffset);
+ break;
+ }
+
+ default:
+ MOZ_CRASH();
+ }
+
+ current->add(load);
+ return load;
+}
+
MDefinition*
IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
@@ -12729,6 +12852,66 @@ IonBuilder::setPropTryDefiniteSlot(bool* emitted, MDefinition* obj,
return true;
}
+MInstruction*
+IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
+ MDefinition* value)
+{
+ size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
+ MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
+ current->add(scaledOffset);
+
+ return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(),
+ scaledOffset, unboxedType, value);
+}
+
+MInstruction*
+IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset,
+ MDefinition* scaledOffset, JSValueType unboxedType,
+ MDefinition* value, bool preBarrier /* = true */)
+{
+ MInstruction* store;
+ switch (unboxedType) {
+ case JSVAL_TYPE_BOOLEAN:
+ store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Uint8,
+ MStoreUnboxedScalar::DontTruncateInput,
+ DoesNotRequireMemoryBarrier, elementsOffset);
+ break;
+
+ case JSVAL_TYPE_INT32:
+ store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Int32,
+ MStoreUnboxedScalar::DontTruncateInput,
+ DoesNotRequireMemoryBarrier, elementsOffset);
+ break;
+
+ case JSVAL_TYPE_DOUBLE:
+ store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Float64,
+ MStoreUnboxedScalar::DontTruncateInput,
+ DoesNotRequireMemoryBarrier, elementsOffset);
+ break;
+
+ case JSVAL_TYPE_STRING:
+ store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value,
+ elementsOffset, preBarrier);
+ break;
+
+ case JSVAL_TYPE_OBJECT:
+ MOZ_ASSERT(value->type() == MIRType::Object ||
+ value->type() == MIRType::Null ||
+ value->type() == MIRType::Value);
+ MOZ_ASSERT(!value->mightBeType(MIRType::Undefined),
+ "MToObjectOrNull slow path is invalid for unboxed objects");
+ store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, obj,
+ elementsOffset, preBarrier);
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+
+ current->add(store);
+ return store;
+}
+
bool
IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index 6a3b61232..78af0e412 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -1050,6 +1050,19 @@ class IonBuilder
ResultWithOOM<bool> testNotDefinedProperty(MDefinition* obj, jsid id);
uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed);
+ uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
+ JSValueType* punboxedType);
+ MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
+ BarrierKind barrier, TemporaryTypeSet* types);
+ MInstruction* loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
+ MDefinition* scaledOffset, JSValueType unboxedType,
+ BarrierKind barrier, TemporaryTypeSet* types);
+ MInstruction* storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
+ MDefinition* value);
+ MInstruction* storeUnboxedValue(MDefinition* obj,
+ MDefinition* elements, int32_t elementsOffset,
+ MDefinition* scaledOffset, JSValueType unboxedType,
+ MDefinition* value, bool preBarrier = true);
MOZ_MUST_USE bool checkPreliminaryGroups(MDefinition *obj);
MOZ_MUST_USE bool freezePropTypeSets(TemporaryTypeSet* types,
JSObject* foundProto, PropertyName* name);
diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp
index fb4291188..f5e4659c1 100644
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -31,6 +31,7 @@
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Shape-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -619,7 +620,26 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
Register object, JSObject* obj, Label* failure,
bool alwaysCheckGroup = false)
{
- if (obj->is<TypedObject>()) {
+ if (obj->is<UnboxedPlainObject>()) {
+ MOZ_ASSERT(failure);
+
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
+ Label success;
+ masm.push(object);
+ masm.loadPtr(expandoAddress, object);
+ masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(),
+ &success);
+ masm.pop(object);
+ masm.jump(failure);
+ masm.bind(&success);
+ masm.pop(object);
+ } else {
+ masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
+ }
+ } else if (obj->is<TypedObject>()) {
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(obj->group()), failure);
@@ -736,6 +756,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
// jump directly. Otherwise, jump to the end of the stub, so there's a
// common point to patch.
bool multipleFailureJumps = (obj != holder)
+ || obj->is<UnboxedPlainObject>()
|| (checkTDZ && output.hasValue())
|| (failures != nullptr && failures->used());
@@ -754,6 +775,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
Register scratchReg = Register::FromCode(0); // Quell compiler warning.
if (obj != holder ||
+ obj->is<UnboxedPlainObject>() ||
!holder->as<NativeObject>().isFixedSlot(shape->slot()))
{
if (output.hasValue()) {
@@ -814,6 +836,10 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
holderReg = InvalidReg;
}
+ } else if (obj->is<UnboxedPlainObject>()) {
+ holder = obj->as<UnboxedPlainObject>().maybeExpando();
+ holderReg = scratchReg;
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg);
} else {
holderReg = object;
}
@@ -841,6 +867,30 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
attacher.jumpNextStub(masm);
}
+static void
+GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
+ IonCache::StubAttacher& attacher, JSObject* obj,
+ const UnboxedLayout::Property* property,
+ Register object, TypedOrValueRegister output,
+ Label* failures = nullptr)
+{
+ // Guard on the group of the object.
+ attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+ Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(obj->group()), failures);
+
+ Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ masm.loadUnboxedProperty(address, property->type, output);
+
+ attacher.jumpRejoin(masm);
+
+ if (failures) {
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+ }
+}
+
static bool
EmitGetterCall(JSContext* cx, MacroAssembler& masm,
IonCache::StubAttacher& attacher, JSObject* obj,
@@ -1448,6 +1498,67 @@ GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip
}
bool
+GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+ if (!property)
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ StubAttacher attacher(*this);
+ GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedRead);
+}
+
+bool
+GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
+{
+ MOZ_ASSERT(canAttachStub());
+ MOZ_ASSERT(!*emitted);
+ MOZ_ASSERT(outerScript->ionScript() == ion);
+
+ if (!obj->is<UnboxedPlainObject>())
+ return true;
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando)
+ return true;
+
+ Shape* shape = expando->lookup(cx, id);
+ if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+ Label* maybeFailures = failures.used() ? &failures : nullptr;
+
+ StubAttacher attacher(*this);
+ GenerateReadSlot(cx, ion, masm, attacher, DontCheckTDZ, obj, obj,
+ shape, object(), output(), maybeFailures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
+}
+
+bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
@@ -2016,6 +2127,12 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
+ if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
return false;
}
@@ -2194,6 +2311,12 @@ GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
NativeObject::slotsSizeMustNotOverflow();
+ if (obj->is<UnboxedPlainObject>()) {
+ obj = obj->as<UnboxedPlainObject>().maybeExpando();
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
+ object = tempReg;
+ }
+
if (obj->as<NativeObject>().isFixedSlot(shape->slot())) {
Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));
@@ -2831,13 +2954,23 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
if (obj->maybeShape()) {
masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
+ } else {
+ MOZ_ASSERT(obj->is<UnboxedPlainObject>());
+
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);
+
+ masm.loadPtr(expandoAddress, tempReg);
+ masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
}
Shape* newShape = obj->maybeShape();
+ if (!newShape)
+ newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
// Guard that the incoming value is in the type set for the property
// if a type barrier is required.
- if (newShape && checkTypeset)
+ if (checkTypeset)
CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
// Guard shapes along prototype chain.
@@ -2858,7 +2991,9 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
}
// Call a stub to (re)allocate dynamic slots, if necessary.
- uint32_t newNumDynamicSlots = obj->as<NativeObject>().numDynamicSlots();
+ uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
+ ? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
+ : obj->as<NativeObject>().numDynamicSlots();
if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) {
AllocatableRegisterSet regs(RegisterSet::Volatile());
LiveRegisterSet save(regs.asLiveSet());
@@ -2869,6 +3004,12 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
Register temp1 = regs.takeAnyGeneral();
Register temp2 = regs.takeAnyGeneral();
+ if (obj->is<UnboxedPlainObject>()) {
+ // Pass the expando object to the stub.
+ masm.Push(object);
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
+ }
+
masm.setupUnalignedABICall(temp1);
masm.loadJSContext(temp1);
masm.passABIArg(temp1);
@@ -2885,16 +3026,27 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
masm.jump(&allocDone);
masm.bind(&allocFailed);
+ if (obj->is<UnboxedPlainObject>())
+ masm.Pop(object);
masm.PopRegsInMask(save);
masm.jump(failures);
masm.bind(&allocDone);
masm.setFramePushed(framePushedAfterCall);
+ if (obj->is<UnboxedPlainObject>())
+ masm.Pop(object);
masm.PopRegsInMask(save);
}
bool popObject = false;
+ if (obj->is<UnboxedPlainObject>()) {
+ masm.push(object);
+ popObject = true;
+ obj = obj->as<UnboxedPlainObject>().maybeExpando();
+ masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
+ }
+
// Write the object or expando object's new shape.
Address shapeAddr(object, ShapedObject::offsetOfShape());
if (cx->zone()->needsIncrementalBarrier())
@@ -2902,6 +3054,8 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
masm.storePtr(ImmGCPtr(newShape), shapeAddr);
if (oldGroup != obj->group()) {
+ MOZ_ASSERT(!obj->is<UnboxedPlainObject>());
+
// Changing object's group from a partially to fully initialized group,
// per the acquired properties analysis. Only change the group if the
// old group still has a newScript.
@@ -3144,6 +3298,141 @@ CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, const Const
return SetPropertyIC::CanAttachNone;
}
+static void
+GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
+ Register object, Register tempReg, const ConstantOrRegister& value,
+ bool checkTypeset, Label* failures)
+{
+ // Guard on the type of the object.
+ masm.branchPtr(Assembler::NotEqual,
+ Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(obj->group()), failures);
+
+ if (checkTypeset)
+ CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);
+
+ Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
+
+ if (cx->zone()->needsIncrementalBarrier()) {
+ if (unboxedType == JSVAL_TYPE_OBJECT)
+ masm.callPreBarrier(address, MIRType::Object);
+ else if (unboxedType == JSVAL_TYPE_STRING)
+ masm.callPreBarrier(address, MIRType::String);
+ else
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
+ }
+
+ masm.storeUnboxedProperty(address, unboxedType, value, failures);
+
+ attacher.jumpRejoin(masm);
+
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
+static bool
+CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset,
+ uint32_t* unboxedOffset, JSValueType* unboxedType)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+ if (property) {
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+ *unboxedOffset = property->offset;
+ *unboxedType = property->type;
+ return true;
+ }
+
+ return false;
+}
+
+static bool
+CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id,
+ const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset, Shape** pshape)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando)
+ return false;
+
+ Shape* shape = expando->lookupPure(id);
+ if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable())
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ *pshape = shape;
+ return true;
+}
+
+static bool
+CanAttachAddUnboxedExpando(JSContext* cx, HandleObject obj, HandleShape oldShape,
+ HandleId id, const ConstantOrRegister& val,
+ bool needsTypeBarrier, bool* checkTypeset)
+{
+ if (!obj->is<UnboxedPlainObject>())
+ return false;
+
+ Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
+ if (!expando || expando->inDictionaryMode())
+ return false;
+
+ Shape* newShape = expando->lastProperty();
+ if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape)
+ return false;
+
+ MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable());
+
+ if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
+ return false;
+
+ *checkTypeset = false;
+ if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+ return false;
+
+ return true;
+}
+
+bool
+SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ bool checkTypeset = false;
+ uint32_t unboxedOffset;
+ JSValueType unboxedType;
+ if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset,
+ &unboxedOffset, &unboxedType))
+ {
+ return true;
+ }
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+ StubAttacher attacher(*this);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
+ object(), temp(), value(), checkTypeset, &failures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
+ JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
+}
+
bool
SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
@@ -3225,6 +3514,26 @@ SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip
}
bool
+SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, bool* emitted)
+{
+ MOZ_ASSERT(!*emitted);
+
+ RootedShape shape(cx);
+ bool checkTypeset = false;
+ if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(),
+ &checkTypeset, shape.address()))
+ {
+ return true;
+ }
+
+ if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+}
+
+bool
SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleValue idval, HandleValue value,
MutableHandleId id, bool* emitted, bool* tryNativeAddSlot)
@@ -3249,6 +3558,12 @@ SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
return false;
+
+ if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
+ return false;
+
+ if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
+ return false;
}
if (idval.isInt32()) {
@@ -3300,6 +3615,16 @@ SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScri
return true;
}
+ checkTypeset = false;
+ if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
+ &checkTypeset))
+ {
+ if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
+ return false;
+ *emitted = true;
+ return true;
+ }
+
return true;
}
@@ -3321,6 +3646,11 @@ SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex
return false;
oldShape = obj->maybeShape();
+ if (obj->is<UnboxedPlainObject>()) {
+ MOZ_ASSERT(!oldShape);
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ oldShape = expando->lastProperty();
+ }
}
RootedId id(cx);
diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h
index b00646538..173e06c6b 100644
--- a/js/src/jit/IonCaches.h
+++ b/js/src/jit/IonCaches.h
@@ -529,6 +529,18 @@ class GetPropertyIC : public IonCache
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted);
+ MOZ_MUST_USE bool tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
+ HandleObject obj, HandleId id, void* returnAddr,
+ bool* emitted);
+
+ MOZ_MUST_USE bool tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
+ IonScript* ion, HandleObject obj, HandleId id,
+ void* returnAddr, bool* emitted);
+
+ MOZ_MUST_USE bool tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript,
+ IonScript* ion, HandleObject obj, HandleId id,
+ void* returnAddr, bool* emitted);
+
MOZ_MUST_USE bool tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript,
IonScript* ion, HandleObject obj, HandleId id,
bool* emitted);
diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp
index 3f9d9db88..b9a7c7b27 100644
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -221,6 +221,9 @@ DefaultJitOptions::DefaultJitOptions()
Warn(forcedRegisterAllocatorEnv, env);
}
+ // Toggles whether unboxed plain objects can be created by the VM.
+ SET_DEFAULT(disableUnboxedObjects, true);
+
// Test whether Atomics are allowed in asm.js code.
SET_DEFAULT(asmJSAtomicsEnable, false);
diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h
index 719ee14d9..076980b4e 100644
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -91,6 +91,9 @@ struct DefaultJitOptions
mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold;
mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
+ // The options below affect the rest of the VM, and not just the JIT.
+ bool disableUnboxedObjects;
+
DefaultJitOptions();
bool isSmallFunction(JSScript* script) const;
void setEagerCompilation();
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index 236354530..a1c336391 100644
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -30,6 +30,7 @@
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/StringObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using mozilla::ArrayLength;
using mozilla::AssertedCast;
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp
index 0cf31adb3..1e4ee170f 100644
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4783,14 +4783,15 @@ MCreateThisWithTemplate::canRecoverOnBailout() const
MObjectState::MObjectState(MObjectState* state)
: numSlots_(state->numSlots_),
- numFixedSlots_(state->numFixedSlots_)
+ numFixedSlots_(state->numFixedSlots_),
+ operandIndex_(state->operandIndex_)
{
// This instruction is only used as a summary for bailout paths.
setResultType(MIRType::Object);
setRecoveredOnBailout();
}
-MObjectState::MObjectState(JSObject* templateObject)
+MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex)
{
// This instruction is only used as a summary for bailout paths.
setResultType(MIRType::Object);
@@ -4801,6 +4802,8 @@ MObjectState::MObjectState(JSObject* templateObject)
NativeObject* nativeObject = &templateObject->as<NativeObject>();
numSlots_ = nativeObject->slotSpan();
numFixedSlots_ = nativeObject->numFixedSlots();
+
+ operandIndex_ = operandIndex;
}
JSObject*
@@ -4860,7 +4863,7 @@ MObjectState::New(TempAllocator& alloc, MDefinition* obj)
JSObject* templateObject = templateObjectOf(obj);
MOZ_ASSERT(templateObject, "Unexpected object creation.");
- MObjectState* res = new(alloc) MObjectState(templateObject);
+ MObjectState* res = new(alloc) MObjectState(templateObject, nullptr);
if (!res || !res->init(alloc, obj))
return nullptr;
return res;
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
index 0c1e77f80..cafdbab71 100644
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -375,7 +375,7 @@ class AliasSet {
Element = 1 << 1, // A Value member of obj->elements or
// a typed object.
UnboxedElement = 1 << 2, // An unboxed scalar or reference member of
- // typed object.
+ // typed object or unboxed object.
DynamicSlot = 1 << 3, // A Value member of obj->slots.
FixedSlot = 1 << 4, // A Value member of obj->fixedSlots().
DOMProperty = 1 << 5, // A DOM property
@@ -3758,9 +3758,14 @@ class MObjectState
{
private:
uint32_t numSlots_;
- uint32_t numFixedSlots_;
+ uint32_t numFixedSlots_; // valid if isUnboxed() == false.
+ OperandIndexMap* operandIndex_; // valid if isUnboxed() == true.
- MObjectState(JSObject *templateObject);
+ bool isUnboxed() const {
+ return operandIndex_ != nullptr;
+ }
+
+ MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex);
explicit MObjectState(MObjectState* state);
MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj);
@@ -3820,6 +3825,18 @@ class MObjectState
setSlot(slot + numFixedSlots(), def);
}
+ // Interface reserved for unboxed objects.
+ bool hasOffset(uint32_t offset) const {
+ MOZ_ASSERT(isUnboxed());
+ return offset < operandIndex_->map.length() && operandIndex_->map[offset] != 0;
+ }
+ MDefinition* getOffset(uint32_t offset) const {
+ return getOperand(operandIndex_->map[offset]);
+ }
+ void setOffset(uint32_t offset, MDefinition* def) {
+ replaceOperand(operandIndex_->map[offset], def);
+ }
+
MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
bool canRecoverOnBailout() const override {
return true;
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
index a739b9325..e50f68722 100644
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -126,14 +126,20 @@ MacroAssembler::guardTypeSetMightBeIncomplete(TypeSet* types, Register obj, Regi
{
// Type set guards might miss when an object's group changes. In this case
// either its old group's properties will become unknown, or it will change
- // to a native object. Jump to label if this might have happened for the
- // input object.
+ // to a native object with an original unboxed group. Jump to label if this
+ // might have happened for the input object.
if (types->unknownObject()) {
jump(label);
return;
}
+ loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
+ load32(Address(scratch, ObjectGroup::offsetOfFlags()), scratch);
+ and32(Imm32(OBJECT_FLAG_ADDENDUM_MASK), scratch);
+ branch32(Assembler::Equal,
+ scratch, Imm32(ObjectGroup::addendumOriginalUnboxedGroupValue()), label);
+
for (size_t i = 0; i < types->getObjectCount(); i++) {
if (JSObject* singleton = types->getSingletonNoBarrier(i)) {
movePtr(ImmGCPtr(singleton), scratch);
@@ -462,6 +468,243 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A
template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest,
bool allowDouble, Register temp, Label* fail);
+template <typename T>
+void
+MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output)
+{
+ switch (type) {
+ case JSVAL_TYPE_INT32: {
+ // Handle loading an int32 into a double reg.
+ if (output.type() == MIRType::Double) {
+ convertInt32ToDouble(address, output.typedReg().fpu());
+ break;
+ }
+ MOZ_FALLTHROUGH;
+ }
+
+ case JSVAL_TYPE_BOOLEAN:
+ case JSVAL_TYPE_STRING: {
+ Register outReg;
+ if (output.hasValue()) {
+ outReg = output.valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output.type() == MIRTypeFromValueType(type));
+ outReg = output.typedReg().gpr();
+ }
+
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ load8ZeroExtend(address, outReg);
+ break;
+ case JSVAL_TYPE_INT32:
+ load32(address, outReg);
+ break;
+ case JSVAL_TYPE_STRING:
+ loadPtr(address, outReg);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ if (output.hasValue())
+ tagValue(type, outReg, output.valueReg());
+ break;
+ }
+
+ case JSVAL_TYPE_OBJECT:
+ if (output.hasValue()) {
+ Register scratch = output.valueReg().scratchReg();
+ loadPtr(address, scratch);
+
+ Label notNull, done;
+ branchPtr(Assembler::NotEqual, scratch, ImmWord(0), &notNull);
+
+ moveValue(NullValue(), output.valueReg());
+ jump(&done);
+
+ bind(&notNull);
+ tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg());
+
+ bind(&done);
+ } else {
+ // Reading null can't be possible here, as otherwise the result
+ // would be a value (either because null has been read before or
+ // because there is a barrier).
+ Register reg = output.typedReg().gpr();
+ loadPtr(address, reg);
+#ifdef DEBUG
+ Label ok;
+ branchTestPtr(Assembler::NonZero, reg, reg, &ok);
+ assumeUnreachable("Null not possible");
+ bind(&ok);
+#endif
+ }
+ break;
+
+ case JSVAL_TYPE_DOUBLE:
+ // Note: doubles in unboxed objects are not accessed through other
+ // views and do not need canonicalization.
+ if (output.hasValue())
+ loadValue(address, output.valueReg());
+ else
+ loadDouble(address, output.typedReg().fpu());
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+template void
+MacroAssembler::loadUnboxedProperty(Address address, JSValueType type,
+ TypedOrValueRegister output);
+
+template void
+MacroAssembler::loadUnboxedProperty(BaseIndex address, JSValueType type,
+ TypedOrValueRegister output);
+
+static void
+StoreUnboxedFailure(MacroAssembler& masm, Label* failure)
+{
+ // Storing a value to an unboxed property is a fallible operation and
+ // the caller must provide a failure label if a particular unboxed store
+ // might fail. Sometimes, however, a store that cannot succeed (such as
+ // storing a string to an int32 property) will be marked as infallible.
+ // This can only happen if the code involved is unreachable.
+ if (failure)
+ masm.jump(failure);
+ else
+ masm.assumeUnreachable("Incompatible write to unboxed property");
+}
+
+template <typename T>
+void
+MacroAssembler::storeUnboxedProperty(T address, JSValueType type,
+ const ConstantOrRegister& value, Label* failure)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (value.constant()) {
+ if (value.value().isBoolean())
+ store8(Imm32(value.value().toBoolean()), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else if (value.reg().hasTyped()) {
+ if (value.reg().type() == MIRType::Boolean)
+ store8(value.reg().typedReg().gpr(), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else {
+ if (failure)
+ branchTestBoolean(Assembler::NotEqual, value.reg().valueReg(), failure);
+ storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 1);
+ }
+ break;
+
+ case JSVAL_TYPE_INT32:
+ if (value.constant()) {
+ if (value.value().isInt32())
+ store32(Imm32(value.value().toInt32()), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else if (value.reg().hasTyped()) {
+ if (value.reg().type() == MIRType::Int32)
+ store32(value.reg().typedReg().gpr(), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else {
+ if (failure)
+ branchTestInt32(Assembler::NotEqual, value.reg().valueReg(), failure);
+ storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 4);
+ }
+ break;
+
+ case JSVAL_TYPE_DOUBLE:
+ if (value.constant()) {
+ if (value.value().isNumber()) {
+ loadConstantDouble(value.value().toNumber(), ScratchDoubleReg);
+ storeDouble(ScratchDoubleReg, address);
+ } else {
+ StoreUnboxedFailure(*this, failure);
+ }
+ } else if (value.reg().hasTyped()) {
+ if (value.reg().type() == MIRType::Int32) {
+ convertInt32ToDouble(value.reg().typedReg().gpr(), ScratchDoubleReg);
+ storeDouble(ScratchDoubleReg, address);
+ } else if (value.reg().type() == MIRType::Double) {
+ storeDouble(value.reg().typedReg().fpu(), address);
+ } else {
+ StoreUnboxedFailure(*this, failure);
+ }
+ } else {
+ ValueOperand reg = value.reg().valueReg();
+ Label notInt32, end;
+ branchTestInt32(Assembler::NotEqual, reg, &notInt32);
+ int32ValueToDouble(reg, ScratchDoubleReg);
+ storeDouble(ScratchDoubleReg, address);
+ jump(&end);
+ bind(&notInt32);
+ if (failure)
+ branchTestDouble(Assembler::NotEqual, reg, failure);
+ storeValue(reg, address);
+ bind(&end);
+ }
+ break;
+
+ case JSVAL_TYPE_OBJECT:
+ if (value.constant()) {
+ if (value.value().isObjectOrNull())
+ storePtr(ImmGCPtr(value.value().toObjectOrNull()), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else if (value.reg().hasTyped()) {
+ MOZ_ASSERT(value.reg().type() != MIRType::Null);
+ if (value.reg().type() == MIRType::Object)
+ storePtr(value.reg().typedReg().gpr(), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else {
+ if (failure) {
+ Label ok;
+ branchTestNull(Assembler::Equal, value.reg().valueReg(), &ok);
+ branchTestObject(Assembler::NotEqual, value.reg().valueReg(), failure);
+ bind(&ok);
+ }
+ storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
+ }
+ break;
+
+ case JSVAL_TYPE_STRING:
+ if (value.constant()) {
+ if (value.value().isString())
+ storePtr(ImmGCPtr(value.value().toString()), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else if (value.reg().hasTyped()) {
+ if (value.reg().type() == MIRType::String)
+ storePtr(value.reg().typedReg().gpr(), address);
+ else
+ StoreUnboxedFailure(*this, failure);
+ } else {
+ if (failure)
+ branchTestString(Assembler::NotEqual, value.reg().valueReg(), failure);
+ storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
+ }
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+template void
+MacroAssembler::storeUnboxedProperty(Address address, JSValueType type,
+ const ConstantOrRegister& value, Label* failure);
+
+template void
+MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
+ const ConstantOrRegister& value, Label* failure);
+
// Inlined version of gc::CheckAllocatorState that checks the bare essentials
// and bails for anything that cannot be handled with our jit allocators.
void
@@ -1009,6 +1252,10 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
offset += sizeof(uintptr_t);
}
+ } else if (templateObj->is<UnboxedPlainObject>()) {
+ storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando()));
+ if (initContents)
+ initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>());
} else {
MOZ_CRASH("Unknown object");
}
@@ -1030,6 +1277,29 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
}
void
+MacroAssembler::initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject)
+{
+ const UnboxedLayout& layout = templateObject->layoutDontCheckGeneration();
+
+ // Initialize reference fields of the object, per UnboxedPlainObject::create.
+ if (const int32_t* list = layout.traceList()) {
+ while (*list != -1) {
+ storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty),
+ Address(object, UnboxedPlainObject::offsetOfData() + *list));
+ list++;
+ }
+ list++;
+ while (*list != -1) {
+ storePtr(ImmWord(0),
+ Address(object, UnboxedPlainObject::offsetOfData() + *list));
+ list++;
+ }
+ // Unboxed objects don't have Values to initialize.
+ MOZ_ASSERT(*(list + 1) == -1);
+ }
+}
+
+void
MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result,
Label* fail)
{
diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h
index d5cc95839..6ee989463 100644
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -36,6 +36,7 @@
#include "vm/ProxyObject.h"
#include "vm/Shape.h"
#include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
using mozilla::FloatingPoint;
@@ -1625,6 +1626,17 @@ class MacroAssembler : public MacroAssemblerSpecific
void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
unsigned numElems = 0);
+ // Load a property from an UnboxedPlainObject.
+ template <typename T>
+ void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
+
+ // Store a property to an UnboxedPlainObject, without triggering barriers.
+ // If failure is null, the value definitely has a type suitable for storing
+ // in the property.
+ template <typename T>
+ void storeUnboxedProperty(T address, JSValueType type,
+ const ConstantOrRegister& value, Label* failure);
+
Register extractString(const Address& address, Register scratch) {
return extractObject(address, scratch);
}
@@ -1701,6 +1713,8 @@ class MacroAssembler : public MacroAssemblerSpecific
LiveRegisterSet liveRegs, Label* fail,
TypedArrayObject* templateObj, TypedArrayLength lengthKind);
+ void initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject);
+
void newGCString(Register result, Register temp, Label* fail);
void newGCFatInlineString(Register result, Register temp, Label* fail);
diff --git a/js/src/jit/OptimizationTracking.cpp b/js/src/jit/OptimizationTracking.cpp
index 7d72795a0..b42634d43 100644
--- a/js/src/jit/OptimizationTracking.cpp
+++ b/js/src/jit/OptimizationTracking.cpp
@@ -15,9 +15,11 @@
#include "jit/JitcodeMap.h"
#include "jit/JitSpewer.h"
#include "js/TrackedOptimizationInfo.h"
+#include "vm/UnboxedObject.h"
#include "vm/ObjectGroup-inl.h"
#include "vm/TypeInference-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -844,6 +846,8 @@ MaybeConstructorFromType(TypeSet::Type ty)
return nullptr;
ObjectGroup* obj = ty.group();
TypeNewScript* newScript = obj->newScript();
+ if (!newScript && obj->maybeUnboxedLayout())
+ newScript = obj->unboxedLayout().newScript();
return newScript ? newScript->function() : nullptr;
}
diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp
index 793b631df..8fe6ee3fb 100644
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -30,6 +30,7 @@
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -1539,12 +1540,37 @@ RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const
RootedObject object(cx, &iter.read().toObject());
RootedValue val(cx);
- RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
- MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
+ if (object->is<UnboxedPlainObject>()) {
+ const UnboxedLayout& layout = object->as<UnboxedPlainObject>().layout();
- for (size_t i = 0; i < numSlots(); i++) {
- val = iter.read();
- nativeObject->setSlot(i, val);
+ RootedId id(cx);
+ RootedValue receiver(cx, ObjectValue(*object));
+ const UnboxedLayout::PropertyVector& properties = layout.properties();
+ for (size_t i = 0; i < properties.length(); i++) {
+ val = iter.read();
+
+ // This is the default placeholder value of MObjectState, when no
+ // properties are defined yet.
+ if (val.isUndefined())
+ continue;
+
+ id = NameToId(properties[i].name);
+ ObjectOpResult result;
+
+ // SetProperty can only fail due to OOM.
+ if (!SetProperty(cx, object, id, val, receiver, result))
+ return false;
+ if (!result)
+ return result.reportError(cx, object, id);
+ }
+ } else {
+ RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
+ MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
+
+ for (size_t i = 0; i < numSlots(); i++) {
+ val = iter.read();
+ nativeObject->setSlot(i, val);
+ }
}
val.setObject(*object);
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
index 97ba52349..be9ceee2e 100644
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -285,6 +285,10 @@ class ObjectMemoryView : public MDefinitionVisitorDefaultNoop
void visitGuardShape(MGuardShape* ins);
void visitFunctionEnvironment(MFunctionEnvironment* ins);
void visitLambda(MLambda* ins);
+
+ private:
+ void storeOffset(MInstruction* ins, size_t offset, MDefinition* value);
+ void loadOffset(MInstruction* ins, size_t offset);
};
const char* ObjectMemoryView::phaseName = "Scalar Replacement of Object";
@@ -626,6 +630,35 @@ ObjectMemoryView::visitLambda(MLambda* ins)
ins->setIncompleteObject();
}
+void
+ObjectMemoryView::storeOffset(MInstruction* ins, size_t offset, MDefinition* value)
+{
+ // Clone the state and update the slot value.
+ MOZ_ASSERT(state_->hasOffset(offset));
+ state_ = BlockState::Copy(alloc_, state_);
+ if (!state_) {
+ oom_ = true;
+ return;
+ }
+
+ state_->setOffset(offset, value);
+ ins->block()->insertBefore(ins, state_);
+
+ // Remove original instruction.
+ ins->block()->discard(ins);
+}
+
+void
+ObjectMemoryView::loadOffset(MInstruction* ins, size_t offset)
+{
+ // Replace load by the slot value.
+ MOZ_ASSERT(state_->hasOffset(offset));
+ ins->replaceAllUsesWith(state_->getOffset(offset));
+
+ // Remove original instruction.
+ ins->block()->discard(ins);
+}
+
static bool
IndexOf(MDefinition* ins, int32_t* res)
{
diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp
index 05a95824f..313957462 100644
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2244,7 +2244,8 @@ IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy)
if (!isDOMProxy && !obj->isNative()) {
if (obj == holder)
return false;
- if (!obj->is<TypedObject>())
+ if (!obj->is<UnboxedPlainObject>() &&
+ !obj->is<TypedObject>())
{
return false;
}
@@ -2572,6 +2573,9 @@ CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, PropertyName* name,
} else if (curObj != obj) {
// Non-native objects are only handled as the original receiver.
return false;
+ } else if (curObj->is<UnboxedPlainObject>()) {
+ if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
+ return false;
} else if (curObj->is<TypedObject>()) {
if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
return false;
@@ -2836,15 +2840,34 @@ GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard,
{
Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup());
Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape());
+ Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
if (guard.group) {
masm.loadPtr(groupAddress, scratch);
masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
+
+ if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
+ // Guard the unboxed object has no expando object.
+ masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
+ }
}
if (guard.shape) {
masm.loadPtr(shapeAddress, scratch);
- masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
+ if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
+ // Guard the unboxed object has a matching expando object.
+ masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
+ Label done;
+ masm.push(object);
+ masm.loadPtr(expandoAddress, object);
+ masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
+ masm.pop(object);
+ masm.jump(failure);
+ masm.bind(&done);
+ masm.pop(object);
+ } else {
+ masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
+ }
}
}
@@ -4228,7 +4251,8 @@ DoNewObject(JSContext* cx, void* payload, ICNewObject_Fallback* stub, MutableHan
return false;
if (!stub->invalid() &&
- !templateObject->as<PlainObject>().hasDynamicSlots())
+ (templateObject->is<UnboxedPlainObject>() ||
+ !templateObject->as<PlainObject>().hasDynamicSlots()))
{
JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject);
if (!code)
diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp
index 652c23bf1..1cb731de8 100644
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -28,7 +28,7 @@
#include "vm/NativeObject-inl.h"
#include "vm/StringObject-inl.h"
#include "vm/TypeInference-inl.h"
-#include "gc/StoreBuffer-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
index 73243d918..87d1a5acc 100644
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -45,6 +45,7 @@
#include "vm/Caches-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/NativeObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::gc;
diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h
index 05ff40b43..d7c6765ff 100644
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -528,6 +528,9 @@ struct JSCompartment
// table manages references from such typed objects to their buffers.
js::ObjectWeakMap* lazyArrayBuffers;
+ // All unboxed layouts in the compartment.
+ mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
+
// WebAssembly state for the compartment.
js::wasm::Compartment wasm;
diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp
index bdb3c0a4d..f818bb290 100644
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -268,7 +268,7 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls)
if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
return Proxy::getBuiltinClass(cx, obj, cls);
- if (obj->is<PlainObject>())
+ if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>())
*cls = ESClass::Object;
else if (obj->is<ArrayObject>())
*cls = ESClass::Array;
diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp
index ef1291079..3d7f294fe 100644
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -42,7 +42,6 @@
#include "frontend/BytecodeCompiler.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
-#include "gc/StoreBuffer-inl.h"
#include "jit/BaselineJIT.h"
#include "js/MemoryMetrics.h"
#include "js/Proxy.h"
@@ -54,6 +53,7 @@
#include "vm/RegExpStaticsObject.h"
#include "vm/Shape.h"
#include "vm/TypedArrayCommon.h"
+#include "vm/UnboxedObject-inl.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h
index 98e740142..889033143 100644
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -32,6 +32,19 @@
#include "vm/ShapedObject-inl.h"
#include "vm/TypeInference-inl.h"
+namespace js {
+
+// This is needed here for ensureShape() below.
+inline bool
+MaybeConvertUnboxedObjectToNative(ExclusiveContext* cx, JSObject* obj)
+{
+ if (obj->is<UnboxedPlainObject>())
+ return UnboxedPlainObject::convertToNative(cx->asJSContext(), obj);
+ return true;
+}
+
+} // namespace js
+
inline js::Shape*
JSObject::maybeShape() const
{
@@ -44,6 +57,8 @@ JSObject::maybeShape() const
inline js::Shape*
JSObject::ensureShape(js::ExclusiveContext* cx)
{
+ if (!js::MaybeConvertUnboxedObjectToNative(cx, this))
+ return nullptr;
js::Shape* shape = maybeShape();
MOZ_ASSERT(shape);
return shape;
diff --git a/js/src/moz.build b/js/src/moz.build
index 5287aef00..c0cef9929 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -355,6 +355,7 @@ UNIFIED_SOURCES += [
'vm/UbiNode.cpp',
'vm/UbiNodeCensus.cpp',
'vm/UbiNodeShortestPaths.cpp',
+ 'vm/UnboxedObject.cpp',
'vm/Unicode.cpp',
'vm/Value.cpp',
'vm/WeakMapPtr.cpp',
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
index 4b0c858a4..e077bbbf8 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7539,6 +7539,7 @@ SetWorkerContextOptions(JSContext* cx)
.setWasm(enableWasm)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
+ .setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues);
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
@@ -7708,6 +7709,7 @@ main(int argc, char** argv, char** envp)
|| !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
|| !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
|| !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
+ || !op.addBoolOption('\0', "no-unboxed-objects", "Disable creating unboxed plain objects")
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 3cf9b57f6..b87d12924 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -5016,6 +5016,11 @@ js::NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+ if (templateObject->group()->maybeUnboxedLayout()) {
+ RootedObjectGroup group(cx, templateObject->group());
+ return UnboxedPlainObject::create(cx, group, newKind);
+ }
+
JSObject* obj = CopyInitializerObject(cx, templateObject.as<PlainObject>(), newKind);
if (!obj)
return nullptr;
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp
index e7de36f13..8b7543d12 100644
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -388,6 +388,33 @@ NativeObject::setLastPropertyMakeNonNative(Shape* shape)
shape_ = shape;
}
+void
+NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
+{
+ MOZ_ASSERT(getClass()->isNative());
+ MOZ_ASSERT(shape->getObjectClass()->isNative());
+ MOZ_ASSERT(!shape->inDictionary());
+
+ // This method is used to convert unboxed objects into native objects. In
+ // this case, the shape_ field was previously used to store other data and
+ // this should be treated as an initialization.
+ shape_.init(shape);
+
+ slots_ = nullptr;
+ elements_ = emptyObjectElements;
+
+ size_t oldSpan = shape->numFixedSlots();
+ size_t newSpan = shape->slotSpan();
+
+ initializeSlotRange(0, oldSpan);
+
+ // A failure at this point will leave the object as a mutant, and we
+ // can't recover.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan))
+ oomUnsafe.crash("NativeObject::setLastPropertyMakeNative");
+}
+
bool
NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span)
{
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index b17920ef9..c774308af 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -470,6 +470,11 @@ class NativeObject : public ShapedObject
// that are (temporarily) inconsistent.
void setLastPropertyMakeNonNative(Shape* shape);
+ // As for setLastProperty(), but changes the class associated with the
+ // object to a native one. The object's type has already been changed, and
+ // this brings the shape into sync with it.
+ void setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape);
+
// Newly-created TypedArrays that map a SharedArrayBuffer are
// marked as shared by giving them an ObjectElements that has the
// ObjectElements::SHARED_MEMORY flag set.
diff --git a/js/src/vm/ObjectGroup-inl.h b/js/src/vm/ObjectGroup-inl.h
index d41343be6..9074f4d97 100644
--- a/js/src/vm/ObjectGroup-inl.h
+++ b/js/src/vm/ObjectGroup-inl.h
@@ -108,6 +108,20 @@ ObjectGroup::maybePreliminaryObjects()
return maybePreliminaryObjectsDontCheckGeneration();
}
+inline UnboxedLayout*
+ObjectGroup::maybeUnboxedLayout()
+{
+ maybeSweep(nullptr);
+ return maybeUnboxedLayoutDontCheckGeneration();
+}
+
+inline UnboxedLayout&
+ObjectGroup::unboxedLayout()
+{
+ maybeSweep(nullptr);
+ return unboxedLayoutDontCheckGeneration();
+}
+
} // namespace js
#endif /* vm_ObjectGroup_inl_h */
diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp
index 91070b3f6..95fcada94 100644
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -18,10 +18,11 @@
#include "vm/ArrayObject.h"
#include "vm/Shape.h"
#include "vm/TaggedProto.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
-#include "vm/NativeObject-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
@@ -55,6 +56,7 @@ ObjectGroup::finalize(FreeOp* fop)
if (newScriptDontCheckGeneration())
newScriptDontCheckGeneration()->clear();
fop->delete_(newScriptDontCheckGeneration());
+ fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
if (maybePreliminaryObjectsDontCheckGeneration())
maybePreliminaryObjectsDontCheckGeneration()->clear();
fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
@@ -81,6 +83,8 @@ ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
size_t n = 0;
if (TypeNewScript* newScript = newScriptDontCheckGeneration())
n += newScript->sizeOfIncludingThis(mallocSizeOf);
+ if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration())
+ n += layout->sizeOfIncludingThis(mallocSizeOf);
return n;
}
@@ -529,7 +533,8 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
if (p) {
ObjectGroup* group = p->group;
MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
- MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_);
+ MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
+ group->clasp() == &UnboxedPlainObject::class_);
MOZ_ASSERT(group->proto() == proto);
return group;
}
@@ -969,6 +974,46 @@ js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj,
}
}
}
+ } else if (newObj->is<UnboxedPlainObject>()) {
+ const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout();
+ const int32_t* traceList = layout.traceList();
+ if (!traceList)
+ return true;
+
+ uint8_t* newData = newObj->as<UnboxedPlainObject>().data();
+ uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data();
+
+ for (; *traceList != -1; traceList++) {}
+ traceList++;
+ for (; *traceList != -1; traceList++) {
+ JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList);
+ JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList);
+
+ if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj)) {
+ for (size_t i = 1; i < ncompare; i++) {
+ if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
+ uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data();
+ JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList);
+ if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) {
+ if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj))
+ return false;
+ }
+ }
+ }
+ }
+ }
}
return true;
@@ -1192,6 +1237,12 @@ ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_
RootedObjectGroup group(cx, p->value().group);
+ // Watch for existing groups which now use an unboxed layout.
+ if (group->maybeUnboxedLayout()) {
+ MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties);
+ return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties);
+ }
+
// Update property types according to the properties we are about to add.
// Do this before we do anything which can GC, which might move or remove
// this table entry.
diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h
index 0b6eaee51..553cb8366 100644
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -20,6 +20,7 @@
namespace js {
class TypeDescr;
+class UnboxedLayout;
class PreliminaryObjectArrayWithTemplate;
class TypeNewScript;
@@ -153,6 +154,16 @@ class ObjectGroup : public gc::TenuredCell
// For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate.
Addendum_PreliminaryObjects,
+ // When objects in this group have an unboxed representation, the
+ // addendum stores an UnboxedLayout (which might have a TypeNewScript
+ // as well, if the group is also constructed using 'new').
+ Addendum_UnboxedLayout,
+
+ // If this group is used by objects that have been converted from an
+ // unboxed representation and/or have the same allocation kind as such
+ // objects, the addendum points to that unboxed group.
+ Addendum_OriginalUnboxedGroup,
+
// When used by typed objects, the addendum stores a TypeDescr.
Addendum_TypeDescr
};
@@ -174,6 +185,7 @@ class ObjectGroup : public gc::TenuredCell
return nullptr;
}
+ TypeNewScript* anyNewScript();
void detachNewScript(bool writeBarrier, ObjectGroup* replacement);
ObjectGroupFlags flagsDontCheckGeneration() const {
@@ -213,6 +225,34 @@ class ObjectGroup : public gc::TenuredCell
maybePreliminaryObjectsDontCheckGeneration();
}
+ inline UnboxedLayout* maybeUnboxedLayout();
+ inline UnboxedLayout& unboxedLayout();
+
+ UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const {
+ if (addendumKind() == Addendum_UnboxedLayout)
+ return reinterpret_cast<UnboxedLayout*>(addendum_);
+ return nullptr;
+ }
+
+ UnboxedLayout& unboxedLayoutDontCheckGeneration() const {
+ MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
+ return *maybeUnboxedLayoutDontCheckGeneration();
+ }
+
+ void setUnboxedLayout(UnboxedLayout* layout) {
+ setAddendum(Addendum_UnboxedLayout, layout);
+ }
+
+ ObjectGroup* maybeOriginalUnboxedGroup() const {
+ if (addendumKind() == Addendum_OriginalUnboxedGroup)
+ return reinterpret_cast<ObjectGroup*>(addendum_);
+ return nullptr;
+ }
+
+ void setOriginalUnboxedGroup(ObjectGroup* group) {
+ setAddendum(Addendum_OriginalUnboxedGroup, group);
+ }
+
TypeDescr* maybeTypeDescr() {
// Note: there is no need to sweep when accessing the type descriptor
// of an object, as it is strongly held and immutable.
@@ -273,8 +313,9 @@ class ObjectGroup : public gc::TenuredCell
* that can be read out of that property in actual JS objects. In native
* objects, property types account for plain data properties (those with a
* slot and no getter or setter hook) and dense elements. In typed objects
- * property types account for object and value properties and elements in
- * the object.
+ * and unboxed objects, property types account for object and value
+ * properties and elements in the object, and expando properties in unboxed
+ * objects.
*
* For accesses on these properties, the correspondence is as follows:
*
@@ -297,9 +338,10 @@ class ObjectGroup : public gc::TenuredCell
* 2. Array lengths are special cased by the compiler and VM and are not
* reflected in property types.
*
- * 3. In typed objects, the initial values of properties (null pointers and
- * undefined values) are not reflected in the property types. These
- * values are always possible when reading the property.
+ * 3. In typed objects (but not unboxed objects), the initial values of
+ * properties (null pointers and undefined values) are not reflected in
+ * the property types. These values are always possible when reading the
+ * property.
*
* We establish these by using write barriers on calls to setProperty and
* defineProperty which are on native properties, and on any jitcode which
@@ -413,6 +455,12 @@ class ObjectGroup : public gc::TenuredCell
return &flags_;
}
+ // Get the bit pattern stored in an object's addendum when it has an
+ // original unboxed group.
+ static inline int32_t addendumOriginalUnboxedGroupValue() {
+ return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT;
+ }
+
inline uint32_t basePropertyCount();
private:
@@ -463,8 +511,8 @@ class ObjectGroup : public gc::TenuredCell
NewObjectKind newKind,
NewArrayKind arrayKind = NewArrayKind::Normal);
- // Create a PlainObject with the specified properties and a group specialized
- // for those properties.
+ // Create a PlainObject or UnboxedPlainObject with the specified properties
+ // and a group specialized for those properties.
static JSObject* newPlainObject(ExclusiveContext* cx,
IdValuePair* properties, size_t nproperties,
NewObjectKind newKind);
diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp
index e95e8a208..e37bf8ee5 100644
--- a/js/src/vm/ReceiverGuard.cpp
+++ b/js/src/vm/ReceiverGuard.cpp
@@ -15,7 +15,11 @@ ReceiverGuard::ReceiverGuard(JSObject* obj)
: group(nullptr), shape(nullptr)
{
if (obj) {
- if (obj->is<TypedObject>()) {
+ if (obj->is<UnboxedPlainObject>()) {
+ group = obj->group();
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ shape = expando->lastProperty();
+ } else if (obj->is<TypedObject>()) {
group = obj->group();
} else {
shape = obj->maybeShape();
@@ -28,7 +32,9 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
{
if (group) {
const Class* clasp = group->clasp();
- if (IsTypedObjectClass(clasp)) {
+ if (clasp == &UnboxedPlainObject::class_) {
+ // Keep both group and shape.
+ } else if (IsTypedObjectClass(clasp)) {
this->shape = nullptr;
} else {
this->group = nullptr;
@@ -39,6 +45,10 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
/* static */ int32_t
HeapReceiverGuard::keyBits(JSObject* obj)
{
+ if (obj->is<UnboxedPlainObject>()) {
+ // Both the group and shape need to be guarded for unboxed plain objects.
+ return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1;
+ }
if (obj->is<TypedObject>()) {
// Only the group needs to be guarded for typed objects.
return 2;
diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h
index 2af252cea..da47fa898 100644
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -23,6 +23,7 @@
#include "vm/SharedArrayObject.h"
#include "vm/StringObject.h"
#include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
#include "jscntxtinlines.h"
@@ -284,6 +285,10 @@ TypeIdString(jsid id)
*/
struct AutoEnterAnalysis
{
+ // For use when initializing an UnboxedLayout. The UniquePtr's destructor
+ // must run when GC is not suppressed.
+ UniquePtr<UnboxedLayout> unboxedLayoutToCleanUp;
+
// Prevent GC activity in the middle of analysis.
gc::AutoSuppressGC suppressGC;
diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp
index 2b1fa0e3b..39206539b 100644
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -35,6 +35,7 @@
#include "vm/Opcodes.h"
#include "vm/Shape.h"
#include "vm/Time.h"
+#include "vm/UnboxedObject.h"
#include "jsatominlines.h"
#include "jsscriptinlines.h"
@@ -296,6 +297,9 @@ js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Val
return true;
}
}
+ JSObject* obj = &value.toObject();
+ if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup())
+ return true;
}
if (!types->hasType(type)) {
@@ -1944,6 +1948,33 @@ class ConstraintDataFreezeObjectForTypedArrayData
}
};
+// Constraint which triggers recompilation if an unboxed object in some group
+// is converted to a native object.
+class ConstraintDataFreezeObjectForUnboxedConvertedToNative
+{
+ public:
+ ConstraintDataFreezeObjectForUnboxedConvertedToNative()
+ {}
+
+ const char* kind() { return "freezeObjectForUnboxedConvertedToNative"; }
+
+ bool invalidateOnNewType(TypeSet::Type type) { return false; }
+ bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
+ bool invalidateOnNewObjectState(ObjectGroup* group) {
+ return group->unboxedLayout().nativeGroup() != nullptr;
+ }
+
+ bool constraintHolds(JSContext* cx,
+ const HeapTypeSetKey& property, TemporaryTypeSet* expected)
+ {
+ return !invalidateOnNewObjectState(property.object()->maybeGroup());
+ }
+
+ bool shouldSweep() { return false; }
+
+ JSCompartment* maybeCompartment() { return nullptr; }
+};
+
} /* anonymous namespace */
void
@@ -2478,6 +2509,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
bool
js::ClassCanHaveExtraProperties(const Class* clasp)
{
+ if (clasp == &UnboxedPlainObject::class_)
+ return false;
return clasp->getResolve()
|| clasp->getOpsLookupProperty()
|| clasp->getOpsGetProperty()
@@ -2768,6 +2801,15 @@ js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, j
// from acquiring the fully initialized group.
if (group->newScript() && group->newScript()->initializedGroup())
AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type);
+
+ // Maintain equivalent type information for unboxed object groups and their
+ // corresponding native group. Since type sets might contain the unboxed
+ // group but not the native group, this ensures optimizations based on the
+ // unboxed group are valid for the native group.
+ if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
+ AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type);
+ if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
+ AddTypePropertyId(cx, unboxedGroup, nullptr, id, type);
}
void
@@ -2839,6 +2881,12 @@ ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags)
// acquired properties analysis.
if (newScript() && newScript()->initializedGroup())
newScript()->initializedGroup()->setFlags(cx, flags);
+
+ // Propagate flag changes between unboxed and corresponding native groups.
+ if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
+ maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags);
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
+ unboxedGroup->setFlags(cx, flags);
}
void
@@ -2871,6 +2919,23 @@ ObjectGroup::markUnknown(ExclusiveContext* cx)
prop->types.setNonDataProperty(cx);
}
}
+
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
+ MarkObjectGroupUnknownProperties(cx, unboxedGroup);
+ if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
+ MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup());
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
+ MarkObjectGroupUnknownProperties(cx, unboxedGroup);
+}
+
+TypeNewScript*
+ObjectGroup::anyNewScript()
+{
+ if (newScript())
+ return newScript();
+ if (maybeUnboxedLayout())
+ return unboxedLayout().newScript();
+ return nullptr;
}
void
@@ -2880,7 +2945,7 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
// analyzed, remove it from the newObjectGroups table so that it will not be
// produced by calling 'new' on the associated function anymore.
// The TypeNewScript is not actually destroyed.
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
MOZ_ASSERT(newScript);
if (newScript->analyzed()) {
@@ -2899,7 +2964,10 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
MOZ_ASSERT(!replacement);
}
- setAddendum(Addendum_None, nullptr, writeBarrier);
+ if (this->newScript())
+ setAddendum(Addendum_None, nullptr, writeBarrier);
+ else
+ unboxedLayout().setNewScript(nullptr, writeBarrier);
}
void
@@ -2910,7 +2978,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
if (!isMarked())
return;
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -2925,7 +2993,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
void
ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/)
{
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -3390,6 +3458,22 @@ PreliminaryObjectArray::sweep()
for (size_t i = 0; i < COUNT; i++) {
JSObject** ptr = &objects[i];
if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) {
+ // Before we clear this reference, change the object's group to the
+ // Object.prototype group. This is done to ensure JSObject::finalize
+ // sees a NativeObject Class even if we change the current group's
+ // Class to one of the unboxed object classes in the meantime. If
+ // the compartment's global is dead, we don't do anything as the
+ // group's Class is not going to change in that case.
+ JSObject* obj = *ptr;
+ GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal();
+ if (global && !obj->isSingleton()) {
+ JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object);
+ obj->setGroup(objectProto->groupRaw());
+ MOZ_ASSERT(obj->is<NativeObject>());
+ MOZ_ASSERT(obj->getClass() == objectProto->getClass());
+ MOZ_ASSERT(!obj->getClass()->hasFinalize());
+ }
+
*ptr = nullptr;
}
}
@@ -3489,11 +3573,16 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro
}
}
- // Since the preliminary objects still reflect the template object's
- // properties, and all objects in the future will be created with those
- // properties, the properties can be marked as definitive for objects in
- // the group.
- group->addDefiniteProperties(cx, shape());
+ if (group->maybeUnboxedLayout())
+ return;
+
+ if (shape()) {
+ // We weren't able to use an unboxed layout, but since the preliminary
+ // objects still reflect the template object's properties, and all
+ // objects in the future will be created with those properties, the
+ // properties can be marked as definite for objects in the group.
+ group->addDefiniteProperties(cx, shape());
+ }
}
/////////////////////////////////////////////////////////////////////
@@ -3507,6 +3596,7 @@ TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun)
{
MOZ_ASSERT(cx->zone()->types.activeAnalysis);
MOZ_ASSERT(!group->newScript());
+ MOZ_ASSERT(!group->maybeUnboxedLayout());
// rollbackPartiallyInitializedObjects expects function_ to be
// canonicalized.
@@ -3814,6 +3904,27 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
js_delete(preliminaryObjects);
preliminaryObjects = nullptr;
+ if (group->maybeUnboxedLayout()) {
+ // An unboxed layout was constructed for the group, and this has already
+ // been hooked into it.
+ MOZ_ASSERT(group->unboxedLayout().newScript() == this);
+ destroyNewScript.group = nullptr;
+
+ // Clear out the template object, which is not used for TypeNewScripts
+ // with an unboxed layout. Currently it is a mutant object with a
+ // non-native group and native shape, so make it safe for GC by changing
+ // its group to the default for its prototype.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_,
+ group->proto());
+ if (!plainGroup)
+ oomUnsafe.crash("TypeNewScript::maybeAnalyze");
+ templateObject_->setGroup(plainGroup);
+ templateObject_ = nullptr;
+
+ return true;
+ }
+
if (prefixShape->slotSpan() == templateObject()->slotSpan()) {
// The definite properties analysis found exactly the properties that
// are held in common by the preliminary objects. No further analysis
@@ -3927,6 +4038,12 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* g
continue;
}
+ if (thisv.toObject().is<UnboxedPlainObject>()) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
+ oomUnsafe.crash("rollbackPartiallyInitializedObjects");
+ }
+
// Found a matching frame.
RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
@@ -4120,6 +4237,12 @@ ConstraintTypeSet::sweep(Zone* zone, AutoClearTypeInferenceStateOnOOM& oom)
// Object sets containing objects with unknown properties might
// not be complete. Mark the type set as unknown, which it will
// be treated as during Ion compilation.
+ //
+ // Note that we don't have to do this when the type set might
+ // be missing the native group corresponding to an unboxed
+ // object group. In this case, the native group points to the
+ // unboxed object group via its addendum, so as long as objects
+ // with either group exist, neither group will be finalized.
flags |= TYPE_FLAG_ANYOBJECT;
clearObjects();
objectCount = 0;
@@ -4203,6 +4326,21 @@ ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom)
Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM;
EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM);
+ if (maybeUnboxedLayout()) {
+ // Remove unboxed layouts that are about to be finalized from the
+ // compartment wide list while we are still on the main thread.
+ ObjectGroup* group = this;
+ if (IsAboutToBeFinalizedUnbarriered(&group))
+ unboxedLayout().detachFromCompartment();
+
+ if (unboxedLayout().newScript())
+ unboxedLayout().newScript()->sweep();
+
+ // Discard constructor code to avoid holding onto ExecutablePools.
+ if (zone()->isGCCompacting())
+ unboxedLayout().setConstructorCode(nullptr);
+ }
+
if (maybePreliminaryObjects())
maybePreliminaryObjects()->sweep();
diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h
new file mode 100644
index 000000000..c1468a5b1
--- /dev/null
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_UnboxedObject_inl_h
+#define vm_UnboxedObject_inl_h
+
+#include "vm/UnboxedObject.h"
+
+#include "gc/StoreBuffer-inl.h"
+#include "vm/ArrayObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+namespace js {
+
+static inline Value
+GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ return BooleanValue(*p != 0);
+
+ case JSVAL_TYPE_INT32:
+ return Int32Value(*reinterpret_cast<int32_t*>(p));
+
+ case JSVAL_TYPE_DOUBLE: {
+ // During unboxed plain object creation, non-GC thing properties are
+ // left uninitialized. This is normally fine, since the properties will
+ // be filled in shortly, but if they are read before that happens we
+ // need to make sure that doubles are canonical.
+ double d = *reinterpret_cast<double*>(p);
+ if (maybeUninitialized)
+ return DoubleValue(JS::CanonicalizeNaN(d));
+ return DoubleValue(d);
+ }
+
+ case JSVAL_TYPE_STRING:
+ return StringValue(*reinterpret_cast<JSString**>(p));
+
+ case JSVAL_TYPE_OBJECT:
+ return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+static inline void
+SetUnboxedValueNoTypeChange(JSObject* unboxedObject,
+ uint8_t* p, JSValueType type, const Value& v,
+ bool preBarrier)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ *p = v.toBoolean();
+ return;
+
+ case JSVAL_TYPE_INT32:
+ *reinterpret_cast<int32_t*>(p) = v.toInt32();
+ return;
+
+ case JSVAL_TYPE_DOUBLE:
+ *reinterpret_cast<double*>(p) = v.toNumber();
+ return;
+
+ case JSVAL_TYPE_STRING: {
+ MOZ_ASSERT(!IsInsideNursery(v.toString()));
+ JSString** np = reinterpret_cast<JSString**>(p);
+ if (preBarrier)
+ JSString::writeBarrierPre(*np);
+ *np = v.toString();
+ return;
+ }
+
+ case JSVAL_TYPE_OBJECT: {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+
+ // Manually trigger post barriers on the whole object. If we treat
+ // the pointer as a HeapPtrObject we will get confused later if the
+ // object is converted to its native representation.
+ JSObject* obj = v.toObjectOrNull();
+ if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) {
+ JSRuntime* rt = unboxedObject->runtimeFromMainThread();
+ rt->gc.storeBuffer.putWholeCell(unboxedObject);
+ }
+
+ if (preBarrier)
+ JSObject::writeBarrierPre(*np);
+ *np = obj;
+ return;
+ }
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+static inline bool
+SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
+ uint8_t* p, JSValueType type, const Value& v, bool preBarrier)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (v.isBoolean()) {
+ *p = v.toBoolean();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_INT32:
+ if (v.isInt32()) {
+ *reinterpret_cast<int32_t*>(p) = v.toInt32();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_DOUBLE:
+ if (v.isNumber()) {
+ *reinterpret_cast<double*>(p) = v.toNumber();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_STRING:
+ if (v.isString()) {
+ MOZ_ASSERT(!IsInsideNursery(v.toString()));
+ JSString** np = reinterpret_cast<JSString**>(p);
+ if (preBarrier)
+ JSString::writeBarrierPre(*np);
+ *np = v.toString();
+ return true;
+ }
+ return false;
+
+ case JSVAL_TYPE_OBJECT:
+ if (v.isObjectOrNull()) {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+
+ // Update property types when writing object properties. Types for
+ // other properties were captured when the unboxed layout was
+ // created.
+ AddTypePropertyId(cx, unboxedObject, id, v);
+
+ // As above, trigger post barriers on the whole object.
+ JSObject* obj = v.toObjectOrNull();
+ if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) {
+ JSRuntime* rt = unboxedObject->runtimeFromMainThread();
+ rt->gc.storeBuffer.putWholeCell(unboxedObject);
+ }
+
+ if (preBarrier)
+ JSObject::writeBarrierPre(*np);
+ *np = obj;
+ return true;
+ }
+ return false;
+
+ default:
+ MOZ_CRASH("Invalid type for unboxed value");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+inline const UnboxedLayout&
+UnboxedPlainObject::layout() const
+{
+ return group()->unboxedLayout();
+}
+
+} // namespace js
+
+#endif // vm_UnboxedObject_inl_h
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp
new file mode 100644
index 000000000..2e017ca3b
--- /dev/null
+++ b/js/src/vm/UnboxedObject.cpp
@@ -0,0 +1,946 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/UnboxedObject-inl.h"
+
+#include "jit/BaselineIC.h"
+#include "jit/ExecutableAllocator.h"
+#include "jit/JitCommon.h"
+#include "jit/Linker.h"
+
+#include "jsobjinlines.h"
+
+#include "gc/Nursery-inl.h"
+#include "jit/MacroAssembler-inl.h"
+#include "vm/Shape-inl.h"
+
+using mozilla::ArrayLength;
+using mozilla::DebugOnly;
+using mozilla::PodCopy;
+
+using namespace js;
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedLayout
+/////////////////////////////////////////////////////////////////////
+
+void
+UnboxedLayout::trace(JSTracer* trc)
+{
+ for (size_t i = 0; i < properties_.length(); i++)
+ TraceManuallyBarrieredEdge(trc, &properties_[i].name, "unboxed_layout_name");
+
+ if (newScript())
+ newScript()->trace(trc);
+
+ TraceNullableEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
+ TraceNullableEdge(trc, &nativeShape_, "unboxed_layout_nativeShape");
+ TraceNullableEdge(trc, &allocationScript_, "unboxed_layout_allocationScript");
+ TraceNullableEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup");
+ TraceNullableEdge(trc, &constructorCode_, "unboxed_layout_constructorCode");
+}
+
+size_t
+UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+ return mallocSizeOf(this)
+ + properties_.sizeOfExcludingThis(mallocSizeOf)
+ + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
+ + mallocSizeOf(traceList());
+}
+
+void
+UnboxedLayout::setNewScript(TypeNewScript* newScript, bool writeBarrier /* = true */)
+{
+ if (newScript_ && writeBarrier)
+ TypeNewScript::writeBarrierPre(newScript_);
+ newScript_ = newScript;
+}
+
+// Constructor code returns a 0x1 value to indicate the constructor code should
+// be cleared.
+static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
+
+/* static */ bool
+UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
+{
+ gc::AutoSuppressGC suppress(cx);
+
+ using namespace jit;
+
+ if (!cx->compartment()->ensureJitCompartmentExists(cx))
+ return false;
+
+ UnboxedLayout& layout = group->unboxedLayout();
+ MOZ_ASSERT(!layout.constructorCode());
+
+ UnboxedPlainObject* templateObject = UnboxedPlainObject::create(cx, group, TenuredObject);
+ if (!templateObject)
+ return false;
+
+ JitContext jitContext(cx, nullptr);
+
+ MacroAssembler masm;
+
+ Register propertiesReg, newKindReg;
+#ifdef JS_CODEGEN_X86
+ propertiesReg = eax;
+ newKindReg = ecx;
+ masm.loadPtr(Address(masm.getStackPointer(), sizeof(void*)), propertiesReg);
+ masm.loadPtr(Address(masm.getStackPointer(), 2 * sizeof(void*)), newKindReg);
+#else
+ propertiesReg = IntArgReg0;
+ newKindReg = IntArgReg1;
+#endif
+
+#ifdef JS_CODEGEN_ARM64
+ // ARM64 communicates stack address via sp, but uses a pseudo-sp for addressing.
+ masm.initStackPtr();
+#endif
+
+ MOZ_ASSERT(propertiesReg.volatile_());
+ MOZ_ASSERT(newKindReg.volatile_());
+
+ AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
+ regs.take(propertiesReg);
+ regs.take(newKindReg);
+ Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny();
+
+ LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs);
+ masm.PushRegsInMask(savedNonVolatileRegisters);
+
+ // The scratch double register might be used by MacroAssembler methods.
+ if (ScratchDoubleReg.volatile_())
+ masm.push(ScratchDoubleReg);
+
+ Label failure, tenuredObject, allocated;
+ masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject);
+ masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()),
+ Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject);
+
+ // Allocate an object in the nursery
+ masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure,
+ /* initFixedSlots = */ false);
+
+ masm.jump(&allocated);
+ masm.bind(&tenuredObject);
+
+ // Allocate an object in the tenured heap.
+ masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure,
+ /* initFixedSlots = */ false);
+
+ // If any of the properties being stored are in the nursery, add a store
+ // buffer entry for the new object.
+ Label postBarrier;
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
+ Label notObject;
+ masm.branchTestObject(Assembler::NotEqual, valueAddress, &notObject);
+ Register valueObject = masm.extractObject(valueAddress, scratch1);
+ masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier);
+ masm.bind(&notObject);
+ }
+ }
+
+ masm.jump(&allocated);
+ masm.bind(&postBarrier);
+
+ LiveGeneralRegisterSet liveVolatileRegisters;
+ liveVolatileRegisters.add(propertiesReg);
+ if (object.volatile_())
+ liveVolatileRegisters.add(object);
+ masm.PushRegsInMask(liveVolatileRegisters);
+
+ masm.mov(ImmPtr(cx->runtime()), scratch1);
+ masm.setupUnalignedABICall(scratch2);
+ masm.passABIArg(scratch1);
+ masm.passABIArg(object);
+ masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
+
+ masm.PopRegsInMask(liveVolatileRegisters);
+
+ masm.bind(&allocated);
+
+ ValueOperand valueOperand;
+#ifdef JS_NUNBOX32
+ valueOperand = ValueOperand(scratch1, scratch2);
+#else
+ valueOperand = ValueOperand(scratch1);
+#endif
+
+ Label failureStoreOther, failureStoreObject;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
+ Address targetAddress(object, UnboxedPlainObject::offsetOfData() + property.offset);
+
+ masm.loadValue(valueAddress, valueOperand);
+
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ HeapTypeSet* types = group->maybeGetProperty(IdToTypeId(NameToId(property.name)));
+
+ Label notObject;
+ masm.branchTestObject(Assembler::NotEqual, valueOperand,
+ types->mightBeMIRType(MIRType::Null) ? &notObject : &failureStoreObject);
+
+ Register payloadReg = masm.extractObject(valueOperand, scratch1);
+
+ if (!types->hasType(TypeSet::AnyObjectType())) {
+ Register scratch = (payloadReg == scratch1) ? scratch2 : scratch1;
+ masm.guardObjectType(payloadReg, types, scratch, &failureStoreObject);
+ }
+
+ masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT,
+ TypedOrValueRegister(MIRType::Object,
+ AnyRegister(payloadReg)), nullptr);
+
+ if (notObject.used()) {
+ Label done;
+ masm.jump(&done);
+ masm.bind(&notObject);
+ masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
+ masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, NullValue(), nullptr);
+ masm.bind(&done);
+ }
+ } else {
+ masm.storeUnboxedProperty(targetAddress, property.type,
+ ConstantOrRegister(valueOperand), &failureStoreOther);
+ }
+ }
+
+ Label done;
+ masm.bind(&done);
+
+ if (object != ReturnReg)
+ masm.movePtr(object, ReturnReg);
+
+ // Restore non-volatile registers which were saved on entry.
+ if (ScratchDoubleReg.volatile_())
+ masm.pop(ScratchDoubleReg);
+ masm.PopRegsInMask(savedNonVolatileRegisters);
+
+ masm.abiret();
+
+ masm.bind(&failureStoreOther);
+
+ // There was a failure while storing a value which cannot be stored at all
+ // in the unboxed object. Initialize the object so it is safe for GC and
+ // return null.
+ masm.initUnboxedObjectContents(object, templateObject);
+
+ masm.bind(&failure);
+
+ masm.movePtr(ImmWord(0), object);
+ masm.jump(&done);
+
+ masm.bind(&failureStoreObject);
+
+ // There was a failure while storing a value to an object slot of the
+ // unboxed object. If the value is storable, the failure occurred due to
+ // incomplete type information in the object, so return a token to trigger
+ // regeneration of the jitcode after a new object is created in the VM.
+ {
+ Label isObject;
+ masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
+ masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
+ masm.bind(&isObject);
+ }
+
+ // Initialize the object so it is safe for GC.
+ masm.initUnboxedObjectContents(object, templateObject);
+
+ masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
+ masm.jump(&done);
+
+ Linker linker(masm);
+ AutoFlushICache afc("UnboxedObject");
+ JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
+ if (!code)
+ return false;
+
+ layout.setConstructorCode(code);
+ return true;
+}
+
+void
+UnboxedLayout::detachFromCompartment()
+{
+ if (isInList())
+ remove();
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+bool
+UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
+ const Value& v)
+{
+ uint8_t* p = &data_[property.offset];
+ return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v,
+ /* preBarrier = */ true);
+}
+
+Value
+UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
+ bool maybeUninitialized /* = false */)
+{
+ uint8_t* p = &data_[property.offset];
+ return GetUnboxedValue(p, property.type, maybeUninitialized);
+}
+
+void
+UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
+{
+ if (obj->as<UnboxedPlainObject>().expando_) {
+ TraceManuallyBarrieredEdge(trc,
+ reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
+ "unboxed_expando");
+ }
+
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+ const int32_t* list = layout.traceList();
+ if (!list)
+ return;
+
+ uint8_t* data = obj->as<UnboxedPlainObject>().data();
+ while (*list != -1) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
+ TraceEdge(trc, heap, "unboxed_string");
+ list++;
+ }
+ list++;
+ while (*list != -1) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
+ TraceNullableEdge(trc, heap, "unboxed_object");
+ list++;
+ }
+
+ // Unboxed objects don't have Values to trace.
+ MOZ_ASSERT(*(list + 1) == -1);
+}
+
+/* static */ UnboxedExpandoObject*
+UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
+{
+ if (obj->expando_)
+ return obj->expando_;
+
+ UnboxedExpandoObject* expando =
+ NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4);
+ if (!expando)
+ return nullptr;
+
+ // Don't track property types for expando objects. This allows Baseline
+ // and Ion AddSlot ICs to guard on the unboxed group without guarding on
+ // the expando group.
+ MarkObjectGroupUnknownProperties(cx, expando->group());
+
+ // If the expando is tenured then the original object must also be tenured.
+ // Otherwise barriers triggered on the original object for writes to the
+ // expando (as can happen in the JIT) won't see the tenured->nursery edge.
+ // See WholeCellEdges::mark.
+ MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
+
+ // As with setValue(), we need to manually trigger post barriers on the
+ // whole object. If we treat the field as a GCPtrObject and later
+ // convert the object to its native representation, we will end up with a
+ // corrupted store buffer entry.
+ if (IsInsideNursery(expando) && !IsInsideNursery(obj))
+ cx->runtime()->gc.storeBuffer.putWholeCell(obj);
+
+ obj->expando_ = expando;
+ return expando;
+}
+
+bool
+UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const
+{
+ if (layout().lookup(id))
+ return true;
+
+ if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id))
+ return true;
+
+ return false;
+}
+
+static bool
+PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup)
+{
+ HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id);
+ TypeSet::TypeList types;
+ if (!typeProperty->enumerateTypes(&types)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ for (size_t j = 0; j < types.length(); j++)
+ AddTypePropertyId(cx, newGroup, nullptr, id, types[j]);
+ return true;
+}
+
+static PlainObject*
+MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout)
+{
+ PlainObject* obj = NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(),
+ TenuredObject);
+ if (!obj)
+ return nullptr;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE))
+ return nullptr;
+ MOZ_ASSERT(obj->slotSpan() == i + 1);
+ MOZ_ASSERT(!obj->inDictionaryMode());
+ }
+
+ return obj;
+}
+
+/* static */ bool
+UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
+{
+ AutoEnterAnalysis enter(cx);
+
+ UnboxedLayout& layout = group->unboxedLayout();
+ Rooted<TaggedProto> proto(cx, group->proto());
+
+ MOZ_ASSERT(!layout.nativeGroup());
+
+ RootedObjectGroup replacementGroup(cx);
+
+ // Immediately clear any new script on the group. This is done by replacing
+ // the existing new script with one for a replacement default new group.
+ // This is done so that the size of the replacment group's objects is the
+ // same as that for the unboxed group, so that we do not see polymorphic
+ // slot accesses later on for sites that see converted objects from this
+ // group and objects that were allocated using the replacement new group.
+ if (layout.newScript()) {
+ replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout);
+ if (!templateObject)
+ return false;
+
+ TypeNewScript* replacementNewScript =
+ TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject);
+ if (!replacementNewScript)
+ return false;
+
+ replacementGroup->setNewScript(replacementNewScript);
+ gc::TraceTypeNewScript(replacementGroup);
+
+ group->clearNewScript(cx, replacementGroup);
+ }
+
+ // Similarly, if this group is keyed to an allocation site, replace its
+ // entry with a new group that has no unboxed layout.
+ if (layout.allocationScript()) {
+ RootedScript script(cx, layout.allocationScript());
+ jsbytecode* pc = layout.allocationPc();
+
+ replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
+ replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
+
+ cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object,
+ replacementGroup);
+
+ // Clear any baseline information at this opcode which might use the old group.
+ if (script->hasBaselineScript()) {
+ jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
+ jit::ICFallbackStub* fallback = entry.fallbackStub();
+ for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++)
+ iter.unlink(cx);
+ if (fallback->isNewObject_Fallback())
+ fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
+ else if (fallback->isNewArray_Fallback())
+ fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
+ }
+ }
+
+ size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
+
+ RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0));
+ if (!shape)
+ return false;
+
+ // Add shapes for each property, if this is for a plain object.
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+
+ Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name),
+ i, JSPROP_ENUMERATE, 0));
+ shape = cx->zone()->propertyTree.getChild(cx, shape, child);
+ if (!shape)
+ return false;
+ }
+
+ ObjectGroup* nativeGroup =
+ ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
+ group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
+ if (!nativeGroup)
+ return false;
+
+ // No sense propagating if we don't know what we started with.
+ if (!group->unknownProperties()) {
+ // Propagate all property types from the old group to the new group.
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ jsid id = NameToId(property.name);
+ if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
+ return false;
+
+ // If we are OOM we may not be able to propagate properties.
+ if (nativeGroup->unknownProperties())
+ break;
+
+ HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
+ if (nativeProperty && nativeProperty->canSetDefinite(i))
+ nativeProperty->setDefinite(i);
+ }
+ } else {
+ // If we skip, though, the new group had better agree.
+ MOZ_ASSERT(nativeGroup->unknownProperties());
+ }
+
+ layout.nativeGroup_ = nativeGroup;
+ layout.nativeShape_ = shape;
+ layout.replacementGroup_ = replacementGroup;
+
+ nativeGroup->setOriginalUnboxedGroup(group);
+
+ group->markStateChange(cx);
+
+ return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+ UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
+
+ if (!layout.nativeGroup()) {
+ if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
+ return false;
+
+ // makeNativeGroup can reentrantly invoke this method.
+ if (obj->is<PlainObject>())
+ return true;
+ }
+
+ AutoValueVector values(cx);
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ // We might be reading properties off the object which have not been
+ // initialized yet. Make sure any double values we read here are
+ // canonicalized.
+ if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
+ return false;
+ }
+
+ // We are eliminating the expando edge with the conversion, so trigger a
+ // pre barrier.
+ JSObject::writeBarrierPre(expando);
+
+ // Additionally trigger a post barrier on the expando itself. Whole cell
+ // store buffer entries can be added on the original unboxed object for
+ // writes to the expando (see WholeCellEdges::trace), so after conversion
+ // we need to make sure the expando itself will still be traced.
+ if (expando && !IsInsideNursery(expando))
+ cx->runtime()->gc.storeBuffer.putWholeCell(expando);
+
+ obj->setGroup(layout.nativeGroup());
+ obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
+
+ for (size_t i = 0; i < values.length(); i++)
+ obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
+
+ if (expando) {
+ // Add properties from the expando object to the object, in order.
+ // Suppress GC here, so that callers don't need to worry about this
+ // method collecting. The stuff below can only fail due to OOM, in
+ // which case the object will not have been completely filled back in.
+ gc::AutoSuppressGC suppress(cx);
+
+ Vector<jsid> ids(cx);
+ for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
+ if (!ids.append(r.front().propid()))
+ return false;
+ }
+ for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
+ if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
+ if (!ids.append(INT_TO_JSID(i)))
+ return false;
+ }
+ }
+ ::Reverse(ids.begin(), ids.end());
+
+ RootedPlainObject nobj(cx, &obj->as<PlainObject>());
+ Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
+ RootedId id(cx);
+ Rooted<PropertyDescriptor> desc(cx);
+ for (size_t i = 0; i < ids.length(); i++) {
+ id = ids[i];
+ if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
+ return false;
+ ObjectOpResult result;
+ if (!DefineProperty(cx, nobj, id, desc, result))
+ return false;
+ MOZ_ASSERT(result.ok());
+ }
+ }
+
+ return true;
+}
+
+/* static */
+UnboxedPlainObject*
+UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind)
+{
+ AutoSetNewObjectMetadata metadata(cx);
+
+ MOZ_ASSERT(group->clasp() == &class_);
+ gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
+
+ UnboxedPlainObject* res =
+ NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind);
+ if (!res)
+ return nullptr;
+
+ // Overwrite the dummy shape which was written to the object's expando field.
+ res->initExpando();
+
+ // Initialize reference fields of the object. All fields in the object will
+ // be overwritten shortly, but references need to be safe for the GC.
+ const int32_t* list = res->layout().traceList();
+ if (list) {
+ uint8_t* data = res->data();
+ while (*list != -1) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
+ heap->init(cx->names().empty);
+ list++;
+ }
+ list++;
+ while (*list != -1) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
+ heap->init(nullptr);
+ list++;
+ }
+ // Unboxed objects don't have Values to initialize.
+ MOZ_ASSERT(*(list + 1) == -1);
+ }
+
+ return res;
+}
+
+/* static */ JSObject*
+UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind, IdValuePair* properties)
+{
+ MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject);
+
+ UnboxedLayout& layout = group->unboxedLayout();
+
+ if (layout.constructorCode()) {
+ MOZ_ASSERT(cx->isJSContext());
+
+ typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind);
+ ConstructorCodeSignature function =
+ reinterpret_cast<ConstructorCodeSignature>(layout.constructorCode()->raw());
+
+ JSObject* obj;
+ {
+ JS::AutoSuppressGCAnalysis nogc;
+ obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind));
+ }
+ if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
+ return obj;
+
+ if (obj == reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
+ layout.setConstructorCode(nullptr);
+ }
+
+ UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind);
+ if (!obj)
+ return nullptr;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ if (!obj->setValue(cx, layout.properties()[i], properties[i].value))
+ return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
+ }
+
+#ifndef JS_CODEGEN_NONE
+ if (cx->isJSContext() &&
+ !group->unknownProperties() &&
+ !layout.constructorCode() &&
+ cx->asJSContext()->runtime()->jitSupportsFloatingPoint &&
+ jit::CanLikelyAllocateMoreExecutableMemory())
+ {
+ if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group))
+ return nullptr;
+ }
+#endif
+
+ return obj;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp)
+{
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ MarkNonNativePropertyFound<CanGC>(propp);
+ objp.set(obj);
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ objp.set(nullptr);
+ propp.set(nullptr);
+ return true;
+ }
+
+ return LookupProperty(cx, proto, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+ // This define is equivalent to setting an existing property.
+ if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value()))
+ return result.succeed();
+ }
+
+ // Trying to incompatibly redefine an existing property requires the
+ // object to be converted to a native object.
+ if (!convertToNative(cx, obj))
+ return false;
+
+ return DefineProperty(cx, obj, id, desc, result);
+ }
+
+ // Define the property on the expando object.
+ Rooted<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>()));
+ if (!expando)
+ return false;
+
+ // Update property types on the unboxed object as well.
+ AddTypePropertyId(cx, obj, id, desc.value());
+
+ return DefineProperty(cx, expando, id, desc, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ *foundp = true;
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ *foundp = false;
+ return true;
+ }
+
+ return HasProperty(cx, proto, id, foundp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ vp.set(obj->as<UnboxedPlainObject>().getValue(*property));
+ return true;
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ RootedObject nexpando(cx, expando);
+ return GetProperty(cx, nexpando, receiver, id, vp);
+ }
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
+ return result.succeed();
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return SetProperty(cx, obj, id, v, receiver, result);
+ }
+
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ // Update property types on the unboxed object as well.
+ AddTypePropertyId(cx, obj, id, v);
+
+ RootedObject nexpando(cx, expando);
+ return SetProperty(cx, nexpando, id, v, receiver, result);
+ }
+ }
+
+ return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
+
+ if (const UnboxedLayout::Property* property = layout.lookup(id)) {
+ desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property));
+ desc.setAttributes(JSPROP_ENUMERATE);
+ desc.object().set(obj);
+ return true;
+ }
+
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
+ if (expando->containsShapeOrElement(cx, id)) {
+ RootedObject nexpando(cx, expando);
+ if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc))
+ return false;
+ if (desc.object() == nexpando)
+ desc.object().set(obj);
+ return true;
+ }
+ }
+
+ desc.object().set(nullptr);
+ return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result)
+{
+ if (!convertToNative(cx, obj))
+ return false;
+ return DeleteProperty(cx, obj, id, result);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly)
+{
+ // Ignore expando properties here, they are special-cased by the property
+ // enumeration code.
+
+ const UnboxedLayout::PropertyVector& unboxed = obj->as<UnboxedPlainObject>().layout().properties();
+ for (size_t i = 0; i < unboxed.length(); i++) {
+ if (!properties.append(NameToId(unboxed[i].name)))
+ return false;
+ }
+
+ return true;
+}
+
+const Class UnboxedExpandoObject::class_ = {
+ "UnboxedExpandoObject",
+ 0
+};
+
+static const ClassOps UnboxedPlainObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ UnboxedPlainObject::trace,
+};
+
+static const ObjectOps UnboxedPlainObjectObjectOps = {
+ UnboxedPlainObject::obj_lookupProperty,
+ UnboxedPlainObject::obj_defineProperty,
+ UnboxedPlainObject::obj_hasProperty,
+ UnboxedPlainObject::obj_getProperty,
+ UnboxedPlainObject::obj_setProperty,
+ UnboxedPlainObject::obj_getOwnPropertyDescriptor,
+ UnboxedPlainObject::obj_deleteProperty,
+ nullptr, /* getElements */
+ UnboxedPlainObject::obj_enumerate,
+ nullptr /* funToString */
+};
+
+const Class UnboxedPlainObject::class_ = {
+ js_Object_str,
+ Class::NON_NATIVE |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
+ JSCLASS_DELAY_METADATA_BUILDER,
+ &UnboxedPlainObjectClassOps,
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ &UnboxedPlainObjectObjectOps
+};
+
+/////////////////////////////////////////////////////////////////////
+// API
+/////////////////////////////////////////////////////////////////////
+
+static inline Value
+NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ return values[(*valueCursor)++];
+}
+
+void
+UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ initExpando();
+ memset(data(), 0, layout().size());
+ for (size_t i = 0; i < layout().properties().length(); i++)
+ JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
+}
diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h
new file mode 100644
index 000000000..ba66434bc
--- /dev/null
+++ b/js/src/vm/UnboxedObject.h
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_UnboxedObject_h
+#define vm_UnboxedObject_h
+
+#include "jsgc.h"
+#include "jsobj.h"
+
+#include "vm/Runtime.h"
+#include "vm/TypeInference.h"
+
+namespace js {
+
+// Memory required for an unboxed value of a given type. Returns zero for types
+// which can't be used for unboxed objects.
+static inline size_t
+UnboxedTypeSize(JSValueType type)
+{
+ switch (type) {
+ case JSVAL_TYPE_BOOLEAN: return 1;
+ case JSVAL_TYPE_INT32: return 4;
+ case JSVAL_TYPE_DOUBLE: return 8;
+ case JSVAL_TYPE_STRING: return sizeof(void*);
+ case JSVAL_TYPE_OBJECT: return sizeof(void*);
+ default: return 0;
+ }
+}
+
+static inline bool
+UnboxedTypeNeedsPreBarrier(JSValueType type)
+{
+ return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT;
+}
+
+static inline bool
+UnboxedTypeNeedsPostBarrier(JSValueType type)
+{
+ return type == JSVAL_TYPE_OBJECT;
+}
+
+// Class tracking information specific to unboxed objects.
+class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
+{
+ public:
+ struct Property {
+ PropertyName* name;
+ uint32_t offset;
+ JSValueType type;
+
+ Property()
+ : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
+ {}
+ };
+
+ typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
+
+ private:
+ // If objects in this group have ever been converted to native objects,
+ // these store the corresponding native group and initial shape for such
+ // objects. Type information for this object is reflected in nativeGroup.
+ GCPtrObjectGroup nativeGroup_;
+ GCPtrShape nativeShape_;
+
+ // Any script/pc which the associated group is created for.
+ GCPtrScript allocationScript_;
+ jsbytecode* allocationPc_;
+
+ // If nativeGroup is set and this object originally had a TypeNewScript or
+ // was keyed to an allocation site, this points to the group which replaced
+ // this one. This link is only needed to keep the replacement group from
+ // being GC'ed. If it were GC'ed and a new one regenerated later, that new
+ // group might have a different allocation kind from this group.
+ GCPtrObjectGroup replacementGroup_;
+
+ // The following members are only used for unboxed plain objects.
+
+ // All properties on objects with this layout, in enumeration order.
+ PropertyVector properties_;
+
+ // Byte size of the data for objects with this layout.
+ size_t size_;
+
+ // Any 'new' script information associated with this layout.
+ TypeNewScript* newScript_;
+
+ // List for use in tracing objects with this layout. This has the same
+ // structure as the trace list on a TypeDescr.
+ int32_t* traceList_;
+
+ // If this layout has been used to construct script or JSON constant
+ // objects, this code might be filled in to more quickly fill in objects
+ // from an array of values.
+ GCPtrJitCode constructorCode_;
+
+ public:
+ UnboxedLayout()
+ : nativeGroup_(nullptr), nativeShape_(nullptr),
+ allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
+ size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr)
+ {}
+
+ bool initProperties(const PropertyVector& properties, size_t size) {
+ size_ = size;
+ return properties_.appendAll(properties);
+ }
+
+ ~UnboxedLayout() {
+ if (newScript_)
+ newScript_->clear();
+ js_delete(newScript_);
+ js_free(traceList_);
+
+ nativeGroup_.init(nullptr);
+ nativeShape_.init(nullptr);
+ replacementGroup_.init(nullptr);
+ constructorCode_.init(nullptr);
+ }
+
+ void detachFromCompartment();
+
+ const PropertyVector& properties() const {
+ return properties_;
+ }
+
+ TypeNewScript* newScript() const {
+ return newScript_;
+ }
+
+ void setNewScript(TypeNewScript* newScript, bool writeBarrier = true);
+
+ JSScript* allocationScript() const {
+ return allocationScript_;
+ }
+
+ jsbytecode* allocationPc() const {
+ return allocationPc_;
+ }
+
+ void setAllocationSite(JSScript* script, jsbytecode* pc) {
+ allocationScript_ = script;
+ allocationPc_ = pc;
+ }
+
+ const int32_t* traceList() const {
+ return traceList_;
+ }
+
+ void setTraceList(int32_t* traceList) {
+ traceList_ = traceList;
+ }
+
+ const Property* lookup(JSAtom* atom) const {
+ for (size_t i = 0; i < properties_.length(); i++) {
+ if (properties_[i].name == atom)
+ return &properties_[i];
+ }
+ return nullptr;
+ }
+
+ const Property* lookup(jsid id) const {
+ if (JSID_IS_STRING(id))
+ return lookup(JSID_TO_ATOM(id));
+ return nullptr;
+ }
+
+ size_t size() const {
+ return size_;
+ }
+
+ ObjectGroup* nativeGroup() const {
+ return nativeGroup_;
+ }
+
+ Shape* nativeShape() const {
+ return nativeShape_;
+ }
+
+ jit::JitCode* constructorCode() const {
+ return constructorCode_;
+ }
+
+ void setConstructorCode(jit::JitCode* code) {
+ constructorCode_ = code;
+ }
+
+ inline gc::AllocKind getAllocKind() const;
+
+ void trace(JSTracer* trc);
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ static bool makeNativeGroup(JSContext* cx, ObjectGroup* group);
+ static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group);
+};
+
+// Class for expando objects holding extra properties given to an unboxed plain
+// object. These objects behave identically to normal native plain objects, and
+// have a separate Class to distinguish them for memory usage reporting.
+class UnboxedExpandoObject : public NativeObject
+{
+ public:
+ static const Class class_;
+};
+
+// Class for a plain object using an unboxed representation. The physical
+// layout of these objects is identical to that of an InlineTypedObject, though
+// these objects use an UnboxedLayout instead of a TypeDescr to keep track of
+// how their properties are stored.
+class UnboxedPlainObject : public JSObject
+{
+ // Optional object which stores extra properties on this object. This is
+ // not automatically barriered to avoid problems if the object is converted
+ // to a native. See ensureExpando().
+ UnboxedExpandoObject* expando_;
+
+ // Start of the inline data, which immediately follows the group and extra properties.
+ uint8_t data_[1];
+
+ public:
+ static const Class class_;
+
+ static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp);
+
+ static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result);
+
+ static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
+
+ static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp);
+
+ static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result);
+
+ static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc);
+
+ static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result);
+
+ static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly);
+ static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
+
+ inline const UnboxedLayout& layout() const;
+
+ const UnboxedLayout& layoutDontCheckGeneration() const {
+ return group()->unboxedLayoutDontCheckGeneration();
+ }
+
+ uint8_t* data() {
+ return &data_[0];
+ }
+
+ UnboxedExpandoObject* maybeExpando() const {
+ return expando_;
+ }
+
+ void initExpando() {
+ expando_ = nullptr;
+ }
+
+ // For use during GC.
+ JSObject** addressOfExpando() {
+ return reinterpret_cast<JSObject**>(&expando_);
+ }
+
+ bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const;
+
+ static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj);
+
+ bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v);
+ Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false);
+
+ static bool convertToNative(JSContext* cx, JSObject* obj);
+ static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind);
+ static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
+ NewObjectKind newKind, IdValuePair* properties);
+
+ void fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor);
+
+ static void trace(JSTracer* trc, JSObject* object);
+
+ static size_t offsetOfExpando() {
+ return offsetof(UnboxedPlainObject, expando_);
+ }
+
+ static size_t offsetOfData() {
+ return offsetof(UnboxedPlainObject, data_[0]);
+ }
+};
+
+inline gc::AllocKind
+UnboxedLayout::getAllocKind() const
+{
+ MOZ_ASSERT(size());
+ return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
+}
+
+} // namespace js
+
+namespace JS {
+
+template <>
+struct DeletePolicy<js::UnboxedLayout> : public js::GCManagedDeletePolicy<js::UnboxedLayout>
+{};
+
+} /* namespace JS */
+
+#endif /* vm_UnboxedObject_h */