diff options
author | Moonchild <git-repo@palemoon.org> | 2020-02-26 02:38:18 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-26 02:38:18 +0100 |
commit | cce2bc96771613f51659e9209181e0d54a3fee96 (patch) | |
tree | cbeec2384bd44bab37cd787215ec830fa8b356cd /js | |
parent | 35c26c6c19e66fabcb230fb074e76e243df04d2b (diff) | |
parent | ecdeefc4dd5624e824e696ac1c492c0b103f4acd (diff) | |
download | UXP-cce2bc96771613f51659e9209181e0d54a3fee96.tar UXP-cce2bc96771613f51659e9209181e0d54a3fee96.tar.gz UXP-cce2bc96771613f51659e9209181e0d54a3fee96.tar.lz UXP-cce2bc96771613f51659e9209181e0d54a3fee96.tar.xz UXP-cce2bc96771613f51659e9209181e0d54a3fee96.zip |
Merge pull request #1461 from MoonchildProductions/ubbo
Back out unboxed array/object removals
Diffstat (limited to 'js')
83 files changed, 8482 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), ¬Null); + + moveValue(NullValue(), output.valueReg()); + jump(&done); + + bind(¬Null); + tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg()); + + bind(&done); + } else { + // Reading null can't be possible here, as otherwise the result + // would be a value (either because null has been read before or + // because there is a barrier). + Register reg = output.typedReg().gpr(); + loadPtr(address, reg); +#ifdef DEBUG + Label ok; + branchTestPtr(Assembler::NonZero, reg, reg, &ok); + assumeUnreachable("Null not possible"); + bind(&ok); +#endif + } + break; + + case JSVAL_TYPE_DOUBLE: + // Note: doubles in unboxed objects are not accessed through other + // views and do not need canonicalization. + if (output.hasValue()) + loadValue(address, output.valueReg()); + else + loadDouble(address, output.typedReg().fpu()); + break; + + default: + MOZ_CRASH(); + } +} + +template void +MacroAssembler::loadUnboxedProperty(Address address, JSValueType type, + TypedOrValueRegister output); + +template void +MacroAssembler::loadUnboxedProperty(BaseIndex address, JSValueType type, + TypedOrValueRegister output); + +static void +StoreUnboxedFailure(MacroAssembler& masm, Label* failure) +{ + // Storing a value to an unboxed property is a fallible operation and + // the caller must provide a failure label if a particular unboxed store + // might fail. Sometimes, however, a store that cannot succeed (such as + // storing a string to an int32 property) will be marked as infallible. + // This can only happen if the code involved is unreachable. + if (failure) + masm.jump(failure); + else + masm.assumeUnreachable("Incompatible write to unboxed property"); +} + +template <typename T> +void +MacroAssembler::storeUnboxedProperty(T address, JSValueType type, + const ConstantOrRegister& value, Label* failure) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: + if (value.constant()) { + if (value.value().isBoolean()) + store8(Imm32(value.value().toBoolean()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Boolean) + store8(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestBoolean(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 1); + } + break; + + case JSVAL_TYPE_INT32: + if (value.constant()) { + if (value.value().isInt32()) + store32(Imm32(value.value().toInt32()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Int32) + store32(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestInt32(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 4); + } + break; + + case JSVAL_TYPE_DOUBLE: + if (value.constant()) { + if (value.value().isNumber()) { + loadConstantDouble(value.value().toNumber(), ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + } else { + StoreUnboxedFailure(*this, failure); + } + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::Int32) { + convertInt32ToDouble(value.reg().typedReg().gpr(), ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + } else if (value.reg().type() == MIRType::Double) { + storeDouble(value.reg().typedReg().fpu(), address); + } else { + StoreUnboxedFailure(*this, failure); + } + } else { + ValueOperand reg = value.reg().valueReg(); + Label notInt32, end; + branchTestInt32(Assembler::NotEqual, reg, ¬Int32); + int32ValueToDouble(reg, ScratchDoubleReg); + storeDouble(ScratchDoubleReg, address); + jump(&end); + bind(¬Int32); + if (failure) + branchTestDouble(Assembler::NotEqual, reg, failure); + storeValue(reg, address); + bind(&end); + } + break; + + case JSVAL_TYPE_OBJECT: + if (value.constant()) { + if (value.value().isObjectOrNull()) + storePtr(ImmGCPtr(value.value().toObjectOrNull()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + MOZ_ASSERT(value.reg().type() != MIRType::Null); + if (value.reg().type() == MIRType::Object) + storePtr(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) { + Label ok; + branchTestNull(Assembler::Equal, value.reg().valueReg(), &ok); + branchTestObject(Assembler::NotEqual, value.reg().valueReg(), failure); + bind(&ok); + } + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t)); + } + break; + + case JSVAL_TYPE_STRING: + if (value.constant()) { + if (value.value().isString()) + storePtr(ImmGCPtr(value.value().toString()), address); + else + StoreUnboxedFailure(*this, failure); + } else if (value.reg().hasTyped()) { + if (value.reg().type() == MIRType::String) + storePtr(value.reg().typedReg().gpr(), address); + else + StoreUnboxedFailure(*this, failure); + } else { + if (failure) + branchTestString(Assembler::NotEqual, value.reg().valueReg(), failure); + storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t)); + } + break; + + default: + MOZ_CRASH(); + } +} + +template void +MacroAssembler::storeUnboxedProperty(Address address, JSValueType type, + const ConstantOrRegister& value, Label* failure); + +template void +MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type, + const ConstantOrRegister& value, Label* failure); + +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 = ®S.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, ¬Object); + Register valueObject = masm.extractObject(valueAddress, scratch1); + masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier); + masm.bind(¬Object); + } + } + + 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) ? ¬Object : &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(¬Object); + 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() |