Revert Remove unboxed arrays.

This commit is contained in:
Fedor 2020-03-12 20:43:30 +03:00
parent c9e0d090cd
commit 3f1a99cf0b
59 changed files with 3545 additions and 632 deletions

View File

@ -927,7 +927,7 @@ ModuleObject::evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValu
ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports) ModuleObject::createNamespace(JSContext* cx, HandleModuleObject self, HandleObject exports)
{ {
MOZ_ASSERT(!self->namespace_()); MOZ_ASSERT(!self->namespace_());
MOZ_ASSERT(exports->is<ArrayObject>()); MOZ_ASSERT(exports->is<ArrayObject>() || exports->is<UnboxedArrayObject>());
RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self)); RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self));
if (!ns) if (!ns)

View File

@ -6495,8 +6495,8 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje
} }
MOZ_ASSERT(idx == count); MOZ_ASSERT(idx == count);
ArrayObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(), JSObject* obj = ObjectGroup::newArrayObject(cx, values.begin(), values.length(),
newKind, arrayKind); newKind, arrayKind);
if (!obj) if (!obj)
return false; return false;
@ -9623,7 +9623,7 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn, ValueUsage valueUsage /* = ValueUs
return false; return false;
} }
if (!emitArray(args, argc)) if (!emitArray(args, argc, JSOP_SPREADCALLARRAY))
return false; return false;
if (optCodeEmitted) { 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 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 * to avoid dup'ing and popping the array as each element is added, as
* JSOP_SETELEM/JSOP_SETPROP would do. * JSOP_SETELEM/JSOP_SETPROP would do.
*/ */
MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY);
uint32_t nspread = 0; uint32_t nspread = 0;
for (ParseNode* elt = pn; elt; elt = elt->pn_next) { 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 // For arrays with spread, this is a very pessimistic allocation, the
// minimum possible final size. // minimum possible final size.
if (!emitUint32Operand(JSOP_NEWARRAY, count - nspread)) // ARRAY if (!emitUint32Operand(op, count - nspread)) // ARRAY
return false; return false;
ParseNode* pn2 = pn; 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. */ /* Maybe this offset was already set to a four-byte value. */
if (!(*sn & SN_4BYTE_OFFSET_FLAG)) { if (!(*sn & SN_4BYTE_OFFSET_FLAG)) {
/* Insert three dummy bytes that will be overwritten shortly. */ /* Insert three dummy bytes that will be overwritten shortly. */
if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) { if (MOZ_UNLIKELY(notes.length() + 3 > MaxSrcNotesLength)) {
ReportAllocationOverflow(cx); ReportAllocationOverflow(cx);
return false; return false;
} }
jssrcnote dummy = 0; jssrcnote dummy = 0;

View File

@ -543,7 +543,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn); 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 emitArrayComp(ParseNode* pn);
MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op); MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);

View File

@ -2504,6 +2504,8 @@ js::TenuringTracer::moveObjectToTenured(JSObject* dst, JSObject* src, AllocKind
InlineTypedObject::objectMovedDuringMinorGC(this, dst, src); InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<TypedArrayObject>()) { } else if (src->is<TypedArrayObject>()) {
tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind); tenuredSize += TypedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<UnboxedArrayObject>()) {
tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
} else if (src->is<ArgumentsObject>()) { } else if (src->is<ArgumentsObject>()) {
tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src); tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
} else if (src->is<ProxyObject>()) { } else if (src->is<ProxyObject>()) {

View File

@ -91,6 +91,10 @@ GetObject(const MDefinition* ins)
case MDefinition::Op_Elements: case MDefinition::Op_Elements:
case MDefinition::Op_MaybeCopyElementsForWrite: case MDefinition::Op_MaybeCopyElementsForWrite:
case MDefinition::Op_MaybeToDoubleElement: 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_TypedArrayLength:
case MDefinition::Op_SetTypedObjectOffset: case MDefinition::Op_SetTypedObjectOffset:
case MDefinition::Op_SetDisjointTypedElements: case MDefinition::Op_SetDisjointTypedElements:

View File

@ -787,6 +787,9 @@ BaselineCacheIRCompiler::emitGuardClass()
case GuardClassKind::Array: case GuardClassKind::Array:
clasp = &ArrayObject::class_; clasp = &ArrayObject::class_;
break; break;
case GuardClassKind::UnboxedArray:
clasp = &UnboxedArrayObject::class_;
break;
case GuardClassKind::MappedArguments: case GuardClassKind::MappedArguments:
clasp = &MappedArgumentsObject::class_; clasp = &MappedArgumentsObject::class_;
break; break;
@ -1000,6 +1003,19 @@ BaselineCacheIRCompiler::emitLoadInt32ArrayLengthResult()
return true; return true;
} }
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 bool
BaselineCacheIRCompiler::emitLoadArgumentsObjectLengthResult() BaselineCacheIRCompiler::emitLoadArgumentsObjectLengthResult()
{ {

View File

@ -2050,7 +2050,13 @@ BaselineCompiler::emit_JSOP_NEWARRAY()
return true; 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 = const VMFunction jit::NewArrayCopyOnWriteInfo =
FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray, "NewDenseCopyOnWriteArray"); FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray, "NewDenseCopyOnWriteArray");
@ -4181,14 +4187,14 @@ BaselineCompiler::emit_JSOP_REST()
{ {
frame.syncStack(0); frame.syncStack(0);
ArrayObject* templateObject = JSObject* templateObject =
ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject, ObjectGroup::newArrayObject(cx, nullptr, 0, TenuredObject,
ObjectGroup::NewArrayKind::UnknownIndex); ObjectGroup::NewArrayKind::UnknownIndex);
if (!templateObject) if (!templateObject)
return false; return false;
// Call IC. // Call IC.
ICRest_Fallback::Compiler compiler(cx, templateObject); ICRest_Fallback::Compiler compiler(cx, &templateObject->as<ArrayObject>());
if (!emitOpIC(compiler.getStub(&stubSpace_))) if (!emitOpIC(compiler.getStub(&stubSpace_)))
return false; return false;

View File

@ -100,6 +100,7 @@ namespace jit {
_(JSOP_BITNOT) \ _(JSOP_BITNOT) \
_(JSOP_NEG) \ _(JSOP_NEG) \
_(JSOP_NEWARRAY) \ _(JSOP_NEWARRAY) \
_(JSOP_SPREADCALLARRAY) \
_(JSOP_NEWARRAY_COPYONWRITE) \ _(JSOP_NEWARRAY_COPYONWRITE) \
_(JSOP_INITELEM_ARRAY) \ _(JSOP_INITELEM_ARRAY) \
_(JSOP_NEWOBJECT) \ _(JSOP_NEWOBJECT) \

View File

@ -1375,7 +1375,7 @@ IsNativeDenseElementAccess(HandleObject obj, HandleValue key)
static bool static bool
IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key) IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
{ {
if (!obj->isNative()) if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
return false; return false;
if (key.isInt32() && key.toInt32() >= 0 && !obj->is<TypedArrayObject>()) if (key.isInt32() && key.toInt32() >= 0 && !obj->is<TypedArrayObject>())
return true; return true;
@ -1479,6 +1479,20 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
script = rootedScript; 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. // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) && if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) &&
rhs.isNumber() && rhs.isNumber() &&
@ -2084,6 +2098,56 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm)
return true; return true;
} }
//
// 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 // GetElem_TypedArray
// //
@ -2388,8 +2452,8 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength, Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
bool* isAddingCaseOut, size_t* protoDepthOut) bool* isAddingCaseOut, size_t* protoDepthOut)
{ {
uint32_t initLength = obj->as<NativeObject>().getDenseInitializedLength(); uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
uint32_t capacity = obj->as<NativeObject>().getDenseCapacity(); uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj);
*isAddingCaseOut = false; *isAddingCaseOut = false;
*protoDepthOut = 0; *protoDepthOut = 0;
@ -2398,6 +2462,10 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
if (initLength < oldInitLength || capacity < oldCapacity) if (initLength < oldInitLength || capacity < oldCapacity)
return false; 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(); Shape* shape = obj->maybeShape();
// Cannot optimize if the shape changed. // Cannot optimize if the shape changed.
@ -2479,8 +2547,8 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
uint32_t oldCapacity = 0; uint32_t oldCapacity = 0;
uint32_t oldInitLength = 0; uint32_t oldInitLength = 0;
if (index.isInt32() && index.toInt32() >= 0) { if (index.isInt32() && index.toInt32() >= 0) {
oldCapacity = obj->as<NativeObject>().getDenseCapacity(); oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj);
oldInitLength = obj->as<NativeObject>().getDenseInitializedLength(); oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
} }
if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) { if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) {
@ -2818,6 +2886,29 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
masm.loadValue(valueAddr, tmpVal); masm.loadValue(valueAddr, tmpVal);
EmitPreBarrier(masm, element, MIRType::Value); EmitPreBarrier(masm, element, MIRType::Value);
masm.storeValue(tmpVal, element); 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); EmitReturnFromIC(masm);
@ -3011,6 +3102,40 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
BaseIndex element(scratchReg, key, TimesEight); BaseIndex element(scratchReg, key, TimesEight);
masm.loadValue(valueAddr, tmpVal); masm.loadValue(valueAddr, tmpVal);
masm.storeValue(tmpVal, element); 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); EmitReturnFromIC(masm);
@ -5374,6 +5499,13 @@ GetTemplateObjectForSimd(JSContext* cx, JSFunction* target, MutableHandleObject
return true; return true;
} }
static void
EnsureArrayGroupAnalyzed(JSContext* cx, JSObject* obj)
{
if (PreliminaryObjectArrayWithTemplate* objects = obj->group()->maybePreliminaryObjects())
objects->maybeAnalyze(cx, obj->group(), /* forceAnalyze = */ true);
}
static bool static bool
GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args, GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args,
MutableHandleObject res, bool* skipAttach) MutableHandleObject res, bool* skipAttach)
@ -5405,7 +5537,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
// With this and other array templates, analyze the group so that // With this and other array templates, analyze the group so that
// we don't end up with a template whose structure might change later. // we don't end up with a template whose structure might change later.
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject)); res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject));
return !!res; if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
} }
} }
@ -5430,7 +5565,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
return true; return true;
} }
res.set(NewFullyAllocatedArrayTryReuseGroup(cx, obj, 0, TenuredObject)); res.set(NewFullyAllocatedArrayTryReuseGroup(cx, obj, 0, TenuredObject));
return !!res; if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
} }
} }
} }
@ -5447,7 +5585,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
} }
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, 0, TenuredObject)); res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, 0, TenuredObject));
return !!res; if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
} }
if (native == StringConstructor) { if (native == StringConstructor) {
@ -5760,24 +5901,15 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
} }
static bool static bool
CopyArray(JSContext* cx, HandleArrayObject arr, MutableHandleValue result) CopyArray(JSContext* cx, HandleObject obj, MutableHandleValue result)
{ {
uint32_t length = arr->length(); uint32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
ArrayObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, arr, length, TenuredObject); JSObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, obj, length, TenuredObject);
if (!nobj) if (!nobj)
return false; return false;
EnsureArrayGroupAnalyzed(cx, nobj);
MOZ_ASSERT(arr->isNative()); CopyAnyBoxedOrUnboxedDenseElements(cx, nobj, obj, 0, 0, length);
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);
result.setObject(*nobj); result.setObject(*nobj);
return true; return true;
} }
@ -5808,22 +5940,26 @@ TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script,
RootedValue arr(cx); RootedValue arr(cx);
// Copy the array before storing in stub. // Copy the array before storing in stub.
if (!CopyArray(cx, obj.as<ArrayObject>(), &arr)) if (!CopyArray(cx, obj, &arr))
return false; return false;
// Atomize all elements of the array. // Atomize all elements of the array.
RootedArrayObject arrObj(cx, &arr.toObject().as<ArrayObject>()); RootedObject arrObj(cx, &arr.toObject());
uint32_t initLength = arrObj->length(); uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(arrObj);
for (uint32_t i = 0; i < initLength; i++) { 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) if (!str)
return false; 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(), 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)); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub) if (!newStub)
return false; return false;
@ -6711,7 +6847,7 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm)
return true; return true;
} }
typedef bool (*CopyArrayFn)(JSContext*, HandleArrayObject, MutableHandleValue); typedef bool (*CopyArrayFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction CopyArrayInfo = FunctionInfo<CopyArrayFn>(CopyArray, "CopyArray"); static const VMFunction CopyArrayInfo = FunctionInfo<CopyArrayFn>(CopyArray, "CopyArray");
bool bool
@ -8188,6 +8324,19 @@ ICGetElem_Dense::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorSt
return New<ICGetElem_Dense>(cx, space, other.jitCode(), firstMonitorStub, other.shape_); 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) ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type)
: ICStub(GetElem_TypedArray, stubCode), : ICStub(GetElem_TypedArray, stubCode),
shape_(shape) shape_(shape)
@ -8563,8 +8712,8 @@ static bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback*
unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
Value* rest = frame->argv() + numFormals; Value* rest = frame->argv() + numFormals;
ArrayObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject, JSObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject,
ObjectGroup::NewArrayKind::UnknownIndex); ObjectGroup::NewArrayKind::UnknownIndex);
if (!obj) if (!obj)
return false; return false;
res.setObject(*obj); res.setObject(*obj);

View File

@ -892,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. // Accesses scalar elements of a typed array or typed object.
class ICGetElem_TypedArray : public ICStub class ICGetElem_TypedArray : public ICStub
{ {
@ -1067,7 +1115,9 @@ class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub
: ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray, Engine::Baseline), : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray, Engine::Baseline),
shape_(cx, shape), shape_(cx, shape),
group_(cx, group), group_(cx, group),
unboxedType_(JSVAL_TYPE_MAGIC) unboxedType_(shape
? JSVAL_TYPE_MAGIC
: group->unboxedLayoutDontCheckGeneration().elementType())
{} {}
ICUpdatedStub* getStub(ICStubSpace* space) { ICUpdatedStub* getStub(ICStubSpace* space) {
@ -1175,7 +1225,9 @@ class ICSetElemDenseOrUnboxedArrayAddCompiler : public ICStubCompiler {
: ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd, Engine::Baseline), : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd, Engine::Baseline),
obj_(cx, obj), obj_(cx, obj),
protoChainDepth_(protoChainDepth), protoChainDepth_(protoChainDepth),
unboxedType_(JSVAL_TYPE_MAGIC) unboxedType_(obj->is<UnboxedArrayObject>()
? obj->as<UnboxedArrayObject>().elementType()
: JSVAL_TYPE_MAGIC)
{} {}
template <size_t ProtoChainDepth> template <size_t ProtoChainDepth>
@ -2822,10 +2874,10 @@ class ICCall_StringSplit : public ICMonitoredStub
uint32_t pcOffset_; uint32_t pcOffset_;
GCPtrString expectedStr_; GCPtrString expectedStr_;
GCPtrString expectedSep_; GCPtrString expectedSep_;
GCPtrArrayObject templateObject_; GCPtrObject templateObject_;
ICCall_StringSplit(JitCode* stubCode, ICStub* firstMonitorStub, uint32_t pcOffset, JSString* str, 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), : ICMonitoredStub(ICStub::Call_StringSplit, stubCode, firstMonitorStub),
pcOffset_(pcOffset), expectedStr_(str), expectedSep_(sep), pcOffset_(pcOffset), expectedStr_(str), expectedSep_(sep),
templateObject_(templateObject) templateObject_(templateObject)
@ -2852,7 +2904,7 @@ class ICCall_StringSplit : public ICMonitoredStub
return expectedSep_; return expectedSep_;
} }
GCPtrArrayObject& templateObject() { GCPtrObject& templateObject() {
return templateObject_; return templateObject_;
} }
@ -2862,7 +2914,7 @@ class ICCall_StringSplit : public ICMonitoredStub
uint32_t pcOffset_; uint32_t pcOffset_;
RootedString expectedStr_; RootedString expectedStr_;
RootedString expectedSep_; RootedString expectedSep_;
RootedArrayObject templateObject_; RootedObject templateObject_;
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm); MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
@ -2873,13 +2925,13 @@ class ICCall_StringSplit : public ICMonitoredStub
public: public:
Compiler(JSContext* cx, ICStub* firstMonitorStub, uint32_t pcOffset, HandleString str, Compiler(JSContext* cx, ICStub* firstMonitorStub, uint32_t pcOffset, HandleString str,
HandleString sep, HandleArrayObject templateObject) HandleString sep, HandleValue templateObject)
: ICCallStubCompiler(cx, ICStub::Call_StringSplit), : ICCallStubCompiler(cx, ICStub::Call_StringSplit),
firstMonitorStub_(firstMonitorStub), firstMonitorStub_(firstMonitorStub),
pcOffset_(pcOffset), pcOffset_(pcOffset),
expectedStr_(cx, str), expectedStr_(cx, str),
expectedSep_(cx, sep), expectedSep_(cx, sep),
templateObject_(cx, templateObject) templateObject_(cx, &templateObject.toObject())
{ } { }
ICStub* getStub(ICStubSpace* space) { ICStub* getStub(ICStubSpace* space) {

View File

@ -580,7 +580,7 @@ BaselineInspector::getTemplateObjectForNative(jsbytecode* pc, Native native)
bool bool
BaselineInspector::isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut, JSString** sepOut, BaselineInspector::isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut, JSString** sepOut,
ArrayObject** objOut) JSObject** objOut)
{ {
if (!hasBaselineScript()) if (!hasBaselineScript())
return false; return false;

View File

@ -113,7 +113,7 @@ class BaselineInspector
bool hasSeenNonStringIterMore(jsbytecode* pc); bool hasSeenNonStringIterMore(jsbytecode* pc);
MOZ_MUST_USE bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut, MOZ_MUST_USE bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** strOut,
JSString** sepOut, ArrayObject** objOut); JSString** sepOut, JSObject** objOut);
JSObject* getTemplateObject(jsbytecode* pc); JSObject* getTemplateObject(jsbytecode* pc);
JSObject* getTemplateObjectForNative(jsbytecode* pc, Native native); JSObject* getTemplateObjectForNative(jsbytecode* pc, Native native);
JSObject* getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp); JSObject* getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp);

View File

@ -175,7 +175,7 @@ TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOper
} else { } else {
writer.guardNoUnboxedExpando(objId); writer.guardNoUnboxedExpando(objId);
} }
} else if (obj->is<TypedObject>()) { } else if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
writer.guardGroup(objId, obj->group()); writer.guardGroup(objId, obj->group());
} else { } else {
Shape* shape = obj->maybeShape(); Shape* shape = obj->maybeShape();
@ -368,6 +368,13 @@ GetPropIRGenerator::tryAttachObjectLength(CacheIRWriter& writer, HandleObject ob
return true; 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<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) {
if (obj->is<MappedArgumentsObject>()) { if (obj->is<MappedArgumentsObject>()) {
writer.guardClass(objId, GuardClassKind::MappedArguments); writer.guardClass(objId, GuardClassKind::MappedArguments);

View File

@ -96,6 +96,7 @@ class ObjOperandId : public OperandId
_(LoadUnboxedPropertyResult) \ _(LoadUnboxedPropertyResult) \
_(LoadTypedObjectResult) \ _(LoadTypedObjectResult) \
_(LoadInt32ArrayLengthResult) \ _(LoadInt32ArrayLengthResult) \
_(LoadUnboxedArrayLengthResult) \
_(LoadArgumentsObjectLengthResult) \ _(LoadArgumentsObjectLengthResult) \
_(LoadUndefinedResult) _(LoadUndefinedResult)
@ -127,6 +128,7 @@ struct StubField {
enum class GuardClassKind enum class GuardClassKind
{ {
Array, Array,
UnboxedArray,
MappedArguments, MappedArguments,
UnmappedArguments, UnmappedArguments,
}; };
@ -325,6 +327,9 @@ class MOZ_RAII CacheIRWriter
void loadInt32ArrayLengthResult(ObjOperandId obj) { void loadInt32ArrayLengthResult(ObjOperandId obj) {
writeOpWithOperandId(CacheOp::LoadInt32ArrayLengthResult, obj); writeOpWithOperandId(CacheOp::LoadInt32ArrayLengthResult, obj);
} }
void loadUnboxedArrayLengthResult(ObjOperandId obj) {
writeOpWithOperandId(CacheOp::LoadUnboxedArrayLengthResult, obj);
}
void loadArgumentsObjectLengthResult(ObjOperandId obj) { void loadArgumentsObjectLengthResult(ObjOperandId obj) {
writeOpWithOperandId(CacheOp::LoadArgumentsObjectLengthResult, obj); writeOpWithOperandId(CacheOp::LoadArgumentsObjectLengthResult, obj);
} }

View File

@ -3184,7 +3184,9 @@ CodeGenerator::visitSetPropertyPolymorphicT(LSetPropertyPolymorphicT* ins)
void void
CodeGenerator::visitElements(LElements* lir) 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())); masm.loadPtr(elements, ToRegister(lir->output()));
} }
@ -5169,11 +5171,11 @@ static JSObject*
NewArrayWithGroup(JSContext* cx, uint32_t length, HandleObjectGroup group, NewArrayWithGroup(JSContext* cx, uint32_t length, HandleObjectGroup group,
bool convertDoubleElements) bool convertDoubleElements)
{ {
ArrayObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length); JSObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
if (!res) if (!res)
return nullptr; return nullptr;
if (convertDoubleElements) if (convertDoubleElements)
res->setShouldConvertDoubleElements(); res->as<ArrayObject>().setShouldConvertDoubleElements();
return res; return res;
} }
@ -5319,7 +5321,7 @@ CodeGenerator::visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite* lir)
masm.bind(ool->rejoin()); masm.bind(ool->rejoin());
} }
typedef ArrayObject* (*ArrayConstructorOneArgFn)(JSContext*, HandleObjectGroup, int32_t length); typedef JSObject* (*ArrayConstructorOneArgFn)(JSContext*, HandleObjectGroup, int32_t length);
static const VMFunction ArrayConstructorOneArgInfo = static const VMFunction ArrayConstructorOneArgInfo =
FunctionInfo<ArrayConstructorOneArgFn>(ArrayConstructorOneArg, "ArrayConstructorOneArg"); FunctionInfo<ArrayConstructorOneArgFn>(ArrayConstructorOneArg, "ArrayConstructorOneArg");
@ -5339,11 +5341,21 @@ CodeGenerator::visitNewArrayDynamicLength(LNewArrayDynamicLength* lir)
bool canInline = true; bool canInline = true;
size_t inlineLength = 0; size_t inlineLength = 0;
if (templateObject->as<ArrayObject>().hasFixedElements()) { if (templateObject->is<ArrayObject>()) {
size_t numSlots = gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()); if (templateObject->as<ArrayObject>().hasFixedElements()) {
inlineLength = numSlots - ObjectElements::VALUES_PER_HEADER; size_t numSlots = gc::GetGCKindSlots(templateObject->asTenured().getAllocKind());
inlineLength = numSlots - ObjectElements::VALUES_PER_HEADER;
} else {
canInline = false;
}
} else { } 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) { if (canInline) {
@ -7765,7 +7777,7 @@ CodeGenerator::visitSinCos(LSinCos *lir)
masm.freeStack(sizeof(double) * 2); 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 = static const VMFunction StringSplitInfo =
FunctionInfo<StringSplitFn>(js::str_split_string, "str_split_string"); FunctionInfo<StringSplitFn>(js::str_split_string, "str_split_string");
@ -7799,6 +7811,49 @@ CodeGenerator::visitSetInitializedLength(LSetInitializedLength* lir)
masm.dec32(&index); masm.dec32(&index);
} }
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 void
CodeGenerator::visitNotO(LNotO* lir) CodeGenerator::visitNotO(LNotO* lir)
{ {
@ -8095,19 +8150,46 @@ CodeGenerator::emitStoreElementHoleT(T* lir)
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir); OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
addOutOfLineCode(ool, lir->mir()); addOutOfLineCode(ool, lir->mir());
Register obj = ToRegister(lir->object());
Register elements = ToRegister(lir->elements()); Register elements = ToRegister(lir->elements());
const LAllocation* index = lir->index(); const LAllocation* index = lir->index();
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index); RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
Address initLength(elements, ObjectElements::offsetOfInitializedLength()); JSValueType unboxedType = lir->mir()->unboxedType();
masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry()); if (unboxedType == JSVAL_TYPE_MAGIC) {
Address initLength(elements, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
if (lir->mir()->needsBarrier()) if (lir->mir()->needsBarrier())
emitPreBarrier(elements, index, 0); emitPreBarrier(elements, index, 0);
masm.bind(ool->rejoinStore()); masm.bind(ool->rejoinStore());
emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(), emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
elements, index, 0); 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());
masm.storeUnboxedProperty(address, unboxedType, v, nullptr);
}
}
masm.bind(ool->rejoin()); masm.bind(ool->rejoin());
} }
@ -8127,22 +8209,47 @@ CodeGenerator::emitStoreElementHoleV(T* lir)
OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir); OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
addOutOfLineCode(ool, lir->mir()); addOutOfLineCode(ool, lir->mir());
Register obj = ToRegister(lir->object());
Register elements = ToRegister(lir->elements()); Register elements = ToRegister(lir->elements());
const LAllocation* index = lir->index(); const LAllocation* index = lir->index();
const ValueOperand value = ToValue(lir, T::Value); const ValueOperand value = ToValue(lir, T::Value);
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index); RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
Address initLength(elements, ObjectElements::offsetOfInitializedLength()); JSValueType unboxedType = lir->mir()->unboxedType();
masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry()); if (unboxedType == JSVAL_TYPE_MAGIC) {
Address initLength(elements, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, key, ool->entry());
if (lir->mir()->needsBarrier()) if (lir->mir()->needsBarrier())
emitPreBarrier(elements, index, 0); emitPreBarrier(elements, index, 0);
masm.bind(ool->rejoinStore()); masm.bind(ool->rejoinStore());
if (index->isConstant()) if (index->isConstant())
masm.storeValue(value, Address(elements, ToInt32(index) * sizeof(js::Value))); masm.storeValue(value, Address(elements, ToInt32(index) * sizeof(js::Value)));
else else
masm.storeValue(value, BaseIndex(elements, ToRegister(index), TimesEight)); 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()); masm.bind(ool->rejoin());
} }
@ -8213,10 +8320,11 @@ CodeGenerator::visitFallibleStoreElementV(LFallibleStoreElementV* lir)
masm.bind(&isFrozen); masm.bind(&isFrozen);
} }
typedef bool (*SetDenseElementFn)(JSContext*, HandleNativeObject, int32_t, HandleValue, typedef bool (*SetDenseOrUnboxedArrayElementFn)(JSContext*, HandleObject, int32_t,
bool strict); HandleValue, bool strict);
static const VMFunction SetDenseElementInfo = static const VMFunction SetDenseOrUnboxedArrayElementInfo =
FunctionInfo<SetDenseElementFn>(jit::SetDenseElement, "SetDenseElement"); FunctionInfo<SetDenseOrUnboxedArrayElementFn>(SetDenseOrUnboxedArrayElement,
"SetDenseOrUnboxedArrayElement");
void void
CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
@ -8226,6 +8334,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
const LAllocation* index; const LAllocation* index;
MIRType valueType; MIRType valueType;
ConstantOrRegister value; ConstantOrRegister value;
JSValueType unboxedType;
LDefinition *temp = nullptr;
if (ins->isStoreElementHoleV()) { if (ins->isStoreElementHoleV()) {
LStoreElementHoleV* store = ins->toStoreElementHoleV(); LStoreElementHoleV* store = ins->toStoreElementHoleV();
@ -8234,6 +8344,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
index = store->index(); index = store->index();
valueType = store->mir()->value()->type(); valueType = store->mir()->value()->type();
value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value)); value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value));
unboxedType = store->mir()->unboxedType();
temp = store->getTemp(0);
} else if (ins->isFallibleStoreElementV()) { } else if (ins->isFallibleStoreElementV()) {
LFallibleStoreElementV* store = ins->toFallibleStoreElementV(); LFallibleStoreElementV* store = ins->toFallibleStoreElementV();
object = ToRegister(store->object()); object = ToRegister(store->object());
@ -8241,6 +8353,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
index = store->index(); index = store->index();
valueType = store->mir()->value()->type(); valueType = store->mir()->value()->type();
value = TypedOrValueRegister(ToValue(store, LFallibleStoreElementV::Value)); value = TypedOrValueRegister(ToValue(store, LFallibleStoreElementV::Value));
unboxedType = store->mir()->unboxedType();
temp = store->getTemp(0);
} else if (ins->isStoreElementHoleT()) { } else if (ins->isStoreElementHoleT()) {
LStoreElementHoleT* store = ins->toStoreElementHoleT(); LStoreElementHoleT* store = ins->toStoreElementHoleT();
object = ToRegister(store->object()); object = ToRegister(store->object());
@ -8251,6 +8365,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
value = ConstantOrRegister(store->value()->toConstant()->toJSValue()); value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
else else
value = TypedOrValueRegister(valueType, ToAnyRegister(store->value())); value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
unboxedType = store->mir()->unboxedType();
temp = store->getTemp(0);
} else { // ins->isFallibleStoreElementT() } else { // ins->isFallibleStoreElementT()
LFallibleStoreElementT* store = ins->toFallibleStoreElementT(); LFallibleStoreElementT* store = ins->toFallibleStoreElementT();
object = ToRegister(store->object()); object = ToRegister(store->object());
@ -8261,6 +8377,8 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
value = ConstantOrRegister(store->value()->toConstant()->toJSValue()); value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
else else
value = TypedOrValueRegister(valueType, ToAnyRegister(store->value())); value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
unboxedType = store->mir()->unboxedType();
temp = store->getTemp(0);
} }
RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index); RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index);
@ -8271,32 +8389,54 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
Label callStub; Label callStub;
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) #if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// Had to reimplement for MIPS because there are no flags. // Had to reimplement for MIPS because there are no flags.
Address initLength(elements, ObjectElements::offsetOfInitializedLength()); if (unboxedType == JSVAL_TYPE_MAGIC) {
masm.branch32(Assembler::NotEqual, initLength, key, &callStub); 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 #else
masm.j(Assembler::NotEqual, &callStub); masm.j(Assembler::NotEqual, &callStub);
#endif #endif
// Check array capacity. if (unboxedType == JSVAL_TYPE_MAGIC) {
masm.branch32(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()), // Check array capacity.
key, &callStub); masm.branch32(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
key, &callStub);
// Update initialized length. The capacity guard above ensures this won't overflow, // Update initialized length. The capacity guard above ensures this won't overflow,
// due to MAX_DENSE_ELEMENTS_COUNT. // due to MAX_DENSE_ELEMENTS_COUNT.
masm.inc32(&key); masm.inc32(&key);
masm.store32(key, Address(elements, ObjectElements::offsetOfInitializedLength())); masm.store32(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
// Update length if length < initializedLength. // Update length if length < initializedLength.
Label dontUpdate; Label dontUpdate;
masm.branch32(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()), masm.branch32(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
key, &dontUpdate); key, &dontUpdate);
masm.store32(key, Address(elements, ObjectElements::offsetOfLength())); masm.store32(key, Address(elements, ObjectElements::offsetOfLength()));
masm.bind(&dontUpdate); 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()) && 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 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 // the type tag, so we do the store on the OOL path. We use MIRType::None for the element
@ -8325,7 +8465,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
else else
pushArg(ToRegister(index)); pushArg(ToRegister(index));
pushArg(object); pushArg(object);
callVM(SetDenseElementInfo, ins); callVM(SetDenseOrUnboxedArrayElementInfo, ins);
restoreLive(ins); restoreLive(ins);
masm.jump(ool->rejoin()); masm.jump(ool->rejoin());
@ -8386,6 +8526,9 @@ typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
static const VMFunction ConvertUnboxedPlainObjectToNativeInfo = static const VMFunction ConvertUnboxedPlainObjectToNativeInfo =
FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative, FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative,
"UnboxedPlainObject::convertToNative"); "UnboxedPlainObject::convertToNative");
static const VMFunction ConvertUnboxedArrayObjectToNativeInfo =
FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedArrayObject::convertToNative,
"UnboxedArrayObject::convertToNative");
typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue); typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction ArrayPopDenseInfo = static const VMFunction ArrayPopDenseInfo =
@ -8411,11 +8554,20 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
// Load elements and length, and VM call if length != initializedLength. // Load elements and length, and VM call if length != initializedLength.
RegisterOrInt32Constant key = RegisterOrInt32Constant(lengthTemp); RegisterOrInt32Constant key = RegisterOrInt32Constant(lengthTemp);
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp); if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp); masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp);
Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength()); Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::NotEqual, initLength, key, ool->entry()); 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 // 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 // an undefined value, depending on whether the call is known to produce
@ -8427,10 +8579,13 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
// According to the spec we need to set the length 0 (which is already 0). // 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. // This is observable when the array length is made non-writable.
// Handle this case in the OOL. // Handle this case in the OOL. When freezing an unboxed array it is converted
Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags()); // to an normal array.
Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH); if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry()); 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.moveValue(UndefinedValue(), out.valueReg());
masm.jump(&done); masm.jump(&done);
@ -8442,25 +8597,41 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R
masm.dec32(&key); masm.dec32(&key);
if (mir->mode() == MArrayPopShift::Pop) { if (mir->mode() == MArrayPopShift::Pop) {
BaseIndex addr(elementsTemp, lengthTemp, TimesEight); if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
masm.loadElementTypedOrValue(addr, out, mir->needsHoleCheck(), ool->entry()); 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 { } else {
MOZ_ASSERT(mir->mode() == MArrayPopShift::Shift); MOZ_ASSERT(mir->mode() == MArrayPopShift::Shift);
Address addr(elementsTemp, 0); 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 if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
// OOL path. (Unlike in the adding-an-element cases, we can't rely on the // Handle the failure case when the array length is non-writable in the
// capacity <= length invariant for such arrays to avoid an explicit // OOL path. (Unlike in the adding-an-element cases, we can't rely on the
// check.) // capacity <= length invariant for such arrays to avoid an explicit
Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags()); // check.)
Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH); Address elementFlags(elementsTemp, ObjectElements::offsetOfFlags());
masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry()); Imm32 bit(ObjectElements::NONWRITABLE_ARRAY_LENGTH);
masm.branchTest32(Assembler::NonZero, elementFlags, bit, ool->entry());
// Now adjust length and initializedLength. // Now adjust length and initializedLength.
masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfLength())); masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfLength()));
masm.store32(lengthTemp, Address(elementsTemp, ObjectElements::offsetOfInitializedLength())); 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) { if (mir->mode() == MArrayPopShift::Shift) {
// Don't save the temp registers. // Don't save the temp registers.
@ -8499,7 +8670,7 @@ CodeGenerator::visitArrayPopShiftT(LArrayPopShiftT* lir)
emitArrayPopShift(lir, lir->mir(), obj, elements, length, out); 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 = static const VMFunction ArrayPushDenseInfo =
FunctionInfo<ArrayPushDenseFn>(jit::ArrayPushDense, "ArrayPushDense"); FunctionInfo<ArrayPushDenseFn>(jit::ArrayPushDense, "ArrayPushDense");
@ -8510,27 +8681,50 @@ CodeGenerator::emitArrayPush(LInstruction* lir, const MArrayPush* mir, Register
OutOfLineCode* ool = oolCallVM(ArrayPushDenseInfo, lir, ArgList(obj, value), StoreRegisterTo(length)); OutOfLineCode* ool = oolCallVM(ArrayPushDenseInfo, lir, ArgList(obj, value), StoreRegisterTo(length));
RegisterOrInt32Constant key = RegisterOrInt32Constant(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. // Guard length == initializedLength.
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp); Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length); masm.branch32(Assembler::NotEqual, initLength, key, ool->entry());
// Guard length == initializedLength. // Guard length < capacity.
Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength()); Address capacity(elementsTemp, ObjectElements::offsetOfCapacity());
masm.branch32(Assembler::NotEqual, initLength, key, ool->entry()); masm.branch32(Assembler::BelowOrEqual, capacity, key, ool->entry());
// Guard length < capacity. // Do the store.
Address capacity(elementsTemp, ObjectElements::offsetOfCapacity()); masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight));
masm.branch32(Assembler::BelowOrEqual, capacity, key, ool->entry()); } else {
// Load initialized length.
masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), length);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), length);
// Do the store. // Guard length == initializedLength.
masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight)); 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); masm.inc32(&key);
// Update length and initialized length. // Update length and initialized length.
masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfLength())); if (mir->unboxedType() == JSVAL_TYPE_MAGIC) {
masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfInitializedLength())); 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()); masm.bind(ool->rejoin());
} }
@ -10357,11 +10551,22 @@ CodeGenerator::visitLoadElementHole(LLoadElementHole* lir)
else else
masm.branch32(Assembler::BelowOrEqual, initLength, ToRegister(lir->index()), &undefined); masm.branch32(Assembler::BelowOrEqual, initLength, ToRegister(lir->index()), &undefined);
if (lir->index()->isConstant()) { if (mir->unboxedType() != JSVAL_TYPE_MAGIC) {
NativeObject::elementsSizeMustNotOverflow(); size_t width = UnboxedTypeSize(mir->unboxedType());
masm.loadValue(Address(elements, ToInt32(lir->index()) * sizeof(Value)), out); 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 { } 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. // If a hole check is needed, and the value wasn't a hole, we're done.
@ -10739,7 +10944,7 @@ CodeGenerator::visitInArray(LInArray* lir)
} }
masm.branch32(Assembler::BelowOrEqual, initLength, Imm32(index), failedInitLength); masm.branch32(Assembler::BelowOrEqual, initLength, Imm32(index), failedInitLength);
if (mir->needsHoleCheck()) { if (mir->needsHoleCheck() && mir->unboxedType() == JSVAL_TYPE_MAGIC) {
NativeObject::elementsSizeMustNotOverflow(); NativeObject::elementsSizeMustNotOverflow();
Address address = Address(elements, index * sizeof(Value)); Address address = Address(elements, index * sizeof(Value));
masm.branchTestMagic(Assembler::Equal, address, &falseBranch); masm.branchTestMagic(Assembler::Equal, address, &falseBranch);
@ -10752,7 +10957,7 @@ CodeGenerator::visitInArray(LInArray* lir)
failedInitLength = &negativeIntCheck; failedInitLength = &negativeIntCheck;
masm.branch32(Assembler::BelowOrEqual, initLength, index, failedInitLength); 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); BaseIndex address = BaseIndex(elements, ToRegister(lir->index()), TimesEight);
masm.branchTestMagic(Assembler::Equal, address, &falseBranch); masm.branchTestMagic(Assembler::Equal, address, &falseBranch);
} }

View File

@ -234,6 +234,10 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitSubstr(LSubstr* lir); void visitSubstr(LSubstr* lir);
void visitInitializedLength(LInitializedLength* lir); void visitInitializedLength(LInitializedLength* lir);
void visitSetInitializedLength(LSetInitializedLength* 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 visitNotO(LNotO* ins);
void visitNotV(LNotV* ins); void visitNotV(LNotV* ins);
void visitBoundsCheck(LBoundsCheck* lir); void visitBoundsCheck(LBoundsCheck* lir);

View File

@ -2227,8 +2227,6 @@ IonBuilder::inspectOpcode(JSOp op)
// update that stale value. // update that stale value.
#endif #endif
default: default:
// Any unused opcodes and JSOP_LIMIT will end up here without having
// to explicitly specify
break; break;
} }
@ -7355,6 +7353,12 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
if (!templateObject) if (!templateObject)
return true; 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); MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
size_t arraySlots = size_t arraySlots =
@ -7610,6 +7614,7 @@ IonBuilder::jsop_initelem_array()
// intializer, and that arrays are marked as non-packed when writing holes // intializer, and that arrays are marked as non-packed when writing holes
// to them during initialization. // to them during initialization.
bool needStub = false; bool needStub = false;
JSValueType unboxedType = JSVAL_TYPE_MAGIC;
if (shouldAbortOnPreliminaryGroups(obj)) { if (shouldAbortOnPreliminaryGroups(obj)) {
needStub = true; needStub = true;
} else if (!obj->resultTypeSet() || } else if (!obj->resultTypeSet() ||
@ -7620,6 +7625,12 @@ IonBuilder::jsop_initelem_array()
} else { } else {
MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1); MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1);
TypeSet::ObjectKey* initializer = obj->resultTypeSet()->getObject(0); 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 (value->type() == MIRType::MagicHole) {
if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED)) if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED))
needStub = true; needStub = true;
@ -7639,46 +7650,60 @@ IonBuilder::jsop_initelem_array()
return resumeAfter(store); return resumeAfter(store);
} }
return initializeArrayElement(obj, index, value, /* addResumePoint = */ true); return initializeArrayElement(obj, index, value, unboxedType, /* addResumePoint = */ true);
} }
bool bool
IonBuilder::initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value, IonBuilder::initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
JSValueType unboxedType,
bool addResumePointAndIncrementInitializedLength) bool addResumePointAndIncrementInitializedLength)
{ {
MConstant* id = MConstant::New(alloc(), Int32Value(index)); MConstant* id = MConstant::New(alloc(), Int32Value(index));
current->add(id); current->add(id);
// Get the elements vector. // Get the elements vector.
MElements* elements = MElements::New(alloc(), obj); MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements); current->add(elements);
if (NeedsPostBarrier(value)) if (unboxedType != JSVAL_TYPE_MAGIC) {
current->add(MPostWriteBarrier::New(alloc(), obj, value)); // 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()) || if (addResumePointAndIncrementInitializedLength) {
(obj->isNullarySharedStub() && MInstruction* increment = MIncrementUnboxedArrayInitializedLength::New(alloc(), obj);
obj->resultTypeSet()->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles)) current->add(increment);
{
MInstruction* valueDouble = MToDouble::New(alloc(), value);
current->add(valueDouble);
value = valueDouble;
}
// Store the value. if (!resumeAfter(increment))
MStoreElement* store = MStoreElement::New(alloc(), elements, id, value, 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); /* needsHoleCheck = */ false);
current->add(store); current->add(store);
if (addResumePointAndIncrementInitializedLength) { if (addResumePointAndIncrementInitializedLength) {
// Update the initialized length. (The template object for this // Update the initialized length. (The template object for this
// array has the array's ultimate length, so the length field is // array has the array's ultimate length, so the length field is
// already correct: no updating needed.) // already correct: no updating needed.)
MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id); MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id);
current->add(initLength); current->add(initLength);
if (!resumeAfter(initLength)) if (!resumeAfter(initLength))
return false; return false;
}
} }
return true; return true;
@ -8167,7 +8192,8 @@ IonBuilder::maybeMarkEmpty(MDefinition* ins)
static bool static bool
ClassHasEffectlessLookup(const Class* clasp) ClassHasEffectlessLookup(const Class* clasp)
{ {
return IsTypedObjectClass(clasp) || return (clasp == &UnboxedArrayObject::class_) ||
IsTypedObjectClass(clasp) ||
(clasp->isNative() && !clasp->getOpsLookupProperty()); (clasp->isNative() && !clasp->getOpsLookupProperty());
} }
@ -9441,9 +9467,12 @@ IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
{ {
MOZ_ASSERT(*emitted == false); MOZ_ASSERT(*emitted == false);
if (!ElementAccessIsDenseNative(constraints(), obj, index)) { JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, index);
trackOptimizationOutcome(TrackedOutcome::AccessNotDense); if (unboxedType == JSVAL_TYPE_MAGIC) {
return true; if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
return true;
}
} }
// Don't generate a fast path if there have been bounds check failures // Don't generate a fast path if there have been bounds check failures
@ -9460,7 +9489,7 @@ IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
return true; return true;
} }
if (!jsop_getelem_dense(obj, index)) if (!jsop_getelem_dense(obj, index, unboxedType))
return false; return false;
trackOptimizationSuccess(); trackOptimizationSuccess();
@ -9812,7 +9841,7 @@ IonBuilder::computeHeapType(const TemporaryTypeSet* objTypes, const jsid id)
} }
bool bool
IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index) IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index, JSValueType unboxedType)
{ {
TemporaryTypeSet* types = bytecodeTypes(pc); TemporaryTypeSet* types = bytecodeTypes(pc);
@ -9836,7 +9865,7 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
!ElementAccessHasExtraIndexedProperty(this, obj); !ElementAccessHasExtraIndexedProperty(this, obj);
MIRType knownType = MIRType::Value; MIRType knownType = MIRType::Value;
if (barrier == BarrierKind::NoBarrier) if (unboxedType == JSVAL_TYPE_MAGIC && barrier == BarrierKind::NoBarrier)
knownType = GetElemKnownType(needsHoleCheck, types); knownType = GetElemKnownType(needsHoleCheck, types);
// Ensure index is an integer. // Ensure index is an integer.
@ -9845,13 +9874,13 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
index = idInt32; index = idInt32;
// Get the elements vector. // Get the elements vector.
MInstruction* elements = MElements::New(alloc(), obj); MInstruction* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements); current->add(elements);
// Note: to help GVN, use the original MElements instruction and not // Note: to help GVN, use the original MElements instruction and not
// MConvertElementsToDoubles as operand. This is fine because converting // MConvertElementsToDoubles as operand. This is fine because converting
// elements to double does not change the initialized length. // 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 // If we can load the element as a definite double, make sure to check that
// the array has been converted to homogenous doubles first. // the array has been converted to homogenous doubles first.
@ -9867,6 +9896,7 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
} }
bool loadDouble = bool loadDouble =
unboxedType == JSVAL_TYPE_MAGIC &&
barrier == BarrierKind::NoBarrier && barrier == BarrierKind::NoBarrier &&
loopDepth_ && loopDepth_ &&
inBounds && inBounds &&
@ -9885,13 +9915,18 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
// hoisting. // hoisting.
index = addBoundsCheck(index, initLength); index = addBoundsCheck(index, initLength);
load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble); if (unboxedType != JSVAL_TYPE_MAGIC) {
current->add(load); load = loadUnboxedValue(elements, 0, index, unboxedType, barrier, types);
} else {
load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
current->add(load);
}
} else { } else {
// This load may return undefined, so assume that we *can* read holes, // 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 // or that we can read out-of-bounds accesses. In this case, the bounds
// check is part of the opcode. // 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); current->add(load);
// If maybeUndefined was true, the typeset must have undefined, and // If maybeUndefined was true, the typeset must have undefined, and
@ -9901,7 +9936,8 @@ IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
} }
if (knownType != MIRType::Value) { if (knownType != MIRType::Value) {
load->setResultType(knownType); if (unboxedType == JSVAL_TYPE_MAGIC)
load->setResultType(knownType);
load->setResultTypeSet(types); load->setResultTypeSet(types);
} }
@ -10348,9 +10384,12 @@ IonBuilder::setElemTryDense(bool* emitted, MDefinition* object,
{ {
MOZ_ASSERT(*emitted == false); MOZ_ASSERT(*emitted == false);
if (!ElementAccessIsDenseNative(constraints(), object, index)) { JSValueType unboxedType = UnboxedArrayElementType(constraints(), object, index);
trackOptimizationOutcome(TrackedOutcome::AccessNotDense); if (unboxedType == JSVAL_TYPE_MAGIC) {
return true; if (!ElementAccessIsDenseNative(constraints(), object, index)) {
trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
return true;
}
} }
if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
@ -10384,7 +10423,7 @@ IonBuilder::setElemTryDense(bool* emitted, MDefinition* object,
} }
// Emit dense setelem variant. // 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; return false;
if (!*emitted) { if (!*emitted) {
@ -10474,11 +10513,13 @@ IonBuilder::setElemTryCache(bool* emitted, MDefinition* object,
bool bool
IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* obj, MDefinition* id, MDefinition* value, MDefinition* obj, MDefinition* id, MDefinition* value,
bool writeHole, bool* emitted) JSValueType unboxedType, bool writeHole, bool* emitted)
{ {
MOZ_ASSERT(*emitted == false); 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); bool packed = ElementAccessIsPacked(constraints(), obj);
// Writes which are on holes in the object do not have to bail out if they // Writes which are on holes in the object do not have to bail out if they
@ -10508,7 +10549,7 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
// Get the elements vector. // Get the elements vector.
MElements* elements = MElements::New(alloc(), obj); MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements); current->add(elements);
// Ensure the value is a double, if double conversion might be needed. // Ensure the value is a double, if double conversion might be needed.
@ -10545,7 +10586,7 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MInstruction* store; MInstruction* store;
MStoreElementCommon* common = nullptr; MStoreElementCommon* common = nullptr;
if (writeHole && hasNoExtraIndexedProperty && !mayBeFrozen) { 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; store = ins;
common = ins; common = ins;
@ -10557,23 +10598,27 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
bool strict = IsStrictSetPC(pc); bool strict = IsStrictSetPC(pc);
MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id, MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id,
newValue, strict); newValue, unboxedType, strict);
store = ins; store = ins;
common = ins; common = ins;
current->add(ins); current->add(ins);
current->push(value); current->push(value);
} else { } else {
MInstruction* initLength = initializedLength(obj, elements); MInstruction* initLength = initializedLength(obj, elements, unboxedType);
id = addBoundsCheck(id, initLength); id = addBoundsCheck(id, initLength);
bool needsHoleCheck = !packed && !hasNoExtraIndexedProperty; bool needsHoleCheck = !packed && !hasNoExtraIndexedProperty;
MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck); if (unboxedType != JSVAL_TYPE_MAGIC) {
store = ins; store = storeUnboxedValue(obj, elements, 0, id, unboxedType, newValue);
common = ins; } else {
MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
store = ins;
common = ins;
current->add(store); current->add(store);
}
current->push(value); current->push(value);
} }
@ -10691,6 +10736,18 @@ IonBuilder::jsop_length_fastPath()
return true; 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. // Compute the length for array typed objects.
TypedObjectPrediction prediction = typedObjectPrediction(obj); TypedObjectPrediction prediction = typedObjectPrediction(obj);
if (!prediction.isUseless()) { if (!prediction.isUseless()) {
@ -13675,8 +13732,11 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
if (shouldAbortOnPreliminaryGroups(obj)) if (shouldAbortOnPreliminaryGroups(obj))
return true; return true;
if (!ElementAccessIsDenseNative(constraints(), obj, id)) JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, id);
return true; if (unboxedType == JSVAL_TYPE_MAGIC) {
if (!ElementAccessIsDenseNative(constraints(), obj, id))
return true;
}
if (ElementAccessHasExtraIndexedProperty(this, obj)) if (ElementAccessHasExtraIndexedProperty(this, obj))
return true; return true;
@ -13691,10 +13751,10 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
id = idInt32; id = idInt32;
// Get the elements vector. // Get the elements vector.
MElements* elements = MElements::New(alloc(), obj); MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
current->add(elements); 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 there are no holes, speculate the InArray check will not fail.
if (!needsHoleCheck && !failedBoundsCheck_) { if (!needsHoleCheck && !failedBoundsCheck_) {
@ -13704,7 +13764,8 @@ IonBuilder::inTryDense(bool* emitted, MDefinition* obj, MDefinition* id)
} }
// Check if id < initLength and elem[id] not a hole. // 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->add(ins);
current->push(ins); current->push(ins);
@ -14382,24 +14443,32 @@ IonBuilder::constantInt(int32_t i)
} }
MInstruction* 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); current->add(res);
return res; return res;
} }
MInstruction* MInstruction*
IonBuilder::setInitializedLength(MDefinition* obj, size_t count) IonBuilder::setInitializedLength(MDefinition* obj, JSValueType unboxedType, size_t count)
{ {
MOZ_ASSERT(count); MOZ_ASSERT(count);
// MSetInitializedLength takes the index of the last element, rather MInstruction* res;
// than the count itself. if (unboxedType != JSVAL_TYPE_MAGIC) {
MInstruction* elements = MElements::New(alloc(), obj, /* unboxed = */ false); res = MSetUnboxedArrayInitializedLength::New(alloc(), obj, constant(Int32Value(count)));
current->add(elements); } else {
MInstruction* res = // MSetInitializedLength takes the index of the last element, rather
MSetInitializedLength::New(alloc(), elements, constant(Int32Value(count - 1))); // 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); current->add(res);
return res; return res;
} }

View File

@ -346,8 +346,9 @@ class IonBuilder
MConstant* constant(const Value& v); MConstant* constant(const Value& v);
MConstant* constantInt(int32_t i); MConstant* constantInt(int32_t i);
MInstruction* initializedLength(MDefinition* obj, MDefinition* elements); MInstruction* initializedLength(MDefinition* obj, MDefinition* elements,
MInstruction* setInitializedLength(MDefinition* obj, size_t count); JSValueType unboxedType);
MInstruction* setInitializedLength(MDefinition* obj, JSValueType unboxedType, size_t count);
// Improve the type information at tests // Improve the type information at tests
MOZ_MUST_USE bool improveTypesAtTest(MDefinition* ins, bool trueBranch, MTest* test); MOZ_MUST_USE bool improveTypesAtTest(MDefinition* ins, bool trueBranch, MTest* test);
@ -610,6 +611,7 @@ class IonBuilder
TypedObjectPrediction elemTypeReprs, TypedObjectPrediction elemTypeReprs,
uint32_t elemSize); uint32_t elemSize);
MOZ_MUST_USE bool initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value, MOZ_MUST_USE bool initializeArrayElement(MDefinition* obj, size_t index, MDefinition* value,
JSValueType unboxedType,
bool addResumePointAndIncrementInitializedLength); bool addResumePointAndIncrementInitializedLength);
// jsop_getelem() helpers. // jsop_getelem() helpers.
@ -722,13 +724,15 @@ class IonBuilder
MOZ_MUST_USE bool jsop_bindname(PropertyName* name); MOZ_MUST_USE bool jsop_bindname(PropertyName* name);
MOZ_MUST_USE bool jsop_bindvar(); MOZ_MUST_USE bool jsop_bindvar();
MOZ_MUST_USE bool jsop_getelem(); 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, MOZ_MUST_USE bool jsop_getelem_typed(MDefinition* obj, MDefinition* index,
ScalarTypeDescr::Type arrayType); ScalarTypeDescr::Type arrayType);
MOZ_MUST_USE bool jsop_setelem(); MOZ_MUST_USE bool jsop_setelem();
MOZ_MUST_USE bool jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, MOZ_MUST_USE bool jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
MDefinition* object, MDefinition* index, 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, MOZ_MUST_USE bool jsop_setelem_typed(ScalarTypeDescr::Type arrayType,
MDefinition* object, MDefinition* index, MDefinition* object, MDefinition* index,
MDefinition* value); MDefinition* value);

View File

@ -639,6 +639,9 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
} else { } else {
masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure); 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>()) { } else if (obj->is<TypedObject>()) {
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()), Address(object, JSObject::offsetOfGroup()),
@ -1185,6 +1188,39 @@ GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher&
return true; 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, // 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 // because the code embeds pointers to the respective class arrays. Code that
// caches the stub code must distinguish between the two cases. // caches the stub code must distinguish between the two cases.
@ -1558,6 +1594,40 @@ GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando); 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 bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted) HandleObject obj, HandleId id, bool* emitted)
@ -2133,6 +2203,9 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted)) if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false; return false;
if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted)) if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
return false; return false;
} }
@ -3953,7 +4026,7 @@ GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript
GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval, GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
TypedOrValueRegister output) TypedOrValueRegister output)
{ {
if (!obj->is<TypedArrayObject>()) if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
return false; return false;
MOZ_ASSERT(idval.isInt32() || idval.isString()); MOZ_ASSERT(idval.isInt32() || idval.isString());
@ -3984,6 +4057,13 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
return output.hasValue() || !output.typedReg().isFloat(); 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(); return output.hasValue() || !output.typedReg().isFloat();
} }
@ -4060,27 +4140,46 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
Label popObjectAndFail; Label popObjectAndFail;
// Guard on the initialized length. if (array->is<TypedArrayObject>()) {
Address length(object, TypedArrayObject::lengthOffset()); // Guard on the initialized length.
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures); Address length(object, TypedArrayObject::lengthOffset());
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
// Save the object register on the stack in case of failure. // Save the object register on the stack in case of failure.
Register elementReg = object; Register elementReg = object;
masm.push(object); masm.push(object);
// Load elements vector. // Load elements vector.
masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg); masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
// Load the value. We use an invalid register because the destination // Load the value. We use an invalid register because the destination
// register is necessary a non double register. // register is necessary a non double register.
Scalar::Type arrayType = array->as<TypedArrayObject>().type(); Scalar::Type arrayType = array->as<TypedArrayObject>().type();
int width = Scalar::byteSize(arrayType); int width = Scalar::byteSize(arrayType);
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width)); BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
if (output.hasValue()) { if (output.hasValue()) {
masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult, masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
elementReg, &popObjectAndFail); elementReg, &popObjectAndFail);
} else {
masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
}
} else { } 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); masm.pop(object);

View File

@ -2894,6 +2894,32 @@ LIRGenerator::visitSetInitializedLength(MSetInitializedLength* ins)
useRegisterOrConstant(ins->index())), ins); useRegisterOrConstant(ins->index())), 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 void
LIRGenerator::visitNot(MNot* ins) LIRGenerator::visitNot(MNot* ins)
{ {
@ -3143,16 +3169,22 @@ LIRGenerator::visitStoreElementHole(MStoreElementHole* ins)
const LUse elements = useRegister(ins->elements()); const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index()); 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; LInstruction* lir;
switch (ins->value()->type()) { switch (ins->value()->type()) {
case MIRType::Value: 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; break;
default: default:
{ {
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value()); const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
lir = new(alloc()) LStoreElementHoleT(object, elements, index, value); lir = new(alloc()) LStoreElementHoleT(object, elements, index, value, tempDef);
break; break;
} }
} }
@ -3171,14 +3203,20 @@ LIRGenerator::visitFallibleStoreElement(MFallibleStoreElement* ins)
const LUse elements = useRegister(ins->elements()); const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index()); 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; LInstruction* lir;
switch (ins->value()->type()) { switch (ins->value()->type()) {
case MIRType::Value: 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; break;
default: default:
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value()); const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value); lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value, tempDef);
break; break;
} }

View File

@ -217,6 +217,10 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitTypedObjectDescr(MTypedObjectDescr* ins); void visitTypedObjectDescr(MTypedObjectDescr* ins);
void visitInitializedLength(MInitializedLength* ins); void visitInitializedLength(MInitializedLength* ins);
void visitSetInitializedLength(MSetInitializedLength* 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 visitNot(MNot* ins);
void visitBoundsCheck(MBoundsCheck* ins); void visitBoundsCheck(MBoundsCheck* ins);
void visitBoundsCheckLower(MBoundsCheckLower* ins); void visitBoundsCheckLower(MBoundsCheckLower* ins);

View File

@ -471,6 +471,11 @@ IonBuilder::inlineArray(CallInfo& callInfo)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
} }
if (templateObject->is<UnboxedArrayObject>()) {
if (templateObject->group()->unboxedLayout().nativeGroup())
return InliningStatus_NotInlined;
}
// Multiple arguments imply array initialization, not just construction. // Multiple arguments imply array initialization, not just construction.
if (callInfo.argc() >= 2) { if (callInfo.argc() >= 2) {
initLength = callInfo.argc(); initLength = callInfo.argc();
@ -518,7 +523,7 @@ IonBuilder::inlineArray(CallInfo& callInfo)
// Make sure initLength matches the template object's length. This is // Make sure initLength matches the template object's length. This is
// not guaranteed to be the case, for instance if we're inlining the // not guaranteed to be the case, for instance if we're inlining the
// MConstant may come from an outer script. // MConstant may come from an outer script.
if (initLength != templateObject->as<ArrayObject>().length()) if (initLength != GetAnyBoxedOrUnboxedArrayLength(templateObject))
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
// Don't inline large allocations. // Don't inline large allocations.
@ -533,15 +538,16 @@ IonBuilder::inlineArray(CallInfo& callInfo)
MDefinition* array = current->peek(-1); MDefinition* array = current->peek(-1);
if (callInfo.argc() >= 2) { if (callInfo.argc() >= 2) {
JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
for (uint32_t i = 0; i < initLength; i++) { for (uint32_t i = 0; i < initLength; i++) {
if (!alloc().ensureBallast()) if (!alloc().ensureBallast())
return InliningStatus_Error; return InliningStatus_Error;
MDefinition* value = callInfo.getArg(i); MDefinition* value = callInfo.getArg(i);
if (!initializeArrayElement(array, i, value, /* addResumePoint = */ false)) if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error; return InliningStatus_Error;
} }
MInstruction* setLength = setInitializedLength(array, initLength); MInstruction* setLength = setInitializedLength(array, unboxedType, initLength);
if (!resumeAfter(setLength)) if (!resumeAfter(setLength))
return InliningStatus_Error; return InliningStatus_Error;
} }
@ -574,7 +580,7 @@ IonBuilder::inlineArrayIsArray(CallInfo& callInfo)
if (!clasp || clasp->isProxy()) if (!clasp || clasp->isProxy())
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
isArray = (clasp == &ArrayObject::class_); isArray = (clasp == &ArrayObject::class_ || clasp == &UnboxedArrayObject::class_);
} }
pushConstant(BooleanValue(isArray)); pushConstant(BooleanValue(isArray));
@ -610,7 +616,7 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
if (!thisTypes) if (!thisTypes)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
const Class* clasp = thisTypes->getKnownClass(constraints()); const Class* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) { if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) {
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
@ -623,9 +629,17 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
return InliningStatus_NotInlined; 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(); callInfo.setImplicitlyUsedUnchecked();
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); if (clasp == &ArrayObject::class_)
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
TemporaryTypeSet* returnTypes = getInlineReturnTypeSet(); TemporaryTypeSet* returnTypes = getInlineReturnTypeSet();
bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED); bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED);
@ -636,7 +650,8 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
if (barrier != BarrierKind::NoBarrier) if (barrier != BarrierKind::NoBarrier)
returnType = MIRType::Value; 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->add(ins);
current->push(ins); current->push(ins);
ins->setResultType(returnType); ins->setResultType(returnType);
@ -718,6 +733,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
return InliningStatus_NotInlined; 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(); callInfo.setImplicitlyUsedUnchecked();
if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles || if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles ||
@ -728,12 +750,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo)
value = valueDouble; value = valueDouble;
} }
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); if (unboxedType == JSVAL_TYPE_MAGIC)
obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false);
if (NeedsPostBarrier(value)) if (NeedsPostBarrier(value))
current->add(MPostWriteBarrier::New(alloc(), obj, 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->add(ins);
current->push(ins); current->push(ins);
@ -774,9 +797,16 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
const Class* clasp = thisTypes->getKnownClass(constraints()); const Class* clasp = thisTypes->getKnownClass(constraints());
if (clasp != &ArrayObject::class_) if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_)
return InliningStatus_NotInlined; 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. // Watch out for indexed properties on the object or its prototype.
if (ElementAccessHasExtraIndexedProperty(this, obj)) { if (ElementAccessHasExtraIndexedProperty(this, obj)) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
@ -797,8 +827,15 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
if (!templateObj) if (!templateObj)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
if (!templateObj->is<ArrayObject>()) if (unboxedType == JSVAL_TYPE_MAGIC) {
return InliningStatus_NotInlined; 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(); callInfo.setImplicitlyUsedUnchecked();
@ -817,12 +854,16 @@ IonBuilder::inlineArraySlice(CallInfo& callInfo)
end = MArrayLength::New(alloc(), elements); end = MArrayLength::New(alloc(), elements);
current->add(end->toInstruction()); current->add(end->toInstruction());
} else {
end = MUnboxedArrayLength::New(alloc(), obj);
current->add(end->toInstruction());
} }
MArraySlice* ins = MArraySlice::New(alloc(), constraints(), MArraySlice* ins = MArraySlice::New(alloc(), constraints(),
obj, begin, end, obj, begin, end,
templateObj, templateObj,
templateObj->group()->initialHeap(constraints())); templateObj->group()->initialHeap(constraints()),
unboxedType);
current->add(ins); current->add(ins);
current->push(ins); current->push(ins);
@ -1341,7 +1382,7 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
// Check if exist a template object in stub. // Check if exist a template object in stub.
JSString* stringStr = nullptr; JSString* stringStr = nullptr;
JSString* stringSep = nullptr; JSString* stringSep = nullptr;
ArrayObject* templateObject = nullptr; JSObject* templateObject = nullptr;
if (!inspector->isOptimizableCallStringSplit(pc, &stringStr, &stringSep, &templateObject)) if (!inspector->isOptimizableCallStringSplit(pc, &stringStr, &stringSep, &templateObject))
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
@ -1367,13 +1408,13 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
if (!key.maybeTypes()->hasType(TypeSet::StringType())) if (!key.maybeTypes()->hasType(TypeSet::StringType()))
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
uint32_t initLength = templateObject->length(); uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(templateObject);
if (templateObject->getDenseInitializedLength() != initLength) if (GetAnyBoxedOrUnboxedInitializedLength(templateObject) != initLength)
return InliningStatus_NotInlined; return InliningStatus_NotInlined;
Vector<MConstant*, 0, SystemAllocPolicy> arrayValues; Vector<MConstant*, 0, SystemAllocPolicy> arrayValues;
for (uint32_t i = 0; i < initLength; i++) { for (uint32_t i = 0; i < initLength; i++) {
Value str = templateObject->getDenseElement(i); Value str = GetAnyBoxedOrUnboxedDenseElement(templateObject, i);
MOZ_ASSERT(str.toString()->isAtom()); MOZ_ASSERT(str.toString()->isAtom());
MConstant* value = MConstant::New(alloc().fallible(), str, constraints()); MConstant* value = MConstant::New(alloc().fallible(), str, constraints());
if (!value) if (!value)
@ -1404,6 +1445,8 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
return InliningStatus_Inlined; return InliningStatus_Inlined;
} }
JSValueType unboxedType = GetBoxedOrUnboxedType(templateObject);
// Store all values, no need to initialize the length after each as // Store all values, no need to initialize the length after each as
// jsop_initelem_array is doing because we do not expect to bailout // jsop_initelem_array is doing because we do not expect to bailout
// because the memory is supposed to be allocated by now. // because the memory is supposed to be allocated by now.
@ -1414,11 +1457,11 @@ IonBuilder::inlineConstantStringSplitString(CallInfo& callInfo)
MConstant* value = arrayValues[i]; MConstant* value = arrayValues[i];
current->add(value); current->add(value);
if (!initializeArrayElement(array, i, value, /* addResumePoint = */ false)) if (!initializeArrayElement(array, i, value, unboxedType, /* addResumePoint = */ false))
return InliningStatus_Error; return InliningStatus_Error;
} }
MInstruction* setLength = setInitializedLength(array, initLength); MInstruction* setLength = setInitializedLength(array, unboxedType, initLength);
if (!resumeAfter(setLength)) if (!resumeAfter(setLength))
return InliningStatus_Error; return InliningStatus_Error;

View File

@ -5789,6 +5789,46 @@ jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints,
return clasp && clasp->isNative() && !IsTypedArrayClass(clasp); 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;
}
return elementType;
}
bool bool
jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints, jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id, MDefinition* obj, MDefinition* id,
@ -5948,6 +5988,11 @@ ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
firstElements.maybeTypes()->equals(secondElements.maybeTypes()); firstElements.maybeTypes()->equals(secondElements.maybeTypes());
} }
if (first->clasp() == &UnboxedArrayObject::class_) {
return first->group()->unboxedLayout().elementType() ==
second->group()->unboxedLayout().elementType();
}
return false; return false;
} }

View File

@ -375,7 +375,8 @@ class AliasSet {
Element = 1 << 1, // A Value member of obj->elements or Element = 1 << 1, // A Value member of obj->elements or
// a typed object. // a typed object.
UnboxedElement = 1 << 2, // An unboxed scalar or reference member of UnboxedElement = 1 << 2, // An unboxed scalar or reference member of
// typed object or unboxed object. // a typed array, typed object, or unboxed
// object.
DynamicSlot = 1 << 3, // A Value member of obj->slots. DynamicSlot = 1 << 3, // A Value member of obj->slots.
FixedSlot = 1 << 4, // A Value member of obj->fixedSlots(). FixedSlot = 1 << 4, // A Value member of obj->fixedSlots().
DOMProperty = 1 << 5, // A DOM property DOMProperty = 1 << 5, // A DOM property
@ -432,6 +433,9 @@ class AliasSet {
MOZ_ASSERT(flags && !(flags & Store_)); MOZ_ASSERT(flags && !(flags & Store_));
return AliasSet(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; typedef Vector<MDefinition*, 6, JitAllocPolicy> MDefinitionVector;
@ -8758,6 +8762,102 @@ class MSetInitializedLength
ALLOW_CLONE(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. // Load the array length from an elements header.
class MArrayLength class MArrayLength
: public MUnaryInstruction, : public MUnaryInstruction,
@ -9251,19 +9351,23 @@ class MLoadElement
ALLOW_CLONE(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 // If the index is out-of-bounds, or the indexed slot has a hole, undefined is
// returned instead. // returned instead.
class MLoadElementHole class MLoadElementHole
: public MTernaryInstruction, : public MTernaryInstruction,
public SingleObjectPolicy::Data public SingleObjectPolicy::Data
{ {
// Unboxed element type, JSVAL_TYPE_MAGIC for dense native elements.
JSValueType unboxedType_;
bool needsNegativeIntCheck_; bool needsNegativeIntCheck_;
bool needsHoleCheck_; bool needsHoleCheck_;
MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength, MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength,
bool needsHoleCheck) JSValueType unboxedType, bool needsHoleCheck)
: MTernaryInstruction(elements, index, initLength), : MTernaryInstruction(elements, index, initLength),
unboxedType_(unboxedType),
needsNegativeIntCheck_(true), needsNegativeIntCheck_(true),
needsHoleCheck_(needsHoleCheck) needsHoleCheck_(needsHoleCheck)
{ {
@ -9285,6 +9389,9 @@ class MLoadElementHole
TRIVIAL_NEW_WRAPPERS TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, elements), (1, index), (2, initLength)) NAMED_OPERANDS((0, elements), (1, index), (2, initLength))
JSValueType unboxedType() const {
return unboxedType_;
}
bool needsNegativeIntCheck() const { bool needsNegativeIntCheck() const {
return needsNegativeIntCheck_; return needsNegativeIntCheck_;
} }
@ -9295,6 +9402,8 @@ class MLoadElementHole
if (!ins->isLoadElementHole()) if (!ins->isLoadElementHole())
return false; return false;
const MLoadElementHole* other = ins->toLoadElementHole(); const MLoadElementHole* other = ins->toLoadElementHole();
if (unboxedType() != other->unboxedType())
return false;
if (needsHoleCheck() != other->needsHoleCheck()) if (needsHoleCheck() != other->needsHoleCheck())
return false; return false;
if (needsNegativeIntCheck() != other->needsNegativeIntCheck()) if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
@ -9302,7 +9411,7 @@ class MLoadElementHole
return congruentIfOperandsEqual(other); return congruentIfOperandsEqual(other);
} }
AliasSet getAliasSet() const override { AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::Element); return AliasSet::Load(AliasSet::BoxedOrUnboxedElements(unboxedType()));
} }
void collectRangeInfoPreTrunc() override; void collectRangeInfoPreTrunc() override;
@ -9482,17 +9591,20 @@ class MStoreElement
ALLOW_CLONE(MStoreElement) ALLOW_CLONE(MStoreElement)
}; };
// Like MStoreElement, but supports indexes >= initialized length. The downside // Like MStoreElement, but supports indexes >= initialized length, and can
// is that we cannot hoist the elements vector and bounds check, since this // handle unboxed arrays. The downside is that we cannot hoist the elements
// instruction may update the (initialized) length and reallocate the elements // vector and bounds check, since this instruction may update the (initialized)
// vector. // length and reallocate the elements vector.
class MStoreElementHole class MStoreElementHole
: public MAryInstruction<4>, : public MAryInstruction<4>,
public MStoreElementCommon, public MStoreElementCommon,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
{ {
JSValueType unboxedType_;
MStoreElementHole(MDefinition* object, MDefinition* elements, MStoreElementHole(MDefinition* object, MDefinition* elements,
MDefinition* index, MDefinition* value) MDefinition* index, MDefinition* value, JSValueType unboxedType)
: unboxedType_(unboxedType)
{ {
initOperand(0, object); initOperand(0, object);
initOperand(1, elements); initOperand(1, elements);
@ -9507,6 +9619,10 @@ class MStoreElementHole
TRIVIAL_NEW_WRAPPERS TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value)) NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
JSValueType unboxedType() const {
return unboxedType_;
}
ALLOW_CLONE(MStoreElementHole) ALLOW_CLONE(MStoreElementHole)
}; };
@ -9517,11 +9633,13 @@ class MFallibleStoreElement
public MStoreElementCommon, public MStoreElementCommon,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
{ {
JSValueType unboxedType_;
bool strict_; bool strict_;
MFallibleStoreElement(MDefinition* object, MDefinition* elements, MFallibleStoreElement(MDefinition* object, MDefinition* elements,
MDefinition* index, MDefinition* value, MDefinition* index, MDefinition* value,
bool strict) JSValueType unboxedType, bool strict)
: unboxedType_(unboxedType)
{ {
initOperand(0, object); initOperand(0, object);
initOperand(1, elements); initOperand(1, elements);
@ -9537,6 +9655,10 @@ class MFallibleStoreElement
TRIVIAL_NEW_WRAPPERS TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value)) NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))
JSValueType unboxedType() const {
return unboxedType_;
}
bool strict() const { bool strict() const {
return strict_; return strict_;
} }
@ -9640,12 +9762,13 @@ class MArrayPopShift
private: private:
Mode mode_; Mode mode_;
JSValueType unboxedType_;
bool needsHoleCheck_; bool needsHoleCheck_;
bool maybeUndefined_; bool maybeUndefined_;
MArrayPopShift(MDefinition* object, Mode mode, MArrayPopShift(MDefinition* object, Mode mode, JSValueType unboxedType,
bool needsHoleCheck, bool maybeUndefined) bool needsHoleCheck, bool maybeUndefined)
: MUnaryInstruction(object), mode_(mode), : MUnaryInstruction(object), mode_(mode), unboxedType_(unboxedType),
needsHoleCheck_(needsHoleCheck), maybeUndefined_(maybeUndefined) needsHoleCheck_(needsHoleCheck), maybeUndefined_(maybeUndefined)
{ } { }
@ -9663,8 +9786,12 @@ class MArrayPopShift
bool mode() const { bool mode() const {
return mode_; return mode_;
} }
JSValueType unboxedType() const {
return unboxedType_;
}
AliasSet getAliasSet() const override { AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element); return AliasSet::Store(AliasSet::ObjectFields |
AliasSet::BoxedOrUnboxedElements(unboxedType()));
} }
ALLOW_CLONE(MArrayPopShift) ALLOW_CLONE(MArrayPopShift)
@ -9675,8 +9802,10 @@ class MArrayPush
: public MBinaryInstruction, : public MBinaryInstruction,
public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >::Data
{ {
MArrayPush(MDefinition* object, MDefinition* value) JSValueType unboxedType_;
: MBinaryInstruction(object, value)
MArrayPush(MDefinition* object, MDefinition* value, JSValueType unboxedType)
: MBinaryInstruction(object, value), unboxedType_(unboxedType)
{ {
setResultType(MIRType::Int32); setResultType(MIRType::Int32);
} }
@ -9686,8 +9815,12 @@ class MArrayPush
TRIVIAL_NEW_WRAPPERS TRIVIAL_NEW_WRAPPERS
NAMED_OPERANDS((0, object), (1, value)) NAMED_OPERANDS((0, object), (1, value))
JSValueType unboxedType() const {
return unboxedType_;
}
AliasSet getAliasSet() const override { AliasSet getAliasSet() const override {
return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element); return AliasSet::Store(AliasSet::ObjectFields |
AliasSet::BoxedOrUnboxedElements(unboxedType()));
} }
void computeRange(TempAllocator& alloc) override; void computeRange(TempAllocator& alloc) override;
@ -9701,13 +9834,15 @@ class MArraySlice
{ {
CompilerObject templateObj_; CompilerObject templateObj_;
gc::InitialHeap initialHeap_; gc::InitialHeap initialHeap_;
JSValueType unboxedType_;
MArraySlice(CompilerConstraintList* constraints, MDefinition* obj, MArraySlice(CompilerConstraintList* constraints, MDefinition* obj,
MDefinition* begin, MDefinition* end, MDefinition* begin, MDefinition* end,
JSObject* templateObj, gc::InitialHeap initialHeap) JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType)
: MTernaryInstruction(obj, begin, end), : MTernaryInstruction(obj, begin, end),
templateObj_(templateObj), templateObj_(templateObj),
initialHeap_(initialHeap) initialHeap_(initialHeap),
unboxedType_(unboxedType)
{ {
setResultType(MIRType::Object); setResultType(MIRType::Object);
} }
@ -9725,6 +9860,10 @@ class MArraySlice
return initialHeap_; return initialHeap_;
} }
JSValueType unboxedType() const {
return unboxedType_;
}
bool possiblyCalls() const override { bool possiblyCalls() const override {
return true; return true;
} }
@ -12093,13 +12232,15 @@ class MInArray
{ {
bool needsHoleCheck_; bool needsHoleCheck_;
bool needsNegativeIntCheck_; bool needsNegativeIntCheck_;
JSValueType unboxedType_;
MInArray(MDefinition* elements, MDefinition* index, MInArray(MDefinition* elements, MDefinition* index,
MDefinition* initLength, MDefinition* object, MDefinition* initLength, MDefinition* object,
bool needsHoleCheck) bool needsHoleCheck, JSValueType unboxedType)
: MQuaternaryInstruction(elements, index, initLength, object), : MQuaternaryInstruction(elements, index, initLength, object),
needsHoleCheck_(needsHoleCheck), needsHoleCheck_(needsHoleCheck),
needsNegativeIntCheck_(true) needsNegativeIntCheck_(true),
unboxedType_(unboxedType)
{ {
setResultType(MIRType::Boolean); setResultType(MIRType::Boolean);
setMovable(); setMovable();
@ -12119,6 +12260,9 @@ class MInArray
bool needsNegativeIntCheck() const { bool needsNegativeIntCheck() const {
return needsNegativeIntCheck_; return needsNegativeIntCheck_;
} }
JSValueType unboxedType() const {
return unboxedType_;
}
void collectRangeInfoPreTrunc() override; void collectRangeInfoPreTrunc() override;
AliasSet getAliasSet() const override { AliasSet getAliasSet() const override {
return AliasSet::Load(AliasSet::Element); return AliasSet::Load(AliasSet::Element);
@ -12131,6 +12275,8 @@ class MInArray
return false; return false;
if (needsNegativeIntCheck() != other->needsNegativeIntCheck()) if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
return false; return false;
if (unboxedType() != other->unboxedType())
return false;
return congruentIfOperandsEqual(other); return congruentIfOperandsEqual(other);
} }
}; };
@ -14031,6 +14177,8 @@ MDefinition::maybeConstantValue()
bool ElementAccessIsDenseNative(CompilerConstraintList* constraints, bool ElementAccessIsDenseNative(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id); MDefinition* obj, MDefinition* id);
JSValueType UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
MDefinition* id);
bool ElementAccessIsTypedArray(CompilerConstraintList* constraints, bool ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id, MDefinition* obj, MDefinition* id,
Scalar::Type* arrayType); Scalar::Type* arrayType);

View File

@ -199,6 +199,10 @@ namespace jit {
_(SetTypedObjectOffset) \ _(SetTypedObjectOffset) \
_(InitializedLength) \ _(InitializedLength) \
_(SetInitializedLength) \ _(SetInitializedLength) \
_(UnboxedArrayLength) \
_(UnboxedArrayInitializedLength) \
_(IncrementUnboxedArrayInitializedLength) \
_(SetUnboxedArrayInitializedLength) \
_(Not) \ _(Not) \
_(BoundsCheck) \ _(BoundsCheck) \
_(BoundsCheckLower) \ _(BoundsCheckLower) \

View File

@ -705,6 +705,31 @@ template void
MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type, MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
const ConstantOrRegister& value, Label* failure); 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 // Inlined version of gc::CheckAllocatorState that checks the bare essentials
// and bails for anything that cannot be handled with our jit allocators. // and bails for anything that cannot be handled with our jit allocators.
void void
@ -1256,6 +1281,16 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando())); storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando()));
if (initContents) if (initContents)
initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>()); 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 { } else {
MOZ_CRASH("Unknown object"); MOZ_CRASH("Unknown object");
} }

View File

@ -1626,7 +1626,7 @@ class MacroAssembler : public MacroAssemblerSpecific
void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
unsigned numElems = 0); unsigned numElems = 0);
// Load a property from an UnboxedPlainObject. // Load a property from an UnboxedPlainObject or UnboxedArrayObject.
template <typename T> template <typename T>
void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output); void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
@ -1637,6 +1637,9 @@ class MacroAssembler : public MacroAssemblerSpecific
void storeUnboxedProperty(T address, JSValueType type, void storeUnboxedProperty(T address, JSValueType type,
const ConstantOrRegister& value, Label* failure); const ConstantOrRegister& value, Label* failure);
void checkUnboxedArrayCapacity(Register obj, const RegisterOrInt32Constant& index,
Register temp, Label* failure);
Register extractString(const Address& address, Register scratch) { Register extractString(const Address& address, Register scratch) {
return extractObject(address, scratch); return extractObject(address, scratch);
} }

View File

@ -1355,7 +1355,7 @@ RNewArray::recover(JSContext* cx, SnapshotIterator& iter) const
RootedValue result(cx); RootedValue result(cx);
RootedObjectGroup group(cx, templateObject->group()); RootedObjectGroup group(cx, templateObject->group());
ArrayObject* resultObject = NewFullyAllocatedArrayTryUseGroup(cx, group, count_); JSObject* resultObject = NewFullyAllocatedArrayTryUseGroup(cx, group, count_);
if (!resultObject) if (!resultObject)
return false; return false;

View File

@ -795,6 +795,11 @@ IsArrayEscaped(MInstruction* ins)
return true; return true;
} }
if (obj->is<UnboxedArrayObject>()) {
JitSpew(JitSpew_Escape, "Template object is an unboxed plain object.");
return true;
}
if (length >= 16) { if (length >= 16) {
JitSpew(JitSpew_Escape, "Array has too many elements"); JitSpew(JitSpew_Escape, "Array has too many elements");
return true; return true;

View File

@ -286,6 +286,11 @@ ICStub::trace(JSTracer* trc)
TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape"); TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape");
break; break;
} }
case ICStub::GetElem_UnboxedArray: {
ICGetElem_UnboxedArray* getElemStub = toGetElem_UnboxedArray();
TraceEdge(trc, &getElemStub->group(), "baseline-getelem-unboxed-array-group");
break;
}
case ICStub::GetElem_TypedArray: { case ICStub::GetElem_TypedArray: {
ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray(); ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray();
TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape"); TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape");
@ -2245,6 +2250,7 @@ IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy)
if (obj == holder) if (obj == holder)
return false; return false;
if (!obj->is<UnboxedPlainObject>() && if (!obj->is<UnboxedPlainObject>() &&
!obj->is<UnboxedArrayObject>() &&
!obj->is<TypedObject>()) !obj->is<TypedObject>())
{ {
return false; return false;
@ -2576,6 +2582,9 @@ CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, PropertyName* name,
} else if (curObj->is<UnboxedPlainObject>()) { } else if (curObj->is<UnboxedPlainObject>()) {
if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name))) if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
return false; return false;
} else if (curObj->is<UnboxedArrayObject>()) {
if (name == cx->names().length)
return false;
} else if (curObj->is<TypedObject>()) { } else if (curObj->is<TypedObject>()) {
if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name))) if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
return false; return false;

View File

@ -306,7 +306,7 @@ template bool StringsEqual<false>(JSContext* cx, HandleString lhs, HandleString
bool bool
ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{ {
MOZ_ASSERT(obj->is<ArrayObject>()); MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
AutoDetectInvalidation adi(cx, rval); AutoDetectInvalidation adi(cx, rval);
@ -325,11 +325,12 @@ ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
} }
bool bool
ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* length) ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length)
{ {
*length = arr->length(); *length = GetAnyBoxedOrUnboxedArrayLength(obj);
DenseElementResult result = arr->setOrExtendDenseElements(cx, *length, v.address(), 1, DenseElementResult result =
ShouldUpdateTypes::DontUpdate); SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, *length, v.address(), 1,
ShouldUpdateTypes::DontUpdate);
if (result != DenseElementResult::Incomplete) { if (result != DenseElementResult::Incomplete) {
(*length)++; (*length)++;
return result == DenseElementResult::Success; return result == DenseElementResult::Success;
@ -337,7 +338,7 @@ ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* le
JS::AutoValueArray<3> argv(cx); JS::AutoValueArray<3> argv(cx);
argv[0].setUndefined(); argv[0].setUndefined();
argv[1].setObject(*arr); argv[1].setObject(*obj);
argv[2].set(v); argv[2].set(v);
if (!js::array_push(cx, 1, argv.begin())) if (!js::array_push(cx, 1, argv.begin()))
return false; return false;
@ -349,7 +350,7 @@ ArrayPushDense(JSContext* cx, HandleArrayObject arr, HandleValue v, uint32_t* le
bool bool
ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{ {
MOZ_ASSERT(obj->is<ArrayObject>()); MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
AutoDetectInvalidation adi(cx, rval); AutoDetectInvalidation adi(cx, rval);
@ -1130,14 +1131,16 @@ Recompile(JSContext* cx)
} }
bool 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. // 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 // In this case we know the object is native or an unboxed array and that
// needed. // no type changes are needed.
DenseElementResult result = obj->setOrExtendDenseElements(cx, index, value.address(), 1, DenseElementResult result =
ShouldUpdateTypes::DontUpdate); SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, index, value.address(), 1,
ShouldUpdateTypes::DontUpdate);
if (result != DenseElementResult::Incomplete) if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success; return result == DenseElementResult::Success;

View File

@ -622,7 +622,7 @@ template<bool Equal>
bool StringsEqual(JSContext* cx, HandleString left, HandleString right, bool* res); 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 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); MOZ_MUST_USE bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval);
JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep); JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep);
@ -745,8 +745,8 @@ ForcedRecompile(JSContext* cx);
JSString* StringReplace(JSContext* cx, HandleString string, HandleString pattern, JSString* StringReplace(JSContext* cx, HandleString string, HandleString pattern,
HandleString repl); HandleString repl);
MOZ_MUST_USE bool SetDenseElement(JSContext* cx, HandleNativeObject obj, int32_t index, MOZ_MUST_USE bool SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
HandleValue value, bool strict); HandleValue value, bool strict);
void AssertValidObjectPtr(JSContext* cx, JSObject* obj); void AssertValidObjectPtr(JSContext* cx, JSObject* obj);
void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj); void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj);

View File

@ -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. // Load the length from an elements header.
class LArrayLength : public LInstructionHelper<1, 1, 0> 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. // 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: public:
LIR_HEADER(StoreElementHoleV) LIR_HEADER(StoreElementHoleV)
LStoreElementHoleV(const LAllocation& object, const LAllocation& elements, 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(0, object);
setOperand(1, elements); setOperand(1, elements);
setOperand(2, index); setOperand(2, index);
setBoxOperand(Value, value); setBoxOperand(Value, value);
setTemp(0, temp);
} }
static const size_t Value = 3; 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. // Like LStoreElementT, but supports indexes >= initialized length.
class LStoreElementHoleT : public LInstructionHelper<0, 4, 0> class LStoreElementHoleT : public LInstructionHelper<0, 4, 1>
{ {
public: public:
LIR_HEADER(StoreElementHoleT) LIR_HEADER(StoreElementHoleT)
LStoreElementHoleT(const LAllocation& object, const LAllocation& elements, 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(0, object);
setOperand(1, elements); setOperand(1, elements);
setOperand(2, index); setOperand(2, index);
setOperand(3, value); setOperand(3, value);
setTemp(0, temp);
} }
const MStoreElementHole* mir() const { 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) // 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: public:
LIR_HEADER(FallibleStoreElementV) LIR_HEADER(FallibleStoreElementV)
LFallibleStoreElementV(const LAllocation& object, const LAllocation& elements, 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(0, object);
setOperand(1, elements); setOperand(1, elements);
setOperand(2, index); setOperand(2, index);
setBoxOperand(Value, value); setBoxOperand(Value, value);
setTemp(0, temp);
} }
static const size_t Value = 3; 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) // 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: public:
LIR_HEADER(FallibleStoreElementT) LIR_HEADER(FallibleStoreElementT)
LFallibleStoreElementT(const LAllocation& object, const LAllocation& elements, 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(0, object);
setOperand(1, elements); setOperand(1, elements);
setOperand(2, index); setOperand(2, index);
setOperand(3, value); setOperand(3, value);
setTemp(0, temp);
} }
const MFallibleStoreElement* mir() const { const MFallibleStoreElement* mir() const {

View File

@ -266,6 +266,10 @@
_(PostWriteElementBarrierV) \ _(PostWriteElementBarrierV) \
_(InitializedLength) \ _(InitializedLength) \
_(SetInitializedLength) \ _(SetInitializedLength) \
_(UnboxedArrayLength) \
_(UnboxedArrayInitializedLength) \
_(IncrementUnboxedArrayInitializedLength) \
_(SetUnboxedArrayInitializedLength) \
_(BoundsCheck) \ _(BoundsCheck) \
_(BoundsCheckRange) \ _(BoundsCheckRange) \
_(BoundsCheckLower) \ _(BoundsCheckLower) \

View File

@ -1096,6 +1096,7 @@ class JS_PUBLIC_API(ContextOptions) {
wasmAlwaysBaseline_(false), wasmAlwaysBaseline_(false),
throwOnAsmJSValidationFailure_(false), throwOnAsmJSValidationFailure_(false),
nativeRegExp_(true), nativeRegExp_(true),
unboxedArrays_(false),
asyncStack_(true), asyncStack_(true),
throwOnDebuggeeWouldRun_(true), throwOnDebuggeeWouldRun_(true),
dumpStackOnDebuggeeWouldRun_(false), dumpStackOnDebuggeeWouldRun_(false),
@ -1172,6 +1173,12 @@ class JS_PUBLIC_API(ContextOptions) {
return *this; return *this;
} }
bool unboxedArrays() const { return unboxedArrays_; }
ContextOptions& setUnboxedArrays(bool flag) {
unboxedArrays_ = flag;
return *this;
}
bool asyncStack() const { return asyncStack_; } bool asyncStack() const { return asyncStack_; }
ContextOptions& setAsyncStack(bool flag) { ContextOptions& setAsyncStack(bool flag) {
asyncStack_ = flag; asyncStack_ = flag;
@ -1234,6 +1241,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool wasmAlwaysBaseline_ : 1; bool wasmAlwaysBaseline_ : 1;
bool throwOnAsmJSValidationFailure_ : 1; bool throwOnAsmJSValidationFailure_ : 1;
bool nativeRegExp_ : 1; bool nativeRegExp_ : 1;
bool unboxedArrays_ : 1;
bool asyncStack_ : 1; bool asyncStack_ : 1;
bool throwOnDebuggeeWouldRun_ : 1; bool throwOnDebuggeeWouldRun_ : 1;
bool dumpStackOnDebuggeeWouldRun_ : 1; bool dumpStackOnDebuggeeWouldRun_ : 1;

View File

@ -64,7 +64,7 @@ using JS::ToUint32;
bool bool
JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer)
{ {
if (obj->is<ArrayObject>()) { if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
*answer = IsArrayAnswer::Array; *answer = IsArrayAnswer::Array;
return true; return true;
} }
@ -100,6 +100,11 @@ js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp)
return true; return true;
} }
if (obj->is<UnboxedArrayObject>()) {
*lengthp = obj->as<UnboxedArrayObject>().length();
return true;
}
if (obj->is<ArgumentsObject>()) { if (obj->is<ArgumentsObject>()) {
ArgumentsObject& argsobj = obj->as<ArgumentsObject>(); ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
if (!argsobj.hasOverriddenLength()) { if (!argsobj.hasOverriddenLength()) {
@ -248,20 +253,18 @@ static bool
GetElement(JSContext* cx, HandleObject obj, HandleObject receiver, GetElement(JSContext* cx, HandleObject obj, HandleObject receiver,
uint32_t index, bool* hole, MutableHandleValue vp) uint32_t index, bool* hole, MutableHandleValue vp)
{ {
if (obj->isNative()) { AssertGreaterThanZero(index);
NativeObject* nobj = &obj->as<NativeObject>(); if (index < GetAnyBoxedOrUnboxedInitializedLength(obj)) {
if (index < nobj->getDenseInitializedLength()) { vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, uint32_t(index)));
vp.set(nobj->getDenseElement(size_t(index))); if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
if (!vp.isMagic(JS_ELEMENTS_HOLE)) { *hole = false;
*hole = false; return true;
return true;
}
} }
if (nobj->is<ArgumentsObject>() && index <= UINT32_MAX) { }
if (nobj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) { if (obj->is<ArgumentsObject>()) {
*hole = false; if (obj->as<ArgumentsObject>().maybeGetElement(uint32_t(index), vp)) {
return true; *hole = false;
} return true;
} }
} }
@ -280,8 +283,8 @@ ElementAdder::append(JSContext* cx, HandleValue v)
{ {
MOZ_ASSERT(index_ < length_); MOZ_ASSERT(index_ < length_);
if (resObj_) { if (resObj_) {
NativeObject* resObj = &resObj_->as<NativeObject>(); DenseElementResult result =
DenseElementResult result = resObj->setOrExtendDenseElements(cx, index_, v.address(), 1); SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, resObj_, index_, v.address(), 1);
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return false; return false;
if (result == DenseElementResult::Incomplete) { if (result == DenseElementResult::Incomplete) {
@ -333,31 +336,37 @@ js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver,
return true; return true;
} }
static bool template <JSValueType Type>
GetDenseElements(NativeObject* aobj, uint32_t length, Value* vp) DenseElementResult
GetBoxedOrUnboxedDenseElements(JSObject* aobj, uint32_t length, Value* vp)
{ {
MOZ_ASSERT(!ObjectMayHaveExtraIndexedProperties(aobj)); MOZ_ASSERT(!ObjectMayHaveExtraIndexedProperties(aobj));
if (length > aobj->getDenseInitializedLength()) if (length > GetBoxedOrUnboxedInitializedLength<Type>(aobj))
return false; return DenseElementResult::Incomplete;
for (size_t i = 0; i < length; i++) { 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. // No other indexed properties so hole => undefined.
if (vp[i].isMagic(JS_ELEMENTS_HOLE)) if (vp[i].isMagic(JS_ELEMENTS_HOLE))
vp[i] = UndefinedValue(); vp[i] = UndefinedValue();
} }
return true; return DenseElementResult::Success;
} }
DefineBoxedOrUnboxedFunctor3(GetBoxedOrUnboxedDenseElements,
JSObject*, uint32_t, Value*);
bool bool
js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp) js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp)
{ {
if (!ObjectMayHaveExtraIndexedProperties(aobj)) { if (!ObjectMayHaveExtraIndexedProperties(aobj)) {
if (GetDenseElements(&aobj->as<NativeObject>(), length, vp)) GetBoxedOrUnboxedDenseElementsFunctor functor(aobj, length, vp);
return true; DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, aobj);
if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success;
} }
if (aobj->is<ArgumentsObject>()) { if (aobj->is<ArgumentsObject>()) {
@ -389,9 +398,9 @@ SetArrayElement(JSContext* cx, HandleObject obj, double index, HandleValue v)
{ {
MOZ_ASSERT(index >= 0); MOZ_ASSERT(index >= 0);
if (obj->is<ArrayObject>() && !obj->isIndexed() && index <= UINT32_MAX) { if ((obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) && !obj->isIndexed() && index <= UINT32_MAX) {
NativeObject* nobj = &obj->as<NativeObject>(); DenseElementResult result =
DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(index), v.address(), 1); SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(index), v.address(), 1);
if (result != DenseElementResult::Incomplete) if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success; return result == DenseElementResult::Success;
} }
@ -511,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 */ /* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */
bool bool
js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id, js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
@ -532,22 +559,12 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
} else { } else {
// Step 2 is irrelevant in our implementation. // Step 2 is irrelevant in our implementation.
// Step 3. // Steps 3-7.
if (!ToUint32(cx, value, &newLen)) MOZ_ASSERT_IF(attrs & JSPROP_IGNORE_VALUE, value.isUndefined());
if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
return false; return false;
// Step 4. // Step 8 is irrelevant in our implementation.
double d;
if (!ToNumber(cx, value, &d))
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.
} }
// Steps 9-11. // Steps 9-11.
@ -806,7 +823,7 @@ array_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v)
static inline bool static inline bool
ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj) ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj)
{ {
return !obj->isNative() || return (!obj->isNative() && !obj->is<UnboxedArrayObject>()) ||
obj->isIndexed() || obj->isIndexed() ||
obj->is<TypedArrayObject>() || obj->is<TypedArrayObject>() ||
ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames, ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames,
@ -837,7 +854,7 @@ js::ObjectMayHaveExtraIndexedProperties(JSObject* obj)
if (ObjectMayHaveExtraIndexedOwnProperties(obj)) if (ObjectMayHaveExtraIndexedOwnProperties(obj))
return true; return true;
if (obj->as<NativeObject>().getDenseInitializedLength() != 0) if (GetAnyBoxedOrUnboxedInitializedLength(obj) != 0)
return true; return true;
} while (true); } while (true);
} }
@ -1047,32 +1064,32 @@ struct StringSeparatorOp
} }
}; };
template <typename SeparatorOp> template <typename SeparatorOp, JSValueType Type>
static bool static DenseElementResult
ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, uint32_t length, ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
StringBuffer& sb, uint32_t* numProcessed) StringBuffer& sb, uint32_t* numProcessed)
{ {
// This loop handles all elements up to initializedLength. If // This loop handles all elements up to initializedLength. If
// length > initLength we rely on the second loop to add the // length > initLength we rely on the second loop to add the
// other elements. // other elements.
MOZ_ASSERT(*numProcessed == 0); MOZ_ASSERT(*numProcessed == 0);
uint32_t initLength = Min<uint32_t>(obj->getDenseInitializedLength(), uint32_t initLength = Min<uint32_t>(GetBoxedOrUnboxedInitializedLength<Type>(obj),
length); length);
while (*numProcessed < initLength) { while (*numProcessed < initLength) {
if (!CheckForInterrupt(cx)) if (!CheckForInterrupt(cx))
return false; return DenseElementResult::Failure;
Value elem = obj->as<NativeObject>().getDenseElement(*numProcessed); Value elem = obj->as<NativeObject>().getDenseElement(*numProcessed);
if (elem.isString()) { if (elem.isString()) {
if (!sb.append(elem.toString())) if (!sb.append(elem.toString()))
return false; return DenseElementResult::Failure;
} else if (elem.isNumber()) { } else if (elem.isNumber()) {
if (!NumberValueToStringBuffer(cx, elem, sb)) if (!NumberValueToStringBuffer(cx, elem, sb))
return false; return DenseElementResult::Failure;
} else if (elem.isBoolean()) { } else if (elem.isBoolean()) {
if (!BooleanToStringBuffer(elem.toBoolean(), sb)) if (!BooleanToStringBuffer(elem.toBoolean(), sb))
return false; return DenseElementResult::Failure;
} else if (elem.isObject() || elem.isSymbol()) { } else if (elem.isObject() || elem.isSymbol()) {
/* /*
* Object stringifying could modify the initialized length or make * Object stringifying could modify the initialized length or make
@ -1088,12 +1105,32 @@ ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, u
} }
if (++(*numProcessed) != length && !sepOp(cx, sb)) 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> template <typename SeparatorOp>
static bool static bool
ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length, ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
@ -1102,10 +1139,10 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len
uint32_t i = 0; uint32_t i = 0;
if (!ObjectMayHaveExtraIndexedProperties(obj)) { 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; return false;
}
} }
if (i != length) { if (i != length) {
@ -1176,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 // 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 // the 0th element is a string, ToString() of that element is a no-op and
// so it can be immediately returned as the result. // so it can be immediately returned as the result.
if (length == 1 && obj->isNative()) { if (length == 1 && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) {
NativeObject* nobj = &obj->as<NativeObject>(); Value elem0 = GetAnyBoxedOrUnboxedDenseElement(obj, 0);
if (nobj->getDenseInitializedLength() == 1) { if (elem0.isString()) {
Value elem0 = nobj->getDenseElement(0); args.rval().set(elem0);
if (elem0.isString()) { return true;
args.rval().set(elem0);
return true;
}
} }
} }
@ -1226,7 +1260,7 @@ js::array_join(JSContext* cx, unsigned argc, Value* vp)
} }
// Step 11 // Step 11
JSString* str = sb.finishString(); JSString *str = sb.finishString();
if (!str) if (!str)
return false; return false;
@ -1255,6 +1289,10 @@ array_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
args.rval().setString(cx->names().empty); args.rval().setString(cx->names().empty);
return true; return true;
} }
if (obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().length() == 0) {
args.rval().setString(cx->names().empty);
return true;
}
AutoCycleDetector detector(cx, obj); AutoCycleDetector detector(cx, obj);
if (!detector.init()) if (!detector.init())
@ -1291,9 +1329,8 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start,
return false; return false;
if (!ObjectMayHaveExtraIndexedProperties(obj)) { if (!ObjectMayHaveExtraIndexedProperties(obj)) {
NativeObject* nobj = &obj->as<NativeObject>(); DenseElementResult result =
DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(start), vector, SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, start, vector, count, updateTypes);
count, updateTypes);
if (result != DenseElementResult::Incomplete) if (result != DenseElementResult::Incomplete)
return result == DenseElementResult::Success; return result == DenseElementResult::Success;
} }
@ -1327,45 +1364,54 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start,
return true; return true;
} }
static DenseElementResult template <JSValueType Type>
ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length) DenseElementResult
ArrayReverseDenseKernel(JSContext* cx, HandleObject obj, uint32_t length)
{ {
/* An empty array or an array with no elements is already reversed. */ /* 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; return DenseElementResult::Success;
if (obj->denseElementsAreFrozen()) if (Type == JSVAL_TYPE_MAGIC) {
return DenseElementResult::Incomplete; if (obj->as<NativeObject>().denseElementsAreFrozen())
return DenseElementResult::Incomplete;
/* /*
* It's actually surprisingly complicated to reverse an array due to the * It's actually surprisingly complicated to reverse an array due to the
* orthogonality of array length and array capacity while handling * orthogonality of array length and array capacity while handling
* leading and trailing holes correctly. Reversing seems less likely to * leading and trailing holes correctly. Reversing seems less likely to
* be a common operation than other array mass-mutation methods, so for * 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 * 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 * 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. * sufficient to hold all the elements in the array if it were full.
*/ */
DenseElementResult result = obj->ensureDenseElements(cx, length, 0); DenseElementResult result = obj->as<NativeObject>().ensureDenseElements(cx, length, 0);
if (result != DenseElementResult::Success) if (result != DenseElementResult::Success)
return result; return result;
/* Fill out the array's initialized length to its proper length. */ /* Fill out the array's initialized length to its proper length. */
obj->ensureDenseInitializedLength(cx, length, 0); 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); RootedValue origlo(cx), orighi(cx);
uint32_t lo = 0, hi = length - 1; uint32_t lo = 0, hi = length - 1;
for (; lo < hi; lo++, hi--) { for (; lo < hi; lo++, hi--) {
origlo = obj->getDenseElement(lo); origlo = GetBoxedOrUnboxedDenseElement<Type>(obj, lo);
orighi = obj->getDenseElement(hi); orighi = GetBoxedOrUnboxedDenseElement<Type>(obj, hi);
obj->setDenseElement(lo, orighi); SetBoxedOrUnboxedDenseElementNoTypeChange<Type>(obj, lo, orighi);
if (orighi.isMagic(JS_ELEMENTS_HOLE) && if (orighi.isMagic(JS_ELEMENTS_HOLE) &&
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo))) !SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo)))
{ {
return DenseElementResult::Failure; return DenseElementResult::Failure;
} }
obj->setDenseElement(hi, origlo); SetBoxedOrUnboxedDenseElementNoTypeChange<Type>(obj, hi, origlo);
if (origlo.isMagic(JS_ELEMENTS_HOLE) && if (origlo.isMagic(JS_ELEMENTS_HOLE) &&
!SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi))) !SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi)))
{ {
@ -1376,6 +1422,9 @@ ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length)
return DenseElementResult::Success; return DenseElementResult::Success;
} }
DefineBoxedOrUnboxedFunctor3(ArrayReverseDenseKernel,
JSContext*, HandleObject, uint32_t);
bool bool
js::array_reverse(JSContext* cx, unsigned argc, Value* vp) js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
{ {
@ -1390,8 +1439,8 @@ js::array_reverse(JSContext* cx, unsigned argc, Value* vp)
return false; return false;
if (!ObjectMayHaveExtraIndexedProperties(obj)) { if (!ObjectMayHaveExtraIndexedProperties(obj)) {
DenseElementResult result = ArrayReverseDenseKernelFunctor functor(cx, obj, len);
ArrayReverseDenseKernel(cx, obj.as<NativeObject>(), uint32_t(len)); DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
if (result != DenseElementResult::Incomplete) { if (result != DenseElementResult::Incomplete) {
/* /*
* Per ECMA-262, don't update the length of the array, even if the new * Per ECMA-262, don't update the length of the array, even if the new
@ -2034,8 +2083,8 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
if (!ObjectMayHaveExtraIndexedProperties(obj)) { if (!ObjectMayHaveExtraIndexedProperties(obj)) {
DenseElementResult result = DenseElementResult result =
obj->as<NativeObject>().setOrExtendDenseElements(cx, uint32_t(length), SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, length,
args.array(), args.length()); args.array(), args.length());
if (result != DenseElementResult::Incomplete) { if (result != DenseElementResult::Incomplete) {
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return false; return false;
@ -2043,8 +2092,14 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp)
uint32_t newlength = length + args.length(); uint32_t newlength = length + args.length();
args.rval().setNumber(newlength); args.rval().setNumber(newlength);
// Handle updates to the length of non-arrays here. // SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the
if (!obj->is<ArrayObject>()) // 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 SetLengthProperty(cx, obj, newlength);
return true; return true;
@ -2100,46 +2155,42 @@ js::array_pop(JSContext* cx, unsigned argc, Value* vp)
return SetLengthProperty(cx, obj, index); return SetLengthProperty(cx, obj, index);
} }
void template <JSValueType Type>
js::ArrayShiftMoveElements(NativeObject* obj) 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();
MOZ_ASSERT(initlen > 0);
/* /*
* At this point the length and initialized length have already been * At this point the length and initialized length have already been
* decremented and the result fetched, so just shift the array elements * decremented and the result fetched, so just shift the array elements
* themselves. * themselves.
*/ */
obj->moveDenseElementsNoPreBarrier(0, 1, initlen); size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
} if (Type == JSVAL_TYPE_MAGIC) {
obj->as<NativeObject>().moveDenseElementsNoPreBarrier(0, 1, initlen);
static inline void } else {
SetInitializedLength(JSContext* cx, NativeObject* obj, size_t initlen) uint8_t* data = obj->as<UnboxedArrayObject>().elements();
{ size_t elementSize = UnboxedTypeSize(Type);
size_t oldInitlen = obj->getDenseInitializedLength(); memmove(data, data + elementSize, initlen * elementSize);
obj->setDenseInitializedLength(initlen); }
if (initlen < oldInitlen)
obj->shrinkElements(cx, initlen);
}
static DenseElementResult
MoveDenseElements(JSContext* cx, NativeObject* obj, uint32_t dstStart, uint32_t srcStart,
uint32_t length)
{
if (obj->denseElementsAreFrozen())
return DenseElementResult::Incomplete;
if (!obj->maybeCopyElementsForWrite(cx))
return DenseElementResult::Failure;
obj->moveDenseElements(dstStart, srcStart, length);
return DenseElementResult::Success; return DenseElementResult::Success;
} }
static DenseElementResult DefineBoxedOrUnboxedFunctor1(ShiftMoveBoxedOrUnboxedDenseElements, JSObject*);
void
js::ArrayShiftMoveElements(JSObject* obj)
{
MOZ_ASSERT_IF(obj->is<ArrayObject>(), obj->as<ArrayObject>().lengthIsWritable());
ShiftMoveBoxedOrUnboxedDenseElementsFunctor functor(obj);
JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success);
}
template <JSValueType Type>
DenseElementResult
ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval) ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval)
{ {
if (ObjectMayHaveExtraIndexedProperties(obj)) if (ObjectMayHaveExtraIndexedProperties(obj))
@ -2152,22 +2203,25 @@ ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval)
if (MOZ_UNLIKELY(group->hasAllFlags(OBJECT_FLAG_ITERATED))) if (MOZ_UNLIKELY(group->hasAllFlags(OBJECT_FLAG_ITERATED)))
return DenseElementResult::Incomplete; return DenseElementResult::Incomplete;
size_t initlen = obj->as<NativeObject>().getDenseInitializedLength(); size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
if (initlen == 0) if (initlen == 0)
return DenseElementResult::Incomplete; return DenseElementResult::Incomplete;
rval.set(obj->as<NativeObject>().getDenseElement(0)); rval.set(GetBoxedOrUnboxedDenseElement<Type>(obj, 0));
if (rval.isMagic(JS_ELEMENTS_HOLE)) if (rval.isMagic(JS_ELEMENTS_HOLE))
rval.setUndefined(); 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) if (result != DenseElementResult::Success)
return result; return result;
SetInitializedLength(cx, obj.as<NativeObject>(), initlen - 1); SetBoxedOrUnboxedInitializedLength<Type>(cx, obj, initlen - 1);
return DenseElementResult::Success; return DenseElementResult::Success;
} }
DefineBoxedOrUnboxedFunctor3(ArrayShiftDenseKernel,
JSContext*, HandleObject, MutableHandleValue);
/* ES5 15.4.4.9 */ /* ES5 15.4.4.9 */
bool bool
js::array_shift(JSContext* cx, unsigned argc, Value* vp) 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; uint32_t newlen = len - 1;
/* Fast paths. */ /* 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::Incomplete) {
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return false; return false;
@ -2253,6 +2308,9 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
if (args.length() > 0) { if (args.length() > 0) {
/* Slide up the array to make room for all args at the bottom. */ /* Slide up the array to make room for all args at the bottom. */
if (length > 0) { 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; bool optimized = false;
do { do {
if (!obj->is<ArrayObject>()) 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| * Returns true if this is a dense or unboxed array whose |count| properties
* (exclusive) may be accessed (get, set, delete) directly through its * starting from |startingIndex| may be accessed (get, set, delete) directly
* contiguous vector of elements without fear of getters, setters, etc. along * through its contiguous vector of elements without fear of getters, setters,
* the prototype chain, or of enumerators requiring notification of * etc. along the prototype chain, or of enumerators requiring notification of
* modifications. * modifications.
*/ */
static inline bool static inline bool
@ -2326,11 +2384,11 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
return false; return false;
/* There's no optimizing possible if it's not an array. */ /* There's no optimizing possible if it's not an array. */
if (!arr->is<ArrayObject>()) if (!arr->is<ArrayObject>() && !arr->is<UnboxedArrayObject>())
return false; return false;
/* If it's a frozen array, always pick the slow path */ /* 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; return false;
/* /*
@ -2362,23 +2420,7 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co
* is subsumed by the initializedLength comparison.) * is subsumed by the initializedLength comparison.)
*/ */
return !ObjectMayHaveExtraIndexedProperties(arr) && return !ObjectMayHaveExtraIndexedProperties(arr) &&
startingIndex + count <= arr->as<NativeObject>().getDenseInitializedLength(); startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr);
}
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;
} }
static inline bool static inline bool
@ -2472,9 +2514,7 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
/* Steps 10-11. */ /* Steps 10-11. */
DebugOnly<DenseElementResult> result = DebugOnly<DenseElementResult> result =
CopyDenseElements(cx, &arr->as<NativeObject>(), CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount);
&obj->as<NativeObject>(), 0,
actualStart, actualDeleteCount);
MOZ_ASSERT(result.value == DenseElementResult::Success); MOZ_ASSERT(result.value == DenseElementResult::Success);
/* Step 12 (implicit). */ /* Step 12 (implicit). */
@ -2511,13 +2551,14 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse
if (CanOptimizeForDenseStorage(obj, 0, len, cx)) { if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
/* Steps 15.a-b. */ /* Steps 15.a-b. */
DenseElementResult result = DenseElementResult result =
MoveDenseElements(cx, &obj->as<NativeObject>(), targetIndex, sourceIndex, len - sourceIndex); MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex,
len - sourceIndex);
MOZ_ASSERT(result != DenseElementResult::Incomplete); MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return false; return false;
/* Steps 15.c-d. */ /* Steps 15.c-d. */
SetInitializedLength(cx, obj.as<NativeObject>(), finalLength); SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength);
} else { } else {
/* /*
* This is all very slow if the length is very large. We don't yet * 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)) { if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
DenseElementResult result = DenseElementResult result =
MoveDenseElements(cx, &obj->as<NativeObject>(), actualStart + itemCount, MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount,
actualStart + actualDeleteCount, actualStart + actualDeleteCount,
len - (actualStart + actualDeleteCount)); len - (actualStart + actualDeleteCount));
MOZ_ASSERT(result != DenseElementResult::Incomplete); MOZ_ASSERT(result != DenseElementResult::Incomplete);
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return false; return false;
/* Steps 16.a-b. */ /* Steps 16.a-b. */
SetInitializedLength(cx, obj.as<NativeObject>(), len + itemCount - actualDeleteCount); SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount);
} else { } else {
RootedValue fromValue(cx); RootedValue fromValue(cx);
for (double k = len - actualDeleteCount; k > actualStart; k--) { for (double k = len - actualDeleteCount; k > actualStart; k--) {
@ -2790,7 +2831,7 @@ SliceSlowly(JSContext* cx, HandleObject obj, HandleObject receiver,
} }
static bool 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); MOZ_ASSERT(begin <= end);
@ -2840,28 +2881,26 @@ ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint32_t length, uint32_t be
begin = end; begin = end;
if (!ObjectMayHaveExtraIndexedProperties(obj)) { if (!ObjectMayHaveExtraIndexedProperties(obj)) {
size_t initlen = obj->as<NativeObject>().getDenseInitializedLength(); size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
size_t count = 0; size_t count = 0;
if (initlen > begin) if (initlen > begin)
count = Min<size_t>(initlen - begin, end - 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) if (!narr)
return false; return false;
SetAnyBoxedOrUnboxedArrayLength(cx, narr, end - begin);
MOZ_ASSERT(count >= narr->as<ArrayObject>().length());
narr->as<ArrayObject>().setLength(cx, count);
if (count) { if (count) {
DebugOnly<DenseElementResult> result = 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); MOZ_ASSERT(result.value == DenseElementResult::Success);
} }
arr.set(narr); arr.set(narr);
return true; return true;
} }
RootedArrayObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin)); RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin));
if (!narr) if (!narr)
return false; return false;
@ -2978,10 +3017,11 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp)
return true; return true;
} }
static bool template <JSValueType Type>
ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t endArg, ArrayObject* result) 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 begin = NormalizeSliceTerm(beginArg, length);
uint32_t end = NormalizeSliceTerm(endArg, length); uint32_t end = NormalizeSliceTerm(endArg, length);
@ -2989,33 +3029,33 @@ ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t
if (begin > end) if (begin > end)
begin = end; begin = end;
size_t initlen = arr->getDenseInitializedLength(); size_t initlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
size_t count = Min<size_t>(initlen - begin, end - begin);
if (initlen > begin) { if (initlen > begin) {
size_t count = Min<size_t>(initlen - begin, end - begin);
if (count) { if (count) {
if (!result->ensureElements(cx, count)) DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements<Type>(cx, result, count);
return false; if (rv != DenseElementResult::Success)
CopyDenseElements(cx, &result->as<NativeObject>(), &arr->as<NativeObject>(), 0, begin, count); return rv;
CopyBoxedOrUnboxedDenseElements<Type, Type>(cx, result, obj, 0, begin, count);
} }
} }
MOZ_ASSERT(count >= result->length()); SetAnyBoxedOrUnboxedArrayLength(cx, result, end - begin);
result->setLength(cx, count); return DenseElementResult::Success;
return true;
} }
DefineBoxedOrUnboxedFunctor5(ArraySliceDenseKernel,
JSContext*, JSObject*, int32_t, int32_t, JSObject*);
JSObject* JSObject*
js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end, js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end,
HandleObject result) HandleObject result)
{ {
if (result && IsArraySpecies(cx, obj)) { if (result && IsArraySpecies(cx, obj)) {
if (!ArraySliceDenseKernel(cx, &obj->as<ArrayObject>(), begin, end, ArraySliceDenseKernelFunctor functor(cx, obj, begin, end, result);
&result->as<ArrayObject>())) DenseElementResult rv = CallBoxedOrUnboxedSpecialization(functor, result);
{ MOZ_ASSERT(rv != DenseElementResult::Incomplete);
return nullptr; return rv == DenseElementResult::Success ? result : nullptr;
}
return result;
} }
// Slower path if the JIT wasn't able to allocate an object inline. // 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 static bool
ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr) 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) if (!obj)
return false; 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) if (!obj)
return false; return false;
@ -3246,7 +3286,7 @@ js::array_construct(JSContext* cx, unsigned argc, Value* vp)
return ArrayConstructorImpl(cx, args, /* isConstructor = */ false); return ArrayConstructorImpl(cx, args, /* isConstructor = */ false);
} }
ArrayObject* JSObject*
js::ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt) js::ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt)
{ {
if (lengthInt < 0) { if (lengthInt < 0) {
@ -3541,7 +3581,7 @@ js::NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSOb
return arr; return arr;
} }
ArrayObject* JSObject*
js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap) js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap)
{ {
MOZ_ASSERT(!gc::IsInsideNursery(templateObject)); MOZ_ASSERT(!gc::IsInsideNursery(templateObject));
@ -3554,21 +3594,30 @@ js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc
return arr; return arr;
} }
// Return a new array with the specified length and allocated capacity (up to // Return a new boxed or unboxed array with the specified length and allocated
// maxLength), using the specified group if possible. If the specified group // capacity (up to maxLength), using the specified group if possible. If the
// cannot be used, ensure that the created array at least has the given // specified group cannot be used, ensure that the created array at least has
// [[Prototype]]. // the given [[Prototype]].
template <uint32_t maxLength> template <uint32_t maxLength>
static inline ArrayObject* static inline JSObject*
NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length, NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind = GenericObject) NewObjectKind newKind = GenericObject)
{ {
MOZ_ASSERT(newKind != SingletonObject); MOZ_ASSERT(newKind != SingletonObject);
if (group->shouldPreTenure()) if (group->maybePreliminaryObjects())
group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject; newKind = TenuredObject;
RootedObject proto(cx, group->proto().toObject()); 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); ArrayObject* res = NewArray<maxLength>(cx, length, proto, newKind);
if (!res) if (!res)
return nullptr; return nullptr;
@ -3580,17 +3629,20 @@ NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length
if (res->length() > INT32_MAX) if (res->length() > INT32_MAX)
res->setLength(cx, res->length()); res->setLength(cx, res->length());
if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
preliminaryObjects->registerNewObject(res);
return res; return res;
} }
ArrayObject* JSObject*
js::NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length, js::NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind) NewObjectKind newKind)
{ {
return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind); return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind);
} }
ArrayObject* JSObject*
js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length) js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length)
{ {
return NewArrayTryUseGroup<ArrayObject::EagerAllocationMaxLength>(cx, group, 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 // Return a new array with the default prototype and specified allocated
// capacity and length. If possible, try to reuse the group of the input // 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 // 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> template <uint32_t maxLength>
static inline ArrayObject* static inline JSObject*
NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind = GenericObject) NewObjectKind newKind = GenericObject)
{ {
if (!obj->is<ArrayObject>()) if (!obj->is<ArrayObject>() && !obj->is<UnboxedArrayObject>())
return NewArray<maxLength>(cx, length, nullptr, newKind); return NewArray<maxLength>(cx, length, nullptr, newKind);
if (obj->staticPrototype() != cx->global()->maybeGetArrayPrototype()) 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); return NewArrayTryUseGroup<maxLength>(cx, group, length, newKind);
} }
ArrayObject* JSObject*
js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind) NewObjectKind newKind)
{ {
return NewArrayTryReuseGroup<UINT32_MAX>(cx, obj, length, newKind); return NewArrayTryReuseGroup<UINT32_MAX>(cx, obj, length, newKind);
} }
ArrayObject* JSObject*
js::NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length) js::NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length)
{ {
return NewArrayTryReuseGroup<ArrayObject::EagerAllocationMaxLength>(cx, obj, length); return NewArrayTryReuseGroup<ArrayObject::EagerAllocationMaxLength>(cx, obj, length);
} }
ArrayObject* JSObject*
js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
NewObjectKind newKind) NewObjectKind newKind)
{ {
@ -3641,7 +3696,7 @@ js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind); return NewArrayTryUseGroup<UINT32_MAX>(cx, group, length, newKind);
} }
ArrayObject* JSObject*
js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto) js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto)
{ {
RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, 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); 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, js::NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length, NewObjectKind newKind, const Value* vp, size_t length, NewObjectKind newKind,
ShouldUpdateTypes updateTypes) 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) if (!obj)
return nullptr; return nullptr;
DenseElementResult result = obj->setOrExtendDenseElements(cx, 0, vp, length, updateTypes); DenseElementResult result =
SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, 0, vp, length, updateTypes);
if (result == DenseElementResult::Failure) if (result == DenseElementResult::Failure)
return nullptr; return nullptr;
MOZ_ASSERT(result == DenseElementResult::Success); 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;
return obj; return obj;
} }
ArrayObject* JSObject*
js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length, js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto /* = nullptr */) HandleObject proto /* = nullptr */)
{ {

View File

@ -72,37 +72,49 @@ extern ArrayObject*
NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject* templateObject); NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject* templateObject);
/* Create a dense array with the same copy-on-write elements as another object. */ /* 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); 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, NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind = GenericObject); NewObjectKind newKind = GenericObject);
extern ArrayObject* extern JSObject*
NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length); NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length);
extern ArrayObject* extern JSObject*
NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind = GenericObject); NewObjectKind newKind = GenericObject);
extern ArrayObject* extern JSObject*
NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length); NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length);
extern ArrayObject* extern JSObject*
NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
NewObjectKind newKind = GenericObject); NewObjectKind newKind = GenericObject);
extern ArrayObject* extern JSObject*
NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto); 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, NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length, const Value* vp, size_t length,
NewObjectKind newKind = GenericObject, NewObjectKind newKind = GenericObject,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update); ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
extern ArrayObject* extern JSObject*
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length, NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto = nullptr); HandleObject proto = nullptr);
@ -117,6 +129,13 @@ NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleV
extern bool extern bool
WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index); 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 extern bool
GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp); GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp);
@ -150,7 +169,7 @@ extern bool
array_join(JSContext* cx, unsigned argc, js::Value* vp); array_join(JSContext* cx, unsigned argc, js::Value* vp);
extern void extern void
ArrayShiftMoveElements(NativeObject* obj); ArrayShiftMoveElements(JSObject* obj);
extern bool extern bool
array_shift(JSContext* cx, unsigned argc, js::Value* vp); array_shift(JSContext* cx, unsigned argc, js::Value* vp);
@ -182,7 +201,7 @@ extern const JSJitInfo array_splice_info;
extern bool extern bool
NewbornArrayPush(JSContext* cx, HandleObject obj, const Value& v); NewbornArrayPush(JSContext* cx, HandleObject obj, const Value& v);
extern ArrayObject* extern JSObject*
ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt); ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt);
#ifdef DEBUG #ifdef DEBUG

View File

@ -270,7 +270,7 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls)
if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>()) if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>())
*cls = ESClass::Object; *cls = ESClass::Object;
else if (obj->is<ArrayObject>()) else if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
*cls = ESClass::Array; *cls = ESClass::Array;
else if (obj->is<NumberObject>()) else if (obj->is<NumberObject>())
*cls = ESClass::Number; *cls = ESClass::Number;

View File

@ -1136,18 +1136,19 @@ js::CloneObject(JSContext* cx, HandleObject obj, Handle<js::TaggedProto> proto)
} }
static bool 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(!obj->isSingleton());
MOZ_ASSERT(!arr->isIndexed()); 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)) if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), length))
return false; return false;
size_t initlen = arr->getDenseInitializedLength(); size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj);
for (size_t i = 0; i < initlen; i++) for (size_t i = 0; i < initlen; i++)
values[i].set(arr->getDenseElement(i)); values[i].set(GetAnyBoxedOrUnboxedDenseElement(obj, i));
return true; return true;
} }
@ -1199,12 +1200,13 @@ js::DeepCloneObjectLiteral(JSContext* cx, HandleObject obj, NewObjectKind newKin
MOZ_ASSERT_IF(obj->isSingleton(), MOZ_ASSERT_IF(obj->isSingleton(),
cx->compartment()->behaviors().getSingletonsAsTemplates()); cx->compartment()->behaviors().getSingletonsAsTemplates());
MOZ_ASSERT(obj->is<PlainObject>() || MOZ_ASSERT(obj->is<PlainObject>() ||
obj->is<ArrayObject>()); obj->is<ArrayObject>() ||
obj->is<UnboxedArrayObject>());
MOZ_ASSERT(newKind != SingletonObject); MOZ_ASSERT(newKind != SingletonObject);
if (obj->is<ArrayObject>()) { if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) {
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx)); Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
if (!GetScriptArrayObjectElements(cx, obj.as<ArrayObject>(), &values)) if (!GetScriptArrayObjectElements(cx, obj, &values))
return nullptr; return nullptr;
// Deep clone any elements. // Deep clone any elements.
@ -1318,8 +1320,9 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
{ {
if (mode == XDR_ENCODE) { if (mode == XDR_ENCODE) {
MOZ_ASSERT(obj->is<PlainObject>() || MOZ_ASSERT(obj->is<PlainObject>() ||
obj->is<ArrayObject>()); obj->is<ArrayObject>() ||
isArray = obj->is<ArrayObject>() ? 1 : 0; obj->is<UnboxedArrayObject>());
isArray = (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>()) ? 1 : 0;
} }
if (!xdr->codeUint32(&isArray)) if (!xdr->codeUint32(&isArray))
@ -1331,11 +1334,8 @@ js::XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj)
if (isArray) { if (isArray) {
Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx)); Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
if (mode == XDR_ENCODE) { if (mode == XDR_ENCODE && !GetScriptArrayObjectElements(cx, obj, &values))
RootedArrayObject arr(cx, &obj->as<ArrayObject>()); return false;
if (!GetScriptArrayObjectElements(cx, arr, &values))
return false;
}
uint32_t initialized; uint32_t initialized;
if (mode == XDR_ENCODE) if (mode == XDR_ENCODE)
@ -2315,6 +2315,11 @@ js::LookupOwnPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Shape**
// us the resolve hook won't define a property with this id. // us the resolve hook won't define a property with this id.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj)) if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj))
return false; return false;
} else if (obj->is<UnboxedArrayObject>()) {
if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
MarkNonNativePropertyFound<NoGC>(propp);
return true;
}
} else if (obj->is<TypedObject>()) { } else if (obj->is<TypedObject>()) {
if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) { if (obj->as<TypedObject>().typeDescr().hasProperty(cx->names(), id)) {
MarkNonNativePropertyFound<NoGC>(propp); MarkNonNativePropertyFound<NoGC>(propp);
@ -3606,6 +3611,16 @@ JSObject::allocKindForTenure(const js::Nursery& nursery) const
if (IsProxy(this)) if (IsProxy(this))
return as<ProxyObject>().allocKindForTenure(); return as<ProxyObject>().allocKindForTenure();
// 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 // Inlined typed objects are followed by their data, so make sure we copy
// it all over to the new object. // it all over to the new object.
if (is<InlineTypedObject>()) { if (is<InlineTypedObject>()) {

View File

@ -40,6 +40,8 @@ MaybeConvertUnboxedObjectToNative(ExclusiveContext* cx, JSObject* obj)
{ {
if (obj->is<UnboxedPlainObject>()) if (obj->is<UnboxedPlainObject>())
return UnboxedPlainObject::convertToNative(cx->asJSContext(), obj); return UnboxedPlainObject::convertToNative(cx->asJSContext(), obj);
if (obj->is<UnboxedArrayObject>())
return UnboxedArrayObject::convertToNative(cx->asJSContext(), obj);
return true; return true;
} }

View File

@ -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. // 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, SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
HandleObjectGroup group) 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(""). // 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) CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
{ {
size_t strLength = str->length(); 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. // 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) js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
{ {

View File

@ -465,7 +465,7 @@ FileEscapedString(FILE* fp, const char* chars, size_t length, uint32_t quote)
return res; return res;
} }
ArrayObject* JSObject*
str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep,
uint32_t limit); uint32_t limit);

View File

@ -317,6 +317,7 @@ static bool enableIon = false;
static bool enableAsmJS = false; static bool enableAsmJS = false;
static bool enableWasm = false; static bool enableWasm = false;
static bool enableNativeRegExp = false; static bool enableNativeRegExp = false;
static bool enableUnboxedArrays = false;
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT; static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmAlwaysBaseline = false; static bool enableWasmAlwaysBaseline = false;
static bool enableArrayProtoValues = true; static bool enableArrayProtoValues = true;
@ -7262,6 +7263,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
enableAsmJS = !op.getBoolOption("no-asmjs"); enableAsmJS = !op.getBoolOption("no-asmjs");
enableWasm = !op.getBoolOption("no-wasm"); enableWasm = !op.getBoolOption("no-wasm");
enableNativeRegExp = !op.getBoolOption("no-native-regexp"); enableNativeRegExp = !op.getBoolOption("no-native-regexp");
enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline"); enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values"); enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
@ -7271,6 +7273,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
.setWasm(enableWasm) .setWasm(enableWasm)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline) .setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp) .setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
.setArrayProtoValues(enableArrayProtoValues); .setArrayProtoValues(enableArrayProtoValues);
if (op.getBoolOption("wasm-check-bce")) if (op.getBoolOption("wasm-check-bce"))
@ -7710,6 +7713,7 @@ main(int argc, char** argv, char** envp)
|| !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation") || !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
|| !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp 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', "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-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', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values") || !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")

View File

@ -593,7 +593,7 @@ InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t
JSOp op = JSOp(*pc); JSOp op = JSOp(*pc);
MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC); 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) { if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);

View File

@ -1939,7 +1939,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */ /* Various 1-byte no-ops. */
CASE(JSOP_NOP) CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING) CASE(JSOP_NOP_DESTRUCTURING)
CASE(JSOP_UNUSED126)
CASE(JSOP_UNUSED211) CASE(JSOP_UNUSED211)
CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED221)
@ -3682,6 +3681,7 @@ CASE(JSOP_NEWINIT)
END_CASE(JSOP_NEWINIT) END_CASE(JSOP_NEWINIT)
CASE(JSOP_NEWARRAY) CASE(JSOP_NEWARRAY)
CASE(JSOP_SPREADCALLARRAY)
{ {
uint32_t length = GET_UINT32(REGS.pc); uint32_t length = GET_UINT32(REGS.pc);
JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length); JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length);
@ -4979,7 +4979,7 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
newKind = TenuredObject; newKind = TenuredObject;
} }
RootedPlainObject obj(cx); RootedObject obj(cx);
if (*pc == JSOP_NEWOBJECT) { if (*pc == JSOP_NEWOBJECT) {
RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>()); RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
@ -5047,6 +5047,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
if (group->shouldPreTenure() || group->maybePreliminaryObjects()) if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject; newKind = TenuredObject;
if (group->maybeUnboxedLayout())
return UnboxedArrayObject::create(cx, group, length, newKind);
} }
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind);
@ -5057,6 +5060,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
MOZ_ASSERT(obj->isSingleton()); MOZ_ASSERT(obj->isSingleton());
} else { } else {
obj->setGroup(group); obj->setGroup(group);
if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
preliminaryObjects->registerNewObject(obj);
} }
return obj; return obj;
@ -5069,6 +5075,12 @@ js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; 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(), ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(),
nullptr, newKind); nullptr, newKind);
if (!obj) if (!obj)

View File

@ -606,8 +606,8 @@ JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements)
{ {
MOZ_ASSERT(&elements == &stack.back().elements()); MOZ_ASSERT(&elements == &stack.back().elements());
ArrayObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(), JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
GenericObject); GenericObject);
if (!obj) if (!obj)
return false; return false;

View File

@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t
return DenseElementResult::Success; 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 inline Value
NativeObject::getDenseOrTypedArrayElement(uint32_t idx) NativeObject::getDenseOrTypedArrayElement(uint32_t idx)
{ {

View File

@ -339,19 +339,16 @@ IsObjectValueInCompartment(const Value& v, JSCompartment* comp);
#endif #endif
// Operations which change an object's dense elements can either succeed, fail, // 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 // or be unable to complete. For native objects, the latter is used when the
// become sparse instead. The enum below is used for such operations. // 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 { enum class DenseElementResult {
Failure, Failure,
Success, Success,
Incomplete Incomplete
}; };
enum class ShouldUpdateTypes {
Update,
DontUpdate
};
/* /*
* NativeObject specifies the internal implementation of a native object. * NativeObject specifies the internal implementation of a native object.
* *
@ -1154,10 +1151,6 @@ class NativeObject : public ShapedObject
elementsRangeWriteBarrierPost(dstStart, count); elementsRangeWriteBarrierPost(dstStart, count);
} }
inline DenseElementResult
setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, uint32_t count,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
bool shouldConvertDoubleElements() { bool shouldConvertDoubleElements() {
return getElementsHeader()->shouldConvertDoubleElements(); return getElementsHeader()->shouldConvertDoubleElements();
} }

View File

@ -777,7 +777,7 @@ GetValueTypeForTable(const Value& v)
return type; return type;
} }
/* static */ ArrayObject* /* static */ JSObject*
ObjectGroup::newArrayObject(ExclusiveContext* cx, ObjectGroup::newArrayObject(ExclusiveContext* cx,
const Value* vp, size_t length, const Value* vp, size_t length,
NewObjectKind newKind, NewArrayKind arrayKind) NewObjectKind newKind, NewArrayKind arrayKind)
@ -841,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx,
AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType); 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)) if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
return nullptr; return nullptr;
} }
// The type of the elements being added will already be reflected in type // 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; 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); return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
} }
@ -857,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
{ {
MOZ_ASSERT(source->group() != target->group()); 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; 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++) { return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
Value v = source->as<ArrayObject>().getDenseElement(i);
AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
} }
return true; return true;
@ -1429,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)) { if (!table->add(p, key, res)) {
ReportOutOfMemory(cx); ReportOutOfMemory(cx);
return nullptr; return nullptr;

View File

@ -505,11 +505,11 @@ class ObjectGroup : public gc::TenuredCell
UnknownIndex // Make an array with an unknown element type. UnknownIndex // Make an array with an unknown element type.
}; };
// Create an ArrayObject with the specified elements and a group specialized // Create an ArrayObject or UnboxedArrayObject with the specified elements
// for the elements. // and a group specialized for the elements.
static ArrayObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length, static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
NewObjectKind newKind, NewObjectKind newKind,
NewArrayKind arrayKind = NewArrayKind::Normal); NewArrayKind arrayKind = NewArrayKind::Normal);
// Create a PlainObject or UnboxedPlainObject with the specified properties // Create a PlainObject or UnboxedPlainObject with the specified properties
// and a group specialized for those properties. // and a group specialized for those properties.

View File

@ -1281,7 +1281,17 @@
* Stack: receiver, obj, propval => obj[propval] * 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_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. * Defines the given function on the current scope.

View File

@ -19,7 +19,7 @@ ReceiverGuard::ReceiverGuard(JSObject* obj)
group = obj->group(); group = obj->group();
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
shape = expando->lastProperty(); shape = expando->lastProperty();
} else if (obj->is<TypedObject>()) { } else if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
group = obj->group(); group = obj->group();
} else { } else {
shape = obj->maybeShape(); shape = obj->maybeShape();
@ -34,7 +34,7 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
const Class* clasp = group->clasp(); const Class* clasp = group->clasp();
if (clasp == &UnboxedPlainObject::class_) { if (clasp == &UnboxedPlainObject::class_) {
// Keep both group and shape. // Keep both group and shape.
} else if (IsTypedObjectClass(clasp)) { } else if (clasp == &UnboxedArrayObject::class_ || IsTypedObjectClass(clasp)) {
this->shape = nullptr; this->shape = nullptr;
} else { } else {
this->group = nullptr; this->group = nullptr;
@ -49,8 +49,8 @@ HeapReceiverGuard::keyBits(JSObject* obj)
// Both the group and shape need to be guarded for unboxed plain objects. // Both the group and shape need to be guarded for unboxed plain objects.
return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1; return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1;
} }
if (obj->is<TypedObject>()) { if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
// Only the group needs to be guarded for typed objects. // Only the group needs to be guarded for unboxed arrays and typed objects.
return 2; return 2;
} }
// Other objects only need the shape to be guarded. // Other objects only need the shape to be guarded.

View File

@ -82,7 +82,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const
return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal(); return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal();
} }
ArrayObject* JSObject*
InterpreterFrame::createRestParameter(JSContext* cx) InterpreterFrame::createRestParameter(JSContext* cx)
{ {
MOZ_ASSERT(script()->hasRest()); MOZ_ASSERT(script()->hasRest());

View File

@ -523,7 +523,7 @@ class InterpreterFrame
ArgumentsObject& argsObj() const; ArgumentsObject& argsObj() const;
void initArgsObj(ArgumentsObject& argsobj); void initArgsObj(ArgumentsObject& argsobj);
ArrayObject* createRestParameter(JSContext* cx); JSObject* createRestParameter(JSContext* cx);
/* /*
* Environment chain * Environment chain

View File

@ -2509,7 +2509,7 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
bool bool
js::ClassCanHaveExtraProperties(const Class* clasp) js::ClassCanHaveExtraProperties(const Class* clasp)
{ {
if (clasp == &UnboxedPlainObject::class_) if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
return false; return false;
return clasp->getResolve() return clasp->getResolve()
|| clasp->getOpsLookupProperty() || clasp->getOpsLookupProperty()
@ -3400,7 +3400,7 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun,
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
void void
PreliminaryObjectArray::registerNewObject(PlainObject* res) PreliminaryObjectArray::registerNewObject(JSObject* res)
{ {
// The preliminary object pointers are weak, and won't be swept properly // The preliminary object pointers are weak, and won't be swept properly
// during nursery collections, so the preliminary objects need to be // during nursery collections, so the preliminary objects need to be
@ -3418,7 +3418,7 @@ PreliminaryObjectArray::registerNewObject(PlainObject* res)
} }
void void
PreliminaryObjectArray::unregisterObject(PlainObject* obj) PreliminaryObjectArray::unregisterObject(JSObject* obj)
{ {
for (size_t i = 0; i < COUNT; i++) { for (size_t i = 0; i < COUNT; i++) {
if (objects[i] == obj) { if (objects[i] == obj) {

View File

@ -871,8 +871,8 @@ class PreliminaryObjectArray
public: public:
PreliminaryObjectArray() = default; PreliminaryObjectArray() = default;
void registerNewObject(PlainObject* res); void registerNewObject(JSObject* res);
void unregisterObject(PlainObject* obj); void unregisterObject(JSObject* obj);
JSObject* get(size_t i) const { JSObject* get(size_t i) const {
MOZ_ASSERT(i < COUNT); MOZ_ASSERT(i < COUNT);

View File

@ -172,6 +172,669 @@ UnboxedPlainObject::layout() const
return group()->unboxedLayout(); 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 } // namespace js
#endif // vm_UnboxedObject_inl_h #endif // vm_UnboxedObject_inl_h

View File

@ -417,6 +417,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedObjectGroup replacementGroup(cx); 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 // 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. // 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 // This is done so that the size of the replacment group's objects is the
@ -424,6 +426,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
// slot accesses later on for sites that see converted objects from this // slot accesses later on for sites that see converted objects from this
// group and objects that were allocated using the replacement new group. // group and objects that were allocated using the replacement new group.
if (layout.newScript()) { if (layout.newScript()) {
MOZ_ASSERT(!layout.isArray());
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
if (!replacementGroup) if (!replacementGroup)
return false; return false;
@ -449,14 +453,15 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedScript script(cx, layout.allocationScript()); RootedScript script(cx, layout.allocationScript());
jsbytecode* pc = layout.allocationPc(); jsbytecode* pc = layout.allocationPc();
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto);
if (!replacementGroup) if (!replacementGroup)
return false; return false;
PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>(); PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty()); replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object, JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object;
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key,
replacementGroup); replacementGroup);
// Clear any baseline information at this opcode which might use the old group. // Clear any baseline information at this opcode which might use the old group.
@ -472,12 +477,22 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
} }
} }
size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind()); size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind());
RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0)); 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) if (!shape)
return false; return false;
MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
// Add shapes for each property, if this is for a plain object. // Add shapes for each property, if this is for a plain object.
for (size_t i = 0; i < layout.properties().length(); i++) { for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i]; const UnboxedLayout::Property& property = layout.properties()[i];
@ -490,7 +505,7 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
} }
ObjectGroup* nativeGroup = ObjectGroup* nativeGroup =
ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto, ObjectGroupCompartment::makeGroup(cx, clasp, proto,
group->flags() & OBJECT_FLAG_DYNAMIC_MASK); group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
if (!nativeGroup) if (!nativeGroup)
return false; return false;
@ -498,19 +513,24 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
// No sense propagating if we don't know what we started with. // No sense propagating if we don't know what we started with.
if (!group->unknownProperties()) { if (!group->unknownProperties()) {
// Propagate all property types from the old group to the new group. // Propagate all property types from the old group to the new group.
for (size_t i = 0; i < layout.properties().length(); i++) { if (layout.isArray()) {
const UnboxedLayout::Property& property = layout.properties()[i]; if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
jsid id = NameToId(property.name);
if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
return false; 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 we are OOM we may not be able to propagate properties.
if (nativeGroup->unknownProperties()) if (nativeGroup->unknownProperties())
break; break;
HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id); HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
if (nativeProperty && nativeProperty->canSetDefinite(i)) if (nativeProperty && nativeProperty->canSetDefinite(i))
nativeProperty->setDefinite(i); nativeProperty->setDefinite(i);
}
} }
} else { } else {
// If we skip, though, the new group had better agree. // If we skip, though, the new group had better agree.
@ -925,6 +945,692 @@ const Class UnboxedPlainObject::class_ = {
&UnboxedPlainObjectObjectOps &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 // API
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
@ -935,6 +1641,31 @@ NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
return values[(*valueCursor)++]; return values[(*valueCursor)++];
} }
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)));
}
void void
UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
Handle<GCVector<Value>> values, size_t* valueCursor) Handle<GCVector<Value>> values, size_t* valueCursor)
@ -944,3 +1675,58 @@ UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
for (size_t i = 0; i < layout().properties().length(); i++) for (size_t i = 0; i < layout().properties().length(); i++)
JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor))); JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
} }
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);
}

View File

@ -96,11 +96,17 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
// from an array of values. // from an array of values.
GCPtrJitCode constructorCode_; GCPtrJitCode constructorCode_;
// The following members are only used for unboxed arrays.
// The type of array elements.
JSValueType elementType_;
public: public:
UnboxedLayout() UnboxedLayout()
: nativeGroup_(nullptr), nativeShape_(nullptr), : nativeGroup_(nullptr), nativeShape_(nullptr),
allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr), allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr) size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
elementType_(JSVAL_TYPE_MAGIC)
{} {}
bool initProperties(const PropertyVector& properties, size_t size) { bool initProperties(const PropertyVector& properties, size_t size) {
@ -108,6 +114,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
return properties_.appendAll(properties); return properties_.appendAll(properties);
} }
void initArray(JSValueType elementType) {
elementType_ = elementType;
}
~UnboxedLayout() { ~UnboxedLayout() {
if (newScript_) if (newScript_)
newScript_->clear(); newScript_->clear();
@ -120,6 +130,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_.init(nullptr); constructorCode_.init(nullptr);
} }
bool isArray() const {
return elementType_ != JSVAL_TYPE_MAGIC;
}
void detachFromCompartment(); void detachFromCompartment();
const PropertyVector& properties() const { const PropertyVector& properties() const {
@ -187,6 +201,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_ = code; constructorCode_ = code;
} }
JSValueType elementType() const {
return elementType_;
}
inline gc::AllocKind getAllocKind() const; inline gc::AllocKind getAllocKind() const;
void trace(JSTracer* trc); void trace(JSTracer* trc);
@ -306,6 +324,193 @@ UnboxedLayout::getAllocKind() const
return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + 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
namespace JS { namespace JS {