summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--js/src/builtin/ModuleObject.cpp2
-rw-r--r--js/src/frontend/BytecodeEmitter.cpp17
-rw-r--r--js/src/frontend/BytecodeEmitter.h2
-rw-r--r--js/src/gc/Marking.cpp48
-rw-r--r--js/src/gc/Tracer.cpp69
-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/AliasAnalysisShared.cpp6
-rw-r--r--js/src/jit/BaselineCacheIR.cpp68
-rw-r--r--js/src/jit/BaselineCompiler.cpp12
-rw-r--r--js/src/jit/BaselineCompiler.h1
-rw-r--r--js/src/jit/BaselineIC.cpp482
-rw-r--r--js/src/jit/BaselineIC.h78
-rw-r--r--js/src/jit/BaselineInspector.cpp79
-rw-r--r--js/src/jit/BaselineInspector.h11
-rw-r--r--js/src/jit/CacheIR.cpp79
-rw-r--r--js/src/jit/CacheIR.h25
-rw-r--r--js/src/jit/CodeGenerator.cpp474
-rw-r--r--js/src/jit/CodeGenerator.h7
-rw-r--r--js/src/jit/IonAnalysis.cpp4
-rw-r--r--js/src/jit/IonBuilder.cpp654
-rw-r--r--js/src/jit/IonBuilder.h35
-rw-r--r--js/src/jit/IonCaches.cpp471
-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/Lowering.cpp72
-rw-r--r--js/src/jit/Lowering.h7
-rw-r--r--js/src/jit/MCallOptimize.cpp90
-rw-r--r--js/src/jit/MIR.cpp245
-rw-r--r--js/src/jit/MIR.h340
-rw-r--r--js/src/jit/MOpcodes.h7
-rw-r--r--js/src/jit/MacroAssembler.cpp309
-rw-r--r--js/src/jit/MacroAssembler.h17
-rw-r--r--js/src/jit/OptimizationTracking.cpp2
-rw-r--r--js/src/jit/Recover.cpp38
-rw-r--r--js/src/jit/ScalarReplacement.cpp150
-rw-r--r--js/src/jit/SharedIC.cpp40
-rw-r--r--js/src/jit/VMFunctions.cpp29
-rw-r--r--js/src/jit/VMFunctions.h6
-rw-r--r--js/src/jit/shared/LIR-shared.h138
-rw-r--r--js/src/jit/shared/LOpcodes-shared.h7
-rw-r--r--js/src/jsapi.cpp3
-rw-r--r--js/src/jsapi.h33
-rw-r--r--js/src/jsarray.cpp530
-rw-r--r--js/src/jsarray.h41
-rw-r--r--js/src/jscompartment.h3
-rw-r--r--js/src/jsfriendapi.cpp4
-rw-r--r--js/src/jsgc.cpp6
-rw-r--r--js/src/jsiter.cpp42
-rw-r--r--js/src/jsobj.cpp119
-rw-r--r--js/src/jsobjinlines.h17
-rw-r--r--js/src/jsstr.cpp10
-rw-r--r--js/src/jsstr.h2
-rw-r--r--js/src/moz.build1
-rw-r--r--js/src/shell/js.cpp9
-rw-r--r--js/src/vm/ErrorObject.cpp8
-rw-r--r--js/src/vm/Interpreter-inl.h15
-rw-r--r--js/src/vm/Interpreter.cpp28
-rw-r--r--js/src/vm/JSONParser.cpp4
-rw-r--r--js/src/vm/NativeObject-inl.h32
-rw-r--r--js/src/vm/NativeObject.cpp44
-rw-r--r--js/src/vm/NativeObject.h28
-rw-r--r--js/src/vm/ObjectGroup-inl.h14
-rw-r--r--js/src/vm/ObjectGroup.cpp158
-rw-r--r--js/src/vm/ObjectGroup.h72
-rw-r--r--js/src/vm/Opcodes.h12
-rw-r--r--js/src/vm/ReceiverGuard.cpp19
-rw-r--r--js/src/vm/ReceiverGuard.h5
-rw-r--r--js/src/vm/RegExpObject.cpp3
-rw-r--r--js/src/vm/Stack.cpp2
-rw-r--r--js/src/vm/Stack.h2
-rw-r--r--js/src/vm/TypeInference-inl.h5
-rw-r--r--js/src/vm/TypeInference.cpp176
-rw-r--r--js/src/vm/TypeInference.h5
-rw-r--r--js/src/vm/UnboxedObject-inl.h840
-rw-r--r--js/src/vm/UnboxedObject.cpp2132
-rw-r--r--js/src/vm/UnboxedObject.h531
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp4
-rw-r--r--modules/libpref/init/all.js1
84 files changed, 8483 insertions, 803 deletions
diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp
index 575bab0b0..b275cb968 100644
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -927,7 +927,7 @@ ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValu
ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports)
{
MOZ_ASSERT(!self->namespace_());
- MOZ_ASSERT(exports->is<ArrayObject>());
+ MOZ_ASSERT(exports->is<ArrayObject>() || exports->is<UnboxedArrayObject>());
RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self));
if (!ns)
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp
index f4574b248..8cd06feac 100644
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -6495,8 +6495,8 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje
}
MOZ_ASSERT(idx == count);
- ArrayObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(),
- newKind, arrayKind);
+ JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(),
+ newKind, arrayKind);
if (!obj)
return false;
@@ -9623,7 +9623,7 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs
return false;
}
- if (!emitArray(args, argc))
+ if (!emitArray(args, argc, JSOP_SPREADCALLARRAY))
return false;
if (optCodeEmitted) {
@@ -10138,11 +10138,11 @@ BytecodeEmitter::emitArrayLiteral(ParseNode* pn)
}
}
- return emitArray(pn->pn_head, pn->pn_count);
+ return emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY);
}
bool
-BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count)
+BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op)
{
/*
@@ -10153,6 +10153,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count)
* to avoid dup'ing and popping the array as each element is added, as
* JSOP_SETELEM/JSOP_SETPROP would do.
*/
+ MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY);
uint32_t nspread = 0;
for (ParseNode* elt = pn; elt; elt = elt->pn_next) {
@@ -10173,7 +10174,7 @@ BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count)
// For arrays with spread, this is a very pessimistic allocation, the
// minimum possible final size.
- if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) // ARRAY
+ if (!emitUint32Operand(op, count - nspread)) // ARRAY
return false;
ParseNode* pn2 = pn;
@@ -11314,8 +11315,8 @@ BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offs
/* Maybe this offset was already set to a four-byte value. */
if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
/* Insert three dummy bytes that will be overwritten shortly. */
- if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) {
- ReportAllocationOverflow(cx);
+ if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) {
+ ReportAllocationOverflow(cx);
return false;
}
jssrcnote dummy = 0;
diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h
index 8ad409c11..77f77599c 100644
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -543,7 +543,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn);
- MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count);
+ MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
MOZ_MUST_USE bool emitArrayComp(ParseNode* pn);
MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);
diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp
index 262fc8cbc..43e325394 100644
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -18,7 +18,6 @@
#include "builtin/ModuleObject.h"
#include "gc/GCInternals.h"
#include "gc/Policy.h"
-#include "gc/StoreBuffer-inl.h"
#include "jit/IonCode.h"
#include "js/SliceBudget.h"
#include "vm/ArgumentsObject.h"
@@ -29,6 +28,7 @@
#include "vm/Shape.h"
#include "vm/Symbol.h"
#include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
#include "wasm/WasmJS.h"
#include "jscompartmentinlines.h"
@@ -37,6 +37,7 @@
#include "gc/Nursery-inl.h"
#include "vm/String-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::gc;
@@ -1394,6 +1395,14 @@ js::ObjectGroup::traceChildren(JSTracer* trc)
if (maybePreliminaryObjects())
maybePreliminaryObjects()->trace(trc);
+ if (maybeUnboxedLayout())
+ unboxedLayout().trace(trc);
+
+ if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) {
+ TraceManuallyBarrieredEdge(trc, &unboxedGroup, "group_original_unboxed_group");
+ setOriginalUnboxedGroup(unboxedGroup);
+ }
+
if (JSObject* descr = maybeTypeDescr()) {
TraceManuallyBarrieredEdge(trc, &descr, "group_type_descr");
setTypeDescr(&descr->as<TypeDescr>());
@@ -1427,6 +1436,12 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group)
if (group->maybePreliminaryObjects())
group->maybePreliminaryObjects()->trace(this);
+ if (group->maybeUnboxedLayout())
+ group->unboxedLayout().trace(this);
+
+ if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
+ traverseEdge(group, unboxedGroup);
+
if (TypeDescr* descr = group->maybeTypeDescr())
traverseEdge(group, static_cast<JSObject*>(descr));
@@ -1469,6 +1484,23 @@ CallTraceHook(Functor f, JSTracer* trc, JSObject* obj, CheckGeneration check, Ar
return nullptr;
}
+ if (clasp == &UnboxedPlainObject::class_) {
+ JSObject** pexpando = obj->as<UnboxedPlainObject>().addressOfExpando();
+ if (*pexpando)
+ f(pexpando, mozilla::Forward<Args>(args)...);
+
+ UnboxedPlainObject& unboxed = obj->as<UnboxedPlainObject>();
+ const UnboxedLayout& layout = check == CheckGeneration::DoChecks
+ ? unboxed.layout()
+ : unboxed.layoutDontCheckGeneration();
+ if (layout.traceList()) {
+ VisitTraceList(f, layout.traceList(), unboxed.data(),
+ mozilla::Forward<Args>(args)...);
+ }
+
+ return nullptr;
+ }
+
clasp->doTrace(trc, obj);
if (!clasp->isNative())
@@ -2261,6 +2293,18 @@ static inline void
TraceWholeCell(TenuringTracer& mover, JSObject* object)
{
mover.traceObject(object);
+
+ // Additionally trace the expando object attached to any unboxed plain
+ // objects. Baseline and Ion can write properties to the expando while
+ // only adding a post barrier to the owning unboxed object. Note that
+ // it isn't possible for a nursery unboxed object to have a tenured
+ // expando, so that adding a post barrier on the original object will
+ // capture any tenured->nursery edges in the expando as well.
+
+ if (object->is<UnboxedPlainObject>()) {
+ if (UnboxedExpandoObject* expando = object->as<UnboxedPlainObject>().maybeExpando())
+ expando->traceChildren(&mover);
+ }
}
static inline void
@@ -2504,6 +2548,8 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<TypedArrayObject>()) {
tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
+ } else if (src->is<UnboxedArrayObject>()) {
+ tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<ArgumentsObject>()) {
tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<ProxyObject>()) {
diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp
index 3416464dd..63cd9b08a 100644
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -201,15 +201,80 @@ gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, Shape* shape)
} while (shape);
}
+// Object groups can point to other object groups via an UnboxedLayout or the
+// the original unboxed group link. There can potentially be deep or cyclic
+// chains of such groups to trace through without going through a thing that
+// participates in cycle collection. These need to be handled iteratively to
+// avoid blowing the stack when running the cycle collector's callback tracer.
+struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer
+{
+ explicit ObjectGroupCycleCollectorTracer(JS::CallbackTracer* innerTracer)
+ : JS::CallbackTracer(innerTracer->runtime(), DoNotTraceWeakMaps),
+ innerTracer(innerTracer)
+ {}
+
+ void onChild(const JS::GCCellPtr& thing) override;
+
+ JS::CallbackTracer* innerTracer;
+ Vector<ObjectGroup*, 4, SystemAllocPolicy> seen, worklist;
+};
+
+void
+ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing)
+{
+ if (thing.is<BaseShape>()) {
+ // The CC does not care about BaseShapes, and no additional GC things
+ // will be reached by following this edge.
+ return;
+ }
+
+ if (thing.is<JSObject>() || thing.is<JSScript>()) {
+ // Invoke the inner cycle collector callback on this child. It will not
+ // recurse back into TraceChildren.
+ innerTracer->onChild(thing);
+ return;
+ }
+
+ if (thing.is<ObjectGroup>()) {
+ // If this group is required to be in an ObjectGroup chain, trace it
+ // via the provided worklist rather than continuing to recurse.
+ ObjectGroup& group = thing.as<ObjectGroup>();
+ if (group.maybeUnboxedLayout()) {
+ for (size_t i = 0; i < seen.length(); i++) {
+ if (seen[i] == &group)
+ return;
+ }
+ if (seen.append(&group) && worklist.append(&group)) {
+ return;
+ } else {
+ // If append fails, keep tracing normally. The worst that will
+ // happen is we end up overrecursing.
+ }
+ }
+ }
+
+ TraceChildren(this, thing.asCell(), thing.kind());
+}
+
void
gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, ObjectGroup* group)
{
MOZ_ASSERT(trc->isCallbackTracer());
- group->traceChildren(trc);
-}
+ // Early return if this group is not required to be in an ObjectGroup chain.
+ if (!group->maybeUnboxedLayout())
+ return group->traceChildren(trc);
+
+ ObjectGroupCycleCollectorTracer groupTracer(trc->asCallbackTracer());
+ group->traceChildren(&groupTracer);
+ while (!groupTracer.worklist.empty()) {
+ ObjectGroup* innerGroup = groupTracer.worklist.popCopy();
+ innerGroup->traceChildren(&groupTracer);
+ }
+}
+
/*** Traced Edge Printer *************************************************************************/
static size_t
diff --git a/js/src/jit-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/AliasAnalysisShared.cpp b/js/src/jit/AliasAnalysisShared.cpp
index 400626b33..99c23d2a3 100644
--- a/js/src/jit/AliasAnalysisShared.cpp
+++ b/js/src/jit/AliasAnalysisShared.cpp
@@ -91,6 +91,10 @@ GetObject(const MDefinition* ins)
case MDefinition::Op_Elements:
case MDefinition::Op_MaybeCopyElementsForWrite:
case MDefinition::Op_MaybeToDoubleElement:
+ case MDefinition::Op_UnboxedArrayLength:
+ case MDefinition::Op_UnboxedArrayInitializedLength:
+ case MDefinition::Op_IncrementUnboxedArrayInitializedLength:
+ case MDefinition::Op_SetUnboxedArrayInitializedLength:
case MDefinition::Op_TypedArrayLength:
case MDefinition::Op_SetTypedObjectOffset:
case MDefinition::Op_SetDisjointTypedElements:
@@ -108,6 +112,8 @@ GetObject(const MDefinition* ins)
case MDefinition::Op_GuardObjectGroup:
case MDefinition::Op_GuardObjectIdentity:
case MDefinition::Op_GuardClass:
+ case MDefinition::Op_GuardUnboxedExpando:
+ case MDefinition::Op_LoadUnboxedExpando:
case MDefinition::Op_LoadSlot:
case MDefinition::Op_StoreSlot:
case MDefinition::Op_InArray:
diff --git a/js/src/jit/BaselineCacheIR.cpp b/js/src/jit/BaselineCacheIR.cpp
index 67c80473b..bf96932d1 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:
@@ -787,6 +787,9 @@ BaselineCacheIRCompiler::emitGuardClass()
case GuardClassKind::Array:
clasp = &ArrayObject::class_;
break;
+ case GuardClassKind::UnboxedArray:
+ clasp = &UnboxedArrayObject::class_;
+ break;
case GuardClassKind::MappedArguments:
clasp = &MappedArgumentsObject::class_;
break;
@@ -815,6 +818,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 +874,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;
@@ -951,6 +1004,19 @@ BaselineCacheIRCompiler::emitLoadInt32ArrayLengthResult()
}
bool
+BaselineCacheIRCompiler::emitLoadUnboxedArrayLengthResult()
+{
+ Register obj = allocator.useRegister(masm, reader.objOperandId());
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), R0.scratchReg());
+ masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+
+ // The int32 type was monitored when attaching the stub, so we can
+ // just return.
+ emitReturnFromIC();
+ return true;
+}
+
+bool
BaselineCacheIRCompiler::emitLoadArgumentsObjectLengthResult()
{
Register obj = allocator.useRegister(masm, reader.objOperandId());
diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp
index d254b9826..ae5a2e666 100644
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2050,7 +2050,13 @@ BaselineCompiler::emit_JSOP_NEWARRAY()
return true;
}
-typedef ArrayObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap);
+bool
+BaselineCompiler::emit_JSOP_SPREADCALLARRAY()
+{
+ return emit_JSOP_NEWARRAY();
+}
+
+typedef JSObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap);
const VMFunction jit::NewArrayCopyOnWriteInfo =
FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray, "NewDenseCopyOnWriteArray");
@@ -4181,14 +4187,14 @@ BaselineCompiler::emit_JSOP_REST()
{
frame.syncStack(0);
- ArrayObject* templateObject =
+ JSObject* templateObject =
ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject,
ObjectGroup::NewArrayKind::UnknownIndex);
if (!templateObject)
return false;
// Call IC.
- ICRest_Fallback::Compiler compiler(cx, templateObject);
+ ICRest_Fallback::Compiler compiler(cx, &templateObject->as<ArrayObject>());
if (!emitOpIC(compiler.getStub(&stubSpace_)))
return false;
diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h
index a200f7ab9..7b1af092a 100644
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -100,6 +100,7 @@ namespace jit {
_(JSOP_BITNOT) \
_(JSOP_NEG) \
_(JSOP_NEWARRAY) \
+ _(JSOP_SPREADCALLARRAY) \
_(JSOP_NEWARRAY_COPYONWRITE) \
_(JSOP_INITELEM_ARRAY) \
_(JSOP_NEWOBJECT) \
diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp
index e65f10aac..9c8cd9835 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;
@@ -1320,7 +1375,7 @@ IsNativeDenseElementAccess(HandleObject obj, HandleValue key)
static bool
IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
{
- if (!obj->isNative())
+ if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
return false;
if (key.isInt32() && key.toInt32() >= 0 && !obj->is<TypedArrayObject>())
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,
@@ -1424,6 +1479,20 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
script = rootedScript;
}
+ // Check for UnboxedArray[int] accesses.
+ if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) {
+ JitSpew(JitSpew_BaselineIC, " Generating GetElem(UnboxedArray[Int32]) stub");
+ ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
+ obj->group());
+ ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script));
+ if (!unboxedStub)
+ return false;
+
+ stub->addNewStub(unboxedStub);
+ *attached = true;
+ return true;
+ }
+
// Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) &&
rhs.isNumber() &&
@@ -1816,6 +1885,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 +1943,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);
@@ -2015,6 +2099,56 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm)
}
//
+// GetElem_UnboxedArray
+//
+
+bool
+ICGetElem_UnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
+{
+ MOZ_ASSERT(engine_ == Engine::Baseline);
+
+ Label failure;
+ masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+ masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
+
+ AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
+ Register scratchReg = regs.takeAny();
+
+ // Unbox R0 and group guard.
+ Register obj = masm.extractObject(R0, ExtractTemp0);
+ masm.loadPtr(Address(ICStubReg, ICGetElem_UnboxedArray::offsetOfGroup()), scratchReg);
+ masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
+
+ // Unbox key.
+ Register key = masm.extractInt32(R1, ExtractTemp1);
+
+ // Bounds check.
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()),
+ scratchReg);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+ masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+
+ // Load obj->elements.
+ masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+ // Load value.
+ size_t width = UnboxedTypeSize(elementType_);
+ BaseIndex addr(scratchReg, key, ScaleFromElemWidth(width));
+ masm.loadUnboxedProperty(addr, elementType_, R0);
+
+ // Only monitor the result if its type might change.
+ if (elementType_ == JSVAL_TYPE_OBJECT)
+ EmitEnterTypeMonitorIC(masm);
+ else
+ EmitReturnFromIC(masm);
+
+ // Failure case - jump to next stub
+ masm.bind(&failure);
+ EmitStubGuardFailure(masm);
+ return true;
+}
+
+//
// GetElem_TypedArray
//
@@ -2318,8 +2452,8 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
bool* isAddingCaseOut, size_t* protoDepthOut)
{
- uint32_t initLength = obj->as<NativeObject>().getDenseInitializedLength();
- uint32_t capacity = obj->as<NativeObject>().getDenseCapacity();
+ uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
+ uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj);
*isAddingCaseOut = false;
*protoDepthOut = 0;
@@ -2328,6 +2462,10 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
if (initLength < oldInitLength || capacity < oldCapacity)
return false;
+ // Unboxed arrays need to be able to emit floating point code.
+ if (obj->is<UnboxedArrayObject>() && !obj->runtimeFromMainThread()->jitSupportsFloatingPoint)
+ return false;
+
Shape* shape = obj->maybeShape();
// Cannot optimize if the shape changed.
@@ -2409,8 +2547,8 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
uint32_t oldCapacity = 0;
uint32_t oldInitLength = 0;
if (index.isInt32() && index.toInt32() >= 0) {
- oldCapacity = obj->as<NativeObject>().getDenseCapacity();
- oldInitLength = obj->as<NativeObject>().getDenseInitializedLength();
+ oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj);
+ oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
}
if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) {
@@ -2618,6 +2756,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)
{
@@ -2736,6 +2886,29 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
masm.loadValue(valueAddr, tmpVal);
EmitPreBarrier(masm, element, MIRType::Value);
masm.storeValue(tmpVal, element);
+ } else {
+ // Set element on an unboxed array.
+
+ // Bounds check.
+ Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLength, scratchReg);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+ masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+
+ // Load obj->elements.
+ masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+ // Compute the address being written to.
+ BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
+
+ EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_);
+
+ Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
+ masm.Push(R0);
+ masm.loadValue(valueAddr, R0);
+ masm.storeUnboxedProperty(address, unboxedType_,
+ ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
+ masm.Pop(R0);
}
EmitReturnFromIC(masm);
@@ -2929,6 +3102,40 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
BaseIndex element(scratchReg, key, TimesEight);
masm.loadValue(valueAddr, tmpVal);
masm.storeValue(tmpVal, element);
+ } else {
+ // Adding element to an unboxed array.
+
+ // Bounds check (key == initLength)
+ Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLengthAddr, scratchReg);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+ masm.branch32(Assembler::NotEqual, scratchReg, key, &failure);
+
+ // Capacity check.
+ masm.checkUnboxedArrayCapacity(obj, RegisterOrInt32Constant(key), scratchReg, &failure);
+
+ // Load obj->elements.
+ masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+ // Write the value first, since this can fail. No need for pre-barrier
+ // since we're not overwriting an old value.
+ masm.Push(R0);
+ Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
+ masm.loadValue(valueAddr, R0);
+ BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
+ masm.storeUnboxedProperty(address, unboxedType_,
+ ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
+ masm.Pop(R0);
+
+ // Increment initialized length.
+ masm.add32(Imm32(1), initLengthAddr);
+
+ // If length is now <= key, increment length.
+ Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+ Label skipIncrementLength;
+ masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength);
+ masm.add32(Imm32(1), lengthAddr);
+ masm.bind(&skipIncrementLength);
}
EmitReturnFromIC(masm);
@@ -4061,7 +4268,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 +4427,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 +4543,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 +4621,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 +4712,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 +4764,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 +4907,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);
+
+ // 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_) {
- holderReg = objReg;
+ 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 +4963,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);
@@ -5135,6 +5499,13 @@ GetTemplateObjectForSimd(JSContext* cx, JSFunction* target, MutableHandleObject
return true;
}
+static void
+EnsureArrayGroupAnalyzed(JSContext* cx, JSObject* obj)
+{
+ if (PreliminaryObjectArrayWithTemplate* objects = obj->group()->maybePreliminaryObjects())
+ objects->maybeAnalyze(cx, obj->group(), /* forceAnalyze = */ true);
+}
+
static bool
GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args,
MutableHandleObject res, bool* skipAttach)
@@ -5166,7 +5537,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
// With this and other array templates, analyze the group so that
// we don't end up with a template whose structure might change later.
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject));
- return !!res;
+ if (!res)
+ return false;
+ EnsureArrayGroupAnalyzed(cx, res);
+ return true;
}
}
@@ -5191,7 +5565,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
return true;
}
res.set(NewFullyAllocatedArrayTryReuseGroup(cx, obj, 0, TenuredObject));
- return !!res;
+ if (!res)
+ return false;
+ EnsureArrayGroupAnalyzed(cx, res);
+ return true;
}
}
}
@@ -5208,7 +5585,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
}
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, 0, TenuredObject));
- return !!res;
+ if (!res)
+ return false;
+ EnsureArrayGroupAnalyzed(cx, res);
+ return true;
}
if (native == StringConstructor) {
@@ -5421,7 +5801,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;
}
@@ -5521,24 +5901,15 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
}
static bool
-CopyArray(JSContext* cx, HandleArrayObject arr, MutableHandleValue result)
+CopyArray(JSContext* cx, HandleObject obj, MutableHandleValue result)
{
- uint32_t length = arr->length();
- ArrayObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, arr, length, TenuredObject);
+ uint32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
+ JSObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, obj, length, TenuredObject);
if (!nobj)
return false;
-
- MOZ_ASSERT(arr->isNative());
- MOZ_ASSERT(nobj->isNative());
- MOZ_ASSERT(nobj->as<NativeObject>().getDenseInitializedLength() == 0);
- MOZ_ASSERT(arr->as<NativeObject>().getDenseInitializedLength() >= length);
- MOZ_ASSERT(nobj->as<NativeObject>().getDenseCapacity() >= length);
-
- nobj->as<NativeObject>().setDenseInitializedLength(length);
-
- const Value* vp = arr->as<NativeObject>().getDenseElements();
- nobj->as<NativeObject>().initDenseElements(0, vp, length);
-
+ EnsureArrayGroupAnalyzed(cx, nobj);
+ CopyAnyBoxedOrUnboxedDenseElements(cx, nobj, obj, 0, 0, length);
+
result.setObject(*nobj);
return true;
}
@@ -5569,22 +5940,26 @@ TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script,
RootedValue arr(cx);
// Copy the array before storing in stub.
- if (!CopyArray(cx, obj.as<ArrayObject>(), &arr))
+ if (!CopyArray(cx, obj, &arr))
return false;
// Atomize all elements of the array.
- RootedArrayObject arrObj(cx, &arr.toObject().as<ArrayObject>());
- uint32_t initLength = arrObj->length();
+ RootedObject arrObj(cx, &arr.toObject());
+ uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(arrObj);
for (uint32_t i = 0; i < initLength; i++) {
- JSAtom* str = js::AtomizeString(cx, arrObj->getDenseElement(i).toString());
+ JSAtom* str = js::AtomizeString(cx, GetAnyBoxedOrUnboxedDenseElement(arrObj, i).toString());
if (!str)
return false;
- arrObj->setDenseElementWithType(cx, i, StringValue(str));
+ if (!SetAnyBoxedOrUnboxedDenseElement(cx, arrObj, i, StringValue(str))) {
+ // The value could not be stored to an unboxed dense element.
+ return true;
+ }
}
ICCall_StringSplit::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
- script->pcToOffset(pc), str, sep, arrObj);
+ script->pcToOffset(pc), str, sep,
+ arr);
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
@@ -6472,7 +6847,7 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm)
return true;
}
-typedef bool (*CopyArrayFn)(JSContext*, HandleArrayObject, MutableHandleValue);
+typedef bool (*CopyArrayFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction CopyArrayInfo = FunctionInfo<CopyArrayFn>(CopyArray, "CopyArray");
bool
@@ -7949,6 +8324,19 @@ ICGetElem_Dense::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorSt
return New<ICGetElem_Dense>(cx, space, other.jitCode(), firstMonitorStub, other.shape_);
}
+ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub,
+ ObjectGroup *group)
+ : ICMonitoredStub(GetElem_UnboxedArray, stubCode, firstMonitorStub),
+ group_(group)
+{ }
+
+/* static */ ICGetElem_UnboxedArray*
+ICGetElem_UnboxedArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
+ ICGetElem_UnboxedArray& other)
+{
+ return New<ICGetElem_UnboxedArray>(cx, space, other.jitCode(), firstMonitorStub, other.group_);
+}
+
ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type)
: ICStub(GetElem_TypedArray, stubCode),
shape_(shape)
@@ -8324,8 +8712,8 @@ static bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback*
unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
Value* rest = frame->argv() + numFormals;
- ArrayObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject,
- ObjectGroup::NewArrayKind::UnknownIndex);
+ JSObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject,
+ ObjectGroup::NewArrayKind::UnknownIndex);
if (!obj)
return false;
res.setObject(*obj);
diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h
index e1ad12559..5600f816a 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 {
@@ -891,6 +892,54 @@ class ICGetElem_Dense : public ICMonitoredStub
};
};
+class ICGetElem_UnboxedArray : public ICMonitoredStub
+{
+ friend class ICStubSpace;
+
+ GCPtrObjectGroup group_;
+
+ ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub, ObjectGroup* group);
+
+ public:
+ static ICGetElem_UnboxedArray* Clone(JSContext* cx, ICStubSpace* space,
+ ICStub* firstMonitorStub, ICGetElem_UnboxedArray& other);
+
+ static size_t offsetOfGroup() {
+ return offsetof(ICGetElem_UnboxedArray, group_);
+ }
+
+ GCPtrObjectGroup& group() {
+ return group_;
+ }
+
+ class Compiler : public ICStubCompiler {
+ ICStub* firstMonitorStub_;
+ RootedObjectGroup group_;
+ JSValueType elementType_;
+
+ protected:
+ MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
+
+ virtual int32_t getKey() const {
+ return static_cast<int32_t>(engine_) |
+ (static_cast<int32_t>(kind) << 1) |
+ (static_cast<int32_t>(elementType_) << 17);
+ }
+
+ public:
+ Compiler(JSContext* cx, ICStub* firstMonitorStub, ObjectGroup* group)
+ : ICStubCompiler(cx, ICStub::GetElem_UnboxedArray, Engine::Baseline),
+ firstMonitorStub_(firstMonitorStub),
+ group_(cx, group),
+ elementType_(group->unboxedLayoutDontCheckGeneration().elementType())
+ {}
+
+ ICStub* getStub(ICStubSpace* space) {
+ return newStub<ICGetElem_UnboxedArray>(space, getStubCode(), firstMonitorStub_, group_);
+ }
+ };
+};
+
// Accesses scalar elements of a typed array or typed object.
class ICGetElem_TypedArray : public ICStub
{
@@ -1066,7 +1115,9 @@ class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub
: ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray, Engine::Baseline),
shape_(cx, shape),
group_(cx, group),
- unboxedType_(JSVAL_TYPE_MAGIC)
+ unboxedType_(shape
+ ? JSVAL_TYPE_MAGIC
+ : group->unboxedLayoutDontCheckGeneration().elementType())
{}
ICUpdatedStub* getStub(ICStubSpace* space) {
@@ -1174,7 +1225,9 @@ class ICSetElemDenseOrUnboxedArrayAddCompiler : public ICStubCompiler {
: ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd, Engine::Baseline),
obj_(cx, obj),
protoChainDepth_(protoChainDepth),
- unboxedType_(JSVAL_TYPE_MAGIC)
+ unboxedType_(obj->is<UnboxedArrayObject>()
+ ? obj->as<UnboxedArrayObject>().elementType()
+ : JSVAL_TYPE_MAGIC)
{}
template <size_t ProtoChainDepth>
@@ -1822,7 +1875,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 +1981,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 +2006,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_);
@@ -2816,10 +2874,10 @@ class ICCall_StringSplit : public ICMonitoredStub
uint32_t pcOffset_;
GCPtrString expectedStr_;
GCPtrString expectedSep_;
- GCPtrArrayObject templateObject_;
+ GCPtrObject templateObject_;
ICCall_StringSplit(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset, JSString* str,
- JSString* sep, ArrayObject* templateObject)
+ JSString* sep, JSObject* templateObject)
: ICMonitoredStub(ICStub::Call_StringSplit, stubCode, firstMonitorStub),
pcOffset_(pcOffset), expectedStr_(str), expectedSep_(sep),
templateObject_(templateObject)
@@ -2846,7 +2904,7 @@ class ICCall_StringSplit : public ICMonitoredStub
return expectedSep_;
}
- GCPtrArrayObject& templateObject() {
+ GCPtrObject& templateObject() {
return templateObject_;
}
@@ -2856,7 +2914,7 @@ class ICCall_StringSplit : public ICMonitoredStub
uint32_t pcOffset_;
RootedString expectedStr_;
RootedString expectedSep_;
- RootedArrayObject templateObject_;
+ RootedObject templateObject_;
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
@@ -2867,13 +2925,13 @@ class ICCall_StringSplit : public ICMonitoredStub
public:
Compiler(JSContext* cx, ICStub* firstMonitorStub, uint32_t pcOffset, HandleString str,
- HandleString sep, HandleArrayObject templateObject)
+ HandleString sep, HandleValue templateObject)
: ICCallStubCompiler(cx, ICStub::Call_StringSplit),
firstMonitorStub_(firstMonitorStub),
pcOffset_(pcOffset),
expectedStr_(cx, str),
expectedSep_(cx, sep),
- templateObject_(cx, templateObject)
+ templateObject_(cx, &templateObject.toObject())
{ }
ICStub* getStub(ICStubSpace* space) {
diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp
index 3b852debf..c9e09bed7 100644
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -96,19 +96,32 @@ VectorAppendNoDuplicate(S& list, T value)
static bool
AddReceiver(const ReceiverGuard& receiver,
- BaselineInspector::ReceiverVector& receivers)
+ BaselineInspector::ReceiverVector& receivers,
+ BaselineInspector::ObjectGroupVector& convertUnboxedGroups)
{
+ if (receiver.group && receiver.group->maybeUnboxedLayout()) {
+ if (receiver.group->unboxedLayout().nativeGroup())
+ return VectorAppendNoDuplicate(convertUnboxedGroups, receiver.group);
+ }
return VectorAppendNoDuplicate(receivers, receiver);
}
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 +130,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,13 +146,40 @@ 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)
+BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
// Return a list of the receivers seen by the baseline IC for the current
// op. Empty lists indicate no receivers are known, or there was an
- // uncacheable access.
+ // uncacheable access. convertUnboxedGroups is used for unboxed object
+ // groups which have been seen, but have had instances converted to native
+ // objects and should be eagerly converted by Ion.
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
if (!hasBaselineScript())
return true;
@@ -143,7 +191,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,12 +200,14 @@ 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;
}
- if (!AddReceiver(receiver, receivers))
+ if (!AddReceiver(receiver, receivers, convertUnboxedGroups))
return false;
stub = stub->next();
@@ -538,7 +589,7 @@ BaselineInspector::getTemplateObjectForNative(jsbytecode* pc, Native native)
bool
BaselineInspector::isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut, JSString** sepOut,
- ArrayObject** objOut)
+ JSObject** objOut)
{
if (!hasBaselineScript())
return false;
@@ -649,12 +700,14 @@ bool
BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonGetter, Shape** globalShape,
bool* isOwnProperty,
- ReceiverVector& receivers)
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
if (!hasBaselineScript())
return false;
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
*holder = nullptr;
const ICEntry& entry = icEntryFromPC(pc);
@@ -666,7 +719,7 @@ BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shap
{
ICGetPropCallGetter* nstub = static_cast<ICGetPropCallGetter*>(stub);
bool isOwn = nstub->isOwnGetter();
- if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers))
+ if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers, convertUnboxedGroups))
return false;
if (!*holder) {
@@ -698,19 +751,21 @@ BaselineInspector::commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shap
if (!*holder)
return false;
- MOZ_ASSERT(*isOwnProperty == (receivers.empty()));
+ MOZ_ASSERT(*isOwnProperty == (receivers.empty() && convertUnboxedGroups.empty()));
return true;
}
bool
BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonSetter, bool* isOwnProperty,
- ReceiverVector& receivers)
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups)
{
if (!hasBaselineScript())
return false;
MOZ_ASSERT(receivers.empty());
+ MOZ_ASSERT(convertUnboxedGroups.empty());
*holder = nullptr;
const ICEntry& entry = icEntryFromPC(pc);
@@ -719,7 +774,7 @@ BaselineInspector::commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shap
if (stub->isSetProp_CallScripted() || stub->isSetProp_CallNative()) {
ICSetPropCallSetter* nstub = static_cast<ICSetPropCallSetter*>(stub);
bool isOwn = nstub->isOwnSetter();
- if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers))
+ if (!isOwn && !AddReceiver(nstub->receiverGuard(), receivers, convertUnboxedGroups))
return false;
if (!*holder) {
diff --git a/js/src/jit/BaselineInspector.h b/js/src/jit/BaselineInspector.h
index 1ed4b5547..961df18aa 100644
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -95,7 +95,8 @@ class BaselineInspector
public:
typedef Vector<ReceiverGuard, 4, JitAllocPolicy> ReceiverVector;
typedef Vector<ObjectGroup*, 4, JitAllocPolicy> ObjectGroupVector;
- MOZ_MUST_USE bool maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers);
+ MOZ_MUST_USE bool maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
SetElemICInspector setElemICInspector(jsbytecode* pc) {
return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
@@ -113,7 +114,7 @@ class BaselineInspector
bool hasSeenNonStringIterMore(jsbytecode* pc);
MOZ_MUST_USE bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut,
- JSString** sepOut, ArrayObject** objOut);
+ JSString** sepOut, JSObject** objOut);
JSObject* getTemplateObject(jsbytecode* pc);
JSObject* getTemplateObjectForNative(jsbytecode* pc, Native native);
JSObject* getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp);
@@ -130,10 +131,12 @@ class BaselineInspector
MOZ_MUST_USE bool commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonGetter, Shape** globalShape,
- bool* isOwnProperty, ReceiverVector& receivers);
+ bool* isOwnProperty, ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
MOZ_MUST_USE bool commonSetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
JSFunction** commonSetter, bool* isOwnProperty,
- ReceiverVector& receivers);
+ ReceiverVector& receivers,
+ ObjectGroupVector& convertUnboxedGroups);
MOZ_MUST_USE bool instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot,
JSObject** prototypeObject);
diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp
index d184ea40c..f1061af70 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<UnboxedArrayObject>() || 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_);
@@ -304,6 +368,13 @@ GetPropIRGenerator::tryAttachObjectLength(CacheIRWriter& writer, HandleObject ob
return true;
}
+ if (obj->is<UnboxedArrayObject>()) {
+ writer.guardClass(objId, GuardClassKind::UnboxedArray);
+ writer.loadUnboxedArrayLengthResult(objId);
+ emitted_ = true;
+ return true;
+ }
+
if (obj->is<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) {
if (obj->is<MappedArgumentsObject>()) {
writer.guardClass(objId, GuardClassKind::MappedArguments);
diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h
index ae55cfebb..51e55f48b 100644
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -87,12 +87,16 @@ class ObjOperandId : public OperandId
_(GuardClass) \
_(GuardSpecificObject) \
_(GuardNoDetachedTypedObjects) \
+ _(GuardNoUnboxedExpando) \
+ _(GuardAndLoadUnboxedExpando) \
_(LoadObject) \
_(LoadProto) \
_(LoadFixedSlotResult) \
_(LoadDynamicSlotResult) \
+ _(LoadUnboxedPropertyResult) \
_(LoadTypedObjectResult) \
_(LoadInt32ArrayLengthResult) \
+ _(LoadUnboxedArrayLengthResult) \
_(LoadArgumentsObjectLengthResult) \
_(LoadUndefinedResult)
@@ -124,6 +128,7 @@ struct StubField {
enum class GuardClassKind
{
Array,
+ UnboxedArray,
MappedArguments,
UnmappedArguments,
};
@@ -271,6 +276,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 +310,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);
@@ -308,6 +327,9 @@ class MOZ_RAII CacheIRWriter
void loadInt32ArrayLengthResult(ObjOperandId obj) {
writeOpWithOperandId(CacheOp::LoadInt32ArrayLengthResult, obj);
}
+ void loadUnboxedArrayLengthResult(ObjOperandId obj) {
+ writeOpWithOperandId(CacheOp::LoadUnboxedArrayLengthResult, obj);
+ }
void loadArgumentsObjectLengthResult(ObjOperandId obj) {
writeOpWithOperandId(CacheOp::LoadArgumentsObjectLengthResult, obj);
}
@@ -389,6 +411,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..bb12b09c8 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,10 +3028,19 @@ 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);
+
+ Address expandoAddress(obj, UnboxedPlainObject::offsetOfExpando());
+ if (guard.shape) {
+ masm.loadPtr(expandoAddress, scratch);
+ masm.branchPtr(Assembler::Equal, scratch, ImmWord(0), miss);
+ masm.branchTestObjShape(Assembler::NotEqual, scratch, guard.shape, miss);
+ } else if (checkNullExpando) {
+ masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), miss);
+ }
} else {
masm.branchTestObjShape(Assembler::NotEqual, obj, guard.shape, miss);
}
@@ -3051,11 +3059,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()) {
@@ -3068,6 +3078,13 @@ CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
masm.loadPtr(Address(target, NativeObject::offsetOfSlots()), scratch);
masm.loadTypedOrValue(Address(scratch, offset), output);
}
+ } else {
+ masm.comment("loadUnboxedProperty");
+ const UnboxedLayout::Property* property =
+ receiver.group->unboxedLayout().lookup(mir->name());
+ Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ masm.loadUnboxedProperty(propertyAddr, property->type, output);
}
if (i == mir->numReceivers() - 1) {
@@ -3108,6 +3125,8 @@ EmitUnboxedPreBarrier(MacroAssembler &masm, T address, JSValueType type)
masm.patchableCallPreBarrier(address, MIRType::Object);
else if (type == JSVAL_TYPE_STRING)
masm.patchableCallPreBarrier(address, MIRType::String);
+ else
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
}
void
@@ -3121,10 +3140,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()) {
@@ -3141,6 +3162,13 @@ CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
emitPreBarrier(addr);
masm.storeConstantOrRegister(value, addr);
}
+ } else {
+ const UnboxedLayout::Property* property =
+ receiver.group->unboxedLayout().lookup(mir->name());
+ Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
+
+ EmitUnboxedPreBarrier(masm, propertyAddr, property->type);
+ masm.storeUnboxedProperty(propertyAddr, property->type, value, nullptr);
}
if (i == mir->numReceivers() - 1) {
@@ -3181,7 +3209,9 @@ CodeGenerator::visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins)
void
CodeGenerator::visitElements(LElements* lir)
{
- Address elements(ToRegister(lir->object()), NativeObject::offsetOfElements());
+ Address elements(ToRegister(lir->object()),
+ lir->mir()->unboxed() ? UnboxedArrayObject::offsetOfElements()
+ : NativeObject::offsetOfElements());
masm.loadPtr(elements, ToRegister(lir->output()));
}
@@ -3290,7 +3320,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());
@@ -3304,6 +3334,27 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir)
}
void
+CodeGenerator::visitGuardUnboxedExpando(LGuardUnboxedExpando* lir)
+{
+ Label miss;
+
+ Register obj = ToRegister(lir->object());
+ masm.branchPtr(lir->mir()->requireExpando() ? Assembler::Equal : Assembler::NotEqual,
+ Address(obj, UnboxedPlainObject::offsetOfExpando()), ImmWord(0), &miss);
+
+ bailoutFrom(&miss, lir->snapshot());
+}
+
+void
+CodeGenerator::visitLoadUnboxedExpando(LLoadUnboxedExpando* lir)
+{
+ Register obj = ToRegister(lir->object());
+ Register result = ToRegister(lir->getDef(0));
+
+ masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), result);
+}
+
+void
CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir)
{
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
@@ -5166,11 +5217,11 @@ static JSObject*
NewArrayWithGroup(JSContext* cx, uint32_t length, HandleObjectGroup group,
bool convertDoubleElements)
{
- ArrayObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
+ JSObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
if (!res)
return nullptr;
if (convertDoubleElements)
- res->setShouldConvertDoubleElements();
+ res->as<ArrayObject>().setShouldConvertDoubleElements();
return res;
}
@@ -5316,7 +5367,7 @@ CodeGenerator::visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite* lir)
masm.bind(ool->rejoin());
}
-typedef ArrayObject* (*ArrayConstructorOneArgFn)(JSContext*, HandleObjectGroup, int32_t length);
+typedef JSObject* (*ArrayConstructorOneArgFn)(JSContext*, HandleObjectGroup, int32_t length);
static const VMFunction ArrayConstructorOneArgInfo =
FunctionInfo<ArrayConstructorOneArgFn>(ArrayConstructorOneArg, "ArrayConstructorOneArg");
@@ -5336,11 +5387,21 @@ CodeGenerator::visitNewArrayDynamicLength(LNewArrayDynamicLength* lir)
bool canInline = true;
size_t inlineLength = 0;
- if (templateObject->as<ArrayObject>().hasFixedElements()) {
- size_t numSlots = gc::GetGCKindSlots(templateObject->asTenured().getAllocKind());
- inlineLength = numSlots - ObjectElements::VALUES_PER_HEADER;
+ if (templateObject->is<ArrayObject>()) {
+ if (templateObject->as<ArrayObject>().hasFixedElements()) {
+ size_t numSlots = gc::GetGCKindSlots(templateObject->asTenured().getAllocKind());
+ inlineLength = numSlots - ObjectElements::VALUES_PER_HEADER;
+ } else {
+ canInline = false;
+ }
} else {
- canInline = false;
+ if (templateObject->as<UnboxedArrayObject>().hasInlineElements()) {
+ size_t nbytes =
+ templateObject->tenuredSizeOfThis() - UnboxedArrayObject::offsetOfInlineElements();
+ inlineLength = nbytes / templateObject->as<UnboxedArrayObject>().elementSize();
+ } else {
+ canInline = false;
+ }
}
if (canInline) {
@@ -7762,7 +7823,7 @@ CodeGenerator::visitSinCos(LSinCos *lir)
masm.freeStack(sizeof(double) * 2);
}
-typedef ArrayObject* (*StringSplitFn)(JSContext*, HandleObjectGroup, HandleString, HandleString, uint32_t);
+typedef JSObject* (*StringSplitFn)(JSContext*, HandleObjectGroup, HandleString, HandleString, uint32_t);
static const VMFunction StringSplitInfo =
FunctionInfo<StringSplitFn>(js::str_split_string, "str_split_string");
@@ -7797,6 +7858,49 @@ CodeGenerator::visitSetInitializedLength(LSetInitializedLength* lir)
}
void
+CodeGenerator::visitUnboxedArrayLength(LUnboxedArrayLength* lir)
+{
+ Register obj = ToRegister(lir->object());
+ Register result = ToRegister(lir->output());
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), result);
+}
+
+void
+CodeGenerator::visitUnboxedArrayInitializedLength(LUnboxedArrayInitializedLength* lir)
+{
+ Register obj = ToRegister(lir->object());
+ Register result = ToRegister(lir->output());
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), result);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), result);
+}
+
+void
+CodeGenerator::visitIncrementUnboxedArrayInitializedLength(LIncrementUnboxedArrayInitializedLength* lir)
+{
+ Register obj = ToRegister(lir->object());
+ masm.add32(Imm32(1), Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+}
+
+void
+CodeGenerator::visitSetUnboxedArrayInitializedLength(LSetUnboxedArrayInitializedLength* lir)
+{
+ Register obj = ToRegister(lir->object());
+ RegisterOrInt32Constant key = ToRegisterOrInt32Constant(lir->length());
+ Register temp = ToRegister(lir->temp());
+
+ Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLengthAddr, temp);
+ masm.and32(Imm32(UnboxedArrayObject::CapacityMask), temp);
+
+ if (key.isRegister())
+ masm.or32(key.reg(), temp);
+ else
+ masm.or32(Imm32(key.constant()), temp);
+
+ masm.store32(temp, initLengthAddr);
+}
+
+void
CodeGenerator::visitNotO(LNotO* lir)
{
MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
@@ -8092,19 +8196,46 @@ CodeGenerator::emitStoreElementHoleT(T* lir)
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
addOutOfLineCode(ool, lir->mir());
+ Register obj = ToRegister(lir->object());
Register elements = ToRegister(lir->elements());
const LAllocation* index = lir->index();
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
- Address initLength(elements, ObjectElements::offsetOfInitializedLength());
- masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
+ JSValueType unboxedType = lir->mir()->unboxedType();
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
- if (lir->mir()->needsBarrier())
- emitPreBarrier(elements, index, 0);
+ if (lir->mir()->needsBarrier())
+ emitPreBarrier(elements, index, 0);
+
+ masm.bind(ool->rejoinStore());
+ emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
+ elements, index, 0);
+ } else {
+ Register temp = ToRegister(lir->getTemp(0));
+ Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLength, temp);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp);
+ masm.branch32(Assembler::BelowOrEqual, temp, key, ool->entry());
+
+ ConstantOrRegister v = ToConstantOrRegister(lir->value(), lir->mir()->value()->type());
+
+ if (index->isConstant()) {
+ Address address(elements, ToInt32(index) * UnboxedTypeSize(unboxedType));
+ EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+ masm.bind(ool->rejoinStore());
+ masm.storeUnboxedProperty(address, unboxedType, v, nullptr);
+ } else {
+ BaseIndex address(elements, ToRegister(index),
+ ScaleFromElemWidth(UnboxedTypeSize(unboxedType)));
+ EmitUnboxedPreBarrier(masm, address, unboxedType);
- masm.bind(ool->rejoinStore());
- emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
- elements, index, 0);
+ masm.bind(ool->rejoinStore());
+ masm.storeUnboxedProperty(address, unboxedType, v, nullptr);
+ }
+ }
masm.bind(ool->rejoin());
}
@@ -8124,22 +8255,47 @@ CodeGenerator::emitStoreElementHoleV(T* lir)
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
addOutOfLineCode(ool, lir->mir());
+ Register obj = ToRegister(lir->object());
Register elements = ToRegister(lir->elements());
const LAllocation* index = lir->index();
const ValueOperand value = ToValue(lir, T::Value);
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
- Address initLength(elements, ObjectElements::offsetOfInitializedLength());
- masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
+ JSValueType unboxedType = lir->mir()->unboxedType();
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
- if (lir->mir()->needsBarrier())
- emitPreBarrier(elements, index, 0);
+ if (lir->mir()->needsBarrier())
+ emitPreBarrier(elements, index, 0);
- masm.bind(ool->rejoinStore());
- if (index->isConstant())
- masm.storeValue(value, Address(elements, ToInt32(index) * sizeof(js::Value)));
- else
- masm.storeValue(value, BaseIndex(elements, ToRegister(index), TimesEight));
+ masm.bind(ool->rejoinStore());
+ if (index->isConstant())
+ masm.storeValue(value, Address(elements, ToInt32(index) * sizeof(js::Value)));
+ else
+ masm.storeValue(value, BaseIndex(elements, ToRegister(index), TimesEight));
+ } else {
+ Register temp = ToRegister(lir->getTemp(0));
+ Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLength, temp);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp);
+ masm.branch32(Assembler::BelowOrEqual, temp, key, ool->entry());
+
+ if (index->isConstant()) {
+ Address address(elements, ToInt32(index) * UnboxedTypeSize(unboxedType));
+ EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+ masm.bind(ool->rejoinStore());
+ masm.storeUnboxedProperty(address, unboxedType, ConstantOrRegister(value), nullptr);
+ } else {
+ BaseIndex address(elements, ToRegister(index),
+ ScaleFromElemWidth(UnboxedTypeSize(unboxedType)));
+ EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+ masm.bind(ool->rejoinStore());
+ masm.storeUnboxedProperty(address, unboxedType, ConstantOrRegister(value), nullptr);
+ }
+ }
masm.bind(ool->rejoin());
}
@@ -8210,10 +8366,11 @@ CodeGenerator::visitFallibleStoreElementV(LFallibleStoreElementV* lir)
masm.bind(&isFrozen);
}
-typedef bool (*SetDenseElementFn)(JSContext*, HandleNativeObject, int32_t, HandleValue,
- bool strict);
-static const VMFunction SetDenseElementInfo =
- FunctionInfo<SetDenseElementFn>(jit::SetDenseElement, "SetDenseElement");
+typedef bool (*SetDenseOrUnboxedArrayElementFn)(JSContext*, HandleObject, int32_t,
+ HandleValue, bool strict);
+static const VMFunction SetDenseOrUnboxedArrayElementInfo =
+ FunctionInfo<SetDenseOrUnboxedArrayElementFn>(SetDenseOrUnboxedArrayElement,
+ "SetDenseOrUnboxedArrayElement");
void
CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
@@ -8223,6 +8380,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
const LAllocation* index;
MIRType valueType;
ConstantOrRegister value;
+ JSValueType unboxedType;
+ LDefinition *temp = nullptr;
if (ins->isStoreElementHoleV()) {
LStoreElementHoleV* store = ins->toStoreElementHoleV();
@@ -8231,6 +8390,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
index = store->index();
valueType = store->mir()->value()->type();
value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value));
+ unboxedType = store->mir()->unboxedType();
+ temp = store->getTemp(0);
} else if (ins->isFallibleStoreElementV()) {
LFallibleStoreElementV* store = ins->toFallibleStoreElementV();
object = ToRegister(store->object());
@@ -8238,6 +8399,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
index = store->index();
valueType = store->mir()->value()->type();
value = TypedOrValueRegister(ToValue(store, LFallibleStoreElementV::Value));
+ unboxedType = store->mir()->unboxedType();
+ temp = store->getTemp(0);
} else if (ins->isStoreElementHoleT()) {
LStoreElementHoleT* store = ins->toStoreElementHoleT();
object = ToRegister(store->object());
@@ -8248,6 +8411,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
else
value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
+ unboxedType = store->mir()->unboxedType();
+ temp = store->getTemp(0);
} else { // ins->isFallibleStoreElementT()
LFallibleStoreElementT* store = ins->toFallibleStoreElementT();
object = ToRegister(store->object());
@@ -8258,6 +8423,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
else
value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
+ unboxedType = store->mir()->unboxedType();
+ temp = store->getTemp(0);
}
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
@@ -8268,32 +8435,54 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
Label callStub;
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// Had to reimplement for MIPS because there are no flags.
- Address initLength(elements, ObjectElements::offsetOfInitializedLength());
- masm.branch32(Assembler::NotEqual, initLength, key, &callStub);
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::NotEqual, initLength, key, &callStub);
+ } else {
+ Address initLength(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ masm.load32(initLength, ToRegister(temp));
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), ToRegister(temp));
+ masm.branch32(Assembler::NotEqual, ToRegister(temp), key, &callStub);
+ }
#else
masm.j(Assembler::NotEqual, &callStub);
#endif
- // Check array capacity.
- masm.branch32(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
- key, &callStub);
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ // Check array capacity.
+ masm.branch32(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
+ key, &callStub);
- // Update initialized length. The capacity guard above ensures this won't overflow,
- // due to MAX_DENSE_ELEMENTS_COUNT.
- masm.inc32(&key);
- masm.store32(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
+ // Update initialized length. The capacity guard above ensures this won't overflow,
+ // due to MAX_DENSE_ELEMENTS_COUNT.
+ masm.inc32(&key);
+ masm.store32(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
- // Update length if length < initializedLength.
- Label dontUpdate;
- masm.branch32(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
- key, &dontUpdate);
- masm.store32(key, Address(elements, ObjectElements::offsetOfLength()));
- masm.bind(&dontUpdate);
+ // Update length if length < initializedLength.
+ Label dontUpdate;
+ masm.branch32(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
+ key, &dontUpdate);
+ masm.store32(key, Address(elements, ObjectElements::offsetOfLength()));
+ masm.bind(&dontUpdate);
- masm.dec32(&key);
+ masm.dec32(&key);
+ } else {
+ // Check array capacity.
+ masm.checkUnboxedArrayCapacity(object, key, ToRegister(temp), &callStub);
+
+ // Update initialized length.
+ masm.add32(Imm32(1), Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+
+ // Update length if length < initializedLength.
+ Address lengthAddr(object, UnboxedArrayObject::offsetOfLength());
+ Label dontUpdate;
+ masm.branch32(Assembler::Above, lengthAddr, key, &dontUpdate);
+ masm.add32(Imm32(1), lengthAddr);
+ masm.bind(&dontUpdate);
+ }
if ((ins->isStoreElementHoleT() || ins->isFallibleStoreElementT()) &&
- valueType != MIRType::Double)
+ unboxedType == JSVAL_TYPE_MAGIC && valueType != MIRType::Double)
{
// The inline path for StoreElementHoleT and FallibleStoreElementT does not always store
// the type tag, so we do the store on the OOL path. We use MIRType::None for the element
@@ -8322,7 +8511,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
else
pushArg(ToRegister(index));
pushArg(object);
- callVM(SetDenseElementInfo, ins);
+ callVM(SetDenseOrUnboxedArrayElementInfo, ins);
restoreLive(ins);
masm.jump(ool->rejoin());
@@ -8379,6 +8568,29 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir)
}
}
+typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
+static const VMFunction ConvertUnboxedPlainObjectToNativeInfo =
+ FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative,
+ "UnboxedPlainObject::convertToNative");
+static const VMFunction ConvertUnboxedArrayObjectToNativeInfo =
+ FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedArrayObject::convertToNative,
+ "UnboxedArrayObject::convertToNative");
+
+void
+CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir)
+{
+ Register object = ToRegister(lir->getOperand(0));
+
+ OutOfLineCode* ool = oolCallVM(lir->mir()->group()->unboxedLayoutDontCheckGeneration().isArray()
+ ? ConvertUnboxedArrayObjectToNativeInfo
+ : ConvertUnboxedPlainObjectToNativeInfo,
+ lir, ArgList(object), StoreNothing());
+
+ masm.branchPtr(Assembler::Equal, Address(object, JSObject::offsetOfGroup()),
+ ImmGCPtr(lir->mir()->group()), ool->entry());
+ masm.bind(ool->rejoin());
+}
+
typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction ArrayPopDenseInfo =
FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
@@ -8403,11 +8615,20 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
// Load elements and length, and VM call if length != initializedLength.
RegisterOrInt32Constant key = RegisterOrInt32Constant(lengthTemp);
- masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
- masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp);
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
+ masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp);
- Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
- masm.branch32(Assembler::NotEqual, initLength, key, ool->entry());
+ Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::NotEqual, initLength, key, ool->entry());
+ } else {
+ masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), elementsTemp);
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), lengthTemp);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), lengthTemp);
+
+ Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+ masm.branch32(Assembler::NotEqual, lengthAddr, key, ool->entry());
+ }
// Test for length != 0. On zero length either take a VM call or generate
// an undefined value, depending on whether the call is known to produce
@@ -8419,10 +8640,13 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
// According to the spec we need to set the length 0 (which is already 0).
// This is observable when the array length is made non-writable.
- // Handle this case in the OOL.
- Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
- Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
- masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
+ // Handle this case in the OOL. When freezing an unboxed array it is converted
+ // to an normal array.
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
+ Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
+ masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
+ }
masm.moveValue(UndefinedValue(), out.valueReg());
masm.jump(&done);
@@ -8434,25 +8658,41 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
masm.dec32(&key);
if (mir->mode() == MArrayPopShift::Pop) {
- BaseIndex addr(elementsTemp, lengthTemp, TimesEight);
- masm.loadElementTypedOrValue(addr, out, mir->needsHoleCheck(), ool->entry());
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ BaseIndex addr(elementsTemp, lengthTemp, TimesEight);
+ masm.loadElementTypedOrValue(addr, out, mir->needsHoleCheck(), ool->entry());
+ } else {
+ size_t elemSize = UnboxedTypeSize(mir->unboxedType());
+ BaseIndex addr(elementsTemp, lengthTemp, ScaleFromElemWidth(elemSize));
+ masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
+ }
} else {
MOZ_ASSERT(mir->mode() == MArrayPopShift::Shift);
Address addr(elementsTemp, 0);
- masm.loadElementTypedOrValue(addr, out, mir->needsHoleCheck(), ool->entry());
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC)
+ masm.loadElementTypedOrValue(addr, out, mir->needsHoleCheck(), ool->entry());
+ else
+ masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
}
- // Handle the failure case when the array length is non-writable in the
- // OOL path. (Unlike in the adding-an-element cases, we can't rely on the
- // capacity <= length invariant for such arrays to avoid an explicit
- // check.)
- Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
- Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
- masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ // Handle the failure case when the array length is non-writable in the
+ // OOL path. (Unlike in the adding-an-element cases, we can't rely on the
+ // capacity <= length invariant for such arrays to avoid an explicit
+ // check.)
+ Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
+ Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
+ masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
- // Now adjust length and initializedLength.
- masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfLength()));
- masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfInitializedLength()));
+ // Now adjust length and initializedLength.
+ masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfLength()));
+ masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfInitializedLength()));
+ } else {
+ // Unboxed arrays always have writable lengths. Adjust length and
+ // initializedLength.
+ masm.store32(lengthTemp, Address(obj, UnboxedArrayObject::offsetOfLength()));
+ masm.add32(Imm32(-1), Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+ }
if (mir->mode() == MArrayPopShift::Shift) {
// Don't save the temp registers.
@@ -8491,7 +8731,7 @@ CodeGenerator::visitArrayPopShiftT(LArrayPopShiftT* lir)
emitArrayPopShift(lir, lir->mir(), obj, elements, length, out);
}
-typedef bool (*ArrayPushDenseFn)(JSContext*, HandleArrayObject, HandleValue, uint32_t*);
+typedef bool (*ArrayPushDenseFn)(JSContext*, HandleObject, HandleValue, uint32_t*);
static const VMFunction ArrayPushDenseInfo =
FunctionInfo<ArrayPushDenseFn>(jit::ArrayPushDense, "ArrayPushDense");
@@ -8502,27 +8742,50 @@ CodeGenerator::emitArrayPush(LInstruction* lir, const MArrayPush* mir, Register
OutOfLineCode* ool = oolCallVM(ArrayPushDenseInfo, lir, ArgList(obj, value), StoreRegisterTo(length));
RegisterOrInt32Constant key = RegisterOrInt32Constant(length);
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ // Load elements and length.
+ masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
+ masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length);
- // Load elements and length.
- masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
- masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length);
+ // Guard length == initializedLength.
+ Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
+ masm.branch32(Assembler::NotEqual, initLength, key, ool->entry());
- // Guard length == initializedLength.
- Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
- masm.branch32(Assembler::NotEqual, initLength, key, ool->entry());
+ // Guard length < capacity.
+ Address capacity(elementsTemp, ObjectElements::offsetOfCapacity());
+ masm.branch32(Assembler::BelowOrEqual, capacity, key, ool->entry());
- // Guard length < capacity.
- Address capacity(elementsTemp, ObjectElements::offsetOfCapacity());
- masm.branch32(Assembler::BelowOrEqual, capacity, key, ool->entry());
+ // Do the store.
+ masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight));
+ } else {
+ // Load initialized length.
+ masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), length);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), length);
- // Do the store.
- masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight));
+ // Guard length == initializedLength.
+ Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+ masm.branch32(Assembler::NotEqual, lengthAddr, key, ool->entry());
+
+ // Guard length < capacity.
+ masm.checkUnboxedArrayCapacity(obj, key, elementsTemp, ool->entry());
+
+ // Load elements and do the store.
+ masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), elementsTemp);
+ size_t elemSize = UnboxedTypeSize(mir->unboxedType());
+ BaseIndex addr(elementsTemp, length, ScaleFromElemWidth(elemSize));
+ masm.storeUnboxedProperty(addr, mir->unboxedType(), value, nullptr);
+ }
masm.inc32(&key);
// Update length and initialized length.
- masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfLength()));
- masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfInitializedLength()));
+ if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
+ masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfLength()));
+ masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfInitializedLength()));
+ } else {
+ masm.store32(length, Address(obj, UnboxedArrayObject::offsetOfLength()));
+ masm.add32(Imm32(1), Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+ }
masm.bind(ool->rejoin());
}
@@ -8671,11 +8934,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 +8950,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);
}
@@ -10343,11 +10612,22 @@ CodeGenerator::visitLoadElementHole(LLoadElementHole* lir)
else
masm.branch32(Assembler::BelowOrEqual, initLength, ToRegister(lir->index()), &undefined);
- if (lir->index()->isConstant()) {
- NativeObject::elementsSizeMustNotOverflow();
- masm.loadValue(Address(elements, ToInt32(lir->index()) * sizeof(Value)), out);
+ if (mir->unboxedType() != JSVAL_TYPE_MAGIC) {
+ size_t width = UnboxedTypeSize(mir->unboxedType());
+ if (lir->index()->isConstant()) {
+ Address addr(elements, ToInt32(lir->index()) * width);
+ masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
+ } else {
+ BaseIndex addr(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+ masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
+ }
} else {
- masm.loadValue(BaseObjectElementIndex(elements, ToRegister(lir->index())), out);
+ if (lir->index()->isConstant()) {
+ NativeObject::elementsSizeMustNotOverflow();
+ masm.loadValue(Address(elements, ToInt32(lir->index()) * sizeof(Value)), out);
+ } else {
+ masm.loadValue(BaseObjectElementIndex(elements, ToRegister(lir->index())), out);
+ }
}
// If a hole check is needed, and the value wasn't a hole, we're done.
@@ -10725,7 +11005,7 @@ CodeGenerator::visitInArray(LInArray* lir)
}
masm.branch32(Assembler::BelowOrEqual, initLength, Imm32(index), failedInitLength);
- if (mir->needsHoleCheck()) {
+ if (mir->needsHoleCheck() && mir->unboxedType() == JSVAL_TYPE_MAGIC) {
NativeObject::elementsSizeMustNotOverflow();
Address address = Address(elements, index * sizeof(Value));
masm.branchTestMagic(Assembler::Equal, address, &falseBranch);
@@ -10738,7 +11018,7 @@ CodeGenerator::visitInArray(LInArray* lir)
failedInitLength = &negativeIntCheck;
masm.branch32(Assembler::BelowOrEqual, initLength, index, failedInitLength);
- if (mir->needsHoleCheck()) {
+ if (mir->needsHoleCheck() && mir->unboxedType() == JSVAL_TYPE_MAGIC) {
BaseIndex address = BaseIndex(elements, ToRegister(lir->index()), TimesEight);
masm.branchTestMagic(Assembler::Equal, address, &falseBranch);
}
diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h
index 65acfe274..6a5c7f34f 100644
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -148,6 +148,8 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite* lir);
void visitGuardObjectIdentity(LGuardObjectIdentity* guard);
void visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir);
+ void visitGuardUnboxedExpando(LGuardUnboxedExpando* lir);
+ void visitLoadUnboxedExpando(LLoadUnboxedExpando* lir);
void visitTypeBarrierV(LTypeBarrierV* lir);
void visitTypeBarrierO(LTypeBarrierO* lir);
void visitMonitorTypes(LMonitorTypes* lir);
@@ -234,6 +236,10 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitSubstr(LSubstr* lir);
void visitInitializedLength(LInitializedLength* lir);
void visitSetInitializedLength(LSetInitializedLength* lir);
+ void visitUnboxedArrayLength(LUnboxedArrayLength* lir);
+ void visitUnboxedArrayInitializedLength(LUnboxedArrayInitializedLength* lir);
+ void visitIncrementUnboxedArrayInitializedLength(LIncrementUnboxedArrayInitializedLength* lir);
+ void visitSetUnboxedArrayInitializedLength(LSetUnboxedArrayInitializedLength* lir);
void visitNotO(LNotO* ins);
void visitNotV(LNotV* ins);
void visitBoundsCheck(LBoundsCheck* lir);
@@ -305,6 +311,7 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitFallibleStoreElementV(LFallibleStoreElementV* lir);
void visitFallibleStoreElementT(LFallibleStoreElementT* lir);
void visitStoreUnboxedPointer(LStoreUnboxedPointer* lir);
+ void visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir);
void emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, Register obj,
Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
void visitArrayPopShiftV(LArrayPopShiftV* lir);
diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp
index a4724bca4..3c0f2c4b3 100644
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -3515,6 +3515,8 @@ PassthroughOperand(MDefinition* def)
return def->toConvertElementsToDoubles()->elements();
if (def->isMaybeCopyElementsForWrite())
return def->toMaybeCopyElementsForWrite()->object();
+ if (def->isConvertUnboxedObjectToNative())
+ return def->toConvertUnboxedObjectToNative()->object();
return nullptr;
}
@@ -4060,7 +4062,7 @@ AnalyzePoppedThis(JSContext* cx, DPAConstraintInfo& constraintInfo, ObjectGroup*
// Add the property to the object, being careful not to update type information.
DebugOnly<unsigned> slotSpan = baseobj->slotSpan();
MOZ_ASSERT(!baseobj->containsPure(id));
- if (!NativeObject::addDataProperty(cx, baseobj, id, baseobj->slotSpan(), JSPROP_ENUMERATE))
+ if (!baseobj->addDataProperty(cx, id, baseobj->slotSpan(), JSPROP_ENUMERATE))
return false;
MOZ_ASSERT(baseobj->slotSpan() != slotSpan);
MOZ_ASSERT(!baseobj->inDictionaryMode());
diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp
index 1e12f5dbe..f08baf865 100644
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -32,6 +32,7 @@
#include "vm/EnvironmentObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h"
+#include "vm/UnboxedObject-inl.h"
using namespace js;
using namespace js::jit;
@@ -2227,8 +2228,6 @@ IonBuilder::inspectOpcode(JSOp op)
// update that stale value.
#endif
default:
- // Any unused opcodes and JSOP_LIMIT will end up here without having
- // to explicitly specify
break;
}
@@ -6402,7 +6401,7 @@ IonBuilder::createThisScriptedSingleton(JSFunction* target, MDefinition* callee)
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject)
return nullptr;
- if (!templateObject->is<PlainObject>())
+ if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
return nullptr;
if (templateObject->staticPrototype() != proto)
return nullptr;
@@ -6439,7 +6438,7 @@ IonBuilder::createThisScriptedBaseline(MDefinition* callee)
JSObject* templateObject = inspector->getTemplateObject(pc);
if (!templateObject)
return nullptr;
- if (!templateObject->is<PlainObject>())
+ if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
return nullptr;
Shape* shape = target->lookupPure(compartment->runtime()->names().prototype);
@@ -7355,6 +7354,12 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
if (!templateObject)
return true;
+ if (templateObject->is<UnboxedArrayObject>()) {
+ MOZ_ASSERT(templateObject->as<UnboxedArrayObject>().capacity() >= length);
+ if (!templateObject->as<UnboxedArrayObject>().hasInlineElements())
+ return true;
+ }
+
MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
size_t arraySlots =
@@ -7610,6 +7615,7 @@ IonBuilder::jsop_initelem_array()
// intializer, and that arrays are marked as non-packed when writing holes
// to them during initialization.
bool needStub = false;
+ JSValueType unboxedType = JSVAL_TYPE_MAGIC;
if (shouldAbortOnPreliminaryGroups(obj)) {
needStub = true;
} else if (!obj->resultTypeSet() ||
@@ -7620,6 +7626,12 @@ IonBuilder::jsop_initelem_array()
} else {
MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1);
TypeSet::ObjectKey* initializer = obj->resultTypeSet()->getObject(0);
+ if (initializer->clasp() == &UnboxedArrayObject::class_) {
+ if (initializer->group()->unboxedLayout().nativeGroup())
+ needStub = true;
+ else
+ unboxedType = initializer->group()->unboxedLayout().elementType();
+ }
if (value->type() == MIRType::MagicHole) {
if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED))
needStub = true;
@@ -7639,46 +7651,60 @@ IonBuilder::jsop_initelem_array()
return resumeAfter(store);
}
- return initializeArrayElement(obj, index, value, /* addResumePoint = */ true);
+ return initializeArrayElement(obj, index, value, unboxedType, /* addResumePoint = */ true);
}
bool
IonBuilder::initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
+ JSValueType unboxedType,
bool addResumePointAndIncrementInitializedLength)
{
MConstant* id = MConstant::New(alloc(), Int32Value(index));
current->add(id);
// Get the elements vector.
- MElements* elements = MElements::New(alloc(), obj);
+ MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements);
- if (NeedsPostBarrier(value))
- current->add(MPostWriteBarrier::New(alloc(), obj, value));
+ if (unboxedType != JSVAL_TYPE_MAGIC) {
+ // Note: storeUnboxedValue takes care of any post barriers on the value.
+ storeUnboxedValue(obj, elements, 0, id, unboxedType, value, /* preBarrier = */ false);
- if ((obj->isNewArray() && obj->toNewArray()->convertDoubleElements()) ||
- (obj->isNullarySharedStub() &&
- obj->resultTypeSet()->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles))
- {
- MInstruction* valueDouble = MToDouble::New(alloc(), value);
- current->add(valueDouble);
- value = valueDouble;
- }
+ if (addResumePointAndIncrementInitializedLength) {
+ MInstruction* increment = MIncrementUnboxedArrayInitializedLength::New(alloc(), obj);
+ current->add(increment);
- // Store the value.
- MStoreElement* store = MStoreElement::New(alloc(), elements, id, value,
+ if (!resumeAfter(increment))
+ return false;
+ }
+ } else {
+ if (NeedsPostBarrier(value))
+ current->add(MPostWriteBarrier::New(alloc(), obj, value));
+
+ if ((obj->isNewArray() && obj->toNewArray()->convertDoubleElements()) ||
+ (obj->isNullarySharedStub() &&
+ obj->resultTypeSet()->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles))
+ {
+ MInstruction* valueDouble = MToDouble::New(alloc(), value);
+ current->add(valueDouble);
+ value = valueDouble;
+ }
+
+ // Store the value.
+ MStoreElement* store = MStoreElement::New(alloc(), elements, id, value,
/* needsHoleCheck = */ false);
- current->add(store);
+ current->add(store);
- if (addResumePointAndIncrementInitializedLength) {
- // Update the initialized length. (The template object for this
- // array has the array's ultimate length, so the length field is
- // already correct: no updating needed.)
- MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id);
- current->add(initLength);
+ if (addResumePointAndIncrementInitializedLength) {
+ // Update the initialized length. (The template object for this
+ // array has the array's ultimate length, so the length field is
+ // already correct: no updating needed.)
+ MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id);
+ current->add(initLength);
- if (!resumeAfter(initLength))
- return false;
+ if (!resumeAfter(initLength))
+ return false;
+ }
}
return true;
@@ -7709,6 +7735,8 @@ IonBuilder::jsop_initprop(PropertyName* name)
if (templateObject->is<PlainObject>()) {
if (!templateObject->as<PlainObject>().containsPure(name))
useSlowPath = true;
+ } else {
+ MOZ_ASSERT(templateObject->as<UnboxedPlainObject>().layout().lookup(name));
}
} else {
useSlowPath = true;
@@ -8167,7 +8195,9 @@ IonBuilder::maybeMarkEmpty(MDefinition* ins)
static bool
ClassHasEffectlessLookup(const Class* clasp)
{
- return IsTypedObjectClass(clasp) ||
+ return (clasp == &UnboxedPlainObject::class_) ||
+ (clasp == &UnboxedArrayObject::class_) ||
+ IsTypedObjectClass(clasp) ||
(clasp->isNative() && !clasp->getOpsLookupProperty());
}
@@ -8997,6 +9027,8 @@ IonBuilder::jsop_getelem()
}
obj = maybeUnboxForPropertyAccess(obj);
+ if (obj->type() == MIRType::Object)
+ obj = convertUnboxedObjects(obj);
bool emitted = false;
@@ -9441,9 +9473,12 @@ IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
{
MOZ_ASSERT(*emitted == false);
- if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
- trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
- return true;
+ JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, index);
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
+ trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
+ return true;
+ }
}
// Don't generate a fast path if there have been bounds check failures
@@ -9460,7 +9495,7 @@ IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
return true;
}
- if (!jsop_getelem_dense(obj, index))
+ if (!jsop_getelem_dense(obj, index, unboxedType))
return false;
trackOptimizationSuccess();
@@ -9812,7 +9847,7 @@ IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id)
}
bool
-IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
+IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index, JSValueType unboxedType)
{
TemporaryTypeSet* types = bytecodeTypes(pc);
@@ -9836,7 +9871,7 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
!ElementAccessHasExtraIndexedProperty(this, obj);
MIRType knownType = MIRType::Value;
- if (barrier == BarrierKind::NoBarrier)
+ if (unboxedType == JSVAL_TYPE_MAGIC && barrier == BarrierKind::NoBarrier)
knownType = GetElemKnownType(needsHoleCheck, types);
// Ensure index is an integer.
@@ -9845,13 +9880,13 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
index = idInt32;
// Get the elements vector.
- MInstruction* elements = MElements::New(alloc(), obj);
+ MInstruction* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements);
// Note: to help GVN, use the original MElements instruction and not
// MConvertElementsToDoubles as operand. This is fine because converting
// elements to double does not change the initialized length.
- MInstruction* initLength = initializedLength(obj, elements);
+ MInstruction* initLength = initializedLength(obj, elements, unboxedType);
// If we can load the element as a definite double, make sure to check that
// the array has been converted to homogenous doubles first.
@@ -9867,6 +9902,7 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
}
bool loadDouble =
+ unboxedType == JSVAL_TYPE_MAGIC &&
barrier == BarrierKind::NoBarrier &&
loopDepth_ &&
inBounds &&
@@ -9885,13 +9921,18 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
// hoisting.
index = addBoundsCheck(index, initLength);
- load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
- current->add(load);
+ if (unboxedType != JSVAL_TYPE_MAGIC) {
+ load = loadUnboxedValue(elements, 0, index, unboxedType, barrier, types);
+ } else {
+ load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
+ current->add(load);
+ }
} else {
// This load may return undefined, so assume that we *can* read holes,
// or that we can read out-of-bounds accesses. In this case, the bounds
// check is part of the opcode.
- load = MLoadElementHole::New(alloc(), elements, index, initLength, needsHoleCheck);
+ load = MLoadElementHole::New(alloc(), elements, index, initLength,
+ unboxedType, needsHoleCheck);
current->add(load);
// If maybeUndefined was true, the typeset must have undefined, and
@@ -9901,7 +9942,8 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
}
if (knownType != MIRType::Value) {
- load->setResultType(knownType);
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ load->setResultType(knownType);
load->setResultTypeSet(types);
}
@@ -10110,7 +10152,7 @@ IonBuilder::jsop_setelem()
MDefinition* value = current->pop();
MDefinition* index = current->pop();
- MDefinition* object = current->pop();
+ MDefinition* object = convertUnboxedObjects(current->pop());
trackTypeInfo(TrackedTypeSite::Receiver, object->type(), object->resultTypeSet());
trackTypeInfo(TrackedTypeSite::Index, index->type(), index->resultTypeSet());
@@ -10348,9 +10390,12 @@ IonBuilder::setElemTryDense(bool* emitted, MDefinition* object,
{
MOZ_ASSERT(*emitted == false);
- if (!ElementAccessIsDenseNative(constraints(), object, index)) {
- trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
- return true;
+ JSValueType unboxedType = UnboxedArrayElementType(constraints(), object, index);
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ if (!ElementAccessIsDenseNative(constraints(), object, index)) {
+ trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
+ return true;
+ }
}
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
@@ -10384,7 +10429,7 @@ IonBuilder::setElemTryDense(bool* emitted, MDefinition* object,
}
// Emit dense setelem variant.
- if (!jsop_setelem_dense(conversion, object, index, value, writeHole, emitted))
+ if (!jsop_setelem_dense(conversion, object, index, value, unboxedType, writeHole, emitted))
return false;
if (!*emitted) {
@@ -10474,11 +10519,13 @@ IonBuilder::setElemTryCache(bool* emitted, MDefinition* object,
bool
IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* obj, MDefinition* id, MDefinition* value,
- bool writeHole, bool* emitted)
+ JSValueType unboxedType, bool writeHole, bool* emitted)
{
MOZ_ASSERT(*emitted == false);
- MIRType elementType = DenseNativeElementType(constraints(), obj);
+ MIRType elementType = MIRType::None;
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ elementType = DenseNativeElementType(constraints(), obj);
bool packed = ElementAccessIsPacked(constraints(), obj);
// Writes which are on holes in the object do not have to bail out if they
@@ -10508,7 +10555,7 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
// Get the elements vector.
- MElements* elements = MElements::New(alloc(), obj);
+ MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements);
// Ensure the value is a double, if double conversion might be needed.
@@ -10545,7 +10592,7 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MInstruction* store;
MStoreElementCommon* common = nullptr;
if (writeHole && hasNoExtraIndexedProperty && !mayBeFrozen) {
- MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue);
+ MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue, unboxedType);
store = ins;
common = ins;
@@ -10557,23 +10604,27 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
bool strict = IsStrictSetPC(pc);
MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id,
- newValue, strict);
+ newValue, unboxedType, strict);
store = ins;
common = ins;
current->add(ins);
current->push(value);
} else {
- MInstruction* initLength = initializedLength(obj, elements);
+ MInstruction* initLength = initializedLength(obj, elements, unboxedType);
id = addBoundsCheck(id, initLength);
bool needsHoleCheck = !packed && !hasNoExtraIndexedProperty;
- MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
- store = ins;
- common = ins;
+ if (unboxedType != JSVAL_TYPE_MAGIC) {
+ store = storeUnboxedValue(obj, elements, 0, id, unboxedType, newValue);
+ } else {
+ MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
+ store = ins;
+ common = ins;
- current->add(store);
+ current->add(store);
+ }
current->push(value);
}
@@ -10691,6 +10742,18 @@ IonBuilder::jsop_length_fastPath()
return true;
}
+ // Compute the length for unboxed array objects.
+ if (UnboxedArrayElementType(constraints(), obj, nullptr) != JSVAL_TYPE_MAGIC &&
+ !objTypes->hasObjectFlags(constraints(), OBJECT_FLAG_LENGTH_OVERFLOW))
+ {
+ current->pop();
+
+ MUnboxedArrayLength* length = MUnboxedArrayLength::New(alloc(), obj);
+ current->add(length);
+ current->push(length);
+ return true;
+ }
+
// Compute the length for array typed objects.
TypedObjectPrediction prediction = typedObjectPrediction(obj);
if (!prediction.isUseless()) {
@@ -10924,8 +10987,11 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_
}
// Definite slots will always be fixed slots when they are in the
- // allowable range for fixed slots.
+ // allowable range for fixed slots, except for objects which were
+ // converted from unboxed objects and have a smaller allocation size.
size_t nfixed = NativeObject::MAX_FIXED_SLOTS;
+ if (ObjectGroup* group = key->group()->maybeOriginalUnboxedGroup())
+ nfixed = gc::GetGCKindSlots(group->unboxedLayout().getAllocKind());
uint32_t propertySlot = property.maybeTypes()->definiteSlot();
if (slot == UINT32_MAX) {
@@ -10940,6 +11006,65 @@ 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;
+ }
+
+ key->watchStateChangeForUnboxedConvertedToNative(constraints());
+
+ 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()
{
@@ -11386,6 +11511,8 @@ IonBuilder::jsop_getprop(PropertyName* name)
}
obj = maybeUnboxForPropertyAccess(obj);
+ if (obj->type() == MIRType::Object)
+ obj = convertUnboxedObjects(obj);
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
obj, name, types);
@@ -11457,6 +11584,11 @@ IonBuilder::jsop_getprop(PropertyName* name)
if (!getPropTryDefiniteSlot(&emitted, obj, name, barrier, types) || emitted)
return emitted;
+ // Try to emit loads from unboxed objects.
+ trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
+ if (!getPropTryUnboxed(&emitted, obj, name, barrier, types) || emitted)
+ return emitted;
+
// Try to inline a common property getter, or make a call.
trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
if (!getPropTryCommonGetter(&emitted, obj, name, types) || emitted)
@@ -11822,6 +11954,49 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool* emitted,
fieldPrediction, fieldTypeObj);
}
+MDefinition*
+IonBuilder::convertUnboxedObjects(MDefinition* obj)
+{
+ // If obj might be in any particular unboxed group which should be
+ // converted to a native representation, perform that conversion. This does
+ // not guarantee the object will not have such a group afterwards, if the
+ // object's possible groups are not precisely known.
+ TemporaryTypeSet* types = obj->resultTypeSet();
+ if (!types || types->unknownObject() || !types->objectOrSentinel())
+ return obj;
+
+ BaselineInspector::ObjectGroupVector list(alloc());
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = obj->resultTypeSet()->getObject(i);
+ if (!key || !key->isGroup())
+ continue;
+
+ if (UnboxedLayout* layout = key->group()->maybeUnboxedLayout()) {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (layout->nativeGroup() && !list.append(key->group()))
+ oomUnsafe.crash("IonBuilder::convertUnboxedObjects");
+ }
+ }
+
+ return convertUnboxedObjects(obj, list);
+}
+
+MDefinition*
+IonBuilder::convertUnboxedObjects(MDefinition* obj,
+ const BaselineInspector::ObjectGroupVector& list)
+{
+ for (size_t i = 0; i < list.length(); i++) {
+ ObjectGroup* group = list[i];
+ if (TemporaryTypeSet* types = obj->resultTypeSet()) {
+ if (!types->hasType(TypeSet::ObjectType(group)))
+ continue;
+ }
+ obj = MConvertUnboxedObjectToNative::New(alloc(), obj, group);
+ current->add(obj->toInstruction());
+ }
+ return obj;
+}
+
bool
IonBuilder::getPropTryDefiniteSlot(bool* emitted, MDefinition* obj, PropertyName* name,
BarrierKind barrier, TemporaryTypeSet* types)
@@ -11906,14 +12081,111 @@ 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;
+}
+
+bool
+IonBuilder::getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
+ BarrierKind barrier, TemporaryTypeSet* types)
+{
+ MOZ_ASSERT(*emitted == false);
+
+ JSValueType unboxedType;
+ uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+ if (offset == UINT32_MAX)
+ return true;
+
+ if (obj->type() != MIRType::Object) {
+ MGuardObject* guard = MGuardObject::New(alloc(), obj);
+ current->add(guard);
+ obj = guard;
+ }
+
+ MInstruction* load = loadUnboxedProperty(obj, offset, unboxedType, barrier, types);
+ current->push(load);
+
+ if (!pushTypeBarrier(load, types, barrier))
+ return false;
+
+ trackOptimizationSuccess();
+ *emitted = true;
+ return true;
+}
+
MDefinition*
IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
+ const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
bool isOwnProperty)
{
MOZ_ASSERT(holder);
MOZ_ASSERT(holderShape);
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
if (isOwnProperty) {
MOZ_ASSERT(receivers.empty());
return addShapeGuard(obj, holderShape, Bailout_ShapeGuard);
@@ -11937,8 +12209,10 @@ IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName
JSObject* foundProto = nullptr;
bool isOwnProperty = false;
BaselineInspector::ReceiverVector receivers(alloc());
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->commonGetPropFunction(pc, &foundProto, &lastProperty, &commonGetter,
- &globalShape, &isOwnProperty, receivers))
+ &globalShape, &isOwnProperty,
+ receivers, convertUnboxedGroups))
{
return true;
}
@@ -11954,7 +12228,8 @@ IonBuilder::getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName
// If type information is bad, we can still optimize the getter if we
// shape guard.
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
- receivers, isOwnProperty);
+ receivers, convertUnboxedGroups,
+ isOwnProperty);
if (!obj)
return false;
}
@@ -12122,12 +12397,15 @@ IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName
MOZ_ASSERT(*emitted == false);
BaselineInspector::ReceiverVector receivers(alloc());
- if (!inspector->maybeInfoForPropertyOp(pc, receivers))
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+ if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(receivers))
return true;
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
MIRType rvalType = types->getKnownMIRType();
if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
rvalType = MIRType::Value;
@@ -12150,6 +12428,45 @@ IonBuilder::getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName
return true;
}
+ if (receivers[0].shape) {
+ // Monomorphic load from an unboxed object expando.
+ spew("Inlining monomorphic unboxed expando GETPROP");
+
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ true, Bailout_ShapeGuard);
+
+ MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
+ current->add(expando);
+
+ expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
+
+ Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
+ MOZ_ASSERT(shape);
+
+ if (!loadSlot(expando, shape, rvalType, barrier, types))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
+ return true;
+ }
+
+ // Monomorphic load from an unboxed object.
+ ObjectGroup* group = receivers[0].group;
+ if (obj->resultTypeSet() && !obj->resultTypeSet()->hasType(TypeSet::ObjectType(group)))
+ return true;
+
+ obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
+
+ const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name);
+ MInstruction* load = loadUnboxedProperty(obj, property->offset, property->type, barrier, types);
+ current->push(load);
+
+ if (!pushTypeBarrier(load, types, barrier))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
return true;
}
@@ -12391,7 +12708,7 @@ bool
IonBuilder::jsop_setprop(PropertyName* name)
{
MDefinition* value = current->pop();
- MDefinition* obj = current->pop();
+ MDefinition* obj = convertUnboxedObjects(current->pop());
bool emitted = false;
startTrackingOptimizations();
@@ -12424,6 +12741,13 @@ IonBuilder::jsop_setprop(PropertyName* name)
bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
/* canModify = */ true);
+ if (!forceInlineCaches()) {
+ // Try to emit stores to unboxed objects.
+ trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
+ if (!setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes) || emitted)
+ return emitted;
+ }
+
// Add post barrier if needed. The instructions above manage any post
// barriers they need directly.
if (NeedsPostBarrier(value))
@@ -12457,8 +12781,10 @@ IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
JSObject* foundProto = nullptr;
bool isOwnProperty;
BaselineInspector::ReceiverVector receivers(alloc());
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
if (!inspector->commonSetPropFunction(pc, &foundProto, &lastProperty, &commonSetter,
- &isOwnProperty, receivers))
+ &isOwnProperty,
+ receivers, convertUnboxedGroups))
{
trackOptimizationOutcome(TrackedOutcome::NoProtoFound);
return true;
@@ -12473,7 +12799,8 @@ IonBuilder::setPropTryCommonSetter(bool* emitted, MDefinition* obj,
// If type information is bad, we can still optimize the setter if we
// shape guard.
obj = addShapeGuardsForGetterSetter(obj, foundProto, lastProperty,
- receivers, isOwnProperty);
+ receivers, convertUnboxedGroups,
+ isOwnProperty);
if (!obj)
return false;
}
@@ -12729,6 +13056,100 @@ 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::setPropTryUnboxed(bool* emitted, MDefinition* obj,
+ PropertyName* name, MDefinition* value,
+ bool barrier, TemporaryTypeSet* objTypes)
+{
+ MOZ_ASSERT(*emitted == false);
+
+ if (barrier) {
+ trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
+ return true;
+ }
+
+ JSValueType unboxedType;
+ uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+ if (offset == UINT32_MAX)
+ return true;
+
+ if (obj->type() != MIRType::Object) {
+ MGuardObject* guard = MGuardObject::New(alloc(), obj);
+ current->add(guard);
+ obj = guard;
+ }
+
+ MInstruction* store = storeUnboxedProperty(obj, offset, unboxedType, value);
+
+ current->push(value);
+
+ if (!resumeAfter(store))
+ return false;
+
+ *emitted = true;
+ return true;
+}
+
bool
IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
@@ -12742,12 +13163,15 @@ IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
}
BaselineInspector::ReceiverVector receivers(alloc());
- if (!inspector->maybeInfoForPropertyOp(pc, receivers))
+ BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+ if (!inspector->maybeInfoForPropertyOp(pc, receivers, convertUnboxedGroups))
return false;
if (!canInlinePropertyOpShapes(receivers))
return true;
+ obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
if (receivers.length() == 1) {
if (!receivers[0].group) {
// Monomorphic store to a native object.
@@ -12767,6 +13191,46 @@ IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
return true;
}
+ if (receivers[0].shape) {
+ // Monomorphic store to an unboxed object expando.
+ spew("Inlining monomorphic unboxed expando SETPROP");
+
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ obj = addUnboxedExpandoGuard(obj, /* hasExpando = */ true, Bailout_ShapeGuard);
+
+ MInstruction* expando = MLoadUnboxedExpando::New(alloc(), obj);
+ current->add(expando);
+
+ expando = addShapeGuard(expando, receivers[0].shape, Bailout_ShapeGuard);
+
+ Shape* shape = receivers[0].shape->searchLinear(NameToId(name));
+ MOZ_ASSERT(shape);
+
+ bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
+ if (!storeSlot(expando, shape, value, needsBarrier))
+ return false;
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
+ return true;
+ }
+
+ // Monomorphic store to an unboxed object.
+ spew("Inlining monomorphic unboxed SETPROP");
+
+ ObjectGroup* group = receivers[0].group;
+ if (!objTypes->hasType(TypeSet::ObjectType(group)))
+ return true;
+
+ obj = addGroupGuard(obj, group, Bailout_ShapeGuard);
+
+ const UnboxedLayout::Property* property = group->unboxedLayout().lookup(name);
+ storeUnboxedProperty(obj, property->offset, property->type, value);
+
+ current->push(value);
+
+ trackOptimizationOutcome(TrackedOutcome::Monomorphic);
+ *emitted = true;
return true;
}
@@ -13465,7 +13929,7 @@ IonBuilder::jsop_setaliasedvar(EnvironmentCoordinate ec)
bool
IonBuilder::jsop_in()
{
- MDefinition* obj = current->pop();
+ MDefinition* obj = convertUnboxedObjects(current->pop());
MDefinition* id = current->pop();
bool emitted = false;
@@ -13492,8 +13956,11 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
if (shouldAbortOnPreliminaryGroups(obj))
return true;
- if (!ElementAccessIsDenseNative(constraints(), obj, id))
- return true;
+ JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, id);
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ if (!ElementAccessIsDenseNative(constraints(), obj, id))
+ return true;
+ }
if (ElementAccessHasExtraIndexedProperty(this, obj))
return true;
@@ -13508,10 +13975,10 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
id = idInt32;
// Get the elements vector.
- MElements* elements = MElements::New(alloc(), obj);
+ MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements);
- MInstruction* initLength = initializedLength(obj, elements);
+ MInstruction* initLength = initializedLength(obj, elements, unboxedType);
// If there are no holes, speculate the InArray check will not fail.
if (!needsHoleCheck && !failedBoundsCheck_) {
@@ -13521,7 +13988,8 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
}
// Check if id < initLength and elem[id] not a hole.
- MInArray* ins = MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck);
+ MInArray* ins = MInArray::New(alloc(), elements, id, initLength, obj, needsHoleCheck,
+ unboxedType);
current->add(ins);
current->push(ins);
@@ -13818,6 +14286,19 @@ IonBuilder::addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bail
}
MInstruction*
+IonBuilder::addUnboxedExpandoGuard(MDefinition* obj, bool hasExpando, BailoutKind bailoutKind)
+{
+ MGuardUnboxedExpando* guard = MGuardUnboxedExpando::New(alloc(), obj, hasExpando, bailoutKind);
+ current->add(guard);
+
+ // If a shape guard failed in the past, don't optimize group guards.
+ if (failedShapeGuard_)
+ guard->setNotMovable();
+
+ return guard;
+}
+
+MInstruction*
IonBuilder::addGuardReceiverPolymorphic(MDefinition* obj,
const BaselineInspector::ReceiverVector& receivers)
{
@@ -13826,6 +14307,15 @@ IonBuilder::addGuardReceiverPolymorphic(MDefinition* obj,
// Monomorphic guard on a native object.
return addShapeGuard(obj, receivers[0].shape, Bailout_ShapeGuard);
}
+
+ if (!receivers[0].shape) {
+ // Guard on an unboxed object that does not have an expando.
+ obj = addGroupGuard(obj, receivers[0].group, Bailout_ShapeGuard);
+ return addUnboxedExpandoGuard(obj, /* hasExpando = */ false, Bailout_ShapeGuard);
+ }
+
+ // Monomorphic receiver guards are not yet supported when the receiver
+ // is an unboxed object with an expando.
}
MGuardReceiverPolymorphic* guard = MGuardReceiverPolymorphic::New(alloc(), obj);
@@ -14199,24 +14689,32 @@ IonBuilder::constantInt(int32_t i)
}
MInstruction*
-IonBuilder::initializedLength(MDefinition* obj, MDefinition* elements)
+IonBuilder::initializedLength(MDefinition* obj, MDefinition* elements, JSValueType unboxedType)
{
- MInstruction* res = MInitializedLength::New(alloc(), elements);
+ MInstruction* res;
+ if (unboxedType != JSVAL_TYPE_MAGIC)
+ res = MUnboxedArrayInitializedLength::New(alloc(), obj);
+ else
+ res = MInitializedLength::New(alloc(), elements);
current->add(res);
return res;
}
MInstruction*
-IonBuilder::setInitializedLength(MDefinition* obj, size_t count)
+IonBuilder::setInitializedLength(MDefinition* obj, JSValueType unboxedType, size_t count)
{
MOZ_ASSERT(count);
- // MSetInitializedLength takes the index of the last element, rather
- // than the count itself.
- MInstruction* elements = MElements::New(alloc(), obj, /* unboxed = */ false);
- current->add(elements);
- MInstruction* res =
- MSetInitializedLength::New(alloc(), elements, constant(Int32Value(count - 1)));
+ MInstruction* res;
+ if (unboxedType != JSVAL_TYPE_MAGIC) {
+ res = MSetUnboxedArrayInitializedLength::New(alloc(), obj, constant(Int32Value(count)));
+ } else {
+ // MSetInitializedLength takes the index of the last element, rather
+ // than the count itself.
+ MInstruction* elements = MElements::New(alloc(), obj, /* unboxed = */ false);
+ current->add(elements);
+ res = MSetInitializedLength::New(alloc(), elements, constant(Int32Value(count - 1)));
+ }
current->add(res);
return res;
}
diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h
index 6a3b61232..f359c764f 100644
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -346,8 +346,9 @@ class IonBuilder
MConstant* constant(const Value& v);
MConstant* constantInt(int32_t i);
- MInstruction* initializedLength(MDefinition* obj, MDefinition* elements);
- MInstruction* setInitializedLength(MDefinition* obj, size_t count);
+ MInstruction* initializedLength(MDefinition* obj, MDefinition* elements,
+ JSValueType unboxedType);
+ MInstruction* setInitializedLength(MDefinition* obj, JSValueType unboxedType, size_t count);
// Improve the type information at tests
MOZ_MUST_USE bool improveTypesAtTest(MDefinition* ins, bool trueBranch, MTest* test);
@@ -400,6 +401,7 @@ class IonBuilder
MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length);
MInstruction* addShapeGuard(MDefinition* obj, Shape* const shape, BailoutKind bailoutKind);
MInstruction* addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bailoutKind);
+ MInstruction* addUnboxedExpandoGuard(MDefinition* obj, bool hasExpando, BailoutKind bailoutKind);
MInstruction* addSharedTypedArrayGuard(MDefinition* obj);
MInstruction*
@@ -439,6 +441,8 @@ class IonBuilder
BarrierKind barrier, TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyName* name,
BarrierKind barrier, TemporaryTypeSet* types);
+ MOZ_MUST_USE bool getPropTryUnboxed(bool* emitted, MDefinition* obj, PropertyName* name,
+ BarrierKind barrier, TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryCommonGetter(bool* emitted, MDefinition* obj, PropertyName* name,
TemporaryTypeSet* types);
MOZ_MUST_USE bool getPropTryInlineAccess(bool* emitted, MDefinition* obj, PropertyName* name,
@@ -471,6 +475,9 @@ class IonBuilder
MOZ_MUST_USE bool setPropTryDefiniteSlot(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes);
+ MOZ_MUST_USE bool setPropTryUnboxed(bool* emitted, MDefinition* obj,
+ PropertyName* name, MDefinition* value,
+ bool barrier, TemporaryTypeSet* objTypes);
MOZ_MUST_USE bool setPropTryInlineAccess(bool* emitted, MDefinition* obj,
PropertyName* name, MDefinition* value,
bool barrier, TemporaryTypeSet* objTypes);
@@ -610,6 +617,7 @@ class IonBuilder
TypedObjectPrediction elemTypeReprs,
uint32_t elemSize);
MOZ_MUST_USE bool initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
+ JSValueType unboxedType,
bool addResumePointAndIncrementInitializedLength);
// jsop_getelem() helpers.
@@ -722,13 +730,15 @@ class IonBuilder
MOZ_MUST_USE bool jsop_bindname(PropertyName* name);
MOZ_MUST_USE bool jsop_bindvar();
MOZ_MUST_USE bool jsop_getelem();
- MOZ_MUST_USE bool jsop_getelem_dense(MDefinition* obj, MDefinition* index);
+ MOZ_MUST_USE bool jsop_getelem_dense(MDefinition* obj, MDefinition* index,
+ JSValueType unboxedType);
MOZ_MUST_USE bool jsop_getelem_typed(MDefinition* obj, MDefinition* index,
ScalarTypeDescr::Type arrayType);
MOZ_MUST_USE bool jsop_setelem();
MOZ_MUST_USE bool jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* object, MDefinition* index,
- MDefinition* value, bool writeHole, bool* emitted);
+ MDefinition* value, JSValueType unboxedType,
+ bool writeHole, bool* emitted);
MOZ_MUST_USE bool jsop_setelem_typed(ScalarTypeDescr::Type arrayType,
MDefinition* object, MDefinition* index,
MDefinition* value);
@@ -1033,6 +1043,7 @@ class IonBuilder
MDefinition*
addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
const BaselineInspector::ReceiverVector& receivers,
+ const BaselineInspector::ObjectGroupVector& convertUnboxedGroups,
bool isOwnProperty);
MOZ_MUST_USE bool annotateGetPropertyCache(MDefinition* obj, PropertyName* name,
@@ -1050,6 +1061,22 @@ class IonBuilder
ResultWithOOM<bool> testNotDefinedProperty(MDefinition* obj, jsid id);
uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed);
+ MDefinition* convertUnboxedObjects(MDefinition* obj);
+ MDefinition* convertUnboxedObjects(MDefinition* obj,
+ const BaselineInspector::ObjectGroupVector& list);
+ uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
+ JSValueType* punboxedType);
+ MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
+ 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..c2dc57373 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,29 @@ 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<UnboxedArrayObject>()) {
+ MOZ_ASSERT(failure);
+ masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
+ } else if (obj->is<TypedObject>()) {
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
ImmGCPtr(obj->group()), failure);
@@ -736,6 +759,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 +778,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 +839,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 +870,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,
@@ -1135,6 +1188,39 @@ GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher&
return true;
}
+static void
+GenerateUnboxedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
+ JSObject* array, Register object, TypedOrValueRegister output,
+ Label* failures)
+{
+ Register outReg;
+ if (output.hasValue()) {
+ outReg = output.valueReg().scratchReg();
+ } else {
+ MOZ_ASSERT(output.type() == MIRType::Int32);
+ outReg = output.typedReg().gpr();
+ }
+ MOZ_ASSERT(object != outReg);
+
+ TestMatchingReceiver(masm, attacher, object, array, failures);
+
+ // Load length.
+ masm.load32(Address(object, UnboxedArrayObject::offsetOfLength()), outReg);
+
+ // Check for a length that fits in an int32.
+ masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
+
+ if (output.hasValue())
+ masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
+
+ // Success.
+ attacher.jumpRejoin(masm);
+
+ // Failure.
+ masm.bind(failures);
+ attacher.jumpNextStub(masm);
+}
+
// In this case, the code for TypedArray and SharedTypedArray is not the same,
// because the code embeds pointers to the respective class arrays. Code that
// caches the stub code must distinguish between the two cases.
@@ -1448,6 +1534,101 @@ 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::tryAttachUnboxedArrayLength(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<UnboxedArrayObject>())
+ return true;
+
+ if (!JSID_IS_ATOM(id, cx->names().length))
+ return true;
+
+ if (obj->as<UnboxedArrayObject>().length() > INT32_MAX)
+ return true;
+
+ if (!allowArrayLength(cx))
+ return true;
+
+ *emitted = true;
+
+ MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+ Label failures;
+ emitIdGuard(masm, id, &failures);
+
+ StubAttacher attacher(*this);
+ GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output(), &failures);
+ return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length",
+ JS::TrackedOutcome::ICGetPropStub_UnboxedArrayLength);
+}
+
+bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
{
@@ -2016,6 +2197,15 @@ 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 && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
+ return false;
+
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
return false;
}
@@ -2194,6 +2384,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 +3027,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 +3064,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 +3077,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 +3099,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 +3127,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 +3371,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 +3587,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 +3631,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 +3688,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 +3719,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);
@@ -3623,7 +4026,7 @@ GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript
GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
TypedOrValueRegister output)
{
- if (!obj->is<TypedArrayObject>())
+ if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
return false;
MOZ_ASSERT(idval.isInt32() || idval.isString());
@@ -3654,6 +4057,13 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
return output.hasValue() || !output.typedReg().isFloat();
}
+ if (index >= obj->as<UnboxedArrayObject>().initializedLength())
+ return false;
+
+ JSValueType elementType = obj->as<UnboxedArrayObject>().elementType();
+ if (elementType == JSVAL_TYPE_DOUBLE)
+ return output.hasValue();
+
return output.hasValue() || !output.typedReg().isFloat();
}
@@ -3730,27 +4140,46 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
Label popObjectAndFail;
- // Guard on the initialized length.
- Address length(object, TypedArrayObject::lengthOffset());
- masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
+ if (array->is<TypedArrayObject>()) {
+ // Guard on the initialized length.
+ Address length(object, TypedArrayObject::lengthOffset());
+ masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
- // Save the object register on the stack in case of failure.
- Register elementReg = object;
- masm.push(object);
+ // Save the object register on the stack in case of failure.
+ Register elementReg = object;
+ masm.push(object);
- // Load elements vector.
- masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
+ // Load elements vector.
+ masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
- // Load the value. We use an invalid register because the destination
- // register is necessary a non double register.
- Scalar::Type arrayType = array->as<TypedArrayObject>().type();
- int width = Scalar::byteSize(arrayType);
- BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
- if (output.hasValue()) {
- masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
- elementReg, &popObjectAndFail);
+ // Load the value. We use an invalid register because the destination
+ // register is necessary a non double register.
+ Scalar::Type arrayType = array->as<TypedArrayObject>().type();
+ int width = Scalar::byteSize(arrayType);
+ BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
+ if (output.hasValue()) {
+ masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
+ elementReg, &popObjectAndFail);
+ } else {
+ masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
+ }
} else {
- masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
+ // Save the object register on the stack in case of failure.
+ masm.push(object);
+
+ // Guard on the initialized length.
+ masm.load32(Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), object);
+ masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), object);
+ masm.branch32(Assembler::BelowOrEqual, object, indexReg, &popObjectAndFail);
+
+ // Load elements vector.
+ Register elementReg = object;
+ masm.loadPtr(Address(masm.getStackPointer(), 0), object);
+ masm.loadPtr(Address(object, UnboxedArrayObject::offsetOfElements()), elementReg);
+
+ JSValueType elementType = array->as<UnboxedArrayObject>().elementType();
+ BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
+ masm.loadUnboxedProperty(source, elementType, output);
}
masm.pop(object);
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/Lowering.cpp b/js/src/jit/Lowering.cpp
index 108450983..19266bae8 100644
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2895,6 +2895,32 @@ LIRGenerator::visitSetInitializedLength(MSetInitializedLength* ins)
}
void
+LIRGenerator::visitUnboxedArrayLength(MUnboxedArrayLength* ins)
+{
+ define(new(alloc()) LUnboxedArrayLength(useRegisterAtStart(ins->object())), ins);
+}
+
+void
+LIRGenerator::visitUnboxedArrayInitializedLength(MUnboxedArrayInitializedLength* ins)
+{
+ define(new(alloc()) LUnboxedArrayInitializedLength(useRegisterAtStart(ins->object())), ins);
+}
+
+void
+LIRGenerator::visitIncrementUnboxedArrayInitializedLength(MIncrementUnboxedArrayInitializedLength* ins)
+{
+ add(new(alloc()) LIncrementUnboxedArrayInitializedLength(useRegister(ins->object())), ins);
+}
+
+void
+LIRGenerator::visitSetUnboxedArrayInitializedLength(MSetUnboxedArrayInitializedLength* ins)
+{
+ add(new(alloc()) LSetUnboxedArrayInitializedLength(useRegister(ins->object()),
+ useRegisterOrConstant(ins->length()),
+ temp()), ins);
+}
+
+void
LIRGenerator::visitNot(MNot* ins)
{
MDefinition* op = ins->input();
@@ -3143,16 +3169,22 @@ LIRGenerator::visitStoreElementHole(MStoreElementHole* ins)
const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index());
+ // Use a temp register when adding new elements to unboxed arrays.
+ LDefinition tempDef = LDefinition::BogusTemp();
+ if (ins->unboxedType() != JSVAL_TYPE_MAGIC)
+ tempDef = temp();
+
LInstruction* lir;
switch (ins->value()->type()) {
case MIRType::Value:
- lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()));
+ lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()),
+ tempDef);
break;
default:
{
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
- lir = new(alloc()) LStoreElementHoleT(object, elements, index, value);
+ lir = new(alloc()) LStoreElementHoleT(object, elements, index, value, tempDef);
break;
}
}
@@ -3171,14 +3203,20 @@ LIRGenerator::visitFallibleStoreElement(MFallibleStoreElement* ins)
const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index());
+ // Use a temp register when adding new elements to unboxed arrays.
+ LDefinition tempDef = LDefinition::BogusTemp();
+ if (ins->unboxedType() != JSVAL_TYPE_MAGIC)
+ tempDef = temp();
+
LInstruction* lir;
switch (ins->value()->type()) {
case MIRType::Value:
- lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()));
+ lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()),
+ tempDef);
break;
default:
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
- lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value);
+ lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value, tempDef);
break;
}
@@ -3220,6 +3258,14 @@ LIRGenerator::visitStoreUnboxedString(MStoreUnboxedString* ins)
}
void
+LIRGenerator::visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins)
+{
+ LInstruction* check = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()));
+ add(check, ins);
+ assignSafepoint(check, ins);
+}
+
+void
LIRGenerator::visitEffectiveAddress(MEffectiveAddress* ins)
{
define(new(alloc()) LEffectiveAddress(useRegister(ins->base()), useRegister(ins->index())), ins);
@@ -3737,6 +3783,24 @@ LIRGenerator::visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins)
}
void
+LIRGenerator::visitGuardUnboxedExpando(MGuardUnboxedExpando* ins)
+{
+ LGuardUnboxedExpando* guard =
+ new(alloc()) LGuardUnboxedExpando(useRegister(ins->object()));
+ assignSnapshot(guard, ins->bailoutKind());
+ add(guard, ins);
+ redefine(ins, ins->object());
+}
+
+void
+LIRGenerator::visitLoadUnboxedExpando(MLoadUnboxedExpando* ins)
+{
+ LLoadUnboxedExpando* lir =
+ new(alloc()) LLoadUnboxedExpando(useRegisterAtStart(ins->object()));
+ define(lir, ins);
+}
+
+void
LIRGenerator::visitAssertRange(MAssertRange* ins)
{
MDefinition* input = ins->input();
diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h
index 81e6abbbb..de66f175b 100644
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -217,6 +217,10 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitTypedObjectDescr(MTypedObjectDescr* ins);
void visitInitializedLength(MInitializedLength* ins);
void visitSetInitializedLength(MSetInitializedLength* ins);
+ void visitUnboxedArrayLength(MUnboxedArrayLength* ins);
+ void visitUnboxedArrayInitializedLength(MUnboxedArrayInitializedLength* ins);
+ void visitIncrementUnboxedArrayInitializedLength(MIncrementUnboxedArrayInitializedLength* ins);
+ void visitSetUnboxedArrayInitializedLength(MSetUnboxedArrayInitializedLength* ins);
void visitNot(MNot* ins);
void visitBoundsCheck(MBoundsCheck* ins);
void visitBoundsCheckLower(MBoundsCheckLower* ins);
@@ -229,6 +233,7 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitFallibleStoreElement(MFallibleStoreElement* ins);
void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins);
void visitStoreUnboxedString(MStoreUnboxedString* ins);
+ void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins);
void visitEffectiveAddress(MEffectiveAddress* ins);
void visitArrayPopShift(MArrayPopShift* ins);
void visitArrayPush(MArrayPush* ins);
@@ -252,6 +257,8 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitGuardObject(MGuardObject* ins);
void visitGuardString(MGuardString* ins);
void visitGuardReceiverPolymorphic(MGuardReceiverPolymorphic* ins);
+ void visitGuardUnboxedExpando(MGuardUnboxedExpando* ins);
+ void visitLoadUnboxedExpando(MLoadUnboxedExpando* ins);
void visitPolyInlineGuard(MPolyInlineGuard* ins);
void visitAssertRange(MAssertRange* ins);
void visitCallGetProperty(MCallGetProperty* ins);
diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp
index 236354530..f2071dc6a 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;
@@ -470,6 +471,11 @@ IonBuilder::inlineArray(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
+ if (templateObject->is<UnboxedArrayObject>()) {
+ if (templateObject->group()->unboxedLayout().nativeGroup())
+ return InliningStatus_NotInlined;
+ }
+
// Multiple arguments imply array initialization, not just construction.
if (callInfo.argc() >= 2) {
initLength = callInfo.argc();
@@ -517,7 +523,7 @@ IonBuilder::inlineArray(CallInfo& callInfo)
// Make sure initLength matches the template object's length. This is
// not guaranteed to be the case, for instance if we're inlining the
// MConstant may come from an outer script.
- if (initLength != templateObject->as<ArrayObject>().length())
+ if (initLength != GetAnyBoxedOrUnboxedArrayLength(templateObject))
return InliningStatus_NotInlined;
// Don't inline large allocations.
@@ -532,15 +538,16 @@ IonBuilder::inlineArray(CallInfo& callInfo)
MDefinition* array = current->peek(-1);
if (callInfo.argc() >= 2) {
+ JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
for (uint32_t i = 0; i < initLength; i++) {
if (!alloc().ensureBallast())
return InliningStatus_Error;
MDefinition* value = callInfo.getArg(i);
- if (!initializeArrayElement(array, i, value, /* addResumePoint = */ false))
+ if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error;
}
- MInstruction* setLength = setInitializedLength(array, initLength);
+ MInstruction* setLength = setInitializedLength(array, unboxedType, initLength);
if (!resumeAfter(setLength))
return InliningStatus_Error;
}
@@ -573,7 +580,7 @@ IonBuilder::inlineArrayIsArray(CallInfo& callInfo)
if (!clasp || clasp->isProxy())
return InliningStatus_NotInlined;
- isArray = (clasp == &ArrayObject::class_);
+ isArray = (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_);
}
pushConstant(BooleanValue(isArray));
@@ -604,12 +611,12 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
OBJECT_FLAG_LENGTH_OVERFLOW |
OBJECT_FLAG_ITERATED;
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
TemporaryTypeSet* thisTypes = obj->resultTypeSet();
if (!thisTypes)
return InliningStatus_NotInlined;
const Class* clasp = thisTypes->getKnownClass(constraints());
- if (clasp != &ArrayObject::class_)
+ if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined;
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) {
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
@@ -622,9 +629,17 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
return InliningStatus_NotInlined;
}
+ JSValueType unboxedType = JSVAL_TYPE_MAGIC;
+ if (clasp == &UnboxedArrayObject::class_) {
+ unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr);
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ return InliningStatus_NotInlined;
+ }
+
callInfo.setImplicitlyUsedUnchecked();
- obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
+ if (clasp == &ArrayObject::class_)
+ obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
TemporaryTypeSet* returnTypes = getInlineReturnTypeSet();
bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED);
@@ -635,7 +650,8 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
if (barrier != BarrierKind::NoBarrier)
returnType = MIRType::Value;
- MArrayPopShift* ins = MArrayPopShift::New(alloc(), obj, mode, needsHoleCheck, maybeUndefined);
+ MArrayPopShift* ins = MArrayPopShift::New(alloc(), obj, mode,
+ unboxedType, needsHoleCheck, maybeUndefined);
current->add(ins);
current->push(ins);
ins->setResultType(returnType);
@@ -684,7 +700,7 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
MDefinition* value = callInfo.getArg(0);
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
&obj, nullptr, &value, /* canModify = */ false))
@@ -717,6 +733,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
+ JSValueType unboxedType = JSVAL_TYPE_MAGIC;
+ if (clasp == &UnboxedArrayObject::class_) {
+ unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr);
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ return InliningStatus_NotInlined;
+ }
+
callInfo.setImplicitlyUsedUnchecked();
if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles ||
@@ -727,12 +750,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
value = valueDouble;
}
- obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
if (NeedsPostBarrier(value))
current->add(MPostWriteBarrier::New(alloc(), obj, value));
- MArrayPush* ins = MArrayPush::New(alloc(), obj, value);
+ MArrayPush* ins = MArrayPush::New(alloc(), obj, value, unboxedType);
current->add(ins);
current->push(ins);
@@ -749,7 +773,7 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
return InliningStatus_NotInlined;
}
- MDefinition* obj = callInfo.thisArg();
+ MDefinition* obj = convertUnboxedObjects(callInfo.thisArg());
// Ensure |this| and result are objects.
if (getInlineReturnType() != MIRType::Object)
@@ -773,9 +797,16 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
return InliningStatus_NotInlined;
const Class* clasp = thisTypes->getKnownClass(constraints());
- if (clasp != &ArrayObject::class_)
+ if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined;
+ JSValueType unboxedType = JSVAL_TYPE_MAGIC;
+ if (clasp == &UnboxedArrayObject::class_) {
+ unboxedType = UnboxedArrayElementType(constraints(), obj, nullptr);
+ if (unboxedType == JSVAL_TYPE_MAGIC)
+ return InliningStatus_NotInlined;
+ }
+
// Watch out for indexed properties on the object or its prototype.
if (ElementAccessHasExtraIndexedProperty(this, obj)) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
@@ -796,8 +827,15 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
if (!templateObj)
return InliningStatus_NotInlined;
- if (!templateObj->is<ArrayObject>())
- return InliningStatus_NotInlined;
+ if (unboxedType == JSVAL_TYPE_MAGIC) {
+ if (!templateObj->is<ArrayObject>())
+ return InliningStatus_NotInlined;
+ } else {
+ if (!templateObj->is<UnboxedArrayObject>())
+ return InliningStatus_NotInlined;
+ if (templateObj->as<UnboxedArrayObject>().elementType() != unboxedType)
+ return InliningStatus_NotInlined;
+ }
callInfo.setImplicitlyUsedUnchecked();
@@ -816,12 +854,16 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
end = MArrayLength::New(alloc(), elements);
current->add(end->toInstruction());
+ } else {
+ end = MUnboxedArrayLength::New(alloc(), obj);
+ current->add(end->toInstruction());
}
MArraySlice* ins = MArraySlice::New(alloc(), constraints(),
obj, begin, end,
templateObj,
- templateObj->group()->initialHeap(constraints()));
+ templateObj->group()->initialHeap(constraints()),
+ unboxedType);
current->add(ins);
current->push(ins);
@@ -1340,7 +1382,7 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
// Check if exist a template object in stub.
JSString* stringStr = nullptr;
JSString* stringSep = nullptr;
- ArrayObject* templateObject = nullptr;
+ JSObject* templateObject = nullptr;
if (!inspector->isOptimizableCallStringSplit(pc, &stringStr, &stringSep, &templateObject))
return InliningStatus_NotInlined;
@@ -1366,13 +1408,13 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
if (!key.maybeTypes()->hasType(TypeSet::StringType()))
return InliningStatus_NotInlined;
- uint32_t initLength = templateObject->length();
- if (templateObject->getDenseInitializedLength() != initLength)
+ uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(templateObject);
+ if (GetAnyBoxedOrUnboxedInitializedLength(templateObject) != initLength)
return InliningStatus_NotInlined;
Vector<MConstant*, 0, SystemAllocPolicy> arrayValues;
for (uint32_t i = 0; i < initLength; i++) {
- Value str = templateObject->getDenseElement(i);
+ Value str = GetAnyBoxedOrUnboxedDenseElement(templateObject, i);
MOZ_ASSERT(str.toString()->isAtom());
MConstant* value = MConstant::New(alloc().fallible(), str, constraints());
if (!value)
@@ -1403,6 +1445,8 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
return InliningStatus_Inlined;
}
+ JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
+
// Store all values, no need to initialize the length after each as
// jsop_initelem_array is doing because we do not expect to bailout
// because the memory is supposed to be allocated by now.
@@ -1413,11 +1457,11 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
MConstant* value = arrayValues[i];
current->add(value);
- if (!initializeArrayElement(array, i, value, /* addResumePoint = */ false))
+ if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error;
}
- MInstruction* setLength = setInitializedLength(array, initLength);
+ MInstruction* setLength = setInitializedLength(array, unboxedType, initLength);
if (!resumeAfter(setLength))
return InliningStatus_Error;
@@ -2053,7 +2097,7 @@ IonBuilder::inlineDefineDataProperty(CallInfo& callInfo)
if (callInfo.argc() != 3)
return InliningStatus_NotInlined;
- MDefinition* obj = callInfo.getArg(0);
+ MDefinition* obj = convertUnboxedObjects(callInfo.getArg(0));
MDefinition* id = callInfo.getArg(1);
MDefinition* value = callInfo.getArg(2);
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp
index 0cf31adb3..b9e5e8d61 100644
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2630,6 +2630,40 @@ jit::EqualTypes(MIRType type1, TemporaryTypeSet* typeset1,
return typeset1->equals(typeset2);
}
+// Tests whether input/inputTypes can always be stored to an unboxed
+// object/array property with the given unboxed type.
+bool
+jit::CanStoreUnboxedType(TempAllocator& alloc,
+ JSValueType unboxedType, MIRType input, TypeSet* inputTypes)
+{
+ TemporaryTypeSet types;
+
+ switch (unboxedType) {
+ case JSVAL_TYPE_BOOLEAN:
+ case JSVAL_TYPE_INT32:
+ case JSVAL_TYPE_DOUBLE:
+ case JSVAL_TYPE_STRING:
+ types.addType(TypeSet::PrimitiveType(unboxedType), alloc.lifoAlloc());
+ break;
+
+ case JSVAL_TYPE_OBJECT:
+ types.addType(TypeSet::AnyObjectType(), alloc.lifoAlloc());
+ types.addType(TypeSet::NullType(), alloc.lifoAlloc());
+ break;
+
+ default:
+ MOZ_CRASH("Bad unboxed type");
+ }
+
+ return TypeSetIncludes(&types, input, inputTypes);
+}
+
+static bool
+CanStoreUnboxedType(TempAllocator& alloc, JSValueType unboxedType, MDefinition* value)
+{
+ return CanStoreUnboxedType(alloc, unboxedType, value->type(), value->resultTypeSet());
+}
+
bool
MPhi::specializeType(TempAllocator& alloc)
{
@@ -4776,31 +4810,67 @@ MBeta::printOpcode(GenericPrinter& out) const
bool
MCreateThisWithTemplate::canRecoverOnBailout() const
{
- MOZ_ASSERT(templateObject()->is<PlainObject>());
- MOZ_ASSERT(!templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
+ MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>());
+ MOZ_ASSERT_IF(templateObject()->is<PlainObject>(),
+ !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
+ return true;
+}
+
+bool
+OperandIndexMap::init(TempAllocator& alloc, JSObject* templateObject)
+{
+ const UnboxedLayout& layout =
+ templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+
+ const UnboxedLayout::PropertyVector& properties = layout.properties();
+ MOZ_ASSERT(properties.length() < 255);
+
+ // Allocate an array of indexes, where the top of each field correspond to
+ // the index of the operand in the MObjectState instance.
+ if (!map.init(alloc, layout.size()))
+ return false;
+
+ // Reset all indexes to 0, which is an error code.
+ for (size_t i = 0; i < map.length(); i++)
+ map[i] = 0;
+
+ // Map the property offsets to the indexes of MObjectState operands.
+ uint8_t index = 1;
+ for (size_t i = 0; i < properties.length(); i++, index++)
+ map[properties[i].offset] = index;
+
return true;
}
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);
setRecoveredOnBailout();
- MOZ_ASSERT(templateObject->is<NativeObject>());
+ if (templateObject->is<NativeObject>()) {
+ NativeObject* nativeObject = &templateObject->as<NativeObject>();
+ numSlots_ = nativeObject->slotSpan();
+ numFixedSlots_ = nativeObject->numFixedSlots();
+ } else {
+ const UnboxedLayout& layout =
+ templateObject->as<UnboxedPlainObject>().layoutDontCheckGeneration();
+ // Same as UnboxedLayout::makeNativeGroup
+ numSlots_ = layout.properties().length();
+ numFixedSlots_ = gc::GetGCKindSlots(layout.getAllocKind());
+ }
- NativeObject* nativeObject = &templateObject->as<NativeObject>();
- numSlots_ = nativeObject->slotSpan();
- numFixedSlots_ = nativeObject->numFixedSlots();
+ operandIndex_ = operandIndex;
}
JSObject*
@@ -4835,21 +4905,39 @@ MObjectState::initFromTemplateObject(TempAllocator& alloc, MDefinition* undefine
// the template object. This is needed to account values which are baked in
// the template objects and not visible in IonMonkey, such as the
// uninitialized-lexical magic value of call objects.
- NativeObject& nativeObject = templateObject->as<NativeObject>();
- MOZ_ASSERT(nativeObject.slotSpan() == numSlots());
-
- MOZ_ASSERT(templateObject->is<NativeObject>());
- for (size_t i = 0; i < numSlots(); i++) {
- Value val = nativeObject.getSlot(i);
- MDefinition *def = undefinedVal;
- if (!val.isUndefined()) {
- MConstant* ins = val.isObject() ?
- MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
- MConstant::New(alloc, val);
- block()->insertBefore(this, ins);
- def = ins;
+ if (templateObject->is<UnboxedPlainObject>()) {
+ UnboxedPlainObject& unboxedObject = templateObject->as<UnboxedPlainObject>();
+ const UnboxedLayout& layout = unboxedObject.layoutDontCheckGeneration();
+ const UnboxedLayout::PropertyVector& properties = layout.properties();
+
+ for (size_t i = 0; i < properties.length(); i++) {
+ Value val = unboxedObject.getValue(properties[i], /* maybeUninitialized = */ true);
+ MDefinition *def = undefinedVal;
+ if (!val.isUndefined()) {
+ MConstant* ins = val.isObject() ?
+ MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
+ MConstant::New(alloc, val);
+ block()->insertBefore(this, ins);
+ def = ins;
+ }
+ initSlot(i, def);
+ }
+ } else {
+ NativeObject& nativeObject = templateObject->as<NativeObject>();
+ MOZ_ASSERT(nativeObject.slotSpan() == numSlots());
+
+ for (size_t i = 0; i < numSlots(); i++) {
+ Value val = nativeObject.getSlot(i);
+ MDefinition *def = undefinedVal;
+ if (!val.isUndefined()) {
+ MConstant* ins = val.isObject() ?
+ MConstant::NewConstraintlessObject(alloc, &val.toObject()) :
+ MConstant::New(alloc, val);
+ block()->insertBefore(this, ins);
+ def = ins;
+ }
+ initSlot(i, def);
}
- initSlot(i, def);
}
return true;
}
@@ -4860,7 +4948,14 @@ MObjectState::New(TempAllocator& alloc, MDefinition* obj)
JSObject* templateObject = templateObjectOf(obj);
MOZ_ASSERT(templateObject, "Unexpected object creation.");
- MObjectState* res = new(alloc) MObjectState(templateObject);
+ OperandIndexMap* operandIndex = nullptr;
+ if (templateObject->is<UnboxedPlainObject>()) {
+ operandIndex = new(alloc) OperandIndexMap;
+ if (!operandIndex || !operandIndex->init(alloc, templateObject))
+ return nullptr;
+ }
+
+ MObjectState* res = new(alloc) MObjectState(templateObject, operandIndex);
if (!res || !res->init(alloc, obj))
return nullptr;
return res;
@@ -5767,6 +5862,35 @@ MGetFirstDollarIndex::foldsTo(TempAllocator& alloc)
return MConstant::New(alloc, Int32Value(index));
}
+MConvertUnboxedObjectToNative*
+MConvertUnboxedObjectToNative::New(TempAllocator& alloc, MDefinition* obj, ObjectGroup* group)
+{
+ MConvertUnboxedObjectToNative* res = new(alloc) MConvertUnboxedObjectToNative(obj, group);
+
+ ObjectGroup* nativeGroup = group->unboxedLayout().nativeGroup();
+
+ // Make a new type set for the result of this instruction which replaces
+ // the input group with the native group we will convert it to.
+ TemporaryTypeSet* types = obj->resultTypeSet();
+ if (types && !types->unknownObject()) {
+ TemporaryTypeSet* newTypes = types->cloneWithoutObjects(alloc.lifoAlloc());
+ if (newTypes) {
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (!key)
+ continue;
+ if (key->unknownProperties() || !key->isGroup() || key->group() != group)
+ newTypes->addType(TypeSet::ObjectType(key), alloc.lifoAlloc());
+ else
+ newTypes->addType(TypeSet::ObjectType(nativeGroup), alloc.lifoAlloc());
+ }
+ res->setResultTypeSet(newTypes);
+ }
+ }
+
+ return res;
+}
+
bool
jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id)
@@ -5786,6 +5910,48 @@ jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints,
return clasp && clasp->isNative() && !IsTypedArrayClass(clasp);
}
+JSValueType
+jit::UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
+ MDefinition* id)
+{
+ if (obj->mightBeType(MIRType::String))
+ return JSVAL_TYPE_MAGIC;
+
+ if (id && id->type() != MIRType::Int32 && id->type() != MIRType::Double)
+ return JSVAL_TYPE_MAGIC;
+
+ TemporaryTypeSet* types = obj->resultTypeSet();
+ if (!types || types->unknownObject())
+ return JSVAL_TYPE_MAGIC;
+
+ JSValueType elementType = JSVAL_TYPE_MAGIC;
+ for (unsigned i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (!key)
+ continue;
+
+ if (key->unknownProperties() || !key->isGroup())
+ return JSVAL_TYPE_MAGIC;
+
+ if (key->clasp() != &UnboxedArrayObject::class_)
+ return JSVAL_TYPE_MAGIC;
+
+ const UnboxedLayout &layout = key->group()->unboxedLayout();
+
+ if (layout.nativeGroup())
+ return JSVAL_TYPE_MAGIC;
+
+ if (elementType == layout.elementType() || elementType == JSVAL_TYPE_MAGIC)
+ elementType = layout.elementType();
+ else
+ return JSVAL_TYPE_MAGIC;
+
+ key->watchStateChangeForUnboxedConvertedToNative(constraints);
+ }
+
+ return elementType;
+}
+
bool
jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id,
@@ -5945,6 +6111,11 @@ ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
firstElements.maybeTypes()->equals(secondElements.maybeTypes());
}
+ if (first->clasp() == &UnboxedArrayObject::class_) {
+ return first->group()->unboxedLayout().elementType() ==
+ second->group()->unboxedLayout().elementType();
+ }
+
return false;
}
@@ -6399,6 +6570,23 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
}
}
+ // Perform additional filtering to make sure that any unboxed property
+ // being written can accommodate the value.
+ for (size_t i = 0; i < types->getObjectCount(); i++) {
+ TypeSet::ObjectKey* key = types->getObject(i);
+ if (key && key->isGroup() && key->group()->maybeUnboxedLayout()) {
+ const UnboxedLayout& layout = key->group()->unboxedLayout();
+ if (name) {
+ const UnboxedLayout::Property* property = layout.lookup(name);
+ if (property && !CanStoreUnboxedType(alloc, property->type, *pvalue))
+ return true;
+ } else {
+ if (layout.isArray() && !CanStoreUnboxedType(alloc, layout.elementType(), *pvalue))
+ return true;
+ }
+ }
+ }
+
if (success)
return false;
@@ -6429,6 +6617,17 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
MOZ_ASSERT(excluded);
+ // If the excluded object is a group with an unboxed layout, make sure it
+ // does not have a corresponding native group. Objects with the native
+ // group might appear even though they are not in the type set.
+ if (excluded->isGroup()) {
+ if (UnboxedLayout* layout = excluded->group()->maybeUnboxedLayout()) {
+ if (layout->nativeGroup())
+ return true;
+ excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
+ }
+ }
+
*pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
return false;
}
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
index 0c1e77f80..af0abc695 100644
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -30,6 +30,7 @@
#include "vm/EnvironmentObject.h"
#include "vm/SharedMem.h"
#include "vm/TypedArrayCommon.h"
+#include "vm/UnboxedObject.h"
// Undo windows.h damage on Win64
#undef MemoryBarrier
@@ -375,7 +376,8 @@ 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.
+ // a typed array, 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
@@ -432,6 +434,9 @@ class AliasSet {
MOZ_ASSERT(flags && !(flags & Store_));
return AliasSet(flags | Store_);
}
+ static uint32_t BoxedOrUnboxedElements(JSValueType type) {
+ return (type == JSVAL_TYPE_MAGIC) ? Element : UnboxedElement;
+ }
};
typedef Vector<MDefinition*, 6, JitAllocPolicy> MDefinitionVector;
@@ -3758,9 +3763,14 @@ class MObjectState
{
private:
uint32_t numSlots_;
- uint32_t numFixedSlots_;
+ uint32_t numFixedSlots_; // valid if isUnboxed() == false.
+ OperandIndexMap* operandIndex_; // valid if isUnboxed() == true.
+
+ bool isUnboxed() const {
+ return operandIndex_ != nullptr;
+ }
- MObjectState(JSObject *templateObject);
+ MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex);
explicit MObjectState(MObjectState* state);
MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj);
@@ -3820,6 +3830,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;
@@ -8741,6 +8763,102 @@ class MSetInitializedLength
ALLOW_CLONE(MSetInitializedLength)
};
+// Load the length from an unboxed array.
+class MUnboxedArrayLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MUnboxedArrayLength(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnboxedArrayLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MUnboxedArrayLength)
+};
+
+// Load the initialized length from an unboxed array.
+class MUnboxedArrayInitializedLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MUnboxedArrayInitializedLength(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Int32);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(UnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MUnboxedArrayInitializedLength)
+};
+
+// Increment the initialized length of an unboxed array object.
+class MIncrementUnboxedArrayInitializedLength
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MIncrementUnboxedArrayInitializedLength(MDefinition* obj)
+ : MUnaryInstruction(obj)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(IncrementUnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MIncrementUnboxedArrayInitializedLength)
+};
+
+// Set the initialized length of an unboxed array object.
+class MSetUnboxedArrayInitializedLength
+ : public MBinaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ explicit MSetUnboxedArrayInitializedLength(MDefinition* obj, MDefinition* length)
+ : MBinaryInstruction(obj, length)
+ {}
+
+ public:
+ INSTRUCTION_HEADER(SetUnboxedArrayInitializedLength)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object), (1, length))
+
+ AliasSet getAliasSet() const override {
+ return AliasSet::Store(AliasSet::ObjectFields);
+ }
+
+ ALLOW_CLONE(MSetUnboxedArrayInitializedLength)
+};
+
// Load the array length from an elements header.
class MArrayLength
: public MUnaryInstruction,
@@ -9234,19 +9352,23 @@ class MLoadElement
ALLOW_CLONE(MLoadElement)
};
-// Load a value from the elements vector of a native object.
+// Load a value from the elements vector for a dense native or unboxed array.
// If the index is out-of-bounds, or the indexed slot has a hole, undefined is
// returned instead.
class MLoadElementHole
: public MTernaryInstruction,
public SingleObjectPolicy::Data
{
+ // Unboxed element type, JSVAL_TYPE_MAGIC for dense native elements.
+ JSValueType unboxedType_;
+
bool needsNegativeIntCheck_;
bool needsHoleCheck_;
MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength,
- bool needsHoleCheck)
+ JSValueType unboxedType, bool needsHoleCheck)
: MTernaryInstruction(elements, index, initLength),
+ unboxedType_(unboxedType),
needsNegativeIntCheck_(true),
needsHoleCheck_(needsHoleCheck)
{
@@ -9268,6 +9390,9 @@ class MLoadElementHole
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, elements), (1, index), (2, initLength))
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
bool needsNegativeIntCheck() const {
return needsNegativeIntCheck_;
}
@@ -9278,6 +9403,8 @@ class MLoadElementHole
if (!ins->isLoadElementHole())
return false;
const MLoadElementHole* other = ins->toLoadElementHole();
+ if (unboxedType() != other->unboxedType())
+ return false;
if (needsHoleCheck() != other->needsHoleCheck())
return false;
if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
@@ -9285,7 +9412,7 @@ class MLoadElementHole
return congruentIfOperandsEqual(other);
}
AliasSet getAliasSet() const override {
- return AliasSet::Load(AliasSet::Element);
+ return AliasSet::Load(AliasSet::BoxedOrUnboxedElements(unboxedType()));
}
void collectRangeInfoPreTrunc() override;
@@ -9465,17 +9592,20 @@ class MStoreElement
ALLOW_CLONE(MStoreElement)
};
-// Like MStoreElement, but supports indexes >= initialized length. The downside
-// is that we cannot hoist the elements vector and bounds check, since this
-// instruction may update the (initialized) length and reallocate the elements
-// vector.
+// Like MStoreElement, but supports indexes >= initialized length, and can
+// handle unboxed arrays. The downside is that we cannot hoist the elements
+// vector and bounds check, since this instruction may update the (initialized)
+// length and reallocate the elements vector.
class MStoreElementHole
: public MAryInstruction<4>,
public MStoreElementCommon,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
{
+ JSValueType unboxedType_;
+
MStoreElementHole(MDefinition* object, MDefinition* elements,
- MDefinition* index, MDefinition* value)
+ MDefinition* index, MDefinition* value, JSValueType unboxedType)
+ : unboxedType_(unboxedType)
{
initOperand(0, object);
initOperand(1, elements);
@@ -9490,6 +9620,10 @@ class MStoreElementHole
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+
ALLOW_CLONE(MStoreElementHole)
};
@@ -9500,11 +9634,13 @@ class MFallibleStoreElement
public MStoreElementCommon,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
{
+ JSValueType unboxedType_;
bool strict_;
MFallibleStoreElement(MDefinition* object, MDefinition* elements,
MDefinition* index, MDefinition* value,
- bool strict)
+ JSValueType unboxedType, bool strict)
+ : unboxedType_(unboxedType)
{
initOperand(0, object);
initOperand(1, elements);
@@ -9520,6 +9656,10 @@ class MFallibleStoreElement
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+
bool strict() const {
return strict_;
}
@@ -9610,6 +9750,59 @@ class MStoreUnboxedString
ALLOW_CLONE(MStoreUnboxedString)
};
+// Passes through an object, after ensuring it is converted from an unboxed
+// object to a native representation.
+class MConvertUnboxedObjectToNative
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ CompilerObjectGroup group_;
+
+ explicit MConvertUnboxedObjectToNative(MDefinition* obj, ObjectGroup* group)
+ : MUnaryInstruction(obj),
+ group_(group)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(ConvertUnboxedObjectToNative)
+ NAMED_OPERANDS((0, object))
+
+ static MConvertUnboxedObjectToNative* New(TempAllocator& alloc, MDefinition* obj,
+ ObjectGroup* group);
+
+ ObjectGroup* group() const {
+ return group_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ return ins->toConvertUnboxedObjectToNative()->group() == group();
+ }
+ AliasSet getAliasSet() const override {
+ // This instruction can read and write to all parts of the object, but
+ // is marked as non-effectful so it can be consolidated by LICM and GVN
+ // and avoid inhibiting other optimizations.
+ //
+ // This is valid to do because when unboxed objects might have a native
+ // group they can be converted to, we do not optimize accesses to the
+ // unboxed objects and do not guard on their group or shape (other than
+ // in this opcode).
+ //
+ // Later accesses can assume the object has a native representation
+ // and optimize accordingly. Those accesses cannot be reordered before
+ // this instruction, however. This is prevented by chaining this
+ // instruction with the object itself, in the same way as MBoundsCheck.
+ return AliasSet::None();
+ }
+ bool appendRoots(MRootList& roots) const override {
+ return roots.append(group_);
+ }
+};
+
// Array.prototype.pop or Array.prototype.shift on a dense array.
class MArrayPopShift
: public MUnaryInstruction,
@@ -9623,12 +9816,13 @@ class MArrayPopShift
private:
Mode mode_;
+ JSValueType unboxedType_;
bool needsHoleCheck_;
bool maybeUndefined_;
- MArrayPopShift(MDefinition* object, Mode mode,
+ MArrayPopShift(MDefinition* object, Mode mode, JSValueType unboxedType,
bool needsHoleCheck, bool maybeUndefined)
- : MUnaryInstruction(object), mode_(mode),
+ : MUnaryInstruction(object), mode_(mode), unboxedType_(unboxedType),
needsHoleCheck_(needsHoleCheck), maybeUndefined_(maybeUndefined)
{ }
@@ -9646,8 +9840,12 @@ class MArrayPopShift
bool mode() const {
return mode_;
}
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
AliasSet getAliasSet() const override {
- return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element);
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
}
ALLOW_CLONE(MArrayPopShift)
@@ -9658,8 +9856,10 @@ class MArrayPush
: public MBinaryInstruction,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data
{
- MArrayPush(MDefinition* object, MDefinition* value)
- : MBinaryInstruction(object, value)
+ JSValueType unboxedType_;
+
+ MArrayPush(MDefinition* object, MDefinition* value, JSValueType unboxedType)
+ : MBinaryInstruction(object, value), unboxedType_(unboxedType)
{
setResultType(MIRType::Int32);
}
@@ -9669,8 +9869,12 @@ class MArrayPush
TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, value))
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
AliasSet getAliasSet() const override {
- return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element);
+ return AliasSet::Store(AliasSet::ObjectFields |
+ AliasSet::BoxedOrUnboxedElements(unboxedType()));
}
void computeRange(TempAllocator& alloc) override;
@@ -9684,13 +9888,15 @@ class MArraySlice
{
CompilerObject templateObj_;
gc::InitialHeap initialHeap_;
+ JSValueType unboxedType_;
MArraySlice(CompilerConstraintList* constraints, MDefinition* obj,
MDefinition* begin, MDefinition* end,
- JSObject* templateObj, gc::InitialHeap initialHeap)
+ JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType)
: MTernaryInstruction(obj, begin, end),
templateObj_(templateObj),
- initialHeap_(initialHeap)
+ initialHeap_(initialHeap),
+ unboxedType_(unboxedType)
{
setResultType(MIRType::Object);
}
@@ -9708,6 +9914,10 @@ class MArraySlice
return initialHeap_;
}
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
+
bool possiblyCalls() const override {
return true;
}
@@ -10972,6 +11182,11 @@ class MGuardShape
setMovable();
setResultType(MIRType::Object);
setResultTypeSet(obj->resultTypeSet());
+
+ // Disallow guarding on unboxed object shapes. The group is better to
+ // guard on, and guarding on the shape can interact badly with
+ // MConvertUnboxedObjectToNative.
+ MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_);
}
public:
@@ -11066,6 +11281,11 @@ class MGuardObjectGroup
setGuard();
setMovable();
setResultType(MIRType::Object);
+
+ // Unboxed groups which might be converted to natives can't be guarded
+ // on, due to MConvertUnboxedObjectToNative.
+ MOZ_ASSERT_IF(group->maybeUnboxedLayoutDontCheckGeneration(),
+ !group->unboxedLayoutDontCheckGeneration().nativeGroup());
}
public:
@@ -11174,6 +11394,73 @@ class MGuardClass
ALLOW_CLONE(MGuardClass)
};
+// Guard on the presence or absence of an unboxed object's expando.
+class MGuardUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ bool requireExpando_;
+ BailoutKind bailoutKind_;
+
+ MGuardUnboxedExpando(MDefinition* obj, bool requireExpando, BailoutKind bailoutKind)
+ : MUnaryInstruction(obj),
+ requireExpando_(requireExpando),
+ bailoutKind_(bailoutKind)
+ {
+ setGuard();
+ setMovable();
+ setResultType(MIRType::Object);
+ }
+
+ public:
+ INSTRUCTION_HEADER(GuardUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool requireExpando() const {
+ return requireExpando_;
+ }
+ BailoutKind bailoutKind() const {
+ return bailoutKind_;
+ }
+ bool congruentTo(const MDefinition* ins) const override {
+ if (!congruentIfOperandsEqual(ins))
+ return false;
+ if (requireExpando() != ins->toGuardUnboxedExpando()->requireExpando())
+ return false;
+ return true;
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
+// Load an unboxed plain object's expando.
+class MLoadUnboxedExpando
+ : public MUnaryInstruction,
+ public SingleObjectPolicy::Data
+{
+ private:
+ explicit MLoadUnboxedExpando(MDefinition* object)
+ : MUnaryInstruction(object)
+ {
+ setResultType(MIRType::Object);
+ setMovable();
+ }
+
+ public:
+ INSTRUCTION_HEADER(LoadUnboxedExpando)
+ TRIVIAL_NEW_WRAPPERS
+ NAMED_OPERANDS((0, object))
+
+ bool congruentTo(const MDefinition* ins) const override {
+ return congruentIfOperandsEqual(ins);
+ }
+ AliasSet getAliasSet() const override {
+ return AliasSet::Load(AliasSet::ObjectFields);
+ }
+};
+
// Load from vp[slot] (slots that are not inline in an object).
class MLoadSlot
: public MUnaryInstruction,
@@ -12076,13 +12363,15 @@ class MInArray
{
bool needsHoleCheck_;
bool needsNegativeIntCheck_;
+ JSValueType unboxedType_;
MInArray(MDefinition* elements, MDefinition* index,
MDefinition* initLength, MDefinition* object,
- bool needsHoleCheck)
+ bool needsHoleCheck, JSValueType unboxedType)
: MQuaternaryInstruction(elements, index, initLength, object),
needsHoleCheck_(needsHoleCheck),
- needsNegativeIntCheck_(true)
+ needsNegativeIntCheck_(true),
+ unboxedType_(unboxedType)
{
setResultType(MIRType::Boolean);
setMovable();
@@ -12102,6 +12391,9 @@ class MInArray
bool needsNegativeIntCheck() const {
return needsNegativeIntCheck_;
}
+ JSValueType unboxedType() const {
+ return unboxedType_;
+ }
void collectRangeInfoPreTrunc() override;
AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::Element);
@@ -12114,6 +12406,8 @@ class MInArray
return false;
if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
return false;
+ if (unboxedType() != other->unboxedType())
+ return false;
return congruentIfOperandsEqual(other);
}
};
@@ -14014,6 +14308,8 @@ MDefinition::maybeConstantValue()
bool ElementAccessIsDenseNative(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id);
+JSValueType UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
+ MDefinition* id);
bool ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id,
Scalar::Type* arrayType);
diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h
index 589dde077..2f67f8039 100644
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -188,6 +188,8 @@ namespace jit {
_(GuardObjectGroup) \
_(GuardObjectIdentity) \
_(GuardClass) \
+ _(GuardUnboxedExpando) \
+ _(LoadUnboxedExpando) \
_(ArrayLength) \
_(SetArrayLength) \
_(GetNextEntryForIterator) \
@@ -199,6 +201,10 @@ namespace jit {
_(SetTypedObjectOffset) \
_(InitializedLength) \
_(SetInitializedLength) \
+ _(UnboxedArrayLength) \
+ _(UnboxedArrayInitializedLength) \
+ _(IncrementUnboxedArrayInitializedLength) \
+ _(SetUnboxedArrayInitializedLength) \
_(Not) \
_(BoundsCheck) \
_(BoundsCheckLower) \
@@ -214,6 +220,7 @@ namespace jit {
_(StoreUnboxedScalar) \
_(StoreUnboxedObjectOrNull) \
_(StoreUnboxedString) \
+ _(ConvertUnboxedObjectToNative) \
_(ArrayPopShift) \
_(ArrayPush) \
_(ArraySlice) \
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
index a739b9325..f633b9b7b 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,268 @@ 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);
+
+void
+MacroAssembler::checkUnboxedArrayCapacity(Register obj, const RegisterOrInt32Constant& index,
+ Register temp, Label* failure)
+{
+ Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+ Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+
+ Label capacityIsIndex, done;
+ load32(initLengthAddr, temp);
+ branchTest32(Assembler::NonZero, temp, Imm32(UnboxedArrayObject::CapacityMask), &capacityIsIndex);
+ branch32(Assembler::BelowOrEqual, lengthAddr, index, failure);
+ jump(&done);
+ bind(&capacityIsIndex);
+
+ // Do a partial shift so that we can get an absolute offset from the base
+ // of CapacityArray to use.
+ JS_STATIC_ASSERT(sizeof(UnboxedArrayObject::CapacityArray[0]) == 4);
+ rshiftPtr(Imm32(UnboxedArrayObject::CapacityShift - 2), temp);
+ and32(Imm32(~0x3), temp);
+
+ addPtr(ImmPtr(&UnboxedArrayObject::CapacityArray), temp);
+ branch32(Assembler::BelowOrEqual, Address(temp, 0), index, failure);
+ bind(&done);
+}
+
// 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 +1277,20 @@ 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 if (templateObj->is<UnboxedArrayObject>()) {
+ MOZ_ASSERT(templateObj->as<UnboxedArrayObject>().hasInlineElements());
+ int elementsOffset = UnboxedArrayObject::offsetOfInlineElements();
+ computeEffectiveAddress(Address(obj, elementsOffset), temp);
+ storePtr(temp, Address(obj, UnboxedArrayObject::offsetOfElements()));
+ store32(Imm32(templateObj->as<UnboxedArrayObject>().length()),
+ Address(obj, UnboxedArrayObject::offsetOfLength()));
+ uint32_t capacityIndex = templateObj->as<UnboxedArrayObject>().capacityIndex();
+ store32(Imm32(capacityIndex << UnboxedArrayObject::CapacityShift),
+ Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
} else {
MOZ_CRASH("Unknown object");
}
@@ -1030,6 +1312,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..b6616321c 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,20 @@ class MacroAssembler : public MacroAssemblerSpecific
void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
unsigned numElems = 0);
+ // Load a property from an UnboxedPlainObject or UnboxedArrayObject.
+ 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);
+
+ void checkUnboxedArrayCapacity(Register obj, const RegisterOrInt32Constant& index,
+ Register temp, Label* failure);
+
Register extractString(const Address& address, Register scratch) {
return extractObject(address, scratch);
}
@@ -1701,6 +1716,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..308def041 100644
--- a/js/src/jit/OptimizationTracking.cpp
+++ b/js/src/jit/OptimizationTracking.cpp
@@ -844,6 +844,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..6fd71f377 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;
@@ -1354,7 +1355,7 @@ RNewArray::recover(JSContext* cx, SnapshotIterator& iter) const
RootedValue result(cx);
RootedObjectGroup group(cx, templateObject->group());
- ArrayObject* resultObject = NewFullyAllocatedArrayTryUseGroup(cx, group, count_);
+ JSObject* resultObject = NewFullyAllocatedArrayTryUseGroup(cx, group, count_);
if (!resultObject)
return false;
@@ -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..4614b2162 100644
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -13,6 +13,7 @@
#include "jit/MIR.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
@@ -182,6 +183,25 @@ IsObjectEscaped(MInstruction* ins, JSObject* objDefault)
JitSpewDef(JitSpew_Escape, "is escaped by\n", def);
return true;
+ case MDefinition::Op_LoadUnboxedScalar:
+ case MDefinition::Op_StoreUnboxedScalar:
+ case MDefinition::Op_LoadUnboxedObjectOrNull:
+ case MDefinition::Op_StoreUnboxedObjectOrNull:
+ case MDefinition::Op_LoadUnboxedString:
+ case MDefinition::Op_StoreUnboxedString:
+ // Not escaped if it is the first argument.
+ if (def->indexOf(*i) != 0) {
+ JitSpewDef(JitSpew_Escape, "is escaped by\n", def);
+ return true;
+ }
+
+ if (!def->getOperand(1)->isConstant()) {
+ JitSpewDef(JitSpew_Escape, "is addressed with unknown index\n", def);
+ return true;
+ }
+
+ break;
+
case MDefinition::Op_PostWriteBarrier:
break;
@@ -285,6 +305,16 @@ class ObjectMemoryView : public MDefinitionVisitorDefaultNoop
void visitGuardShape(MGuardShape* ins);
void visitFunctionEnvironment(MFunctionEnvironment* ins);
void visitLambda(MLambda* ins);
+ void visitStoreUnboxedScalar(MStoreUnboxedScalar* ins);
+ void visitLoadUnboxedScalar(MLoadUnboxedScalar* ins);
+ void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins);
+ void visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins);
+ void visitStoreUnboxedString(MStoreUnboxedString* ins);
+ void visitLoadUnboxedString(MLoadUnboxedString* ins);
+
+ private:
+ void storeOffset(MInstruction* ins, size_t offset, MDefinition* value);
+ void loadOffset(MInstruction* ins, size_t offset);
};
const char* ObjectMemoryView::phaseName = "Scalar Replacement of Object";
@@ -626,6 +656,121 @@ ObjectMemoryView::visitLambda(MLambda* ins)
ins->setIncompleteObject();
}
+static size_t
+GetOffsetOf(MDefinition* index, size_t width, int32_t baseOffset)
+{
+ int32_t idx = index->toConstant()->toInt32();
+ MOZ_ASSERT(idx >= 0);
+ MOZ_ASSERT(baseOffset >= 0 && size_t(baseOffset) >= UnboxedPlainObject::offsetOfData());
+ return idx * width + baseOffset - UnboxedPlainObject::offsetOfData();
+}
+
+static size_t
+GetOffsetOf(MDefinition* index, Scalar::Type type, int32_t baseOffset)
+{
+ return GetOffsetOf(index, Scalar::byteSize(type), baseOffset);
+}
+
+void
+ObjectMemoryView::storeOffset(MInstruction* ins, size_t offset, MDefinition* value)
+{
+ // 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);
+}
+
+void
+ObjectMemoryView::visitStoreUnboxedScalar(MStoreUnboxedScalar* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ size_t offset = GetOffsetOf(ins->index(), ins->storageType(), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedScalar(MLoadUnboxedScalar* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), ins->storageType(), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
+void
+ObjectMemoryView::visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Clone the state and update the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
+void
+ObjectMemoryView::visitStoreUnboxedString(MStoreUnboxedString* ins)
+{
+ // Skip stores made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Clone the state and update the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ storeOffset(ins, offset, ins->value());
+}
+
+void
+ObjectMemoryView::visitLoadUnboxedString(MLoadUnboxedString* ins)
+{
+ // Skip loads made on other objects.
+ if (ins->elements() != obj_)
+ return;
+
+ // Replace load by the slot value.
+ size_t offset = GetOffsetOf(ins->index(), sizeof(uintptr_t), ins->offsetAdjustment());
+ loadOffset(ins, offset);
+}
+
static bool
IndexOf(MDefinition* ins, int32_t* res)
{
@@ -762,6 +907,11 @@ IsArrayEscaped(MInstruction* ins)
return true;
}
+ if (obj->is<UnboxedArrayObject>()) {
+ JitSpew(JitSpew_Escape, "Template object is an unboxed plain object.");
+ return true;
+ }
+
if (length >= 16) {
JitSpew(JitSpew_Escape, "Array has too many elements");
return true;
diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp
index 05a95824f..767cff661 100644
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -27,7 +27,6 @@
#endif
#include "jit/VMFunctions.h"
#include "vm/Interpreter.h"
-#include "vm/NativeObject-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/Interpreter-inl.h"
@@ -286,6 +285,11 @@ ICStub::trace(JSTracer* trc)
TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape");
break;
}
+ case ICStub::GetElem_UnboxedArray: {
+ ICGetElem_UnboxedArray* getElemStub = toGetElem_UnboxedArray();
+ TraceEdge(trc, &getElemStub->group(), "baseline-getelem-unboxed-array-group");
+ break;
+ }
case ICStub::GetElem_TypedArray: {
ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray();
TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape");
@@ -2244,7 +2248,9 @@ 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<UnboxedArrayObject>() &&
+ !obj->is<TypedObject>())
{
return false;
}
@@ -2572,6 +2578,12 @@ 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<UnboxedArrayObject>()) {
+ if (name == cx->names().length)
+ return false;
} else if (curObj->is<TypedObject>()) {
if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
return false;
@@ -2836,15 +2848,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 +4259,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..10be2836b 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;
@@ -306,7 +306,7 @@ template bool StringsEqual<false>(JSContext* cx, HandleString lhs, HandleString
bool
ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{
- MOZ_ASSERT(obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
AutoDetectInvalidation adi(cx, rval);
@@ -325,11 +325,12 @@ ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
}
bool
-ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* length)
+ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length)
{
- *length = arr->length();
- DenseElementResult result = arr->setOrExtendDenseElements(cx, *length, v.address(), 1,
- ShouldUpdateTypes::DontUpdate);
+ *length = GetAnyBoxedOrUnboxedArrayLength(obj);
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, *length, v.address(), 1,
+ ShouldUpdateTypes::DontUpdate);
if (result != DenseElementResult::Incomplete) {
(*length)++;
return result == DenseElementResult::Success;
@@ -337,7 +338,7 @@ ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* le
JS::AutoValueArray<3> argv(cx);
argv[0].setUndefined();
- argv[1].setObject(*arr);
+ argv[1].setObject(*obj);
argv[2].set(v);
if (!js::array_push(cx, 1, argv.begin()))
return false;
@@ -349,7 +350,7 @@ ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* le
bool
ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{
- MOZ_ASSERT(obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
AutoDetectInvalidation adi(cx, rval);
@@ -1130,14 +1131,16 @@ Recompile(JSContext* cx)
}
bool
-SetDenseElement(JSContext* cx, HandleNativeObject obj, int32_t index, HandleValue value, bool strict)
+SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
+ HandleValue value, bool strict)
{
// This function is called from Ion code for StoreElementHole's OOL path.
- // In this case we know the object is native and that no type changes are
- // needed.
+ // In this case we know the object is native or an unboxed array and that
+ // no type changes are needed.
- DenseElementResult result = obj->setOrExtendDenseElements(cx, index, value.address(), 1,
- ShouldUpdateTypes::DontUpdate);
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, index, value.address(), 1,
+ ShouldUpdateTypes::DontUpdate);
if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success;
diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h
index 94f741397..7f225c293 100644
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -622,7 +622,7 @@ template<bool Equal>
bool StringsEqual(JSContext* cx, HandleString left, HandleString right, bool* res);
MOZ_MUST_USE bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
-MOZ_MUST_USE bool ArrayPushDense(JSContext* cx, HandleArrayObject obj, HandleValue v, uint32_t* length);
+MOZ_MUST_USE bool ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length);
MOZ_MUST_USE bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep);
@@ -745,8 +745,8 @@ ForcedRecompile(JSContext* cx);
JSString* StringReplace(JSContext* cx, HandleString string, HandleString pattern,
HandleString repl);
-MOZ_MUST_USE bool SetDenseElement(JSContext* cx, HandleNativeObject obj, int32_t index,
- HandleValue value, bool strict);
+MOZ_MUST_USE bool SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
+ HandleValue value, bool strict);
void AssertValidObjectPtr(JSContext* cx, JSObject* obj);
void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj);
diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h
index ff4915d1a..e6aab6ba3 100644
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -5166,6 +5166,72 @@ class LSetInitializedLength : public LInstructionHelper<0, 2, 0>
}
};
+class LUnboxedArrayLength : public LInstructionHelper<1, 1, 0>
+{
+ public:
+ LIR_HEADER(UnboxedArrayLength)
+
+ explicit LUnboxedArrayLength(const LAllocation& object) {
+ setOperand(0, object);
+ }
+
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+};
+
+class LUnboxedArrayInitializedLength : public LInstructionHelper<1, 1, 0>
+{
+ public:
+ LIR_HEADER(UnboxedArrayInitializedLength)
+
+ explicit LUnboxedArrayInitializedLength(const LAllocation& object) {
+ setOperand(0, object);
+ }
+
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+};
+
+class LIncrementUnboxedArrayInitializedLength : public LInstructionHelper<0, 1, 0>
+{
+ public:
+ LIR_HEADER(IncrementUnboxedArrayInitializedLength)
+
+ explicit LIncrementUnboxedArrayInitializedLength(const LAllocation& object) {
+ setOperand(0, object);
+ }
+
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+};
+
+class LSetUnboxedArrayInitializedLength : public LInstructionHelper<0, 2, 1>
+{
+ public:
+ LIR_HEADER(SetUnboxedArrayInitializedLength)
+
+ explicit LSetUnboxedArrayInitializedLength(const LAllocation& object,
+ const LAllocation& length,
+ const LDefinition& temp) {
+ setOperand(0, object);
+ setOperand(1, length);
+ setTemp(0, temp);
+ }
+
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+ const LAllocation* length() {
+ return getOperand(1);
+ }
+ const LDefinition* temp() {
+ return getTemp(0);
+ }
+};
+
// Load the length from an elements header.
class LArrayLength : public LInstructionHelper<1, 1, 0>
{
@@ -5670,17 +5736,19 @@ class LStoreElementT : public LInstructionHelper<0, 3, 0>
};
// Like LStoreElementV, but supports indexes >= initialized length.
-class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
+class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
{
public:
LIR_HEADER(StoreElementHoleV)
LStoreElementHoleV(const LAllocation& object, const LAllocation& elements,
- const LAllocation& index, const LBoxAllocation& value) {
+ const LAllocation& index, const LBoxAllocation& value,
+ const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setBoxOperand(Value, value);
+ setTemp(0, temp);
}
static const size_t Value = 3;
@@ -5700,17 +5768,19 @@ class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
};
// Like LStoreElementT, but supports indexes >= initialized length.
-class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
+class LStoreElementHoleT : public LInstructionHelper<0, 4, 1>
{
public:
LIR_HEADER(StoreElementHoleT)
LStoreElementHoleT(const LAllocation& object, const LAllocation& elements,
- const LAllocation& index, const LAllocation& value) {
+ const LAllocation& index, const LAllocation& value,
+ const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setOperand(3, value);
+ setTemp(0, temp);
}
const MStoreElementHole* mir() const {
@@ -5731,17 +5801,19 @@ class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
};
// Like LStoreElementV, but can just ignore assignment (for eg. frozen objects)
-class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
+class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
{
public:
LIR_HEADER(FallibleStoreElementV)
LFallibleStoreElementV(const LAllocation& object, const LAllocation& elements,
- const LAllocation& index, const LBoxAllocation& value) {
+ const LAllocation& index, const LBoxAllocation& value,
+ const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setBoxOperand(Value, value);
+ setTemp(0, temp);
}
static const size_t Value = 3;
@@ -5761,17 +5833,19 @@ class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
};
// Like LStoreElementT, but can just ignore assignment (for eg. frozen objects)
-class LFallibleStoreElementT : public LInstructionHelper<0, 4, 0>
+class LFallibleStoreElementT : public LInstructionHelper<0, 4, 1>
{
public:
LIR_HEADER(FallibleStoreElementT)
LFallibleStoreElementT(const LAllocation& object, const LAllocation& elements,
- const LAllocation& index, const LAllocation& value) {
+ const LAllocation& index, const LAllocation& value,
+ const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setOperand(3, value);
+ setTemp(0, temp);
}
const MFallibleStoreElement* mir() const {
@@ -5818,6 +5892,22 @@ class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0>
}
};
+// If necessary, convert an unboxed object in a particular group to its native
+// representation.
+class LConvertUnboxedObjectToNative : public LInstructionHelper<0, 1, 0>
+{
+ public:
+ LIR_HEADER(ConvertUnboxedObjectToNative)
+
+ explicit LConvertUnboxedObjectToNative(const LAllocation& object) {
+ setOperand(0, object);
+ }
+
+ MConvertUnboxedObjectToNative* mir() {
+ return mir_->toConvertUnboxedObjectToNative();
+ }
+};
+
class LArrayPopShiftV : public LInstructionHelper<BOX_PIECES, 1, 2>
{
public:
@@ -7340,6 +7430,38 @@ class LGuardReceiverPolymorphic : public LInstructionHelper<0, 1, 1>
}
};
+class LGuardUnboxedExpando : public LInstructionHelper<0, 1, 0>
+{
+ public:
+ LIR_HEADER(GuardUnboxedExpando)
+
+ explicit LGuardUnboxedExpando(const LAllocation& in) {
+ setOperand(0, in);
+ }
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+ const MGuardUnboxedExpando* mir() const {
+ return mir_->toGuardUnboxedExpando();
+ }
+};
+
+class LLoadUnboxedExpando : public LInstructionHelper<1, 1, 0>
+{
+ public:
+ LIR_HEADER(LoadUnboxedExpando)
+
+ explicit LLoadUnboxedExpando(const LAllocation& in) {
+ setOperand(0, in);
+ }
+ const LAllocation* object() {
+ return getOperand(0);
+ }
+ const MLoadUnboxedExpando* mir() const {
+ return mir_->toLoadUnboxedExpando();
+ }
+};
+
// Guard that a value is in a TypeSet.
class LTypeBarrierV : public LInstructionHelper<0, BOX_PIECES, 1>
{
diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h
index 56b98940a..ea185e1b8 100644
--- a/js/src/jit/shared/LOpcodes-shared.h
+++ b/js/src/jit/shared/LOpcodes-shared.h
@@ -257,6 +257,8 @@
_(GuardObjectGroup) \
_(GuardObjectIdentity) \
_(GuardClass) \
+ _(GuardUnboxedExpando) \
+ _(LoadUnboxedExpando) \
_(TypeBarrierV) \
_(TypeBarrierO) \
_(MonitorTypes) \
@@ -266,6 +268,10 @@
_(PostWriteElementBarrierV) \
_(InitializedLength) \
_(SetInitializedLength) \
+ _(UnboxedArrayLength) \
+ _(UnboxedArrayInitializedLength) \
+ _(IncrementUnboxedArrayInitializedLength) \
+ _(SetUnboxedArrayInitializedLength) \
_(BoundsCheck) \
_(BoundsCheckRange) \
_(BoundsCheckLower) \
@@ -280,6 +286,7 @@
_(StoreElementT) \
_(StoreUnboxedScalar) \
_(StoreUnboxedPointer) \
+ _(ConvertUnboxedObjectToNative) \
_(ArrayPopShiftV) \
_(ArrayPopShiftT) \
_(ArrayPushV) \
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 6483641f3..cb3945152 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6588,6 +6588,9 @@ JS_SetGlobalJitCompilerOption(JSContext* cx, JSJitCompilerOption opt, uint32_t v
}
jit::JitOptions.jumpThreshold = value;
break;
+ case JSJITCOMPILER_UNBOXED_OBJECTS:
+ jit::JitOptions.disableUnboxedObjects = !value;
+ break;
case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE:
jit::JitOptions.asmJSAtomicsEnable = !!value;
break;
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 7795c5e4c..9138a4a92 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1096,6 +1096,7 @@ class JS_PUBLIC_API(ContextOptions) {
wasmAlwaysBaseline_(false),
throwOnAsmJSValidationFailure_(false),
nativeRegExp_(true),
+ unboxedArrays_(false),
asyncStack_(true),
throwOnDebuggeeWouldRun_(true),
dumpStackOnDebuggeeWouldRun_(false),
@@ -1172,6 +1173,12 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
+ bool unboxedArrays() const { return unboxedArrays_; }
+ ContextOptions& setUnboxedArrays(bool flag) {
+ unboxedArrays_ = flag;
+ return *this;
+ }
+
bool asyncStack() const { return asyncStack_; }
ContextOptions& setAsyncStack(bool flag) {
asyncStack_ = flag;
@@ -1234,6 +1241,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool wasmAlwaysBaseline_ : 1;
bool throwOnAsmJSValidationFailure_ : 1;
bool nativeRegExp_ : 1;
+ bool unboxedArrays_ : 1;
bool asyncStack_ : 1;
bool throwOnDebuggeeWouldRun_ : 1;
bool dumpStackOnDebuggeeWouldRun_ : 1;
@@ -5888,19 +5896,20 @@ JS_SetParallelParsingEnabled(JSContext* cx, bool enabled);
extern JS_PUBLIC_API(void)
JS_SetOffthreadIonCompilationEnabled(JSContext* cx, bool enabled);
-#define JIT_COMPILER_OPTIONS(Register) \
- Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger") \
- Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger") \
- Register(ION_GVN_ENABLE, "ion.gvn.enable") \
- Register(ION_FORCE_IC, "ion.forceinlineCaches") \
- Register(ION_ENABLE, "ion.enable") \
+#define JIT_COMPILER_OPTIONS(Register) \
+ Register(BASELINE_WARMUP_TRIGGER, "baseline.warmup.trigger") \
+ Register(ION_WARMUP_TRIGGER, "ion.warmup.trigger") \
+ Register(ION_GVN_ENABLE, "ion.gvn.enable") \
+ Register(ION_FORCE_IC, "ion.forceinlineCaches") \
+ Register(ION_ENABLE, "ion.enable") \
Register(ION_INTERRUPT_WITHOUT_SIGNAL, "ion.interrupt-without-signals") \
- Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis") \
- Register(BASELINE_ENABLE, "baseline.enable") \
- Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
- Register(JUMP_THRESHOLD, "jump-threshold") \
- Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \
- Register(WASM_TEST_MODE, "wasm.test-mode") \
+ Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis") \
+ Register(BASELINE_ENABLE, "baseline.enable") \
+ Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
+ Register(JUMP_THRESHOLD, "jump-threshold") \
+ Register(UNBOXED_OBJECTS, "unboxed_objects") \
+ Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable") \
+ Register(WASM_TEST_MODE, "wasm.test-mode") \
Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets")
typedef enum JSJitCompilerOption {
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
index 73243d918..1724a0a66 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;
@@ -63,7 +64,7 @@ using JS::ToUint32;
bool
JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer)
{
- if (obj->is<ArrayObject>()) {
+ if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
*answer = IsArrayAnswer::Array;
return true;
}
@@ -99,6 +100,11 @@ js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp)
return true;
}
+ if (obj->is<UnboxedArrayObject>()) {
+ *lengthp = obj->as<UnboxedArrayObject>().length();
+ return true;
+ }
+
if (obj->is<ArgumentsObject>()) {
ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
if (!argsobj.hasOverriddenLength()) {
@@ -247,20 +253,18 @@ static bool
GetElement(JSContext* cx, HandleObject obj, HandleObject receiver,
uint32_t index, bool* hole, MutableHandleValue vp)
{
- if (obj->isNative()) {
- NativeObject* nobj = &obj->as<NativeObject>();
- if (index < nobj->getDenseInitializedLength()) {
- vp.set(nobj->getDenseElement(size_t(index)));
- if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
- *hole = false;
- return true;
- }
+ AssertGreaterThanZero(index);
+ if (index < GetAnyBoxedOrUnboxedInitializedLength(obj)) {
+ vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, uint32_t(index)));
+ if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
+ *hole = false;
+ return true;
}
- if (nobj->is<ArgumentsObject>() && index <= UINT32_MAX) {
- if (nobj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
- *hole = false;
- return true;
- }
+ }
+ if (obj->is<ArgumentsObject>()) {
+ if (obj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
+ *hole = false;
+ return true;
}
}
@@ -279,8 +283,8 @@ ElementAdder::append(JSContext* cx, HandleValue v)
{
MOZ_ASSERT(index_ < length_);
if (resObj_) {
- NativeObject* resObj = &resObj_->as<NativeObject>();
- DenseElementResult result = resObj->setOrExtendDenseElements(cx, index_, v.address(), 1);
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, resObj_, index_, v.address(), 1);
if (result == DenseElementResult::Failure)
return false;
if (result == DenseElementResult::Incomplete) {
@@ -332,31 +336,37 @@ js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver,
return true;
}
-static bool
-GetDenseElements(NativeObject* aobj, uint32_t length, Value* vp)
+template <JSValueType Type>
+DenseElementResult
+GetBoxedOrUnboxedDenseElements(JSObject* aobj, uint32_t length, Value* vp)
{
MOZ_ASSERT(!ObjectMayHaveExtraIndexedProperties(aobj));
- if (length > aobj->getDenseInitializedLength())
- return false;
+ if (length > GetBoxedOrUnboxedInitializedLength<Type>(aobj))
+ return DenseElementResult::Incomplete;
for (size_t i = 0; i < length; i++) {
- vp[i] = aobj->getDenseElement(i);
+ vp[i] = GetBoxedOrUnboxedDenseElement<Type>(aobj, i);
// No other indexed properties so hole => undefined.
if (vp[i].isMagic(JS_ELEMENTS_HOLE))
vp[i] = UndefinedValue();
}
- return true;
+ return DenseElementResult::Success;
}
+DefineBoxedOrUnboxedFunctor3(GetBoxedOrUnboxedDenseElements,
+ JSObject*, uint32_t, Value*);
+
bool
js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp)
{
if (!ObjectMayHaveExtraIndexedProperties(aobj)) {
- if (GetDenseElements(&aobj->as<NativeObject>(), length, vp))
- return true;
+ GetBoxedOrUnboxedDenseElementsFunctor functor(aobj, length, vp);
+ DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, aobj);
+ if (result != DenseElementResult::Incomplete)
+ return result == DenseElementResult::Success;
}
if (aobj->is<ArgumentsObject>()) {
@@ -388,9 +398,9 @@ SetArrayElement(JSContext* cx, HandleObject obj, double index, HandleValue v)
{
MOZ_ASSERT(index >= 0);
- if (obj->is<ArrayObject>() && !obj->isIndexed() && index <= UINT32_MAX) {
- NativeObject* nobj = &obj->as<NativeObject>();
- DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(index), v.address(), 1);
+ if ((obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) && !obj->isIndexed() && index <= UINT32_MAX) {
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(index), v.address(), 1);
if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success;
}
@@ -510,6 +520,24 @@ struct ReverseIndexComparator
}
};
+bool
+js::CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* newLen)
+{
+ double d;
+
+ if (!ToUint32(cx, v, newLen))
+ return false;
+
+ if (!ToNumber(cx, v, &d))
+ return false;
+
+ if (d == *newLen)
+ return true;
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+ return false;
+}
+
/* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */
bool
js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
@@ -531,22 +559,12 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
} else {
// Step 2 is irrelevant in our implementation.
- // Step 3.
- if (!ToUint32(cx, value, &newLen))
- return false;
-
- // Step 4.
- double d;
- if (!ToNumber(cx, value, &d))
+ // Steps 3-7.
+ MOZ_ASSERT_IF(attrs & JSPROP_IGNORE_VALUE, value.isUndefined());
+ if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
return false;
- // Step 5.
- if (d != newLen) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
- return false;
- }
-
- // Steps 6-8 are irrelevant in our implementation.
+ // Step 8 is irrelevant in our implementation.
}
// Steps 9-11.
@@ -805,7 +823,7 @@ array_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
static inline bool
ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj)
{
- return !obj->isNative() ||
+ return (!obj->isNative() && !obj->is<UnboxedArrayObject>()) ||
obj->isIndexed() ||
obj->is<TypedArrayObject>() ||
ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames,
@@ -836,7 +854,7 @@ js::ObjectMayHaveExtraIndexedProperties(JSObject* obj)
if (ObjectMayHaveExtraIndexedOwnProperties(obj))
return true;
- if (obj->as<NativeObject>().getDenseInitializedLength() != 0)
+ if (GetAnyBoxedOrUnboxedInitializedLength(obj) != 0)
return true;
} while (true);
}
@@ -1046,32 +1064,32 @@ struct StringSeparatorOp
}
};
-template <typename SeparatorOp>
-static bool
-ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, uint32_t length,
+template <typename SeparatorOp, JSValueType Type>
+static DenseElementResult
+ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
StringBuffer& sb, uint32_t* numProcessed)
{
// This loop handles all elements up to initializedLength. If
// length > initLength we rely on the second loop to add the
// other elements.
MOZ_ASSERT(*numProcessed == 0);
- uint32_t initLength = Min<uint32_t>(obj->getDenseInitializedLength(),
+ uint32_t initLength = Min<uint32_t>(GetBoxedOrUnboxedInitializedLength<Type>(obj),
length);
while (*numProcessed < initLength) {
if (!CheckForInterrupt(cx))
- return false;
+ return DenseElementResult::Failure;
Value elem = obj->as<NativeObject>().getDenseElement(*numProcessed);
if (elem.isString()) {
if (!sb.append(elem.toString()))
- return false;
+ return DenseElementResult::Failure;
} else if (elem.isNumber()) {
if (!NumberValueToStringBuffer(cx, elem, sb))
- return false;
+ return DenseElementResult::Failure;
} else if (elem.isBoolean()) {
if (!BooleanToStringBuffer(elem.toBoolean(), sb))
- return false;
+ return DenseElementResult::Failure;
} else if (elem.isObject() || elem.isSymbol()) {
/*
* Object stringifying could modify the initialized length or make
@@ -1087,13 +1105,33 @@ ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, u
}
if (++(*numProcessed) != length && !sepOp(cx, sb))
- return false;
+ return DenseElementResult::Failure;
}
- return true;
+ return DenseElementResult::Incomplete;
}
template <typename SeparatorOp>
+struct ArrayJoinDenseKernelFunctor {
+ JSContext* cx;
+ SeparatorOp sepOp;
+ HandleObject obj;
+ uint32_t length;
+ StringBuffer& sb;
+ uint32_t* numProcessed;
+
+ ArrayJoinDenseKernelFunctor(JSContext* cx, SeparatorOp sepOp, HandleObject obj,
+ uint32_t length, StringBuffer& sb, uint32_t* numProcessed)
+ : cx(cx), sepOp(sepOp), obj(obj), length(length), sb(sb), numProcessed(numProcessed)
+ {}
+
+ template <JSValueType Type>
+ DenseElementResult operator()() {
+ return ArrayJoinDenseKernel<SeparatorOp, Type>(cx, sepOp, obj, length, sb, numProcessed);
+ }
+};
+
+template <typename SeparatorOp>
static bool
ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
StringBuffer& sb)
@@ -1101,10 +1139,10 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len
uint32_t i = 0;
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
- if (!ArrayJoinDenseKernel<SeparatorOp>(cx, sepOp, obj.as<NativeObject>(), length, sb, &i))
- {
+ ArrayJoinDenseKernelFunctor<SeparatorOp> functor(cx, sepOp, obj, length, sb, &i);
+ DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
+ if (result == DenseElementResult::Failure)
return false;
- }
}
if (i != length) {
@@ -1175,14 +1213,11 @@ js::array_join(JSContext* cx, unsigned argc, Value* vp)
// An optimized version of a special case of steps 7-11: when length==1 and
// the 0th element is a string, ToString() of that element is a no-op and
// so it can be immediately returned as the result.
- if (length == 1 && obj->isNative()) {
- NativeObject* nobj = &obj->as<NativeObject>();
- if (nobj->getDenseInitializedLength() == 1) {
- Value elem0 = nobj->getDenseElement(0);
- if (elem0.isString()) {
- args.rval().set(elem0);
- return true;
- }
+ if (length == 1 && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) {
+ Value elem0 = GetAnyBoxedOrUnboxedDenseElement(obj, 0);
+ if (elem0.isString()) {
+ args.rval().set(elem0);
+ return true;
}
}
@@ -1225,7 +1260,7 @@ js::array_join(JSContext* cx, unsigned argc, Value* vp)
}
// Step 11
- JSString* str = sb.finishString();
+ JSString *str = sb.finishString();
if (!str)
return false;
@@ -1254,6 +1289,10 @@ array_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
args.rval().setString(cx->names().empty);
return true;
}
+ if (obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().length() == 0) {
+ args.rval().setString(cx->names().empty);
+ return true;
+ }
AutoCycleDetector detector(cx, obj);
if (!detector.init())
@@ -1290,9 +1329,8 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start,
return false;
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
- NativeObject* nobj = &obj->as<NativeObject>();
- DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(start), vector,
- count, updateTypes);
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, start, vector, count, updateTypes);
if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success;
}
@@ -1326,45 +1364,54 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start,
return true;
}
-static DenseElementResult
-ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length)
+template <JSValueType Type>
+DenseElementResult
+ArrayReverseDenseKernel(JSContext* cx, HandleObject obj, uint32_t length)
{
/* An empty array or an array with no elements is already reversed. */
- if (length == 0 || obj->getDenseInitializedLength() == 0)
+ if (length == 0 || GetBoxedOrUnboxedInitializedLength<Type>(obj) == 0)
return DenseElementResult::Success;
- if (obj->denseElementsAreFrozen())
- return DenseElementResult::Incomplete;
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (obj->as<NativeObject>().denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
- /*
- * It's actually surprisingly complicated to reverse an array due to the
- * orthogonality of array length and array capacity while handling
- * leading and trailing holes correctly. Reversing seems less likely to
- * be a common operation than other array mass-mutation methods, so for
- * now just take a probably-small memory hit (in the absence of too many
- * holes in the array at its start) and ensure that the capacity is
- * sufficient to hold all the elements in the array if it were full.
- */
- DenseElementResult result = obj->ensureDenseElements(cx, length, 0);
- if (result != DenseElementResult::Success)
- return result;
+ /*
+ * It's actually surprisingly complicated to reverse an array due to the
+ * orthogonality of array length and array capacity while handling
+ * leading and trailing holes correctly. Reversing seems less likely to
+ * be a common operation than other array mass-mutation methods, so for
+ * now just take a probably-small memory hit (in the absence of too many
+ * holes in the array at its start) and ensure that the capacity is
+ * sufficient to hold all the elements in the array if it were full.
+ */
+ DenseElementResult result = obj->as<NativeObject>().ensureDenseElements(cx, length, 0);
+ if (result != DenseElementResult::Success)
+ return result;
- /* Fill out the array's initialized length to its proper length. */
- obj->ensureDenseInitializedLength(cx, length, 0);
+ /* Fill out the array's initialized length to its proper length. */
+ obj->as<NativeObject>().ensureDenseInitializedLength(cx, length, 0);
+ } else {
+ // Unboxed arrays can only be reversed here if their initialized length
+ // matches their actual length. Otherwise the reversal will place holes
+ // at the beginning of the array, which we don't support.
+ if (length != obj->as<UnboxedArrayObject>().initializedLength())
+ return DenseElementResult::Incomplete;
+ }
RootedValue origlo(cx), orighi(cx);
uint32_t lo = 0, hi = length - 1;
for (; lo < hi; lo++, hi--) {
- origlo = obj->getDenseElement(lo);
- orighi = obj->getDenseElement(hi);
- obj->setDenseElement(lo, orighi);
+ origlo = GetBoxedOrUnboxedDenseElement<Type>(obj, lo);
+ orighi = GetBoxedOrUnboxedDenseElement<Type>(obj, hi);
+ SetBoxedOrUnboxedDenseElementNoTypeChange<Type>(obj, lo, orighi);
if (orighi.isMagic(JS_ELEMENTS_HOLE) &&
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo)))
{
return DenseElementResult::Failure;
}
- obj->setDenseElement(hi, origlo);
+ SetBoxedOrUnboxedDenseElementNoTypeChange<Type>(obj, hi, origlo);
if (origlo.isMagic(JS_ELEMENTS_HOLE) &&
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi)))
{
@@ -1375,6 +1422,9 @@ ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length)
return DenseElementResult::Success;
}
+DefineBoxedOrUnboxedFunctor3(ArrayReverseDenseKernel,
+ JSContext*, HandleObject, uint32_t);
+
bool
js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
{
@@ -1389,8 +1439,8 @@ js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
return false;
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
- DenseElementResult result =
- ArrayReverseDenseKernel(cx, obj.as<NativeObject>(), uint32_t(len));
+ ArrayReverseDenseKernelFunctor functor(cx, obj, len);
+ DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
if (result != DenseElementResult::Incomplete) {
/*
* Per ECMA-262, don't update the length of the array, even if the new
@@ -2033,8 +2083,8 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
DenseElementResult result =
- obj->as<NativeObject>().setOrExtendDenseElements(cx, uint32_t(length),
- args.array(), args.length());
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, length,
+ args.array(), args.length());
if (result != DenseElementResult::Incomplete) {
if (result == DenseElementResult::Failure)
return false;
@@ -2042,8 +2092,14 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
uint32_t newlength = length + args.length();
args.rval().setNumber(newlength);
- // Handle updates to the length of non-arrays here.
- if (!obj->is<ArrayObject>())
+ // SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the
+ // length for boxed and unboxed arrays. Handle updates to the length of
+ // non-arrays here.
+ bool isArray;
+ if (!IsArray(cx, obj, &isArray))
+ return false;
+
+ if (!isArray)
return SetLengthProperty(cx, obj, newlength);
return true;
@@ -2099,47 +2155,42 @@ js::array_pop(JSContext* cx, unsigned argc, Value* vp)
return SetLengthProperty(cx, obj, index);
}
-void
-js::ArrayShiftMoveElements(NativeObject* obj)
+template <JSValueType Type>
+static inline DenseElementResult
+ShiftMoveBoxedOrUnboxedDenseElements(JSObject* obj)
{
- MOZ_ASSERT_IF(obj->is<ArrayObject>(), obj->as<ArrayObject>().lengthIsWritable());
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));
- size_t initlen = obj->getDenseInitializedLength();
-
- if (initlen > 0) {
- /*
- * At this point the length and initialized length have already been
- * decremented and the result fetched, so just shift the array elements
- * themselves.
- */
- obj->moveDenseElementsNoPreBarrier(0, 1, initlen);
+ /*
+ * At this point the length and initialized length have already been
+ * decremented and the result fetched, so just shift the array elements
+ * themselves.
+ */
+ size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().moveDenseElementsNoPreBarrier(0, 1, initlen);
+ } else {
+ uint8_t* data = obj->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(Type);
+ memmove(data, data + elementSize, initlen * elementSize);
}
-}
-static inline void
-SetInitializedLength(JSContext* cx, NativeObject* obj, size_t initlen)
-{
- size_t oldInitlen = obj->getDenseInitializedLength();
- obj->setDenseInitializedLength(initlen);
- if (initlen < oldInitlen)
- obj->shrinkElements(cx, initlen);
+ return DenseElementResult::Success;
}
-static DenseElementResult
-MoveDenseElements(JSContext* cx, NativeObject* obj, uint32_t dstStart, uint32_t srcStart,
- uint32_t length)
-{
- if (obj->denseElementsAreFrozen())
- return DenseElementResult::Incomplete;
+DefineBoxedOrUnboxedFunctor1(ShiftMoveBoxedOrUnboxedDenseElements, JSObject*);
- if (!obj->maybeCopyElementsForWrite(cx))
- return DenseElementResult::Failure;
- obj->moveDenseElements(dstStart, srcStart, length);
+void
+js::ArrayShiftMoveElements(JSObject* obj)
+{
+ MOZ_ASSERT_IF(obj->is<ArrayObject>(), obj->as<ArrayObject>().lengthIsWritable());
- return DenseElementResult::Success;
+ ShiftMoveBoxedOrUnboxedDenseElementsFunctor functor(obj);
+ JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success);
}
-static DenseElementResult
+template <JSValueType Type>
+DenseElementResult
ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{
if (ObjectMayHaveExtraIndexedProperties(obj))
@@ -2152,22 +2203,25 @@ ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval)
if (MOZ_UNLIKELY(group->hasAllFlags(OBJECT_FLAG_ITERATED)))
return DenseElementResult::Incomplete;
- size_t initlen = obj->as<NativeObject>().getDenseInitializedLength();
+ size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
if (initlen == 0)
return DenseElementResult::Incomplete;
- rval.set(obj->as<NativeObject>().getDenseElement(0));
+ rval.set(GetBoxedOrUnboxedDenseElement<Type>(obj, 0));
if (rval.isMagic(JS_ELEMENTS_HOLE))
rval.setUndefined();
- DenseElementResult result = MoveDenseElements(cx, &obj->as<NativeObject>(), 0, 1, initlen - 1);
+ DenseElementResult result = MoveBoxedOrUnboxedDenseElements<Type>(cx, obj, 0, 1, initlen - 1);
if (result != DenseElementResult::Success)
return result;
- SetInitializedLength(cx, obj.as<NativeObject>(), initlen - 1);
+ SetBoxedOrUnboxedInitializedLength<Type>(cx, obj, initlen - 1);
return DenseElementResult::Success;
}
+DefineBoxedOrUnboxedFunctor3(ArrayShiftDenseKernel,
+ JSContext*, HandleObject, MutableHandleValue);
+
/* ES5 15.4.4.9 */
bool
js::array_shift(JSContext* cx, unsigned argc, Value* vp)
@@ -2199,7 +2253,8 @@ js::array_shift(JSContext* cx, unsigned argc, Value* vp)
uint32_t newlen = len - 1;
/* Fast paths. */
- DenseElementResult result = ArrayShiftDenseKernel(cx, obj, args.rval());
+ ArrayShiftDenseKernelFunctor functor(cx, obj, args.rval());
+ DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
if (result != DenseElementResult::Incomplete) {
if (result == DenseElementResult::Failure)
return false;
@@ -2253,6 +2308,9 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
if (args.length() > 0) {
/* Slide up the array to make room for all args at the bottom. */
if (length > 0) {
+ // Only include a fast path for boxed arrays. Unboxed arrays can'nt
+ // be optimized here because unshifting temporarily places holes at
+ // the start of the array.
bool optimized = false;
do {
if (!obj->is<ArrayObject>())
@@ -2312,10 +2370,10 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
}
/*
- * Returns true if this is a dense array whose properties ending at |endIndex|
- * (exclusive) may be accessed (get, set, delete) directly through its
- * contiguous vector of elements without fear of getters, setters, etc. along
- * the prototype chain, or of enumerators requiring notification of
+ * Returns true if this is a dense or unboxed array whose |count| properties
+ * starting from |startingIndex| may be accessed (get, set, delete) directly
+ * through its contiguous vector of elements without fear of getters, setters,
+ * etc. along the prototype chain, or of enumerators requiring notification of
* modifications.
*/
static inline bool
@@ -2326,11 +2384,11 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
return false;
/* There's no optimizing possible if it's not an array. */
- if (!arr->is<ArrayObject>())
+ if (!arr->is<ArrayObject>() && !arr->is<UnboxedArrayObject>())
return false;
/* If it's a frozen array, always pick the slow path */
- if (arr->as<ArrayObject>().denseElementsAreFrozen())
+ if (arr->is<ArrayObject>() && arr->as<ArrayObject>().denseElementsAreFrozen())
return false;
/*
@@ -2362,23 +2420,7 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
* is subsumed by the initializedLength comparison.)
*/
return !ObjectMayHaveExtraIndexedProperties(arr) &&
- startingIndex + count <= arr->as<NativeObject>().getDenseInitializedLength();
-}
-
-static inline DenseElementResult
-CopyDenseElements(JSContext* cx, NativeObject* dst, NativeObject* src,
- uint32_t dstStart, uint32_t srcStart, uint32_t length)
-{
- MOZ_ASSERT(dst->getDenseInitializedLength() == dstStart);
- MOZ_ASSERT(src->getDenseInitializedLength() >= srcStart + length);
- MOZ_ASSERT(dst->getDenseCapacity() >= dstStart + length);
-
- dst->setDenseInitializedLength(dstStart + length);
-
- const Value* vp = src->getDenseElements() + srcStart;
- dst->initDenseElements(dstStart, vp, length);
-
- return DenseElementResult::Success;
+ startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr);
}
static inline bool
@@ -2472,9 +2514,7 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
/* Steps 10-11. */
DebugOnly<DenseElementResult> result =
- CopyDenseElements(cx, &arr->as<NativeObject>(),
- &obj->as<NativeObject>(), 0,
- actualStart, actualDeleteCount);
+ CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount);
MOZ_ASSERT(result.value == DenseElementResult::Success);
/* Step 12 (implicit). */
@@ -2511,13 +2551,14 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
/* Steps 15.a-b. */
DenseElementResult result =
- MoveDenseElements(cx, &obj->as<NativeObject>(), targetIndex, sourceIndex, len - sourceIndex);
+ MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex,
+ len - sourceIndex);
MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure)
return false;
/* Steps 15.c-d. */
- SetInitializedLength(cx, obj.as<NativeObject>(), finalLength);
+ SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength);
} else {
/*
* This is all very slow if the length is very large. We don't yet
@@ -2597,15 +2638,15 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
DenseElementResult result =
- MoveDenseElements(cx, &obj->as<NativeObject>(), actualStart + itemCount,
- actualStart + actualDeleteCount,
- len - (actualStart + actualDeleteCount));
+ MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount,
+ actualStart + actualDeleteCount,
+ len - (actualStart + actualDeleteCount));
MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure)
return false;
/* Steps 16.a-b. */
- SetInitializedLength(cx, obj.as<NativeObject>(), len + itemCount - actualDeleteCount);
+ SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount);
} else {
RootedValue fromValue(cx);
for (double k = len - actualDeleteCount; k > actualStart; k--) {
@@ -2790,7 +2831,7 @@ SliceSlowly(JSContext* cx, HandleObject obj, HandleObject receiver,
}
static bool
-SliceSparse(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleArrayObject result)
+SliceSparse(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleObject result)
{
MOZ_ASSERT(begin <= end);
@@ -2840,28 +2881,26 @@ ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint32_t length, uint32_t be
begin = end;
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
- size_t initlen = obj->as<NativeObject>().getDenseInitializedLength();
+ size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
size_t count = 0;
if (initlen > begin)
count = Min<size_t>(initlen - begin, end - begin);
- RootedArrayObject narr(cx, NewFullyAllocatedArrayTryReuseGroup(cx, obj, count));
+ RootedObject narr(cx, NewFullyAllocatedArrayTryReuseGroup(cx, obj, count));
if (!narr)
return false;
-
- MOZ_ASSERT(count >= narr->as<ArrayObject>().length());
- narr->as<ArrayObject>().setLength(cx, count);
+ SetAnyBoxedOrUnboxedArrayLength(cx, narr, end - begin);
if (count) {
DebugOnly<DenseElementResult> result =
- CopyDenseElements(cx, &narr->as<NativeObject>(), &obj->as<NativeObject>(), 0, begin, count);
+ CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, 0, begin, count);
MOZ_ASSERT(result.value == DenseElementResult::Success);
}
arr.set(narr);
return true;
}
- RootedArrayObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin));
+ RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin));
if (!narr)
return false;
@@ -2978,10 +3017,11 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp)
return true;
}
-static bool
-ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t endArg, ArrayObject* result)
+template <JSValueType Type>
+DenseElementResult
+ArraySliceDenseKernel(JSContext* cx, JSObject* obj, int32_t beginArg, int32_t endArg, JSObject* result)
{
- int32_t length = arr->length();
+ int32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
uint32_t begin = NormalizeSliceTerm(beginArg, length);
uint32_t end = NormalizeSliceTerm(endArg, length);
@@ -2989,33 +3029,33 @@ ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t
if (begin > end)
begin = end;
- size_t initlen = arr->getDenseInitializedLength();
- size_t count = Min<size_t>(initlen - begin, end - begin);
+ size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
if (initlen > begin) {
+ size_t count = Min<size_t>(initlen - begin, end - begin);
if (count) {
- if (!result->ensureElements(cx, count))
- return false;
- CopyDenseElements(cx, &result->as<NativeObject>(), &arr->as<NativeObject>(), 0, begin, count);
+ DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements<Type>(cx, result, count);
+ if (rv != DenseElementResult::Success)
+ return rv;
+ CopyBoxedOrUnboxedDenseElements<Type, Type>(cx, result, obj, 0, begin, count);
}
}
- MOZ_ASSERT(count >= result->length());
- result->setLength(cx, count);
-
- return true;
+ SetAnyBoxedOrUnboxedArrayLength(cx, result, end - begin);
+ return DenseElementResult::Success;
}
+DefineBoxedOrUnboxedFunctor5(ArraySliceDenseKernel,
+ JSContext*, JSObject*, int32_t, int32_t, JSObject*);
+
JSObject*
js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end,
HandleObject result)
{
if (result && IsArraySpecies(cx, obj)) {
- if (!ArraySliceDenseKernel(cx, &obj->as<ArrayObject>(), begin, end,
- &result->as<ArrayObject>()))
- {
- return nullptr;
- }
- return result;
+ ArraySliceDenseKernelFunctor functor(cx, obj, begin, end, result);
+ DenseElementResult rv = CallBoxedOrUnboxedSpecialization(functor, result);
+ MOZ_ASSERT(rv != DenseElementResult::Incomplete);
+ return rv == DenseElementResult::Success ? result : nullptr;
}
// Slower path if the JIT wasn't able to allocate an object inline.
@@ -3046,7 +3086,7 @@ array_isArray(JSContext* cx, unsigned argc, Value* vp)
static bool
ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr)
{
- ArrayObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto);
+ JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto);
if (!obj)
return false;
@@ -3220,7 +3260,7 @@ ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor)
}
}
- ArrayObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto);
+ JSObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto);
if (!obj)
return false;
@@ -3246,7 +3286,7 @@ js::array_construct(JSContext* cx, unsigned argc, Value* vp)
return ArrayConstructorImpl(cx, args, /* isConstructor = */ false);
}
-ArrayObject*
+JSObject*
js::ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt)
{
if (lengthInt < 0) {
@@ -3541,7 +3581,7 @@ js::NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSOb
return arr;
}
-ArrayObject*
+JSObject*
js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap)
{
MOZ_ASSERT(!gc::IsInsideNursery(templateObject));
@@ -3554,21 +3594,30 @@ js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc
return arr;
}
-// Return a new array with the specified length and allocated capacity (up to
-// maxLength), using the specified group if possible. If the specified group
-// cannot be used, ensure that the created array at least has the given
-// [[Prototype]].
+// Return a new boxed or unboxed array with the specified length and allocated
+// capacity (up to maxLength), using the specified group if possible. If the
+// specified group cannot be used, ensure that the created array at least has
+// the given [[Prototype]].
template <uint32_t maxLength>
-static inline ArrayObject*
+static inline JSObject*
NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind = GenericObject)
{
MOZ_ASSERT(newKind != SingletonObject);
- if (group->shouldPreTenure())
+ if (group->maybePreliminaryObjects())
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+
+ if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
RootedObject proto(cx, group->proto().toObject());
+ if (group->maybeUnboxedLayout()) {
+ if (length > UnboxedArrayObject::MaximumCapacity)
+ return NewArray<maxLength>(cx, length, proto, newKind);
+ return UnboxedArrayObject::create(cx, group, length, newKind, maxLength);
+ }
+
ArrayObject* res = NewArray<maxLength>(cx, length, proto, newKind);
if (!res)
return nullptr;
@@ -3580,17 +3629,20 @@ NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length
if (res->length() > INT32_MAX)
res->setLength(cx, res->length());
+ if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
+ preliminaryObjects->registerNewObject(res);
+
return res;
}
-ArrayObject*
+JSObject*
js::NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind)
{
return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind);
}
-ArrayObject*
+JSObject*
js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length)
{
return NewArrayTryUseGroup<ArrayObject::EagerAllocationMaxLength>(cx, group, length);
@@ -3599,13 +3651,16 @@ js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup g
// Return a new array with the default prototype and specified allocated
// capacity and length. If possible, try to reuse the group of the input
// object. The resulting array will either reuse the input object's group or
-// will have unknown property types.
+// will have unknown property types. Additionally, the result will have the
+// same boxed/unboxed elements representation as the input object, unless
+// |length| is larger than the input object's initialized length (in which case
+// UnboxedArrayObject::MaximumCapacity might be exceeded).
template <uint32_t maxLength>
-static inline ArrayObject*
+static inline JSObject*
NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind = GenericObject)
{
- if (!obj->is<ArrayObject>())
+ if (!obj->is<ArrayObject>() && !obj->is<UnboxedArrayObject>())
return NewArray<maxLength>(cx, length, nullptr, newKind);
if (obj->staticPrototype() != cx->global()->maybeGetArrayPrototype())
@@ -3618,20 +3673,20 @@ NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
return NewArrayTryUseGroup<maxLength>(cx, group, length, newKind);
}
-ArrayObject*
+JSObject*
js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind)
{
return NewArrayTryReuseGroup<UINT32_MAX>(cx, obj, length, newKind);
}
-ArrayObject*
+JSObject*
js::NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length)
{
return NewArrayTryReuseGroup<ArrayObject::EagerAllocationMaxLength>(cx, obj, length);
}
-ArrayObject*
+JSObject*
js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
NewObjectKind newKind)
{
@@ -3641,7 +3696,7 @@ js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind);
}
-ArrayObject*
+JSObject*
js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto)
{
RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
@@ -3650,23 +3705,68 @@ js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length
return NewArrayTryUseGroup<ArrayObject::EagerAllocationMaxLength>(cx, group, length);
}
-ArrayObject*
+bool
+js::MaybeAnalyzeBeforeCreatingLargeArray(ExclusiveContext* cx, HandleObjectGroup group,
+ const Value* vp, size_t length)
+{
+ static const size_t EagerPreliminaryObjectAnalysisThreshold = 800;
+
+ // Force analysis to see if an unboxed array can be used when making a
+ // sufficiently large array, to avoid excessive analysis and copying later
+ // on. If this is the first array of its group that is being created, first
+ // make a dummy array with the initial elements of the array we are about
+ // to make, so there is some basis for the unboxed array analysis.
+ if (length > EagerPreliminaryObjectAnalysisThreshold) {
+ if (PreliminaryObjectArrayWithTemplate* objects = group->maybePreliminaryObjects()) {
+ if (objects->empty()) {
+ size_t nlength = Min<size_t>(length, 100);
+ JSObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, nlength);
+ if (!obj)
+ return false;
+ DebugOnly<DenseElementResult> result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, 0, vp, nlength,
+ ShouldUpdateTypes::Update);
+ MOZ_ASSERT(result.value == DenseElementResult::Success);
+ }
+ objects->maybeAnalyze(cx, group, /* forceAnalyze = */ true);
+ }
+ }
+ return true;
+}
+
+JSObject*
js::NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length, NewObjectKind newKind,
ShouldUpdateTypes updateTypes)
{
- ArrayObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, length, newKind);
+ if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
+ return nullptr;
+
+ JSObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, length, newKind);
if (!obj)
return nullptr;
- DenseElementResult result = obj->setOrExtendDenseElements(cx, 0, vp, length, updateTypes);
+ DenseElementResult result =
+ SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, 0, vp, length, updateTypes);
+ if (result == DenseElementResult::Failure)
+ return nullptr;
+ if (result == DenseElementResult::Success)
+ return obj;
+
+ MOZ_ASSERT(obj->is<UnboxedArrayObject>());
+ if (!UnboxedArrayObject::convertToNative(cx->asJSContext(), obj))
+ return nullptr;
+
+ result = SetOrExtendBoxedOrUnboxedDenseElements<JSVAL_TYPE_MAGIC>(cx, obj, 0, vp, length,
+ updateTypes);
+ MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure)
return nullptr;
- MOZ_ASSERT(result == DenseElementResult::Success);
+
return obj;
}
-ArrayObject*
+JSObject*
js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto /* = nullptr */)
{
diff --git a/js/src/jsarray.h b/js/src/jsarray.h
index d0084731f..0ca580ba7 100644
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -72,37 +72,49 @@ extern ArrayObject*
NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject* templateObject);
/* Create a dense array with the same copy-on-write elements as another object. */
-extern ArrayObject*
+extern JSObject*
NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap);
-extern ArrayObject*
+// The methods below can create either boxed or unboxed arrays.
+
+extern JSObject*
NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind = GenericObject);
-extern ArrayObject*
+extern JSObject*
NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length);
-extern ArrayObject*
+extern JSObject*
NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind = GenericObject);
-extern ArrayObject*
+extern JSObject*
NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length);
-extern ArrayObject*
+extern JSObject*
NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
NewObjectKind newKind = GenericObject);
-extern ArrayObject*
+extern JSObject*
NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto);
-extern ArrayObject*
+enum class ShouldUpdateTypes
+{
+ Update,
+ DontUpdate
+};
+
+extern bool
+MaybeAnalyzeBeforeCreatingLargeArray(ExclusiveContext* cx, HandleObjectGroup group,
+ const Value* vp, size_t length);
+
+extern JSObject*
NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length,
NewObjectKind newKind = GenericObject,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
-extern ArrayObject*
+extern JSObject*
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto = nullptr);
@@ -117,6 +129,13 @@ NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleV
extern bool
WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index);
+/*
+ * Canonicalize |vp| to a uint32_t value potentially suitable for use as an
+ * array length.
+ */
+extern bool
+CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* canonicalized);
+
extern bool
GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp);
@@ -150,7 +169,7 @@ extern bool
array_join(JSContext* cx, unsigned argc, js::Value* vp);
extern void
-ArrayShiftMoveElements(NativeObject* obj);
+ArrayShiftMoveElements(JSObject* obj);
extern bool
array_shift(JSContext* cx, unsigned argc, js::Value* vp);
@@ -182,7 +201,7 @@ extern const JSJitInfo array_splice_info;
extern bool
NewbornArrayPush(JSContext* cx, HandleObject obj, const Value& v);
-extern ArrayObject*
+extern JSObject*
ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt);
#ifdef DEBUG
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..515f62213 100644
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -268,9 +268,9 @@ 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>())
+ else if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
*cls = ESClass::Array;
else if (obj->is<NumberObject>())
*cls = ESClass::Number;
diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp
index 3ad526f74..64573b55a 100644
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6176,6 +6176,12 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
for (auto group = source->zone()->cellIter<ObjectGroup>(); !group.done(); group.next()) {
group->setGeneration(target->zone()->types.generation);
group->compartment_ = target;
+
+ // Remove any unboxed layouts from the list in the off thread
+ // compartment. These do not need to be reinserted in the target
+ // compartment's list, as the list is not required to be complete.
+ if (UnboxedLayout* layout = group->maybeUnboxedLayoutDontCheckGeneration())
+ layout->detachFromCompartment();
}
// Fixup zone pointers in source's zone to refer to target's zone.
diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp
index c58f32382..8b1436b68 100644
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -158,11 +158,8 @@ SortComparatorIntegerIds(jsid a, jsid b, bool* lessOrEqualp)
}
static bool
-EnumerateNativeProperties(JSContext* cx,
- HandleNativeObject pobj,
- unsigned flags,
- Maybe<IdSet>& ht,
- AutoIdVector* props)
+EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
+ AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
{
bool enumerateSymbols;
if (flags & JSITER_SYMBOLSONLY) {
@@ -224,6 +221,16 @@ EnumerateNativeProperties(JSContext* cx,
return false;
}
+ if (unboxed) {
+ // If |unboxed| is set then |pobj| is the expando for an unboxed
+ // plain object we are enumerating. Add the unboxed properties
+ // themselves here since they are all property names that were
+ // given to the object before any of the expando's properties.
+ MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
+ if (!EnumerateExtraProperties(cx, unboxed, flags, ht, props))
+ return false;
+ }
+
size_t initialLength = props->length();
/* Collect all unique property names from this object's shape. */
@@ -349,12 +356,22 @@ Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
do {
if (pobj->getOpsEnumerate()) {
- if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
- return false;
-
- if (pobj->isNative()) {
- if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
+ if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
+ // Special case unboxed objects with an expando object.
+ RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
+ if (!EnumerateNativeProperties(cx, expando, flags, ht, props,
+ pobj.as<UnboxedPlainObject>()))
+ {
+ return false;
+ }
+ } else {
+ if (!EnumerateExtraProperties(cx, pobj, flags, ht, props))
return false;
+
+ if (pobj->isNative()) {
+ if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
+ return false;
+ }
}
} else if (pobj->isNative()) {
// Give the object a chance to resolve all lazy properties
@@ -769,6 +786,11 @@ CanCompareIterableObjectToCache(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().hasEmptyElements();
+ if (obj->is<UnboxedPlainObject>()) {
+ if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
+ return expando->hasEmptyElements();
+ return true;
+ }
return false;
}
diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp
index ef1291079..d4379bd7d 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"
@@ -869,6 +868,9 @@ static inline JSObject*
CreateThisForFunctionWithGroup(JSContext* cx, HandleObjectGroup group,
NewObjectKind newKind)
{
+ if (group->maybeUnboxedLayout() && newKind != SingletonObject)
+ return UnboxedPlainObject::create(cx, group, newKind);
+
if (TypeNewScript* newScript = group->newScript()) {
if (newScript->analyzed()) {
// The definite properties analysis has been performed for this
@@ -1136,18 +1138,19 @@ js::CloneObject(JSContext* cx, HandleObject obj, Handle<js::TaggedProto> proto)
}
static bool
-GetScriptArrayObjectElements(JSContext* cx, HandleArrayObject arr, MutableHandle<GCVector<Value>> values)
+GetScriptArrayObjectElements(JSContext* cx, HandleObject obj, MutableHandle<GCVector<Value>> values)
{
- MOZ_ASSERT(!arr->isSingleton());
- MOZ_ASSERT(!arr->isIndexed());
+ MOZ_ASSERT(!obj->isSingleton());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
+ MOZ_ASSERT(!obj->isIndexed());
- size_t length = arr->length();
+ size_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), length))
return false;
- size_t initlen = arr->getDenseInitializedLength();
+ size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
for (size_t i = 0; i < initlen; i++)
- values[i].set(arr->getDenseElement(i));
+ values[i].set(GetAnyBoxedOrUnboxedDenseElement(obj, i));
return true;
}
@@ -1156,27 +1159,46 @@ static bool
GetScriptPlainObjectProperties(JSContext* cx, HandleObject obj,
MutableHandle<IdValueVector> properties)
{
- MOZ_ASSERT(obj->is<PlainObject>());
- PlainObject* nobj = &obj->as<PlainObject>();
+ if (obj->is<PlainObject>()) {
+ PlainObject* nobj = &obj->as<PlainObject>();
- if (!properties.appendN(IdValuePair(), nobj->slotSpan()))
- return false;
+ if (!properties.appendN(IdValuePair(), nobj->slotSpan()))
+ return false;
+
+ for (Shape::Range<NoGC> r(nobj->lastProperty()); !r.empty(); r.popFront()) {
+ Shape& shape = r.front();
+ MOZ_ASSERT(shape.isDataDescriptor());
+ uint32_t slot = shape.slot();
+ properties[slot].get().id = shape.propid();
+ properties[slot].get().value = nobj->getSlot(slot);
+ }
+
+ for (size_t i = 0; i < nobj->getDenseInitializedLength(); i++) {
+ Value v = nobj->getDenseElement(i);
+ if (!v.isMagic(JS_ELEMENTS_HOLE) && !properties.append(IdValuePair(INT_TO_JSID(i), v)))
+ return false;
+ }
- for (Shape::Range<NoGC> r(nobj->lastProperty()); !r.empty(); r.popFront()) {
- Shape& shape = r.front();
- MOZ_ASSERT(shape.isDataDescriptor());
- uint32_t slot = shape.slot();
- properties[slot].get().id = shape.propid();
- properties[slot].get().value = nobj->getSlot(slot);
+ return true;
}
- for (size_t i = 0; i < nobj->getDenseInitializedLength(); i++) {
- Value v = nobj->getDenseElement(i);
- if (!v.isMagic(JS_ELEMENTS_HOLE) && !properties.append(IdValuePair(INT_TO_JSID(i), v)))
+ if (obj->is<UnboxedPlainObject>()) {
+ UnboxedPlainObject* nobj = &obj->as<UnboxedPlainObject>();
+
+ const UnboxedLayout& layout = nobj->layout();
+ if (!properties.appendN(IdValuePair(), layout.properties().length()))
return false;
+
+ for (size_t i = 0; i < layout.properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout.properties()[i];
+ properties[i].get().id = NameToId(property.name);
+ properties[i].get().value = nobj->getValue(property);
+ }
+
+ return true;
}
- return true;
+ MOZ_CRASH("Bad object kind");
}
static bool
@@ -1198,13 +1220,13 @@ js::DeepCloneObjectLiteral(JSContext* cx, HandleObject obj, NewObjectKind newKin
/* NB: Keep this in sync with XDRObjectLiteral. */
MOZ_ASSERT_IF(obj->isSingleton(),
cx->compartment()->behaviors().getSingletonsAsTemplates());
- MOZ_ASSERT(obj->is<PlainObject>() ||
- obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() ||
+ obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
MOZ_ASSERT(newKind != SingletonObject);
- if (obj->is<ArrayObject>()) {
+ if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
- if (!GetScriptArrayObjectElements(cx, obj.as<ArrayObject>(), &values))
+ if (!GetScriptArrayObjectElements(cx, obj, &values))
return nullptr;
// Deep clone any elements.
@@ -1318,8 +1340,10 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
{
if (mode == XDR_ENCODE) {
MOZ_ASSERT(obj->is<PlainObject>() ||
- obj->is<ArrayObject>());
- isArray = obj->is<ArrayObject>() ? 1 : 0;
+ obj->is<UnboxedPlainObject>() ||
+ obj->is<ArrayObject>() ||
+ obj->is<UnboxedArrayObject>());
+ isArray = (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) ? 1 : 0;
}
if (!xdr->codeUint32(&isArray))
@@ -1331,11 +1355,8 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
if (isArray) {
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
- if (mode == XDR_ENCODE) {
- RootedArrayObject arr(cx, &obj->as<ArrayObject>());
- if (!GetScriptArrayObjectElements(cx, arr, &values))
- return false;
- }
+ if (mode == XDR_ENCODE && !GetScriptArrayObjectElements(cx, obj, &values))
+ return false;
uint32_t initialized;
if (mode == XDR_ENCODE)
@@ -2315,6 +2336,16 @@ js::LookupOwnPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Shape**
// us the resolve hook won't define a property with this id.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
return false;
+ } else if (obj->is<UnboxedPlainObject>()) {
+ if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
+ MarkNonNativePropertyFound<NoGC>(propp);
+ return true;
+ }
+ } else if (obj->is<UnboxedArrayObject>()) {
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ MarkNonNativePropertyFound<NoGC>(propp);
+ return true;
+ }
} else if (obj->is<TypedObject>()) {
if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
MarkNonNativePropertyFound<NoGC>(propp);
@@ -2571,6 +2602,11 @@ js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto, JS::Object
break;
}
+ // Convert unboxed objects to their native representations before changing
+ // their prototype/group, as they depend on the group for their layout.
+ if (!MaybeConvertUnboxedObjectToNative(cx, obj))
+ return false;
+
Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto));
if (!SetClassAndProto(cx, obj, obj->getClass(), taggedProto))
return false;
@@ -2594,6 +2630,9 @@ js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result, I
if (!obj->nonProxyIsExtensible())
return result.succeed();
+ if (!MaybeConvertUnboxedObjectToNative(cx, obj))
+ return false;
+
// Force lazy properties to be resolved.
AutoIdVector props(cx);
if (!js::GetPropertyKeys(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
@@ -3606,6 +3645,22 @@ JSObject::allocKindForTenure(const js::Nursery& nursery) const
if (IsProxy(this))
return as<ProxyObject>().allocKindForTenure();
+ // Unboxed plain objects are sized according to the data they store.
+ if (is<UnboxedPlainObject>()) {
+ size_t nbytes = as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
+ return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
+ }
+
+ // Unboxed arrays use inline data if their size is small enough.
+ if (is<UnboxedArrayObject>()) {
+ const UnboxedArrayObject* nobj = &as<UnboxedArrayObject>();
+ size_t nbytes = UnboxedArrayObject::offsetOfInlineElements() +
+ nobj->capacity() * nobj->elementSize();
+ if (nbytes <= JSObject::MAX_BYTE_SIZE)
+ return GetGCObjectKindForBytes(nbytes);
+ return AllocKind::OBJECT0;
+ }
+
// Inlined typed objects are followed by their data, so make sure we copy
// it all over to the new object.
if (is<InlineTypedObject>()) {
diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h
index 98e740142..a82725ee8 100644
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -32,6 +32,21 @@
#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);
+ if (obj->is<UnboxedArrayObject>())
+ return UnboxedArrayObject::convertToNative(cx->asJSContext(), obj);
+ return true;
+}
+
+} // namespace js
+
inline js::Shape*
JSObject::maybeShape() const
{
@@ -44,6 +59,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/jsstr.cpp b/js/src/jsstr.cpp
index 3964ab84e..5e593846e 100644
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2373,7 +2373,7 @@ js::str_replace_string_raw(JSContext* cx, HandleString string, HandleString patt
}
// ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
-static ArrayObject*
+static JSObject*
SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
HandleObjectGroup group)
{
@@ -2470,7 +2470,7 @@ SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearS
}
// Fast-path for splitting a string into a character array via split("").
-static ArrayObject*
+static JSObject*
CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
{
size_t strLength = str->length();
@@ -2495,7 +2495,7 @@ CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObj
}
// ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
-ArrayObject*
+JSObject*
js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
{
@@ -2906,8 +2906,8 @@ StringObject::assignInitialShape(ExclusiveContext* cx, Handle<StringObject*> obj
{
MOZ_ASSERT(obj->empty());
- return NativeObject::addDataProperty(cx, obj, cx->names().length, LENGTH_SLOT,
- JSPROP_PERMANENT | JSPROP_READONLY);
+ return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT,
+ JSPROP_PERMANENT | JSPROP_READONLY);
}
JSObject*
diff --git a/js/src/jsstr.h b/js/src/jsstr.h
index 68175c826..38fbfa85e 100644
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -465,7 +465,7 @@ FileEscapedString(FILE* fp, const char* chars, size_t length, uint32_t quote)
return res;
}
-ArrayObject*
+JSObject*
str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep,
uint32_t limit);
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..8cd821b31 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -317,6 +317,7 @@ static bool enableIon = false;
static bool enableAsmJS = false;
static bool enableWasm = false;
static bool enableNativeRegExp = false;
+static bool enableUnboxedArrays = false;
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmAlwaysBaseline = false;
static bool enableArrayProtoValues = true;
@@ -7262,6 +7263,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
enableAsmJS = !op.getBoolOption("no-asmjs");
enableWasm = !op.getBoolOption("no-wasm");
enableNativeRegExp = !op.getBoolOption("no-native-regexp");
+ enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
@@ -7271,11 +7273,15 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
.setWasm(enableWasm)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
+ .setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues);
if (op.getBoolOption("wasm-check-bce"))
jit::JitOptions.wasmAlwaysCheckBounds = true;
+ if (op.getBoolOption("no-unboxed-objects"))
+ jit::JitOptions.disableUnboxedObjects = true;
+
if (const char* str = op.getStringOption("cache-ir-stubs")) {
if (strcmp(str, "on") == 0)
jit::JitOptions.disableCacheIR = false;
@@ -7539,6 +7545,7 @@ SetWorkerContextOptions(JSContext* cx)
.setWasm(enableWasm)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
+ .setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues);
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
@@ -7708,6 +7715,8 @@ 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', "unboxed-arrays", "Allow creating unboxed arrays")
|| !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/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp
index 271132801..d8d29830b 100644
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -29,11 +29,11 @@ js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle<ErrorObject*> o
{
MOZ_ASSERT(obj->empty());
- if (!NativeObject::addDataProperty(cx, obj, cx->names().fileName, FILENAME_SLOT, 0))
+ if (!obj->addDataProperty(cx, cx->names().fileName, FILENAME_SLOT, 0))
return nullptr;
- if (!NativeObject::addDataProperty(cx, obj, cx->names().lineNumber, LINENUMBER_SLOT, 0))
+ if (!obj->addDataProperty(cx, cx->names().lineNumber, LINENUMBER_SLOT, 0))
return nullptr;
- return NativeObject::addDataProperty(cx, obj, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
+ return obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0);
}
/* static */ bool
@@ -57,7 +57,7 @@ js::ErrorObject::init(JSContext* cx, Handle<ErrorObject*> obj, JSExnType type,
// |new Error()|.
RootedShape messageShape(cx);
if (message) {
- messageShape = NativeObject::addDataProperty(cx, obj, cx->names().message, MESSAGE_SLOT, 0);
+ messageShape = obj->addDataProperty(cx, cx->names().message, MESSAGE_SLOT, 0);
if (!messageShape)
return false;
MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT);
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
index acfa8f74b..0e81dfef4 100644
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -22,6 +22,7 @@
#include "vm/EnvironmentObject-inl.h"
#include "vm/Stack-inl.h"
#include "vm/String-inl.h"
+#include "vm/UnboxedObject-inl.h"
namespace js {
@@ -336,10 +337,14 @@ InitGlobalLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvAr
inline bool
InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
{
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
- unsigned propAttrs = GetInitDataPropAttrs(op);
- return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs,
- nullptr, nullptr, propAttrs);
+ if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
+ unsigned propAttrs = GetInitDataPropAttrs(op);
+ return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
+ propAttrs);
+ }
+
+ MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
+ return PutProperty(cx, obj, id, rhs, false);
}
inline bool
@@ -593,7 +598,7 @@ InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t
JSOp op = JSOp(*pc);
MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
- MOZ_ASSERT(obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 3cf9b57f6..274392335 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1939,7 +1939,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED126)
CASE(JSOP_UNUSED211)
CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
CASE(JSOP_UNUSED221)
@@ -3682,6 +3681,7 @@ CASE(JSOP_NEWINIT)
END_CASE(JSOP_NEWINIT)
CASE(JSOP_NEWARRAY)
+CASE(JSOP_SPREADCALLARRAY)
{
uint32_t length = GET_UINT32(REGS.pc);
JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length);
@@ -4157,7 +4157,7 @@ CASE(JSOP_INITHOMEOBJECT)
/* Load the home object */
ReservedRooted<JSObject*> obj(&rootObject0);
obj = &REGS.sp[int(-2 - skipOver)].toObject();
- MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
+ MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() || obj->is<JSFunction>());
func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj));
}
@@ -4973,13 +4973,18 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
return nullptr;
if (group->maybePreliminaryObjects()) {
group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout())
+ group->maybeUnboxedLayout()->setAllocationSite(script, pc);
}
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedPlainObject::create(cx, group, newKind);
}
- RootedPlainObject obj(cx);
+ RootedObject obj(cx);
if (*pc == JSOP_NEWOBJECT) {
RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
@@ -5016,6 +5021,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;
@@ -5042,6 +5052,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedArrayObject::create(cx, group, length, newKind);
}
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind);
@@ -5052,6 +5065,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
MOZ_ASSERT(obj->isSingleton());
} else {
obj->setGroup(group);
+
+ if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
+ preliminaryObjects->registerNewObject(obj);
}
return obj;
@@ -5064,6 +5080,12 @@ js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+ if (templateObject->is<UnboxedArrayObject>()) {
+ uint32_t length = templateObject->as<UnboxedArrayObject>().length();
+ RootedObjectGroup group(cx, templateObject->group());
+ return UnboxedArrayObject::create(cx, group, length, newKind);
+ }
+
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(),
nullptr, newKind);
if (!obj)
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp
index e50da3bc4..01883bb15 100644
--- a/js/src/vm/JSONParser.cpp
+++ b/js/src/vm/JSONParser.cpp
@@ -606,8 +606,8 @@ JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements)
{
MOZ_ASSERT(&elements == &stack.back().elements());
- ArrayObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
- GenericObject);
+ JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
+ GenericObject);
if (!obj)
return false;
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h
index 030d92c12..205d99aa2 100644
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t
return DenseElementResult::Success;
}
-inline DenseElementResult
-NativeObject::setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp,
- uint32_t count,
- ShouldUpdateTypes updateTypes)
-{
- if (denseElementsAreFrozen())
- return DenseElementResult::Incomplete;
-
- if (is<ArrayObject>() &&
- !as<ArrayObject>().lengthIsWritable() &&
- start + count >= as<ArrayObject>().length())
- {
- return DenseElementResult::Incomplete;
- }
-
- DenseElementResult result = ensureDenseElements(cx, start, count);
- if (result != DenseElementResult::Success)
- return result;
-
- if (is<ArrayObject>() && start + count >= as<ArrayObject>().length())
- as<ArrayObject>().setLengthInt32(start + count);
-
- if (updateTypes == ShouldUpdateTypes::DontUpdate && !shouldConvertDoubleElements()) {
- copyDenseElements(start, vp, count);
- } else {
- for (size_t i = 0; i < count; i++)
- setDenseElementWithType(cx, start + i, vp[i]);
- }
-
- return DenseElementResult::Success;
-}
-
inline Value
NativeObject::getDenseOrTypedArrayElement(uint32_t idx)
{
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp
index d801fad06..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)
{
@@ -994,22 +1021,23 @@ NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot)
setSlot(slot, UndefinedValue());
}
-/* static */ Shape*
-NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- jsid idArg, uint32_t slot, unsigned attrs)
+Shape*
+NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, unsigned attrs)
{
MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
+ RootedNativeObject self(cx, this);
RootedId id(cx, idArg);
- return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0);
+ return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
}
-/* static */ Shape*
-NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- HandlePropertyName name, uint32_t slot, unsigned attrs)
+Shape*
+NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
+ uint32_t slot, unsigned attrs)
{
MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER)));
+ RootedNativeObject self(cx, this);
RootedId id(cx, NameToId(name));
- return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0);
+ return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0);
}
template <AllowGC allowGC>
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index 9cc6d5436..3a3e50244 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -339,19 +339,16 @@ IsObjectValueInCompartment(const Value& v, JSCompartment* comp);
#endif
// Operations which change an object's dense elements can either succeed, fail,
-// or be unable to complete. The latter is used when the object's elements must
-// become sparse instead. The enum below is used for such operations.
+// or be unable to complete. For native objects, the latter is used when the
+// object's elements must become sparse instead. The enum below is used for
+// such operations, and for similar operations on unboxed arrays and methods
+// that work on both kinds of objects.
enum class DenseElementResult {
Failure,
Success,
Incomplete
};
-enum class ShouldUpdateTypes {
- Update,
- DontUpdate
-};
-
/*
* NativeObject specifies the internal implementation of a native object.
*
@@ -470,6 +467,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.
@@ -744,10 +746,10 @@ class NativeObject : public ShapedObject
bool allowDictionary = true);
/* Add a data property whose id is not yet in this scope. */
- static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- jsid id_, uint32_t slot, unsigned attrs);
- static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj,
- HandlePropertyName name, uint32_t slot, unsigned attrs);
+ Shape* addDataProperty(ExclusiveContext* cx,
+ jsid id_, uint32_t slot, unsigned attrs);
+ Shape* addDataProperty(ExclusiveContext* cx, HandlePropertyName name,
+ uint32_t slot, unsigned attrs);
/* Add or overwrite a property for id in this scope. */
static Shape*
@@ -1149,10 +1151,6 @@ class NativeObject : public ShapedObject
elementsRangeWriteBarrierPost(dstStart, count);
}
- inline DenseElementResult
- setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, uint32_t count,
- ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
-
bool shouldConvertDoubleElements() {
return getElementsHeader()->shouldConvertDoubleElements();
}
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..d05a48646 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;
}
@@ -772,7 +777,7 @@ GetValueTypeForTable(const Value& v)
return type;
}
-/* static */ ArrayObject*
+/* static */ JSObject*
ObjectGroup::newArrayObject(ExclusiveContext* cx,
const Value* vp, size_t length,
NewObjectKind newKind, NewArrayKind arrayKind)
@@ -836,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx,
AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
+ if (elementType != TypeSet::UnknownType()) {
+ // Keep track of the initial objects we create with this type.
+ // If the initial ones have a consistent shape and property types, we
+ // will try to use an unboxed layout for the group.
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (!preliminaryObjects)
+ return nullptr;
+ group->setPreliminaryObjects(preliminaryObjects);
+ }
+
if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
return nullptr;
}
// The type of the elements being added will already be reflected in type
- // information.
+ // information, but make sure when creating an unboxed array that the
+ // common element type is suitable for the unboxed representation.
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
+ if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
+ return nullptr;
+ if (group->maybePreliminaryObjects())
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout()) {
+ switch (group->unboxedLayout().elementType()) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (elementType != TypeSet::BooleanType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_INT32:
+ if (elementType != TypeSet::Int32Type())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_DOUBLE:
+ if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_STRING:
+ if (elementType != TypeSet::StringType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_OBJECT:
+ if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
}
@@ -852,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
{
MOZ_ASSERT(source->group() != target->group());
- if (!target->is<ArrayObject>() || !source->is<ArrayObject>()) {
+ if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
+ return true;
+
+ if (target->group()->maybePreliminaryObjects()) {
+ bool force = IsInsideNursery(source);
+ target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
+ }
+
+ if (target->is<ArrayObject>()) {
+ ObjectGroup* sourceGroup = source->group();
+
+ if (source->is<UnboxedArrayObject>()) {
+ Shape* shape = target->as<ArrayObject>().lastProperty();
+ if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
+ return false;
+ } else if (source->is<ArrayObject>()) {
+ source->setGroup(target->group());
+ } else {
+ return true;
+ }
+
+ if (sourceGroup->maybePreliminaryObjects())
+ sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
+ if (target->group()->maybePreliminaryObjects())
+ target->group()->maybePreliminaryObjects()->registerNewObject(source);
+
+ for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
+ Value v = source->as<ArrayObject>().getDenseElement(i);
+ AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ }
+
return true;
}
- source->setGroup(target->group());
+ if (target->is<UnboxedArrayObject>()) {
+ if (!source->is<UnboxedArrayObject>())
+ return true;
+ if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
+ return true;
+ if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
+ return true;
- for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
- Value v = source->as<ArrayObject>().getDenseElement(i);
- AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
}
return true;
@@ -969,6 +1051,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 +1314,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.
@@ -1378,6 +1506,18 @@ ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode*
}
}
+ if (kind == JSProto_Array &&
+ (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) &&
+ cx->options().unboxedArrays())
+ {
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (preliminaryObjects)
+ res->setPreliminaryObjects(preliminaryObjects);
+ else
+ cx->recoverFromOutOfMemory();
+ }
+
if (!table->add(p, key, res)) {
ReportOutOfMemory(cx);
return nullptr;
diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h
index 0b6eaee51..4e24de9f1 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:
@@ -457,14 +505,14 @@ class ObjectGroup : public gc::TenuredCell
UnknownIndex // Make an array with an unknown element type.
};
- // Create an ArrayObject with the specified elements and a group specialized
- // for the elements.
- static ArrayObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
- NewObjectKind newKind,
- NewArrayKind arrayKind = NewArrayKind::Normal);
+ // Create an ArrayObject or UnboxedArrayObject with the specified elements
+ // and a group specialized for the elements.
+ static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
+ 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/Opcodes.h b/js/src/vm/Opcodes.h
index e13cd6221..b26336815 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1281,7 +1281,17 @@
* Stack: receiver, obj, propval => obj[propval]
*/ \
macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \
- macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \
+ /*
+ * Pushes newly created array for a spread call onto the stack. This has
+ * the same semantics as JSOP_NEWARRAY, but is distinguished to avoid
+ * using unboxed arrays in spread calls, which would make compiling spread
+ * calls in baseline more complex.
+ * Category: Literals
+ * Type: Array
+ * Operands: uint32_t length
+ * Stack: => obj
+ */ \
+ macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 5, 0, 1, JOF_UINT32) \
\
/*
* Defines the given function on the current scope.
diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp
index e95e8a208..97df908c3 100644
--- a/js/src/vm/ReceiverGuard.cpp
+++ b/js/src/vm/ReceiverGuard.cpp
@@ -7,6 +7,7 @@
#include "vm/ReceiverGuard.h"
#include "builtin/TypedObject.h"
+#include "vm/UnboxedObject.h"
#include "jsobjinlines.h"
using namespace js;
@@ -15,7 +16,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<UnboxedArrayObject>() || obj->is<TypedObject>()) {
group = obj->group();
} else {
shape = obj->maybeShape();
@@ -28,7 +33,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 (clasp == &UnboxedArrayObject::class_ || IsTypedObjectClass(clasp)) {
this->shape = nullptr;
} else {
this->group = nullptr;
@@ -39,8 +46,12 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
/* static */ int32_t
HeapReceiverGuard::keyBits(JSObject* obj)
{
- if (obj->is<TypedObject>()) {
- // Only the group needs to be guarded for typed objects.
+ 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<UnboxedArrayObject>() || obj->is<TypedObject>()) {
+ // Only the group needs to be guarded for unboxed arrays and typed objects.
return 2;
}
// Other objects only need the shape to be guarded.
diff --git a/js/src/vm/ReceiverGuard.h b/js/src/vm/ReceiverGuard.h
index c14f0d83b..459cc0012 100644
--- a/js/src/vm/ReceiverGuard.h
+++ b/js/src/vm/ReceiverGuard.h
@@ -28,6 +28,11 @@ namespace js {
// TypedObject: The structure of a typed object is determined by its group.
// All typed objects with the same group have the same class, prototype, and
// own properties.
+//
+// UnboxedPlainObject: The structure of an unboxed plain object is determined
+// by its group and its expando object's shape, if there is one. All unboxed
+// plain objects with the same group and expando shape have the same
+// properties except those stored in the expando's dense elements.
class HeapReceiverGuard;
class RootedReceiverGuard;
diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp
index cd0b54c9d..6223fc10d 100644
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -299,8 +299,7 @@ RegExpObject::assignInitialShape(ExclusiveContext* cx, Handle<RegExpObject*> sel
JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
/* The lastIndex property alone is writable but non-configurable. */
- return NativeObject::addDataProperty(cx, self, cx->names().lastIndex, LAST_INDEX_SLOT,
- JSPROP_PERMANENT);
+ return self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT);
}
void
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp
index 95940eeaf..7eb8f4ab8 100644
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -82,7 +82,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const
return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal();
}
-ArrayObject*
+JSObject*
InterpreterFrame::createRestParameter(JSContext* cx)
{
MOZ_ASSERT(script()->hasRest());
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h
index fe04a00f2..d08544213 100644
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -523,7 +523,7 @@ class InterpreterFrame
ArgumentsObject& argsObj() const;
void initArgsObj(ArgumentsObject& argsobj);
- ArrayObject* createRestParameter(JSContext* cx);
+ JSObject* createRestParameter(JSContext* cx);
/*
* Environment chain
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..438188a36 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
@@ -1968,6 +1999,17 @@ TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* co
ConstraintDataFreezeObjectForTypedArrayData(tarray)));
}
+void
+TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints)
+{
+ HeapTypeSetKey objectProperty = property(JSID_EMPTY);
+ LifoAlloc* alloc = constraints->alloc();
+
+ typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForUnboxedConvertedToNative> T;
+ constraints->add(alloc->new_<T>(alloc, objectProperty,
+ ConstraintDataFreezeObjectForUnboxedConvertedToNative()));
+}
+
static void
ObjectStateChange(ExclusiveContext* cxArg, ObjectGroup* group, bool markingUnknown)
{
@@ -2478,6 +2520,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
bool
js::ClassCanHaveExtraProperties(const Class* clasp)
{
+ if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
+ return false;
return clasp->getResolve()
|| clasp->getOpsLookupProperty()
|| clasp->getOpsGetProperty()
@@ -2768,6 +2812,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 +2892,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 +2930,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 +2956,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 +2975,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 +2989,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
if (!isMarked())
return;
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -2925,7 +3004,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
void
ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/)
{
- TypeNewScript* newScript = this->newScript();
+ TypeNewScript* newScript = anyNewScript();
if (!newScript)
return;
@@ -3332,7 +3411,7 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun,
/////////////////////////////////////////////////////////////////////
void
-PreliminaryObjectArray::registerNewObject(PlainObject* res)
+PreliminaryObjectArray::registerNewObject(JSObject* res)
{
// The preliminary object pointers are weak, and won't be swept properly
// during nursery collections, so the preliminary objects need to be
@@ -3350,7 +3429,7 @@ PreliminaryObjectArray::registerNewObject(PlainObject* res)
}
void
-PreliminaryObjectArray::unregisterObject(PlainObject* obj)
+PreliminaryObjectArray::unregisterObject(JSObject* obj)
{
for (size_t i = 0; i < COUNT; i++) {
if (objects[i] == obj) {
@@ -3390,6 +3469,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 +3584,17 @@ 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());
+ TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects);
+ 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 +3608,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.
@@ -3811,9 +3913,34 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
PodCopy(initializerList, initializerVector.begin(), initializerVector.length());
}
+ // Try to use an unboxed representation for the group.
+ if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects))
+ return false;
+
js_delete(preliminaryObjects);
preliminaryObjects = nullptr;
+ 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 +4054,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 +4253,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 +4342,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/TypeInference.h b/js/src/vm/TypeInference.h
index fd021fc96..9ec1bf927 100644
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -262,6 +262,7 @@ class TypeSet
bool hasStableClassAndProto(CompilerConstraintList* constraints);
void watchStateChangeForInlinedCall(CompilerConstraintList* constraints);
void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints);
+ void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints);
HeapTypeSetKey property(jsid id);
void ensureTrackedProperty(JSContext* cx, jsid id);
@@ -871,8 +872,8 @@ class PreliminaryObjectArray
public:
PreliminaryObjectArray() = default;
- void registerNewObject(PlainObject* res);
- void unregisterObject(PlainObject* obj);
+ void registerNewObject(JSObject* res);
+ void unregisterObject(JSObject* obj);
JSObject* get(size_t i) const {
MOZ_ASSERT(i < COUNT);
diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h
new file mode 100644
index 000000000..93ad7bf28
--- /dev/null
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -0,0 +1,840 @@
+/* -*- 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();
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+inline const UnboxedLayout&
+UnboxedArrayObject::layout() const
+{
+ return group()->unboxedLayout();
+}
+
+inline void
+UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length)
+{
+ if (length > INT32_MAX) {
+ // Track objects with overflowing lengths in type information.
+ MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW);
+ }
+
+ length_ = length;
+}
+
+inline void
+UnboxedArrayObject::setInitializedLength(uint32_t initlen)
+{
+ if (initlen < initializedLength()) {
+ switch (elementType()) {
+ case JSVAL_TYPE_STRING:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_STRING>(i);
+ break;
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_OBJECT>(i);
+ break;
+ default:
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(elementType()));
+ }
+ }
+ setInitializedLengthNoBarrier(initlen);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline Value
+UnboxedArrayObject::getElementSpecific(size_t index)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::triggerPreBarrier(size_t index)
+{
+ MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type));
+
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+
+ switch (Type) {
+ case JSVAL_TYPE_STRING: {
+ JSString** np = reinterpret_cast<JSString**>(p);
+ JSString::writeBarrierPre(*np);
+ break;
+ }
+
+ case JSVAL_TYPE_OBJECT: {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+ JSObject::writeBarrierPre(*np);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+// Combined methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline bool
+HasAnyBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ return obj->isNative() || obj->is<UnboxedArrayObject>();
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().initializedLength();
+ return 0;
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseCapacity();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().capacity();
+ return 0;
+}
+
+static inline Value
+GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElement(index);
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedArrayLength(JSObject* obj)
+{
+ if (obj->is<ArrayObject>())
+ return obj->as<ArrayObject>().length();
+ return obj->as<UnboxedArrayObject>().length();
+}
+
+static inline void
+SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length)
+{
+ if (obj->is<ArrayObject>()) {
+ MOZ_ASSERT(length >= obj->as<ArrayObject>().length());
+ obj->as<ArrayObject>().setLength(cx, length);
+ } else {
+ MOZ_ASSERT(length >= obj->as<UnboxedArrayObject>().length());
+ obj->as<UnboxedArrayObject>().setLength(cx, length);
+ }
+}
+
+static inline bool
+SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElement(cx, index, value);
+}
+
+static inline bool
+InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().initDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().initElement(cx, index, value);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Template methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline JSValueType
+GetBoxedOrUnboxedType(JSObject* obj)
+{
+ if (obj->isNative())
+ return JSVAL_TYPE_MAGIC;
+ return obj->as<UnboxedArrayObject>().elementType();
+}
+
+template <JSValueType Type>
+static inline bool
+HasBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->isNative();
+ return obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().elementType() == Type;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ return obj->as<UnboxedArrayObject>().initializedLength();
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ size_t oldInitlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<NativeObject>().shrinkElements(cx, initlen);
+ } else {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen);
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseCapacity();
+ return obj->as<UnboxedArrayObject>().capacity();
+}
+
+template <JSValueType Type>
+static inline Value
+GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElementSpecific<Type>(index);
+}
+
+template <JSValueType Type>
+static inline void
+SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ obj->as<NativeObject>().setDenseElement(index, value);
+ else
+ obj->as<UnboxedArrayObject>().setElementNoTypeChangeSpecific<Type>(index, value);
+}
+
+template <JSValueType Type>
+static inline bool
+SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElementSpecific<Type>(cx, index, value);
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (!obj->as<ArrayObject>().ensureElements(cx, count))
+ return DenseElementResult::Failure;
+ } else {
+ if (obj->as<UnboxedArrayObject>().capacity() < count) {
+ if (!obj->as<UnboxedArrayObject>().growElements(cx, count))
+ return DenseElementResult::Failure;
+ }
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ if (nobj->denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (obj->is<ArrayObject>() &&
+ !obj->as<ArrayObject>().lengthIsWritable() &&
+ start + count >= obj->as<ArrayObject>().length())
+ {
+ return DenseElementResult::Incomplete;
+ }
+
+ DenseElementResult result = nobj->ensureDenseElements(cx, start, count);
+ if (result != DenseElementResult::Success)
+ return result;
+
+ if (obj->is<ArrayObject>() && start + count >= obj->as<ArrayObject>().length())
+ obj->as<ArrayObject>().setLengthInt32(start + count);
+
+ if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) {
+ nobj->copyDenseElements(start, vp, count);
+ } else {
+ for (size_t i = 0; i < count; i++)
+ nobj->setDenseElementWithType(cx, start + i, vp[i]);
+ }
+
+ return DenseElementResult::Success;
+ }
+
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ if (start > nobj->initializedLength())
+ return DenseElementResult::Incomplete;
+
+ if (start + count >= UnboxedArrayObject::MaximumCapacity)
+ return DenseElementResult::Incomplete;
+
+ if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count))
+ return DenseElementResult::Failure;
+
+ size_t oldInitlen = nobj->initializedLength();
+
+ // Overwrite any existing elements covered by the new range. If we fail
+ // after this point due to some incompatible type being written to the
+ // object's elements, afterwards the contents will be different from when
+ // we started. The caller must retry the operation using a generic path,
+ // which will overwrite the already-modified elements as well as the ones
+ // that were left alone.
+ size_t i = 0;
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++)
+ nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
+ } else {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
+ if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
+ return DenseElementResult::Incomplete;
+ }
+ }
+
+ if (i != count) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (; i < count; i++)
+ nobj->initElementNoTypeChangeSpecific<Type>(start + i, vp[i]);
+ } else {
+ for (; i < count; i++) {
+ if (!nobj->initElementSpecific<Type>(cx, start + i, vp[i])) {
+ nobj->setInitializedLengthNoBarrier(oldInitlen);
+ return DenseElementResult::Incomplete;
+ }
+ }
+ }
+ }
+
+ if (start + count >= nobj->length())
+ nobj->setLength(cx, start + count);
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart,
+ uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));
+
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (obj->as<NativeObject>().denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (!obj->as<NativeObject>().maybeCopyElementsForWrite(cx))
+ return DenseElementResult::Failure;
+ obj->as<NativeObject>().moveDenseElements(dstStart, srcStart, length);
+ } else {
+ uint8_t* data = obj->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(Type);
+
+ if (UnboxedTypeNeedsPreBarrier(Type) &&
+ JS::shadow::Zone::asShadowZone(obj->zone())->needsIncrementalBarrier())
+ {
+ // Trigger pre barriers on any elements we are overwriting. See
+ // NativeObject::moveDenseElements. No post barrier is needed as
+ // only whole cell post barriers are used with unboxed objects.
+ for (size_t i = 0; i < length; i++)
+ obj->as<UnboxedArrayObject>().triggerPreBarrier<Type>(dstStart + i);
+ }
+
+ memmove(data + dstStart * elementSize,
+ data + srcStart * elementSize,
+ length * elementSize);
+ }
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType DstType, JSValueType SrcType>
+static inline DenseElementResult
+CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<SrcType>(src));
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<DstType>(dst));
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(dst) == dstStart);
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<SrcType>(src) >= srcStart + length);
+ MOZ_ASSERT(GetBoxedOrUnboxedCapacity<DstType>(dst) >= dstStart + length);
+
+ SetBoxedOrUnboxedInitializedLength<DstType>(cx, dst, dstStart + length);
+
+ if (DstType == JSVAL_TYPE_MAGIC) {
+ if (SrcType == JSVAL_TYPE_MAGIC) {
+ const Value* vp = src->as<NativeObject>().getDenseElements() + srcStart;
+ dst->as<NativeObject>().initDenseElements(dstStart, vp, length);
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<NativeObject>().initDenseElement(dstStart + i, v);
+ }
+ }
+ } else if (DstType == SrcType) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(DstType);
+
+ memcpy(dstData + dstStart * elementSize,
+ srcData + srcStart * elementSize,
+ length * elementSize);
+
+ // Add a store buffer entry if we might have copied a nursery pointer to dst.
+ if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst))
+ dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCell(dst);
+ } else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+
+ for (size_t i = 0; i < length; i++) {
+ int32_t v = *reinterpret_cast<int32_t*>(srcData + (srcStart + i) * sizeof(int32_t));
+ *reinterpret_cast<double*>(dstData + (dstStart + i) * sizeof(double)) = v;
+ }
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<UnboxedArrayObject>().initElementNoTypeChangeSpecific<DstType>(dstStart + i, v);
+ }
+ }
+
+ return DenseElementResult::Success;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Dispatch to specialized methods based on the type of an object.
+/////////////////////////////////////////////////////////////////////
+
+// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h.
+// The clang-cl front end defines _MSC_VER, but still requires the explicit
+// template declaration, so we must test for __clang__ here as well.
+#if defined(_MSC_VER) && !defined(__clang__)
+# define DEPENDENT_TEMPLATE_HINT
+#else
+# define DEPENDENT_TEMPLATE_HINT template
+#endif
+
+// Function to dispatch a method specialized to whatever boxed or unboxed dense
+// elements which an input object has.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj))
+ return DenseElementResult::Incomplete;
+ switch (GetBoxedOrUnboxedType(obj)) {
+ case JSVAL_TYPE_MAGIC:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_MAGIC>();
+ case JSVAL_TYPE_BOOLEAN:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_BOOLEAN>();
+ case JSVAL_TYPE_INT32:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_INT32>();
+ case JSVAL_TYPE_DOUBLE:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_DOUBLE>();
+ case JSVAL_TYPE_STRING:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_STRING>();
+ case JSVAL_TYPE_OBJECT:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_OBJECT>();
+ default:
+ MOZ_CRASH();
+ }
+}
+
+// As above, except the specialization can reflect the unboxed type of two objects.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2))
+ return DenseElementResult::Incomplete;
+
+#define SPECIALIZE_OBJ2(TYPE) \
+ switch (GetBoxedOrUnboxedType(obj2)) { \
+ case JSVAL_TYPE_MAGIC: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_MAGIC>(); \
+ case JSVAL_TYPE_BOOLEAN: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_BOOLEAN>(); \
+ case JSVAL_TYPE_INT32: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_INT32>(); \
+ case JSVAL_TYPE_DOUBLE: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_DOUBLE>(); \
+ case JSVAL_TYPE_STRING: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_STRING>(); \
+ case JSVAL_TYPE_OBJECT: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_OBJECT>(); \
+ default: \
+ MOZ_CRASH(); \
+ }
+
+ switch (GetBoxedOrUnboxedType(obj1)) {
+ case JSVAL_TYPE_MAGIC:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC)
+ case JSVAL_TYPE_BOOLEAN:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN)
+ case JSVAL_TYPE_INT32:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_INT32)
+ case JSVAL_TYPE_DOUBLE:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE)
+ case JSVAL_TYPE_STRING:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_STRING)
+ case JSVAL_TYPE_OBJECT:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT)
+ default:
+ MOZ_CRASH();
+ }
+
+#undef SPECIALIZE_OBJ2
+}
+
+#undef DEPENDENT_TEMPLATE_HINT
+
+#define DefineBoxedOrUnboxedFunctor1(Signature, A) \
+struct Signature ## Functor { \
+ A a; \
+ explicit Signature ## Functor(A a) \
+ : a(a) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \
+struct Signature ## Functor { \
+ A a; B b; C c; \
+ Signature ## Functor(A a, B b, C c) \
+ : a(a), b(b), c(c) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; \
+ Signature ## Functor(A a, B b, C c, D d, E e) \
+ : a(a), b(b), c(c), d(d), e(e) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e, f); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d, e, f); \
+ } \
+}
+
+DenseElementResult
+SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
+
+DenseElementResult
+MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+DenseElementResult
+CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+void
+SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen);
+
+DenseElementResult
+EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count);
+
+} // 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..d8c9c774a
--- /dev/null
+++ b/js/src/vm/UnboxedObject.cpp
@@ -0,0 +1,2132 @@
+/* -*- 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);
+
+ const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
+
+ // 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()) {
+ MOZ_ASSERT(!layout.isArray());
+
+ 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, clasp, proto);
+ if (!replacementGroup)
+ return false;
+
+ PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
+ replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
+
+ JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object;
+ cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key,
+ 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 = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind());
+
+ if (layout.isArray()) {
+ // The length shape to use for arrays is cached via a modified initial
+ // shape for array objects. Create an array now to make sure this entry
+ // is instantiated.
+ if (!NewDenseEmptyArray(cx))
+ return false;
+ }
+
+ RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0));
+ if (!shape)
+ return false;
+
+ MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
+
+ // 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, clasp, 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.
+ if (layout.isArray()) {
+ if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
+ return false;
+ } else {
+ 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
+};
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+template <JSValueType Type>
+DenseElementResult
+AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen,
+ MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < initlen; i++)
+ values.infallibleAppend(obj->getElementSpecific<Type>(i));
+ return DenseElementResult::Success;
+}
+
+DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements,
+ UnboxedArrayObject*, uint32_t, MutableHandle<GCVector<Value>>);
+
+/* static */ bool
+UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape)
+{
+ size_t length = obj->as<UnboxedArrayObject>().length();
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ if (!values.reserve(initlen))
+ return false;
+
+ AppendUnboxedDenseElementsFunctor functor(&obj->as<UnboxedArrayObject>(), initlen, &values);
+ DebugOnly<DenseElementResult> result = CallBoxedOrUnboxedSpecialization(functor, obj);
+ MOZ_ASSERT(result.value == DenseElementResult::Success);
+
+ obj->setGroup(group);
+
+ ArrayObject* aobj = &obj->as<ArrayObject>();
+ aobj->setLastPropertyMakeNative(cx, shape);
+
+ // Make sure there is at least one element, so that this array does not
+ // use emptyObjectElements / emptyObjectElementsShared.
+ if (!aobj->ensureElements(cx, Max<size_t>(initlen, 1)))
+ return false;
+
+ MOZ_ASSERT(!aobj->getDenseInitializedLength());
+ aobj->setDenseInitializedLength(initlen);
+ aobj->initDenseElements(0, values.begin(), initlen);
+ aobj->setLengthInt32(length);
+
+ return true;
+}
+
+/* static */ bool
+UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj)
+{
+ const UnboxedLayout& layout = obj->as<UnboxedArrayObject>().layout();
+
+ if (!layout.nativeGroup()) {
+ if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
+ return false;
+ }
+
+ return convertToNativeWithGroup(cx, obj, layout.nativeGroup(), layout.nativeShape());
+}
+
+bool
+UnboxedArrayObject::convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group)
+{
+ MOZ_ASSERT(elementType() == JSVAL_TYPE_INT32);
+ MOZ_ASSERT(group->unboxedLayout().elementType() == JSVAL_TYPE_DOUBLE);
+
+ Vector<int32_t> values(cx);
+ if (!values.reserve(initializedLength()))
+ return false;
+ for (size_t i = 0; i < initializedLength(); i++)
+ values.infallibleAppend(getElementSpecific<JSVAL_TYPE_INT32>(i).toInt32());
+
+ uint8_t* newElements;
+ if (hasInlineElements()) {
+ newElements = AllocateObjectBuffer<uint8_t>(cx, this, capacity() * sizeof(double));
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ capacity() * sizeof(int32_t),
+ capacity() * sizeof(double));
+ }
+ if (!newElements)
+ return false;
+
+ setGroup(group);
+ elements_ = newElements;
+
+ for (size_t i = 0; i < initializedLength(); i++)
+ setElementNoTypeChangeSpecific<JSVAL_TYPE_DOUBLE>(i, DoubleValue(values[i]));
+
+ return true;
+}
+
+/* static */ UnboxedArrayObject*
+UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length,
+ NewObjectKind newKind, uint32_t maxLength)
+{
+ MOZ_ASSERT(length <= MaximumCapacity);
+
+ MOZ_ASSERT(group->clasp() == &class_);
+ uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType());
+ uint32_t capacity = Min(length, maxLength);
+ uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity;
+
+ UnboxedArrayObject* res;
+ if (nbytes <= JSObject::MAX_BYTE_SIZE) {
+ gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes);
+
+ // If there was no provided length information, pick an allocation kind
+ // to accommodate small arrays (as is done for normal native arrays).
+ if (capacity == 0)
+ allocKind = gc::AllocKind::OBJECT8;
+
+ res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
+ if (!res)
+ return nullptr;
+ res->setInitializedLengthNoBarrier(0);
+ res->setInlineElements();
+
+ size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
+ MOZ_ASSERT(actualCapacity >= capacity);
+ res->setCapacityIndex(exactCapacityIndex(actualCapacity));
+ } else {
+ res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
+ if (!res)
+ return nullptr;
+ res->setInitializedLengthNoBarrier(0);
+
+ uint32_t capacityIndex = (capacity == length)
+ ? CapacityMatchesLengthIndex
+ : chooseCapacityIndex(capacity, length);
+ uint32_t actualCapacity = computeCapacity(capacityIndex, length);
+
+ res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, actualCapacity * elementSize);
+ if (!res->elements_) {
+ // Make the object safe for GC.
+ res->setInlineElements();
+ return nullptr;
+ }
+
+ res->setCapacityIndex(capacityIndex);
+ }
+
+ res->setLength(cx, length);
+ return res;
+}
+
+bool
+UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
+}
+
+bool
+UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
+}
+
+void
+UnboxedArrayObject::initElementNoTypeChange(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ if (UnboxedTypeNeedsPreBarrier(elementType()))
+ *reinterpret_cast<void**>(p) = nullptr;
+ SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
+}
+
+Value
+UnboxedArrayObject::getElement(size_t index)
+{
+ MOZ_ASSERT(index < initializedLength());
+ uint8_t* p = elements() + index * elementSize();
+ return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false);
+}
+
+/* static */ void
+UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj)
+{
+ JSValueType type = obj->as<UnboxedArrayObject>().elementType();
+ if (!UnboxedTypeNeedsPreBarrier(type))
+ return;
+
+ MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t));
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements());
+
+ switch (type) {
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(elements + i);
+ TraceNullableEdge(trc, heap, "unboxed_object");
+ }
+ break;
+
+ case JSVAL_TYPE_STRING:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(elements + i);
+ TraceEdge(trc, heap, "unboxed_string");
+ }
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+/* static */ void
+UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
+{
+ UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
+ const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
+
+ // Fix up possible inline data pointer.
+ if (src.hasInlineElements())
+ dst.setInlineElements();
+}
+
+/* static */ void
+UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ MOZ_ASSERT(!IsInsideNursery(obj));
+ if (!obj->as<UnboxedArrayObject>().hasInlineElements())
+ js_free(obj->as<UnboxedArrayObject>().elements());
+}
+
+/* static */ size_t
+UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind)
+{
+ UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
+ UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
+ MOZ_ASSERT(ndst->elements() == nsrc->elements());
+
+ Nursery& nursery = trc->runtime()->gc.nursery;
+
+ if (!nursery.isInside(nsrc->elements())) {
+ nursery.removeMallocedBuffer(nsrc->elements());
+ return 0;
+ }
+
+ // Determine if we can use inline data for the target array. If this is
+ // possible, the nursery will have picked an allocation size that is large
+ // enough.
+ size_t nbytes = nsrc->capacity() * nsrc->elementSize();
+ if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) {
+ ndst->setInlineElements();
+ } else {
+ MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0);
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
+ if (!data)
+ oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring.");
+ ndst->elements_ = data;
+ }
+
+ PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize());
+
+ // Set a forwarding pointer for the element buffers in case they were
+ // preserved on the stack by Ion.
+ bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t);
+ nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct);
+
+ return ndst->hasInlineElements() ? 0 : nbytes;
+}
+
+// Possible capacities for unboxed arrays. Some of these capacities might seem
+// a little weird, but were chosen to allow the inline data of objects of each
+// size to be fully utilized for arrays of the various types on both 32 bit and
+// 64 bit platforms.
+//
+// To find the possible inline capacities, the following script was used:
+//
+// var fixedSlotCapacities = [0, 2, 4, 8, 12, 16];
+// var dataSizes = [1, 4, 8];
+// var header32 = 4 * 2 + 4 * 2;
+// var header64 = 8 * 2 + 4 * 2;
+//
+// for (var i = 0; i < fixedSlotCapacities.length; i++) {
+// var nfixed = fixedSlotCapacities[i];
+// var size32 = 4 * 4 + 8 * nfixed - header32;
+// var size64 = 8 * 4 + 8 * nfixed - header64;
+// for (var j = 0; j < dataSizes.length; j++) {
+// print(size32 / dataSizes[j]);
+// print(size64 / dataSizes[j]);
+// }
+// }
+//
+/* static */ const uint32_t
+UnboxedArrayObject::CapacityArray[] = {
+ UINT32_MAX, // For CapacityMatchesLengthIndex.
+ 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 24, 26, 32, 34, 40, 64, 72, 96, 104, 128, 136,
+ 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
+ 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336,
+ 13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464,
+ 46137344, 52428800, 59768832, MaximumCapacity
+};
+
+static const uint32_t
+Pow2CapacityIndexes[] = {
+ 2, // 1
+ 3, // 2
+ 5, // 4
+ 8, // 8
+ 13, // 16
+ 18, // 32
+ 21, // 64
+ 25, // 128
+ 27, // 256
+ 28, // 512
+ 29, // 1024
+ 30, // 2048
+ 31, // 4096
+ 32, // 8192
+ 33, // 16384
+ 34, // 32768
+ 35, // 65536
+ 36, // 131072
+ 37, // 262144
+ 38, // 524288
+ 39 // 1048576
+};
+
+static const uint32_t MebiCapacityIndex = 39;
+
+/* static */ uint32_t
+UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length)
+{
+ // Note: the structure and behavior of this method follow along with
+ // NativeObject::goodAllocated. Changes to the allocation strategy in one
+ // should generally be matched by the other.
+
+ // Make sure we have enough space to store all possible values for the capacity index.
+ // This ought to be a static_assert, but MSVC doesn't like that.
+ MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= (CapacityMask >> CapacityShift));
+
+ // The caller should have ensured the capacity is possible for an unboxed array.
+ MOZ_ASSERT(capacity <= MaximumCapacity);
+
+ static const uint32_t Mebi = 1024 * 1024;
+
+ if (capacity <= Mebi) {
+ capacity = mozilla::RoundUpPow2(capacity);
+
+ // When the required capacity is close to the array length, then round
+ // up to the array length itself, as for NativeObject.
+ if (length >= capacity && capacity > (length / 3) * 2)
+ return CapacityMatchesLengthIndex;
+
+ if (capacity < MinimumDynamicCapacity)
+ capacity = MinimumDynamicCapacity;
+
+ uint32_t bit = mozilla::FloorLog2Size(capacity);
+ MOZ_ASSERT(capacity == uint32_t(1 << bit));
+ MOZ_ASSERT(bit <= 20);
+ MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21);
+
+ uint32_t index = Pow2CapacityIndexes[bit];
+ MOZ_ASSERT(CapacityArray[index] == capacity);
+
+ return index;
+ }
+
+ MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi);
+
+ for (uint32_t i = MebiCapacityIndex + 1;; i++) {
+ if (CapacityArray[i] >= capacity)
+ return i;
+ }
+
+ MOZ_CRASH("Invalid capacity");
+}
+
+/* static */ uint32_t
+UnboxedArrayObject::exactCapacityIndex(uint32_t capacity)
+{
+ for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) {
+ if (CapacityArray[i] == capacity)
+ return i;
+ }
+ MOZ_CRASH();
+}
+
+bool
+UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap)
+{
+ // The caller should have checked if this capacity is possible for an
+ // unboxed array, so the only way this call can fail is from OOM.
+ MOZ_ASSERT(cap <= MaximumCapacity);
+
+ uint32_t oldCapacity = capacity();
+ uint32_t newCapacityIndex = chooseCapacityIndex(cap, length());
+ uint32_t newCapacity = computeCapacity(newCapacityIndex, length());
+
+ MOZ_ASSERT(oldCapacity < cap);
+ MOZ_ASSERT(cap <= newCapacity);
+
+ // The allocation size computation below cannot have integer overflows.
+ JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double));
+
+ uint8_t* newElements;
+ if (hasInlineElements()) {
+ newElements = AllocateObjectBuffer<uint8_t>(cx, this, newCapacity * elementSize());
+ if (!newElements)
+ return false;
+ js_memcpy(newElements, elements(), initializedLength() * elementSize());
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ oldCapacity * elementSize(),
+ newCapacity * elementSize());
+ if (!newElements)
+ return false;
+ }
+
+ elements_ = newElements;
+ setCapacityIndex(newCapacityIndex);
+
+ return true;
+}
+
+void
+UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap)
+{
+ if (hasInlineElements())
+ return;
+
+ uint32_t oldCapacity = capacity();
+ uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0);
+ uint32_t newCapacity = computeCapacity(newCapacityIndex, 0);
+
+ MOZ_ASSERT(cap < oldCapacity);
+ MOZ_ASSERT(cap <= newCapacity);
+
+ if (newCapacity >= oldCapacity)
+ return;
+
+ uint8_t* newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+ oldCapacity * elementSize(),
+ newCapacity * elementSize());
+ if (!newElements)
+ return;
+
+ elements_ = newElements;
+ setCapacityIndex(newCapacityIndex);
+}
+
+bool
+UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id)
+{
+ if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength())
+ return true;
+ if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length)
+ return true;
+ return false;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(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
+UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ uint32_t index = JSID_TO_INT(id);
+ if (index < nobj->initializedLength()) {
+ if (nobj->setElement(cx, index, desc.value()))
+ return result.succeed();
+ } else if (index == nobj->initializedLength() && index < MaximumCapacity) {
+ if (nobj->initializedLength() == nobj->capacity()) {
+ if (!nobj->growElements(cx, index + 1))
+ return false;
+ }
+ nobj->setInitializedLength(index + 1);
+ if (nobj->initElement(cx, index, desc.value())) {
+ if (nobj->length() <= index)
+ nobj->setLengthInt32(index + 1);
+ return result.succeed();
+ }
+ nobj->setInitializedLengthNoBarrier(index);
+ }
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+
+ return DefineProperty(cx, obj, id, desc, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(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
+UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id))
+ vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ else
+ vp.set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+ return true;
+ }
+
+ RootedObject proto(cx, obj->staticPrototype());
+ if (!proto) {
+ vp.setUndefined();
+ return true;
+ }
+
+ return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ if (JSID_IS_INT(id)) {
+ if (obj->as<UnboxedArrayObject>().setElement(cx, JSID_TO_INT(id), v))
+ return result.succeed();
+ } else {
+ uint32_t len;
+ if (!CanonicalizeArrayLengthValue(cx, v, &len))
+ return false;
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+ if (len < nobj->initializedLength()) {
+ nobj->setInitializedLength(len);
+ nobj->shrinkElements(cx, len);
+ }
+ nobj->setLength(cx, len);
+ return result.succeed();
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return SetProperty(cx, obj, id, v, receiver, result);
+ }
+
+ return SetPropertyByDefining(cx, id, v, receiver, result);
+ }
+
+ return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id)) {
+ desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ desc.setAttributes(JSPROP_ENUMERATE);
+ } else {
+ desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+ desc.setAttributes(JSPROP_PERMANENT);
+ }
+ desc.object().set(obj);
+ return true;
+ }
+
+ desc.object().set(nullptr);
+ return true;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+ ObjectOpResult& result)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen - 1);
+ obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen - 1);
+ return result.succeed();
+ }
+ }
+
+ if (!convertToNative(cx, obj))
+ return false;
+ return DeleteProperty(cx, obj, id, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
+ bool enumerableOnly)
+{
+ for (size_t i = 0; i < obj->as<UnboxedArrayObject>().initializedLength(); i++) {
+ if (!properties.append(INT_TO_JSID(i)))
+ return false;
+ }
+
+ if (!enumerableOnly && !properties.append(NameToId(cx->names().length)))
+ return false;
+
+ return true;
+}
+
+static const ClassOps UnboxedArrayObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ UnboxedArrayObject::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ UnboxedArrayObject::trace,
+};
+
+static const ClassExtension UnboxedArrayObjectClassExtension = {
+ nullptr, /* weakmapKeyDelegateOp */
+ UnboxedArrayObject::objectMoved
+};
+
+static const ObjectOps UnboxedArrayObjectObjectOps = {
+ UnboxedArrayObject::obj_lookupProperty,
+ UnboxedArrayObject::obj_defineProperty,
+ UnboxedArrayObject::obj_hasProperty,
+ UnboxedArrayObject::obj_getProperty,
+ UnboxedArrayObject::obj_setProperty,
+ UnboxedArrayObject::obj_getOwnPropertyDescriptor,
+ UnboxedArrayObject::obj_deleteProperty,
+ nullptr, /* getElements */
+ UnboxedArrayObject::obj_enumerate,
+ nullptr /* funToString */
+};
+
+const Class UnboxedArrayObject::class_ = {
+ "Array",
+ Class::NON_NATIVE |
+ JSCLASS_SKIP_NURSERY_FINALIZE |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &UnboxedArrayObjectClassOps,
+ JS_NULL_CLASS_SPEC,
+ &UnboxedArrayObjectClassExtension,
+ &UnboxedArrayObjectObjectOps
+};
+
+/////////////////////////////////////////////////////////////////////
+// API
+/////////////////////////////////////////////////////////////////////
+
+static bool
+UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
+{
+ if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32)
+ return true;
+ if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL)
+ return true;
+ return false;
+}
+
+static bool
+CombineUnboxedTypes(const Value& value, JSValueType* existing)
+{
+ JSValueType type = value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType();
+
+ if (*existing == JSVAL_TYPE_MAGIC || *existing == type || UnboxedTypeIncludes(type, *existing)) {
+ *existing = type;
+ return true;
+ }
+ if (UnboxedTypeIncludes(*existing, type))
+ return true;
+ return false;
+}
+
+// Return whether the property names and types in layout are a subset of the
+// specified vector.
+static bool
+PropertiesAreSuperset(const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout)
+{
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& layoutProperty = layout->properties()[i];
+ bool found = false;
+ for (size_t j = 0; j < properties.length(); j++) {
+ if (layoutProperty.name == properties[j].name) {
+ found = (layoutProperty.type == properties[j].type);
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+static bool
+CombinePlainObjectProperties(PlainObject* obj, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // All preliminary objects must have been created with enough space to
+ // fill in their unboxed data inline. This is ensured either by using
+ // the largest allocation kind (which limits the maximum size of an
+ // unboxed object), or by using an allocation kind that covers all
+ // properties in the template, as the space used by unboxed properties
+ // is less than or equal to that used by boxed properties.
+ MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
+ Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
+
+ if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) {
+ // Only use an unboxed representation if all created objects match
+ // the template shape exactly.
+ return false;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ Value val = obj->getSlot(i);
+
+ JSValueType& existing = properties[i].type;
+ if (!CombineUnboxedTypes(val, &existing))
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+CombineArrayObjectElements(ExclusiveContext* cx, ArrayObject* obj, JSValueType* elementType)
+{
+ if (obj->inDictionaryMode() ||
+ obj->lastProperty()->propid() != AtomToId(cx->names().length) ||
+ !obj->lastProperty()->previous()->isEmptyShape())
+ {
+ // Only use an unboxed representation if the object has no properties.
+ return false;
+ }
+
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ Value val = obj->getDenseElement(i);
+
+ // For now, unboxed arrays cannot have holes.
+ if (val.isMagic(JS_ELEMENTS_HOLE))
+ return false;
+
+ if (!CombineUnboxedTypes(val, elementType))
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+ComputePlainObjectLayout(ExclusiveContext* cx, Shape* templateShape,
+ UnboxedLayout::PropertyVector& properties)
+{
+ // Fill in the names for all the object's properties.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ size_t slot = r.front().slot();
+ MOZ_ASSERT(!properties[slot].name);
+ properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
+ }
+
+ // Fill in all the unboxed object's property offsets.
+ uint32_t offset = 0;
+
+ // Search for an existing unboxed layout which is a subset of this one.
+ // If there are multiple such layouts, use the largest one. If we're able
+ // to find such a layout, use the same property offsets for the shared
+ // properties, which will allow us to generate better code if the objects
+ // have a subtype/supertype relation and are accessed at common sites.
+ UnboxedLayout* bestExisting = nullptr;
+ for (UnboxedLayout* existing : cx->compartment()->unboxedLayouts) {
+ if (PropertiesAreSuperset(properties, existing)) {
+ if (!bestExisting ||
+ existing->properties().length() > bestExisting->properties().length())
+ {
+ bestExisting = existing;
+ }
+ }
+ }
+ if (bestExisting) {
+ for (size_t i = 0; i < bestExisting->properties().length(); i++) {
+ const UnboxedLayout::Property& existingProperty = bestExisting->properties()[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (existingProperty.name == properties[j].name) {
+ MOZ_ASSERT(existingProperty.type == properties[j].type);
+ properties[j].offset = existingProperty.offset;
+ }
+ }
+ }
+ offset = bestExisting->size();
+ }
+
+ // Order remaining properties from the largest down for the best space
+ // utilization.
+ static const size_t typeSizes[] = { 8, 4, 1 };
+
+ for (size_t i = 0; i < ArrayLength(typeSizes); i++) {
+ size_t size = typeSizes[i];
+ for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+ if (properties[j].offset != UINT32_MAX)
+ continue;
+ JSValueType type = properties[j].type;
+ if (UnboxedTypeSize(type) == size) {
+ offset = JS_ROUNDUP(offset, size);
+ properties[j].offset = offset;
+ offset += size;
+ }
+ }
+ }
+
+ // The final offset is the amount of data needed by the object.
+ return offset;
+}
+
+static bool
+SetLayoutTraceList(ExclusiveContext* cx, UnboxedLayout* layout)
+{
+ // Figure out the offsets of any objects or string properties.
+ Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
+ for (size_t i = 0; i < layout->properties().length(); i++) {
+ const UnboxedLayout::Property& property = layout->properties()[i];
+ MOZ_ASSERT(property.offset != UINT32_MAX);
+ if (property.type == JSVAL_TYPE_OBJECT) {
+ if (!objectOffsets.append(property.offset))
+ return false;
+ } else if (property.type == JSVAL_TYPE_STRING) {
+ if (!stringOffsets.append(property.offset))
+ return false;
+ }
+ }
+
+ // Construct the layout's trace list.
+ if (!objectOffsets.empty() || !stringOffsets.empty()) {
+ Vector<int32_t, 8, SystemAllocPolicy> entries;
+ if (!entries.appendAll(stringOffsets) ||
+ !entries.append(-1) ||
+ !entries.appendAll(objectOffsets) ||
+ !entries.append(-1) ||
+ !entries.append(-1))
+ {
+ return false;
+ }
+ int32_t* traceList = cx->zone()->pod_malloc<int32_t>(entries.length());
+ if (!traceList)
+ return false;
+ PodCopy(traceList, entries.begin(), entries.length());
+ layout->setTraceList(traceList);
+ }
+
+ return true;
+}
+
+static inline Value
+NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ return values[(*valueCursor)++];
+}
+
+static bool
+GetValuesFromPreliminaryArrayObject(ArrayObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ if (!values.append(Int32Value(obj->length())))
+ return false;
+ if (!values.append(Int32Value(obj->getDenseInitializedLength())))
+ return false;
+ for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+ if (!values.append(obj->getDenseElement(i)))
+ return false;
+ }
+ return true;
+}
+
+void
+UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ MOZ_ASSERT(CapacityArray[1] == 0);
+ setCapacityIndex(1);
+ setInitializedLengthNoBarrier(0);
+ setInlineElements();
+
+ setLength(cx, NextValue(values, valueCursor).toInt32());
+
+ int32_t initlen = NextValue(values, valueCursor).toInt32();
+ if (!initlen)
+ return;
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!growElements(cx, initlen))
+ oomUnsafe.crash("UnboxedArrayObject::fillAfterConvert");
+
+ setInitializedLength(initlen);
+
+ for (size_t i = 0; i < size_t(initlen); i++)
+ JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor)));
+}
+
+static bool
+GetValuesFromPreliminaryPlainObject(PlainObject* obj, MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < obj->slotSpan(); i++) {
+ if (!values.append(obj->getSlot(i)))
+ return false;
+ }
+ return true;
+}
+
+void
+UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor)
+{
+ 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)));
+}
+
+bool
+js::TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects)
+{
+ bool isArray = !templateShape;
+
+ // Unboxed arrays are nightly only for now. The getenv() call will be
+ // removed when they are on by default. See bug 1153266.
+ if (isArray) {
+#ifdef NIGHTLY_BUILD
+ if (!getenv("JS_OPTION_USE_UNBOXED_ARRAYS")) {
+ if (!cx->options().unboxedArrays())
+ return true;
+ }
+#else
+ return true;
+#endif
+ } else {
+ if (jit::JitOptions.disableUnboxedObjects)
+ return true;
+ }
+
+ MOZ_ASSERT_IF(templateShape, !templateShape->getObjectFlags());
+
+ if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global()))
+ return true;
+
+ if (!isArray && templateShape->slotSpan() == 0)
+ return true;
+
+ UnboxedLayout::PropertyVector properties;
+ if (!isArray) {
+ if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
+ return false;
+ }
+ JSValueType elementType = JSVAL_TYPE_MAGIC;
+
+ size_t objectCount = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (obj->isSingleton() || obj->group() != group)
+ return true;
+
+ objectCount++;
+
+ if (isArray) {
+ if (!CombineArrayObjectElements(cx, &obj->as<ArrayObject>(), &elementType))
+ return true;
+ } else {
+ if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), templateShape, properties))
+ return true;
+ }
+ }
+
+ size_t layoutSize = 0;
+ if (isArray) {
+ // Don't use an unboxed representation if we couldn't determine an
+ // element type for the objects.
+ if (UnboxedTypeSize(elementType) == 0)
+ return true;
+ } else {
+ if (objectCount <= 1) {
+ // If only one of the objects has been created, it is more likely
+ // to have new properties added later. This heuristic is not used
+ // for array objects, where we might want an unboxed representation
+ // even if there is only one large array.
+ return true;
+ }
+
+ for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+ // We can't use an unboxed representation if e.g. all the objects have
+ // a null value for one of the properties, as we can't decide what type
+ // it is supposed to have.
+ if (UnboxedTypeSize(properties[i].type) == 0)
+ return true;
+ }
+
+ // Make sure that all properties on the template shape are property
+ // names, and not indexes.
+ for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+ jsid id = r.front().propid();
+ uint32_t dummy;
+ if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
+ return true;
+ }
+
+ layoutSize = ComputePlainObjectLayout(cx, templateShape, properties);
+
+ // The entire object must be allocatable inline.
+ if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE)
+ return true;
+ }
+
+ UniquePtr<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
+ MOZ_ASSERT(!layout);
+ layout = group->zone()->make_unique<UnboxedLayout>();
+ if (!layout)
+ return false;
+
+ if (isArray) {
+ layout->initArray(elementType);
+ } else {
+ if (!layout->initProperties(properties, layoutSize))
+ return false;
+
+ // The unboxedLayouts list only tracks layouts for plain objects.
+ cx->compartment()->unboxedLayouts.insertFront(layout.get());
+
+ if (!SetLayoutTraceList(cx, layout.get()))
+ return false;
+ }
+
+ // We've determined that all the preliminary objects can use the new layout
+ // just constructed, so convert the existing group to use the unboxed class,
+ // and update the preliminary objects to use the new layout. Do the
+ // fallible stuff first before modifying any objects.
+
+ // Get an empty shape which we can use for the preliminary objects.
+ const Class* clasp = isArray ? &UnboxedArrayObject::class_ : &UnboxedPlainObject::class_;
+ Shape* newShape = EmptyShape::getInitialShape(cx, clasp, group->proto(), 0);
+ if (!newShape) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+
+ // Accumulate a list of all the values in each preliminary object, and
+ // update their shapes.
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ bool ok;
+ if (isArray)
+ ok = GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), &values);
+ else
+ ok = GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), &values);
+
+ if (!ok) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+ }
+
+ if (TypeNewScript* newScript = group->newScript())
+ layout->setNewScript(newScript);
+
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ if (JSObject* obj = objects->get(i))
+ obj->as<NativeObject>().setLastPropertyMakeNonNative(newShape);
+ }
+
+ group->setClasp(clasp);
+ group->setUnboxedLayout(layout.release());
+
+ size_t valueCursor = 0;
+ for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+ JSObject* obj = objects->get(i);
+ if (!obj)
+ continue;
+
+ if (isArray)
+ obj->as<UnboxedArrayObject>().fillAfterConvert(cx, values, &valueCursor);
+ else
+ obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
+ }
+
+ MOZ_ASSERT(valueCursor == values.length());
+ return true;
+}
+
+DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements,
+ ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t,
+ ShouldUpdateTypes);
+
+DenseElementResult
+js::SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes)
+{
+ SetOrExtendBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, start, vp, count, updateTypes);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+};
+
+DefineBoxedOrUnboxedFunctor5(MoveBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, uint32_t, uint32_t, uint32_t);
+
+DenseElementResult
+js::MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ MoveBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, dstStart, srcStart, length);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+}
+
+DefineBoxedOrUnboxedFunctorPair6(CopyBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, JSObject*, uint32_t, uint32_t, uint32_t);
+
+DenseElementResult
+js::CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ CopyBoxedOrUnboxedDenseElementsFunctor functor(cx, dst, src, dstStart, srcStart, length);
+ return CallBoxedOrUnboxedSpecialization(functor, dst, src);
+}
+
+DefineBoxedOrUnboxedFunctor3(SetBoxedOrUnboxedInitializedLength,
+ JSContext*, JSObject*, size_t);
+
+void
+js::SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ SetBoxedOrUnboxedInitializedLengthFunctor functor(cx, obj, initlen);
+ JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success);
+}
+
+DefineBoxedOrUnboxedFunctor3(EnsureBoxedOrUnboxedDenseElements,
+ JSContext*, JSObject*, size_t);
+
+DenseElementResult
+js::EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ EnsureBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, initlen);
+ return CallBoxedOrUnboxedSpecialization(functor, obj);
+}
diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h
new file mode 100644
index 000000000..ecff8be5b
--- /dev/null
+++ b/js/src/vm/UnboxedObject.h
@@ -0,0 +1,531 @@
+/* -*- 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_;
+
+ // The following members are only used for unboxed arrays.
+
+ // The type of array elements.
+ JSValueType elementType_;
+
+ public:
+ UnboxedLayout()
+ : nativeGroup_(nullptr), nativeShape_(nullptr),
+ allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
+ size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
+ elementType_(JSVAL_TYPE_MAGIC)
+ {}
+
+ bool initProperties(const PropertyVector& properties, size_t size) {
+ size_ = size;
+ return properties_.appendAll(properties);
+ }
+
+ void initArray(JSValueType elementType) {
+ elementType_ = elementType;
+ }
+
+ ~UnboxedLayout() {
+ if (newScript_)
+ newScript_->clear();
+ js_delete(newScript_);
+ js_free(traceList_);
+
+ nativeGroup_.init(nullptr);
+ nativeShape_.init(nullptr);
+ replacementGroup_.init(nullptr);
+ constructorCode_.init(nullptr);
+ }
+
+ bool isArray() const {
+ return elementType_ != JSVAL_TYPE_MAGIC;
+ }
+
+ 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;
+ }
+
+ JSValueType elementType() const {
+ return elementType_;
+ }
+
+ 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]);
+ }
+};
+
+// Try to construct an UnboxedLayout for each of the preliminary objects,
+// provided they all match the template shape. If successful, converts the
+// preliminary objects and their group to the new unboxed representation.
+bool
+TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape,
+ ObjectGroup* group, PreliminaryObjectArray* objects);
+
+inline gc::AllocKind
+UnboxedLayout::getAllocKind() const
+{
+ MOZ_ASSERT(size());
+ return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
+}
+
+// Class for an array object using an unboxed representation.
+class UnboxedArrayObject : public JSObject
+{
+ // Elements pointer for the object.
+ uint8_t* elements_;
+
+ // The nominal array length. This always fits in an int32_t.
+ uint32_t length_;
+
+ // Value indicating the allocated capacity and initialized length of the
+ // array. The top CapacityBits bits are an index into CapacityArray, which
+ // indicates the elements capacity. The low InitializedLengthBits store the
+ // initialized length of the array.
+ uint32_t capacityIndexAndInitializedLength_;
+
+ // If the elements are inline, they will point here.
+ uint8_t inlineElements_[1];
+
+ public:
+ static const uint32_t CapacityBits = 6;
+ static const uint32_t CapacityShift = 26;
+
+ static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift;
+ static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1;
+
+ static const uint32_t MaximumCapacity = InitializedLengthMask;
+ static const uint32_t MinimumDynamicCapacity = 8;
+
+ static const uint32_t CapacityArray[];
+
+ // Capacity index which indicates the array's length is also its capacity.
+ static const uint32_t CapacityMatchesLengthIndex = 0;
+
+ private:
+ static inline uint32_t computeCapacity(uint32_t index, uint32_t length) {
+ if (index == CapacityMatchesLengthIndex)
+ return length;
+ return CapacityArray[index];
+ }
+
+ static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length);
+ static uint32_t exactCapacityIndex(uint32_t capacity);
+
+ 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();
+ }
+
+ JSValueType elementType() const {
+ return layoutDontCheckGeneration().elementType();
+ }
+
+ uint32_t elementSize() const {
+ return UnboxedTypeSize(elementType());
+ }
+
+ static bool convertToNative(JSContext* cx, JSObject* obj);
+ static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+ uint32_t length, NewObjectKind newKind,
+ uint32_t maxLength = MaximumCapacity);
+
+ static bool convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape);
+ bool convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group);
+
+ void fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor);
+
+ static void trace(JSTracer* trc, JSObject* object);
+ static void objectMoved(JSObject* obj, const JSObject* old);
+ static void finalize(FreeOp* fop, JSObject* obj);
+
+ static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind);
+
+ uint8_t* elements() {
+ return elements_;
+ }
+
+ bool hasInlineElements() const {
+ return elements_ == &inlineElements_[0];
+ }
+
+ uint32_t length() const {
+ return length_;
+ }
+
+ uint32_t initializedLength() const {
+ return capacityIndexAndInitializedLength_ & InitializedLengthMask;
+ }
+
+ uint32_t capacityIndex() const {
+ return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift;
+ }
+
+ uint32_t capacity() const {
+ return computeCapacity(capacityIndex(), length());
+ }
+
+ bool containsProperty(ExclusiveContext* cx, jsid id);
+
+ bool setElement(ExclusiveContext* cx, size_t index, const Value& v);
+ bool initElement(ExclusiveContext* cx, size_t index, const Value& v);
+ void initElementNoTypeChange(size_t index, const Value& v);
+ Value getElement(size_t index);
+
+ template <JSValueType Type> inline bool setElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void setElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline bool initElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void initElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline Value getElementSpecific(size_t index);
+ template <JSValueType Type> inline void triggerPreBarrier(size_t index);
+
+ bool growElements(ExclusiveContext* cx, size_t cap);
+ void shrinkElements(ExclusiveContext* cx, size_t cap);
+
+ static uint32_t offsetOfElements() {
+ return offsetof(UnboxedArrayObject, elements_);
+ }
+ static uint32_t offsetOfLength() {
+ return offsetof(UnboxedArrayObject, length_);
+ }
+ static uint32_t offsetOfCapacityIndexAndInitializedLength() {
+ return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_);
+ }
+ static uint32_t offsetOfInlineElements() {
+ return offsetof(UnboxedArrayObject, inlineElements_);
+ }
+
+ void setLengthInt32(uint32_t length) {
+ MOZ_ASSERT(length <= INT32_MAX);
+ length_ = length;
+ }
+
+ inline void setLength(ExclusiveContext* cx, uint32_t len);
+ inline void setInitializedLength(uint32_t initlen);
+
+ inline void setInitializedLengthNoBarrier(uint32_t initlen) {
+ MOZ_ASSERT(initlen <= InitializedLengthMask);
+ capacityIndexAndInitializedLength_ =
+ (capacityIndexAndInitializedLength_ & CapacityMask) | initlen;
+ }
+
+ private:
+ void setInlineElements() {
+ elements_ = &inlineElements_[0];
+ }
+
+ void setCapacityIndex(uint32_t index) {
+ MOZ_ASSERT(index <= (CapacityMask >> CapacityShift));
+ capacityIndexAndInitializedLength_ =
+ (index << CapacityShift) | initializedLength();
+ }
+};
+
+} // namespace js
+
+namespace JS {
+
+template <>
+struct DeletePolicy<js::UnboxedLayout> : public js::GCManagedDeletePolicy<js::UnboxedLayout>
+{};
+
+} /* namespace JS */
+
+#endif /* vm_UnboxedObject_h */
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index 0243d80e3..bde949a96 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1427,6 +1427,8 @@ ReloadPrefsCallback(const char* pref, void* data)
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
+ bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
+
sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
#ifdef DEBUG
@@ -1455,6 +1457,8 @@ ReloadPrefsCallback(const char* pref, void* data)
useBaselineEager ? 0 : -1);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
useIonEager ? 0 : -1);
+ JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_UNBOXED_OBJECTS,
+ unboxedObjects);
}
XPCJSContext::~XPCJSContext()
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 6aa0111ef..a2b00391f 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1260,6 +1260,7 @@ pref("javascript.options.strict", false);
#ifdef DEBUG
pref("javascript.options.strict.debug", false);
#endif
+pref("javascript.options.unboxed_objects", false);
pref("javascript.options.baselinejit", true);
pref("javascript.options.ion", true);
pref("javascript.options.asmjs", true);