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)
{
MOZ_ASSERT(!self->namespace_());
MOZ_ASSERT(exports->is<ArrayObject>());
MOZ_ASSERT(exports->is<ArrayObject>() || exports->is<UnboxedArrayObject>());
RootedModuleNamespaceObject ns(cx, ModuleNamespaceObject::create(cx, self));
if (!ns)

View File

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

View File

@ -543,7 +543,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitAtomOp(ParseNode* pn, JSOp op);
MOZ_MUST_USE bool emitArrayLiteral(ParseNode* pn);
MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count);
MOZ_MUST_USE bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
MOZ_MUST_USE bool emitArrayComp(ParseNode* pn);
MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);

View File

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

View File

@ -91,6 +91,10 @@ GetObject(const MDefinition* ins)
case MDefinition::Op_Elements:
case MDefinition::Op_MaybeCopyElementsForWrite:
case MDefinition::Op_MaybeToDoubleElement:
case MDefinition::Op_UnboxedArrayLength:
case MDefinition::Op_UnboxedArrayInitializedLength:
case MDefinition::Op_IncrementUnboxedArrayInitializedLength:
case MDefinition::Op_SetUnboxedArrayInitializedLength:
case MDefinition::Op_TypedArrayLength:
case MDefinition::Op_SetTypedObjectOffset:
case MDefinition::Op_SetDisjointTypedElements:

View File

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

View File

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

View File

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

View File

@ -1375,7 +1375,7 @@ IsNativeDenseElementAccess(HandleObject obj, HandleValue key)
static bool
IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
{
if (!obj->isNative())
if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
return false;
if (key.isInt32() && key.toInt32() >= 0 && !obj->is<TypedArrayObject>())
return true;
@ -1479,6 +1479,20 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
script = rootedScript;
}
// Check for UnboxedArray[int] accesses.
if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) {
JitSpew(JitSpew_BaselineIC, " Generating GetElem(UnboxedArray[Int32]) stub");
ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
obj->group());
ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script));
if (!unboxedStub)
return false;
stub->addNewStub(unboxedStub);
*attached = true;
return true;
}
// Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
if ((obj->is<TypedArrayObject>() || IsPrimitiveArrayTypedObject(obj)) &&
rhs.isNumber() &&
@ -2084,6 +2098,56 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm)
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
//
@ -2388,8 +2452,8 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
bool* isAddingCaseOut, size_t* protoDepthOut)
{
uint32_t initLength = obj->as<NativeObject>().getDenseInitializedLength();
uint32_t capacity = obj->as<NativeObject>().getDenseCapacity();
uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj);
*isAddingCaseOut = false;
*protoDepthOut = 0;
@ -2398,6 +2462,10 @@ CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
if (initLength < oldInitLength || capacity < oldCapacity)
return false;
// Unboxed arrays need to be able to emit floating point code.
if (obj->is<UnboxedArrayObject>() && !obj->runtimeFromMainThread()->jitSupportsFloatingPoint)
return false;
Shape* shape = obj->maybeShape();
// Cannot optimize if the shape changed.
@ -2479,8 +2547,8 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
uint32_t oldCapacity = 0;
uint32_t oldInitLength = 0;
if (index.isInt32() && index.toInt32() >= 0) {
oldCapacity = obj->as<NativeObject>().getDenseCapacity();
oldInitLength = obj->as<NativeObject>().getDenseInitializedLength();
oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj);
oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj);
}
if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) {
@ -2818,6 +2886,29 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
masm.loadValue(valueAddr, tmpVal);
EmitPreBarrier(masm, element, MIRType::Value);
masm.storeValue(tmpVal, element);
} else {
// Set element on an unboxed array.
// Bounds check.
Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
masm.load32(initLength, scratchReg);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
// Load obj->elements.
masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
// Compute the address being written to.
BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_);
Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
masm.Push(R0);
masm.loadValue(valueAddr, R0);
masm.storeUnboxedProperty(address, unboxedType_,
ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
masm.Pop(R0);
}
EmitReturnFromIC(masm);
@ -3011,6 +3102,40 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
BaseIndex element(scratchReg, key, TimesEight);
masm.loadValue(valueAddr, tmpVal);
masm.storeValue(tmpVal, element);
} else {
// Adding element to an unboxed array.
// Bounds check (key == initLength)
Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
masm.load32(initLengthAddr, scratchReg);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
masm.branch32(Assembler::NotEqual, scratchReg, key, &failure);
// Capacity check.
masm.checkUnboxedArrayCapacity(obj, RegisterOrInt32Constant(key), scratchReg, &failure);
// Load obj->elements.
masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
// Write the value first, since this can fail. No need for pre-barrier
// since we're not overwriting an old value.
masm.Push(R0);
Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
masm.loadValue(valueAddr, R0);
BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
masm.storeUnboxedProperty(address, unboxedType_,
ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
masm.Pop(R0);
// Increment initialized length.
masm.add32(Imm32(1), initLengthAddr);
// If length is now <= key, increment length.
Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
Label skipIncrementLength;
masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength);
masm.add32(Imm32(1), lengthAddr);
masm.bind(&skipIncrementLength);
}
EmitReturnFromIC(masm);
@ -5374,6 +5499,13 @@ GetTemplateObjectForSimd(JSContext* cx, JSFunction* target, MutableHandleObject
return true;
}
static void
EnsureArrayGroupAnalyzed(JSContext* cx, JSObject* obj)
{
if (PreliminaryObjectArrayWithTemplate* objects = obj->group()->maybePreliminaryObjects())
objects->maybeAnalyze(cx, obj->group(), /* forceAnalyze = */ true);
}
static bool
GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args,
MutableHandleObject res, bool* skipAttach)
@ -5405,7 +5537,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
// With this and other array templates, analyze the group so that
// we don't end up with a template whose structure might change later.
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject));
return !!res;
if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
}
}
@ -5430,7 +5565,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
return true;
}
res.set(NewFullyAllocatedArrayTryReuseGroup(cx, obj, 0, TenuredObject));
return !!res;
if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
}
}
}
@ -5447,7 +5585,10 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs&
}
res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, 0, TenuredObject));
return !!res;
if (!res)
return false;
EnsureArrayGroupAnalyzed(cx, res);
return true;
}
if (native == StringConstructor) {
@ -5760,24 +5901,15 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
}
static bool
CopyArray(JSContext* cx, HandleArrayObject arr, MutableHandleValue result)
CopyArray(JSContext* cx, HandleObject obj, MutableHandleValue result)
{
uint32_t length = arr->length();
ArrayObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, arr, length, TenuredObject);
uint32_t length = GetAnyBoxedOrUnboxedArrayLength(obj);
JSObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, obj, length, TenuredObject);
if (!nobj)
return false;
MOZ_ASSERT(arr->isNative());
MOZ_ASSERT(nobj->isNative());
MOZ_ASSERT(nobj->as<NativeObject>().getDenseInitializedLength() == 0);
MOZ_ASSERT(arr->as<NativeObject>().getDenseInitializedLength() >= length);
MOZ_ASSERT(nobj->as<NativeObject>().getDenseCapacity() >= length);
EnsureArrayGroupAnalyzed(cx, nobj);
CopyAnyBoxedOrUnboxedDenseElements(cx, nobj, obj, 0, 0, length);
nobj->as<NativeObject>().setDenseInitializedLength(length);
const Value* vp = arr->as<NativeObject>().getDenseElements();
nobj->as<NativeObject>().initDenseElements(0, vp, length);
result.setObject(*nobj);
return true;
}
@ -5808,22 +5940,26 @@ TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script,
RootedValue arr(cx);
// Copy the array before storing in stub.
if (!CopyArray(cx, obj.as<ArrayObject>(), &arr))
if (!CopyArray(cx, obj, &arr))
return false;
// Atomize all elements of the array.
RootedArrayObject arrObj(cx, &arr.toObject().as<ArrayObject>());
uint32_t initLength = arrObj->length();
RootedObject arrObj(cx, &arr.toObject());
uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(arrObj);
for (uint32_t i = 0; i < initLength; i++) {
JSAtom* str = js::AtomizeString(cx, arrObj->getDenseElement(i).toString());
JSAtom* str = js::AtomizeString(cx, GetAnyBoxedOrUnboxedDenseElement(arrObj, i).toString());
if (!str)
return false;
arrObj->setDenseElementWithType(cx, i, StringValue(str));
if (!SetAnyBoxedOrUnboxedDenseElement(cx, arrObj, i, StringValue(str))) {
// The value could not be stored to an unboxed dense element.
return true;
}
}
ICCall_StringSplit::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
script->pcToOffset(pc), str, sep, arrObj);
script->pcToOffset(pc), str, sep,
arr);
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
@ -6711,7 +6847,7 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm)
return true;
}
typedef bool (*CopyArrayFn)(JSContext*, HandleArrayObject, MutableHandleValue);
typedef bool (*CopyArrayFn)(JSContext*, HandleObject, MutableHandleValue);
static const VMFunction CopyArrayInfo = FunctionInfo<CopyArrayFn>(CopyArray, "CopyArray");
bool
@ -8188,6 +8324,19 @@ ICGetElem_Dense::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorSt
return New<ICGetElem_Dense>(cx, space, other.jitCode(), firstMonitorStub, other.shape_);
}
ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub,
ObjectGroup *group)
: ICMonitoredStub(GetElem_UnboxedArray, stubCode, firstMonitorStub),
group_(group)
{ }
/* static */ ICGetElem_UnboxedArray*
ICGetElem_UnboxedArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
ICGetElem_UnboxedArray& other)
{
return New<ICGetElem_UnboxedArray>(cx, space, other.jitCode(), firstMonitorStub, other.group_);
}
ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type)
: ICStub(GetElem_TypedArray, stubCode),
shape_(shape)
@ -8563,8 +8712,8 @@ static bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback*
unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
Value* rest = frame->argv() + numFormals;
ArrayObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject,
ObjectGroup::NewArrayKind::UnknownIndex);
JSObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject,
ObjectGroup::NewArrayKind::UnknownIndex);
if (!obj)
return false;
res.setObject(*obj);

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -234,6 +234,10 @@ class CodeGenerator final : public CodeGeneratorSpecific
void visitSubstr(LSubstr* lir);
void visitInitializedLength(LInitializedLength* lir);
void visitSetInitializedLength(LSetInitializedLength* lir);
void visitUnboxedArrayLength(LUnboxedArrayLength* lir);
void visitUnboxedArrayInitializedLength(LUnboxedArrayInitializedLength* lir);
void visitIncrementUnboxedArrayInitializedLength(LIncrementUnboxedArrayInitializedLength* lir);
void visitSetUnboxedArrayInitializedLength(LSetUnboxedArrayInitializedLength* lir);
void visitNotO(LNotO* ins);
void visitNotV(LNotV* ins);
void visitBoundsCheck(LBoundsCheck* lir);

View File

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

View File

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

View File

@ -639,6 +639,9 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
} else {
masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
}
} else if (obj->is<UnboxedArrayObject>()) {
MOZ_ASSERT(failure);
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
} else if (obj->is<TypedObject>()) {
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfGroup()),
@ -1185,6 +1188,39 @@ GenerateArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher&
return true;
}
static void
GenerateUnboxedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
JSObject* array, Register object, TypedOrValueRegister output,
Label* failures)
{
Register outReg;
if (output.hasValue()) {
outReg = output.valueReg().scratchReg();
} else {
MOZ_ASSERT(output.type() == MIRType::Int32);
outReg = output.typedReg().gpr();
}
MOZ_ASSERT(object != outReg);
TestMatchingReceiver(masm, attacher, object, array, failures);
// Load length.
masm.load32(Address(object, UnboxedArrayObject::offsetOfLength()), outReg);
// Check for a length that fits in an int32.
masm.branchTest32(Assembler::Signed, outReg, outReg, failures);
if (output.hasValue())
masm.tagValue(JSVAL_TYPE_INT32, outReg, output.valueReg());
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(failures);
attacher.jumpNextStub(masm);
}
// In this case, the code for TypedArray and SharedTypedArray is not the same,
// because the code embeds pointers to the respective class arrays. Code that
// caches the stub code must distinguish between the two cases.
@ -1558,6 +1594,40 @@ GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
}
bool
GetPropertyIC::tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, void* returnAddr,
bool* emitted)
{
MOZ_ASSERT(canAttachStub());
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(outerScript->ionScript() == ion);
if (!obj->is<UnboxedArrayObject>())
return true;
if (!JSID_IS_ATOM(id, cx->names().length))
return true;
if (obj->as<UnboxedArrayObject>().length() > INT32_MAX)
return true;
if (!allowArrayLength(cx))
return true;
*emitted = true;
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
Label failures;
emitIdGuard(masm, id, &failures);
StubAttacher attacher(*this);
GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output(), &failures);
return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length",
JS::TrackedOutcome::ICGetPropStub_UnboxedArrayLength);
}
bool
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
HandleObject obj, HandleId id, bool* emitted)
@ -2133,6 +2203,9 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, id, returnAddr, emitted))
return false;
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
return false;
}
@ -3953,7 +4026,7 @@ GetPropertyIC::tryAttachDenseElementHole(JSContext* cx, HandleScript outerScript
GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval,
TypedOrValueRegister output)
{
if (!obj->is<TypedArrayObject>())
if (!obj->is<TypedArrayObject>() && !obj->is<UnboxedArrayObject>())
return false;
MOZ_ASSERT(idval.isInt32() || idval.isString());
@ -3984,6 +4057,13 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i
return output.hasValue() || !output.typedReg().isFloat();
}
if (index >= obj->as<UnboxedArrayObject>().initializedLength())
return false;
JSValueType elementType = obj->as<UnboxedArrayObject>().elementType();
if (elementType == JSVAL_TYPE_DOUBLE)
return output.hasValue();
return output.hasValue() || !output.typedReg().isFloat();
}
@ -4060,27 +4140,46 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm,
Label popObjectAndFail;
// Guard on the initialized length.
Address length(object, TypedArrayObject::lengthOffset());
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
if (array->is<TypedArrayObject>()) {
// Guard on the initialized length.
Address length(object, TypedArrayObject::lengthOffset());
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
// Save the object register on the stack in case of failure.
Register elementReg = object;
masm.push(object);
// Save the object register on the stack in case of failure.
Register elementReg = object;
masm.push(object);
// Load elements vector.
masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
// Load elements vector.
masm.loadPtr(Address(object, TypedArrayObject::dataOffset()), elementReg);
// Load the value. We use an invalid register because the destination
// register is necessary a non double register.
Scalar::Type arrayType = array->as<TypedArrayObject>().type();
int width = Scalar::byteSize(arrayType);
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
if (output.hasValue()) {
masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
elementReg, &popObjectAndFail);
// Load the value. We use an invalid register because the destination
// register is necessary a non double register.
Scalar::Type arrayType = array->as<TypedArrayObject>().type();
int width = Scalar::byteSize(arrayType);
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
if (output.hasValue()) {
masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult,
elementReg, &popObjectAndFail);
} else {
masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
}
} else {
masm.loadFromTypedArray(arrayType, source, output.typedReg(), elementReg, &popObjectAndFail);
// Save the object register on the stack in case of failure.
masm.push(object);
// Guard on the initialized length.
masm.load32(Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), object);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), object);
masm.branch32(Assembler::BelowOrEqual, object, indexReg, &popObjectAndFail);
// Load elements vector.
Register elementReg = object;
masm.loadPtr(Address(masm.getStackPointer(), 0), object);
masm.loadPtr(Address(object, UnboxedArrayObject::offsetOfElements()), elementReg);
JSValueType elementType = array->as<UnboxedArrayObject>().elementType();
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
masm.loadUnboxedProperty(source, elementType, output);
}
masm.pop(object);

View File

@ -2894,6 +2894,32 @@ LIRGenerator::visitSetInitializedLength(MSetInitializedLength* 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
LIRGenerator::visitNot(MNot* ins)
{
@ -3143,16 +3169,22 @@ LIRGenerator::visitStoreElementHole(MStoreElementHole* ins)
const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index());
// Use a temp register when adding new elements to unboxed arrays.
LDefinition tempDef = LDefinition::BogusTemp();
if (ins->unboxedType() != JSVAL_TYPE_MAGIC)
tempDef = temp();
LInstruction* lir;
switch (ins->value()->type()) {
case MIRType::Value:
lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()));
lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()),
tempDef);
break;
default:
{
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
lir = new(alloc()) LStoreElementHoleT(object, elements, index, value);
lir = new(alloc()) LStoreElementHoleT(object, elements, index, value, tempDef);
break;
}
}
@ -3171,14 +3203,20 @@ LIRGenerator::visitFallibleStoreElement(MFallibleStoreElement* ins)
const LUse elements = useRegister(ins->elements());
const LAllocation index = useRegisterOrConstant(ins->index());
// Use a temp register when adding new elements to unboxed arrays.
LDefinition tempDef = LDefinition::BogusTemp();
if (ins->unboxedType() != JSVAL_TYPE_MAGIC)
tempDef = temp();
LInstruction* lir;
switch (ins->value()->type()) {
case MIRType::Value:
lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()));
lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()),
tempDef);
break;
default:
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value);
lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value, tempDef);
break;
}

View File

@ -217,6 +217,10 @@ class LIRGenerator : public LIRGeneratorSpecific
void visitTypedObjectDescr(MTypedObjectDescr* ins);
void visitInitializedLength(MInitializedLength* ins);
void visitSetInitializedLength(MSetInitializedLength* ins);
void visitUnboxedArrayLength(MUnboxedArrayLength* ins);
void visitUnboxedArrayInitializedLength(MUnboxedArrayInitializedLength* ins);
void visitIncrementUnboxedArrayInitializedLength(MIncrementUnboxedArrayInitializedLength* ins);
void visitSetUnboxedArrayInitializedLength(MSetUnboxedArrayInitializedLength* ins);
void visitNot(MNot* ins);
void visitBoundsCheck(MBoundsCheck* ins);
void visitBoundsCheckLower(MBoundsCheckLower* ins);

View File

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

View File

@ -5789,6 +5789,46 @@ jit::ElementAccessIsDenseNative(CompilerConstraintList* constraints,
return clasp && clasp->isNative() && !IsTypedArrayClass(clasp);
}
JSValueType
jit::UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
MDefinition* id)
{
if (obj->mightBeType(MIRType::String))
return JSVAL_TYPE_MAGIC;
if (id && id->type() != MIRType::Int32 && id->type() != MIRType::Double)
return JSVAL_TYPE_MAGIC;
TemporaryTypeSet* types = obj->resultTypeSet();
if (!types || types->unknownObject())
return JSVAL_TYPE_MAGIC;
JSValueType elementType = JSVAL_TYPE_MAGIC;
for (unsigned i = 0; i < types->getObjectCount(); i++) {
TypeSet::ObjectKey* key = types->getObject(i);
if (!key)
continue;
if (key->unknownProperties() || !key->isGroup())
return JSVAL_TYPE_MAGIC;
if (key->clasp() != &UnboxedArrayObject::class_)
return JSVAL_TYPE_MAGIC;
const UnboxedLayout &layout = key->group()->unboxedLayout();
if (layout.nativeGroup())
return JSVAL_TYPE_MAGIC;
if (elementType == layout.elementType() || elementType == JSVAL_TYPE_MAGIC)
elementType = layout.elementType();
else
return JSVAL_TYPE_MAGIC;
}
return elementType;
}
bool
jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints,
MDefinition* obj, MDefinition* id,
@ -5948,6 +5988,11 @@ ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
firstElements.maybeTypes()->equals(secondElements.maybeTypes());
}
if (first->clasp() == &UnboxedArrayObject::class_) {
return first->group()->unboxedLayout().elementType() ==
second->group()->unboxedLayout().elementType();
}
return false;
}

View File

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

View File

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

View File

@ -705,6 +705,31 @@ template void
MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
const ConstantOrRegister& value, Label* failure);
void
MacroAssembler::checkUnboxedArrayCapacity(Register obj, const RegisterOrInt32Constant& index,
Register temp, Label* failure)
{
Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
Label capacityIsIndex, done;
load32(initLengthAddr, temp);
branchTest32(Assembler::NonZero, temp, Imm32(UnboxedArrayObject::CapacityMask), &capacityIsIndex);
branch32(Assembler::BelowOrEqual, lengthAddr, index, failure);
jump(&done);
bind(&capacityIsIndex);
// Do a partial shift so that we can get an absolute offset from the base
// of CapacityArray to use.
JS_STATIC_ASSERT(sizeof(UnboxedArrayObject::CapacityArray[0]) == 4);
rshiftPtr(Imm32(UnboxedArrayObject::CapacityShift - 2), temp);
and32(Imm32(~0x3), temp);
addPtr(ImmPtr(&UnboxedArrayObject::CapacityArray), temp);
branch32(Assembler::BelowOrEqual, Address(temp, 0), index, failure);
bind(&done);
}
// Inlined version of gc::CheckAllocatorState that checks the bare essentials
// and bails for anything that cannot be handled with our jit allocators.
void
@ -1256,6 +1281,16 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando()));
if (initContents)
initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>());
} else if (templateObj->is<UnboxedArrayObject>()) {
MOZ_ASSERT(templateObj->as<UnboxedArrayObject>().hasInlineElements());
int elementsOffset = UnboxedArrayObject::offsetOfInlineElements();
computeEffectiveAddress(Address(obj, elementsOffset), temp);
storePtr(temp, Address(obj, UnboxedArrayObject::offsetOfElements()));
store32(Imm32(templateObj->as<UnboxedArrayObject>().length()),
Address(obj, UnboxedArrayObject::offsetOfLength()));
uint32_t capacityIndex = templateObj->as<UnboxedArrayObject>().capacityIndex();
store32(Imm32(capacityIndex << UnboxedArrayObject::CapacityShift),
Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
} else {
MOZ_CRASH("Unknown object");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
class LArrayLength : public LInstructionHelper<1, 1, 0>
{
@ -5670,17 +5736,19 @@ class LStoreElementT : public LInstructionHelper<0, 3, 0>
};
// Like LStoreElementV, but supports indexes >= initialized length.
class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
{
public:
LIR_HEADER(StoreElementHoleV)
LStoreElementHoleV(const LAllocation& object, const LAllocation& elements,
const LAllocation& index, const LBoxAllocation& value) {
const LAllocation& index, const LBoxAllocation& value,
const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setBoxOperand(Value, value);
setTemp(0, temp);
}
static const size_t Value = 3;
@ -5700,17 +5768,19 @@ class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
};
// Like LStoreElementT, but supports indexes >= initialized length.
class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
class LStoreElementHoleT : public LInstructionHelper<0, 4, 1>
{
public:
LIR_HEADER(StoreElementHoleT)
LStoreElementHoleT(const LAllocation& object, const LAllocation& elements,
const LAllocation& index, const LAllocation& value) {
const LAllocation& index, const LAllocation& value,
const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setOperand(3, value);
setTemp(0, temp);
}
const MStoreElementHole* mir() const {
@ -5731,17 +5801,19 @@ class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
};
// Like LStoreElementV, but can just ignore assignment (for eg. frozen objects)
class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
{
public:
LIR_HEADER(FallibleStoreElementV)
LFallibleStoreElementV(const LAllocation& object, const LAllocation& elements,
const LAllocation& index, const LBoxAllocation& value) {
const LAllocation& index, const LBoxAllocation& value,
const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setBoxOperand(Value, value);
setTemp(0, temp);
}
static const size_t Value = 3;
@ -5761,17 +5833,19 @@ class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
};
// Like LStoreElementT, but can just ignore assignment (for eg. frozen objects)
class LFallibleStoreElementT : public LInstructionHelper<0, 4, 0>
class LFallibleStoreElementT : public LInstructionHelper<0, 4, 1>
{
public:
LIR_HEADER(FallibleStoreElementT)
LFallibleStoreElementT(const LAllocation& object, const LAllocation& elements,
const LAllocation& index, const LAllocation& value) {
const LAllocation& index, const LAllocation& value,
const LDefinition& temp) {
setOperand(0, object);
setOperand(1, elements);
setOperand(2, index);
setOperand(3, value);
setTemp(0, temp);
}
const MFallibleStoreElement* mir() const {

View File

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

View File

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

View File

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

View File

@ -72,37 +72,49 @@ extern ArrayObject*
NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject* templateObject);
/* Create a dense array with the same copy-on-write elements as another object. */
extern ArrayObject*
extern JSObject*
NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap);
extern ArrayObject*
// The methods below can create either boxed or unboxed arrays.
extern JSObject*
NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length,
NewObjectKind newKind = GenericObject);
extern ArrayObject*
extern JSObject*
NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length);
extern ArrayObject*
extern JSObject*
NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length,
NewObjectKind newKind = GenericObject);
extern ArrayObject*
extern JSObject*
NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length);
extern ArrayObject*
extern JSObject*
NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length,
NewObjectKind newKind = GenericObject);
extern ArrayObject*
extern JSObject*
NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto);
extern ArrayObject*
enum class ShouldUpdateTypes
{
Update,
DontUpdate
};
extern bool
MaybeAnalyzeBeforeCreatingLargeArray(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length);
extern JSObject*
NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group,
const Value* vp, size_t length,
NewObjectKind newKind = GenericObject,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
extern ArrayObject*
extern JSObject*
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto = nullptr);
@ -117,6 +129,13 @@ NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleV
extern bool
WouldDefinePastNonwritableLength(HandleNativeObject obj, uint32_t index);
/*
* Canonicalize |vp| to a uint32_t value potentially suitable for use as an
* array length.
*/
extern bool
CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* canonicalized);
extern bool
GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp);
@ -150,7 +169,7 @@ extern bool
array_join(JSContext* cx, unsigned argc, js::Value* vp);
extern void
ArrayShiftMoveElements(NativeObject* obj);
ArrayShiftMoveElements(JSObject* obj);
extern bool
array_shift(JSContext* cx, unsigned argc, js::Value* vp);
@ -182,7 +201,7 @@ extern const JSJitInfo array_splice_info;
extern bool
NewbornArrayPush(JSContext* cx, HandleObject obj, const Value& v);
extern ArrayObject*
extern JSObject*
ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt);
#ifdef DEBUG

View File

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

View File

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

View File

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

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.
static ArrayObject*
static JSObject*
SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearString sep,
HandleObjectGroup group)
{
@ -2470,7 +2470,7 @@ SplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleLinearS
}
// Fast-path for splitting a string into a character array via split("").
static ArrayObject*
static JSObject*
CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObjectGroup group)
{
size_t strLength = str->length();
@ -2495,7 +2495,7 @@ CharSplitHelper(JSContext* cx, HandleLinearString str, uint32_t limit, HandleObj
}
// ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
ArrayObject*
JSObject*
js::str_split_string(JSContext* cx, HandleObjectGroup group, HandleString str, HandleString sep, uint32_t limit)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t
return DenseElementResult::Success;
}
inline DenseElementResult
NativeObject::setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp,
uint32_t count,
ShouldUpdateTypes updateTypes)
{
if (denseElementsAreFrozen())
return DenseElementResult::Incomplete;
if (is<ArrayObject>() &&
!as<ArrayObject>().lengthIsWritable() &&
start + count >= as<ArrayObject>().length())
{
return DenseElementResult::Incomplete;
}
DenseElementResult result = ensureDenseElements(cx, start, count);
if (result != DenseElementResult::Success)
return result;
if (is<ArrayObject>() && start + count >= as<ArrayObject>().length())
as<ArrayObject>().setLengthInt32(start + count);
if (updateTypes == ShouldUpdateTypes::DontUpdate && !shouldConvertDoubleElements()) {
copyDenseElements(start, vp, count);
} else {
for (size_t i = 0; i < count; i++)
setDenseElementWithType(cx, start + i, vp[i]);
}
return DenseElementResult::Success;
}
inline Value
NativeObject::getDenseOrTypedArrayElement(uint32_t idx)
{

View File

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

View File

@ -777,7 +777,7 @@ GetValueTypeForTable(const Value& v)
return type;
}
/* static */ ArrayObject*
/* static */ JSObject*
ObjectGroup::newArrayObject(ExclusiveContext* cx,
const Value* vp, size_t length,
NewObjectKind newKind, NewArrayKind arrayKind)
@ -841,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx,
AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
if (elementType != TypeSet::UnknownType()) {
// Keep track of the initial objects we create with this type.
// If the initial ones have a consistent shape and property types, we
// will try to use an unboxed layout for the group.
PreliminaryObjectArrayWithTemplate* preliminaryObjects =
cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
if (!preliminaryObjects)
return nullptr;
group->setPreliminaryObjects(preliminaryObjects);
}
if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
return nullptr;
}
// The type of the elements being added will already be reflected in type
// information.
// information, but make sure when creating an unboxed array that the
// common element type is suitable for the unboxed representation.
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
return nullptr;
if (group->maybePreliminaryObjects())
group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
if (group->maybeUnboxedLayout()) {
switch (group->unboxedLayout().elementType()) {
case JSVAL_TYPE_BOOLEAN:
if (elementType != TypeSet::BooleanType())
updateTypes = ShouldUpdateTypes::Update;
break;
case JSVAL_TYPE_INT32:
if (elementType != TypeSet::Int32Type())
updateTypes = ShouldUpdateTypes::Update;
break;
case JSVAL_TYPE_DOUBLE:
if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
updateTypes = ShouldUpdateTypes::Update;
break;
case JSVAL_TYPE_STRING:
if (elementType != TypeSet::StringType())
updateTypes = ShouldUpdateTypes::Update;
break;
case JSVAL_TYPE_OBJECT:
if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
updateTypes = ShouldUpdateTypes::Update;
break;
default:
MOZ_CRASH();
}
}
return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
}
@ -857,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
{
MOZ_ASSERT(source->group() != target->group());
if (!target->is<ArrayObject>() || !source->is<ArrayObject>()) {
if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
return true;
if (target->group()->maybePreliminaryObjects()) {
bool force = IsInsideNursery(source);
target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
}
if (target->is<ArrayObject>()) {
ObjectGroup* sourceGroup = source->group();
if (source->is<UnboxedArrayObject>()) {
Shape* shape = target->as<ArrayObject>().lastProperty();
if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
return false;
} else if (source->is<ArrayObject>()) {
source->setGroup(target->group());
} else {
return true;
}
if (sourceGroup->maybePreliminaryObjects())
sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
if (target->group()->maybePreliminaryObjects())
target->group()->maybePreliminaryObjects()->registerNewObject(source);
for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
Value v = source->as<ArrayObject>().getDenseElement(i);
AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
}
return true;
}
source->setGroup(target->group());
if (target->is<UnboxedArrayObject>()) {
if (!source->is<UnboxedArrayObject>())
return true;
if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
return true;
if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
return true;
for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
Value v = source->as<ArrayObject>().getDenseElement(i);
AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
}
return true;
@ -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)) {
ReportOutOfMemory(cx);
return nullptr;

View File

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

View File

@ -1281,7 +1281,17 @@
* Stack: receiver, obj, propval => obj[propval]
*/ \
macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \
macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \
/*
* Pushes newly created array for a spread call onto the stack. This has
* the same semantics as JSOP_NEWARRAY, but is distinguished to avoid
* using unboxed arrays in spread calls, which would make compiling spread
* calls in baseline more complex.
* Category: Literals
* Type: Array
* Operands: uint32_t length
* Stack: => obj
*/ \
macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 5, 0, 1, JOF_UINT32) \
\
/*
* Defines the given function on the current scope.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -172,6 +172,669 @@ UnboxedPlainObject::layout() const
return group()->unboxedLayout();
}
/////////////////////////////////////////////////////////////////////
// UnboxedArrayObject
/////////////////////////////////////////////////////////////////////
inline const UnboxedLayout&
UnboxedArrayObject::layout() const
{
return group()->unboxedLayout();
}
inline void
UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length)
{
if (length > INT32_MAX) {
// Track objects with overflowing lengths in type information.
MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW);
}
length_ = length;
}
inline void
UnboxedArrayObject::setInitializedLength(uint32_t initlen)
{
if (initlen < initializedLength()) {
switch (elementType()) {
case JSVAL_TYPE_STRING:
for (size_t i = initlen; i < initializedLength(); i++)
triggerPreBarrier<JSVAL_TYPE_STRING>(i);
break;
case JSVAL_TYPE_OBJECT:
for (size_t i = initlen; i < initializedLength(); i++)
triggerPreBarrier<JSVAL_TYPE_OBJECT>(i);
break;
default:
MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(elementType()));
}
}
setInitializedLengthNoBarrier(initlen);
}
template <JSValueType Type>
inline bool
UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
MOZ_ASSERT(Type == elementType());
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
}
template <JSValueType Type>
inline void
UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
MOZ_ASSERT(Type == elementType());
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true);
}
template <JSValueType Type>
inline bool
UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
MOZ_ASSERT(Type == elementType());
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
}
template <JSValueType Type>
inline void
UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v)
{
MOZ_ASSERT(index < initializedLength());
MOZ_ASSERT(Type == elementType());
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
}
template <JSValueType Type>
inline Value
UnboxedArrayObject::getElementSpecific(size_t index)
{
MOZ_ASSERT(index < initializedLength());
MOZ_ASSERT(Type == elementType());
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false);
}
template <JSValueType Type>
inline void
UnboxedArrayObject::triggerPreBarrier(size_t index)
{
MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type));
uint8_t* p = elements() + index * UnboxedTypeSize(Type);
switch (Type) {
case JSVAL_TYPE_STRING: {
JSString** np = reinterpret_cast<JSString**>(p);
JSString::writeBarrierPre(*np);
break;
}
case JSVAL_TYPE_OBJECT: {
JSObject** np = reinterpret_cast<JSObject**>(p);
JSObject::writeBarrierPre(*np);
break;
}
default:
MOZ_CRASH("Bad type");
}
}
/////////////////////////////////////////////////////////////////////
// Combined methods for NativeObject and UnboxedArrayObject accesses.
/////////////////////////////////////////////////////////////////////
static inline bool
HasAnyBoxedOrUnboxedDenseElements(JSObject* obj)
{
return obj->isNative() || obj->is<UnboxedArrayObject>();
}
static inline size_t
GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().getDenseInitializedLength();
if (obj->is<UnboxedArrayObject>())
return obj->as<UnboxedArrayObject>().initializedLength();
return 0;
}
static inline size_t
GetAnyBoxedOrUnboxedCapacity(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().getDenseCapacity();
if (obj->is<UnboxedArrayObject>())
return obj->as<UnboxedArrayObject>().capacity();
return 0;
}
static inline Value
GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
{
if (obj->isNative())
return obj->as<NativeObject>().getDenseElement(index);
return obj->as<UnboxedArrayObject>().getElement(index);
}
static inline size_t
GetAnyBoxedOrUnboxedArrayLength(JSObject* obj)
{
if (obj->is<ArrayObject>())
return obj->as<ArrayObject>().length();
return obj->as<UnboxedArrayObject>().length();
}
static inline void
SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length)
{
if (obj->is<ArrayObject>()) {
MOZ_ASSERT(length >= obj->as<ArrayObject>().length());
obj->as<ArrayObject>().setLength(cx, length);
} else {
MOZ_ASSERT(length >= obj->as<UnboxedArrayObject>().length());
obj->as<UnboxedArrayObject>().setLength(cx, length);
}
}
static inline bool
SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
if (obj->isNative()) {
obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
return true;
}
return obj->as<UnboxedArrayObject>().setElement(cx, index, value);
}
static inline bool
InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
if (obj->isNative()) {
obj->as<NativeObject>().initDenseElementWithType(cx, index, value);
return true;
}
return obj->as<UnboxedArrayObject>().initElement(cx, index, value);
}
/////////////////////////////////////////////////////////////////////
// Template methods for NativeObject and UnboxedArrayObject accesses.
/////////////////////////////////////////////////////////////////////
static inline JSValueType
GetBoxedOrUnboxedType(JSObject* obj)
{
if (obj->isNative())
return JSVAL_TYPE_MAGIC;
return obj->as<UnboxedArrayObject>().elementType();
}
template <JSValueType Type>
static inline bool
HasBoxedOrUnboxedDenseElements(JSObject* obj)
{
if (Type == JSVAL_TYPE_MAGIC)
return obj->isNative();
return obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().elementType() == Type;
}
template <JSValueType Type>
static inline size_t
GetBoxedOrUnboxedInitializedLength(JSObject* obj)
{
if (Type == JSVAL_TYPE_MAGIC)
return obj->as<NativeObject>().getDenseInitializedLength();
return obj->as<UnboxedArrayObject>().initializedLength();
}
template <JSValueType Type>
static inline DenseElementResult
SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
{
size_t oldInitlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
if (Type == JSVAL_TYPE_MAGIC) {
obj->as<NativeObject>().setDenseInitializedLength(initlen);
if (initlen < oldInitlen)
obj->as<NativeObject>().shrinkElements(cx, initlen);
} else {
obj->as<UnboxedArrayObject>().setInitializedLength(initlen);
if (initlen < oldInitlen)
obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen);
}
return DenseElementResult::Success;
}
template <JSValueType Type>
static inline size_t
GetBoxedOrUnboxedCapacity(JSObject* obj)
{
if (Type == JSVAL_TYPE_MAGIC)
return obj->as<NativeObject>().getDenseCapacity();
return obj->as<UnboxedArrayObject>().capacity();
}
template <JSValueType Type>
static inline Value
GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
{
if (Type == JSVAL_TYPE_MAGIC)
return obj->as<NativeObject>().getDenseElement(index);
return obj->as<UnboxedArrayObject>().getElementSpecific<Type>(index);
}
template <JSValueType Type>
static inline void
SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value)
{
if (Type == JSVAL_TYPE_MAGIC)
obj->as<NativeObject>().setDenseElement(index, value);
else
obj->as<UnboxedArrayObject>().setElementNoTypeChangeSpecific<Type>(index, value);
}
template <JSValueType Type>
static inline bool
SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
{
if (Type == JSVAL_TYPE_MAGIC) {
obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
return true;
}
return obj->as<UnboxedArrayObject>().setElementSpecific<Type>(cx, index, value);
}
template <JSValueType Type>
static inline DenseElementResult
EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count)
{
if (Type == JSVAL_TYPE_MAGIC) {
if (!obj->as<ArrayObject>().ensureElements(cx, count))
return DenseElementResult::Failure;
} else {
if (obj->as<UnboxedArrayObject>().capacity() < count) {
if (!obj->as<UnboxedArrayObject>().growElements(cx, count))
return DenseElementResult::Failure;
}
}
return DenseElementResult::Success;
}
template <JSValueType Type>
static inline DenseElementResult
SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
uint32_t start, const Value* vp, uint32_t count,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
{
if (Type == JSVAL_TYPE_MAGIC) {
NativeObject* nobj = &obj->as<NativeObject>();
if (nobj->denseElementsAreFrozen())
return DenseElementResult::Incomplete;
if (obj->is<ArrayObject>() &&
!obj->as<ArrayObject>().lengthIsWritable() &&
start + count >= obj->as<ArrayObject>().length())
{
return DenseElementResult::Incomplete;
}
DenseElementResult result = nobj->ensureDenseElements(cx, start, count);
if (result != DenseElementResult::Success)
return result;
if (obj->is<ArrayObject>() && start + count >= obj->as<ArrayObject>().length())
obj->as<ArrayObject>().setLengthInt32(start + count);
if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) {
nobj->copyDenseElements(start, vp, count);
} else {
for (size_t i = 0; i < count; i++)
nobj->setDenseElementWithType(cx, start + i, vp[i]);
}
return DenseElementResult::Success;
}
UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
if (start > nobj->initializedLength())
return DenseElementResult::Incomplete;
if (start + count >= UnboxedArrayObject::MaximumCapacity)
return DenseElementResult::Incomplete;
if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count))
return DenseElementResult::Failure;
size_t oldInitlen = nobj->initializedLength();
// Overwrite any existing elements covered by the new range. If we fail
// after this point due to some incompatible type being written to the
// object's elements, afterwards the contents will be different from when
// we started. The caller must retry the operation using a generic path,
// which will overwrite the already-modified elements as well as the ones
// that were left alone.
size_t i = 0;
if (updateTypes == ShouldUpdateTypes::DontUpdate) {
for (size_t j = start; i < count && j < oldInitlen; i++, j++)
nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
} else {
for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
return DenseElementResult::Incomplete;
}
}
if (i != count) {
obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
if (updateTypes == ShouldUpdateTypes::DontUpdate) {
for (; i < count; i++)
nobj->initElementNoTypeChangeSpecific<Type>(start + i, vp[i]);
} else {
for (; i < count; i++) {
if (!nobj->initElementSpecific<Type>(cx, start + i, vp[i])) {
nobj->setInitializedLengthNoBarrier(oldInitlen);
return DenseElementResult::Incomplete;
}
}
}
}
if (start + count >= nobj->length())
nobj->setLength(cx, start + count);
return DenseElementResult::Success;
}
template <JSValueType Type>
static inline DenseElementResult
MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart,
uint32_t length)
{
MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));
if (Type == JSVAL_TYPE_MAGIC) {
if (obj->as<NativeObject>().denseElementsAreFrozen())
return DenseElementResult::Incomplete;
if (!obj->as<NativeObject>().maybeCopyElementsForWrite(cx))
return DenseElementResult::Failure;
obj->as<NativeObject>().moveDenseElements(dstStart, srcStart, length);
} else {
uint8_t* data = obj->as<UnboxedArrayObject>().elements();
size_t elementSize = UnboxedTypeSize(Type);
if (UnboxedTypeNeedsPreBarrier(Type) &&
JS::shadow::Zone::asShadowZone(obj->zone())->needsIncrementalBarrier())
{
// Trigger pre barriers on any elements we are overwriting. See
// NativeObject::moveDenseElements. No post barrier is needed as
// only whole cell post barriers are used with unboxed objects.
for (size_t i = 0; i < length; i++)
obj->as<UnboxedArrayObject>().triggerPreBarrier<Type>(dstStart + i);
}
memmove(data + dstStart * elementSize,
data + srcStart * elementSize,
length * elementSize);
}
return DenseElementResult::Success;
}
template <JSValueType DstType, JSValueType SrcType>
static inline DenseElementResult
CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
uint32_t dstStart, uint32_t srcStart, uint32_t length)
{
MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<SrcType>(src));
MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<DstType>(dst));
MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(dst) == dstStart);
MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<SrcType>(src) >= srcStart + length);
MOZ_ASSERT(GetBoxedOrUnboxedCapacity<DstType>(dst) >= dstStart + length);
SetBoxedOrUnboxedInitializedLength<DstType>(cx, dst, dstStart + length);
if (DstType == JSVAL_TYPE_MAGIC) {
if (SrcType == JSVAL_TYPE_MAGIC) {
const Value* vp = src->as<NativeObject>().getDenseElements() + srcStart;
dst->as<NativeObject>().initDenseElements(dstStart, vp, length);
} else {
for (size_t i = 0; i < length; i++) {
Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
dst->as<NativeObject>().initDenseElement(dstStart + i, v);
}
}
} else if (DstType == SrcType) {
uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
size_t elementSize = UnboxedTypeSize(DstType);
memcpy(dstData + dstStart * elementSize,
srcData + srcStart * elementSize,
length * elementSize);
// Add a store buffer entry if we might have copied a nursery pointer to dst.
if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst))
dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCell(dst);
} else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) {
uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
for (size_t i = 0; i < length; i++) {
int32_t v = *reinterpret_cast<int32_t*>(srcData + (srcStart + i) * sizeof(int32_t));
*reinterpret_cast<double*>(dstData + (dstStart + i) * sizeof(double)) = v;
}
} else {
for (size_t i = 0; i < length; i++) {
Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
dst->as<UnboxedArrayObject>().initElementNoTypeChangeSpecific<DstType>(dstStart + i, v);
}
}
return DenseElementResult::Success;
}
/////////////////////////////////////////////////////////////////////
// Dispatch to specialized methods based on the type of an object.
/////////////////////////////////////////////////////////////////////
// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h.
// The clang-cl front end defines _MSC_VER, but still requires the explicit
// template declaration, so we must test for __clang__ here as well.
#if defined(_MSC_VER) && !defined(__clang__)
# define DEPENDENT_TEMPLATE_HINT
#else
# define DEPENDENT_TEMPLATE_HINT template
#endif
// Function to dispatch a method specialized to whatever boxed or unboxed dense
// elements which an input object has.
template <typename F>
DenseElementResult
CallBoxedOrUnboxedSpecialization(F f, JSObject* obj)
{
if (!HasAnyBoxedOrUnboxedDenseElements(obj))
return DenseElementResult::Incomplete;
switch (GetBoxedOrUnboxedType(obj)) {
case JSVAL_TYPE_MAGIC:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_MAGIC>();
case JSVAL_TYPE_BOOLEAN:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_BOOLEAN>();
case JSVAL_TYPE_INT32:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_INT32>();
case JSVAL_TYPE_DOUBLE:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_DOUBLE>();
case JSVAL_TYPE_STRING:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_STRING>();
case JSVAL_TYPE_OBJECT:
return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_OBJECT>();
default:
MOZ_CRASH();
}
}
// As above, except the specialization can reflect the unboxed type of two objects.
template <typename F>
DenseElementResult
CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2)
{
if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2))
return DenseElementResult::Incomplete;
#define SPECIALIZE_OBJ2(TYPE) \
switch (GetBoxedOrUnboxedType(obj2)) { \
case JSVAL_TYPE_MAGIC: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_MAGIC>(); \
case JSVAL_TYPE_BOOLEAN: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_BOOLEAN>(); \
case JSVAL_TYPE_INT32: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_INT32>(); \
case JSVAL_TYPE_DOUBLE: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_DOUBLE>(); \
case JSVAL_TYPE_STRING: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_STRING>(); \
case JSVAL_TYPE_OBJECT: \
return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_OBJECT>(); \
default: \
MOZ_CRASH(); \
}
switch (GetBoxedOrUnboxedType(obj1)) {
case JSVAL_TYPE_MAGIC:
SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC)
case JSVAL_TYPE_BOOLEAN:
SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN)
case JSVAL_TYPE_INT32:
SPECIALIZE_OBJ2(JSVAL_TYPE_INT32)
case JSVAL_TYPE_DOUBLE:
SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE)
case JSVAL_TYPE_STRING:
SPECIALIZE_OBJ2(JSVAL_TYPE_STRING)
case JSVAL_TYPE_OBJECT:
SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT)
default:
MOZ_CRASH();
}
#undef SPECIALIZE_OBJ2
}
#undef DEPENDENT_TEMPLATE_HINT
#define DefineBoxedOrUnboxedFunctor1(Signature, A) \
struct Signature ## Functor { \
A a; \
explicit Signature ## Functor(A a) \
: a(a) \
{} \
template <JSValueType Type> \
DenseElementResult operator()() { \
return Signature<Type>(a); \
} \
}
#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \
struct Signature ## Functor { \
A a; B b; C c; \
Signature ## Functor(A a, B b, C c) \
: a(a), b(b), c(c) \
{} \
template <JSValueType Type> \
DenseElementResult operator()() { \
return Signature<Type>(a, b, c); \
} \
}
#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D) \
struct Signature ## Functor { \
A a; B b; C c; D d; \
Signature ## Functor(A a, B b, C c, D d) \
: a(a), b(b), c(c), d(d) \
{} \
template <JSValueType Type> \
DenseElementResult operator()() { \
return Signature<Type>(a, b, c, d); \
} \
}
#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D) \
struct Signature ## Functor { \
A a; B b; C c; D d; \
Signature ## Functor(A a, B b, C c, D d) \
: a(a), b(b), c(c), d(d) \
{} \
template <JSValueType TypeOne, JSValueType TypeTwo> \
DenseElementResult operator()() { \
return Signature<TypeOne, TypeTwo>(a, b, c, d); \
} \
}
#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E) \
struct Signature ## Functor { \
A a; B b; C c; D d; E e; \
Signature ## Functor(A a, B b, C c, D d, E e) \
: a(a), b(b), c(c), d(d), e(e) \
{} \
template <JSValueType Type> \
DenseElementResult operator()() { \
return Signature<Type>(a, b, c, d, e); \
} \
}
#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F) \
struct Signature ## Functor { \
A a; B b; C c; D d; E e; F f; \
Signature ## Functor(A a, B b, C c, D d, E e, F f) \
: a(a), b(b), c(c), d(d), e(e), f(f) \
{} \
template <JSValueType Type> \
DenseElementResult operator()() { \
return Signature<Type>(a, b, c, d, e, f); \
} \
}
#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F) \
struct Signature ## Functor { \
A a; B b; C c; D d; E e; F f; \
Signature ## Functor(A a, B b, C c, D d, E e, F f) \
: a(a), b(b), c(c), d(d), e(e), f(f) \
{} \
template <JSValueType TypeOne, JSValueType TypeTwo> \
DenseElementResult operator()() { \
return Signature<TypeOne, TypeTwo>(a, b, c, d, e, f); \
} \
}
DenseElementResult
SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
uint32_t start, const Value* vp, uint32_t count,
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
DenseElementResult
MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
uint32_t dstStart, uint32_t srcStart, uint32_t length);
DenseElementResult
CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
uint32_t dstStart, uint32_t srcStart, uint32_t length);
void
SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen);
DenseElementResult
EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count);
} // namespace js
#endif // vm_UnboxedObject_inl_h

View File

@ -417,6 +417,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedObjectGroup replacementGroup(cx);
const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
// Immediately clear any new script on the group. This is done by replacing
// the existing new script with one for a replacement default new group.
// This is done so that the size of the replacment group's objects is the
@ -424,6 +426,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
// slot accesses later on for sites that see converted objects from this
// group and objects that were allocated using the replacement new group.
if (layout.newScript()) {
MOZ_ASSERT(!layout.isArray());
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
if (!replacementGroup)
return false;
@ -449,14 +453,15 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedScript script(cx, layout.allocationScript());
jsbytecode* pc = layout.allocationPc();
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto);
if (!replacementGroup)
return false;
PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object,
JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object;
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key,
replacementGroup);
// Clear any baseline information at this opcode which might use the old group.
@ -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)
return false;
MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
// Add shapes for each property, if this is for a plain object.
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
@ -490,7 +505,7 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
}
ObjectGroup* nativeGroup =
ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
ObjectGroupCompartment::makeGroup(cx, clasp, proto,
group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
if (!nativeGroup)
return false;
@ -498,19 +513,24 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
// No sense propagating if we don't know what we started with.
if (!group->unknownProperties()) {
// Propagate all property types from the old group to the new group.
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
jsid id = NameToId(property.name);
if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
if (layout.isArray()) {
if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
return false;
} else {
for (size_t i = 0; i < layout.properties().length(); i++) {
const UnboxedLayout::Property& property = layout.properties()[i];
jsid id = NameToId(property.name);
if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
return false;
// If we are OOM we may not be able to propagate properties.
if (nativeGroup->unknownProperties())
break;
// If we are OOM we may not be able to propagate properties.
if (nativeGroup->unknownProperties())
break;
HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
if (nativeProperty && nativeProperty->canSetDefinite(i))
nativeProperty->setDefinite(i);
HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
if (nativeProperty && nativeProperty->canSetDefinite(i))
nativeProperty->setDefinite(i);
}
}
} else {
// If we skip, though, the new group had better agree.
@ -925,6 +945,692 @@ const Class UnboxedPlainObject::class_ = {
&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
/////////////////////////////////////////////////////////////////////
@ -935,6 +1641,31 @@ NextValue(Handle<GCVector<Value>> values, size_t* 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
UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
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++)
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.
GCPtrJitCode constructorCode_;
// The following members are only used for unboxed arrays.
// The type of array elements.
JSValueType elementType_;
public:
UnboxedLayout()
: nativeGroup_(nullptr), nativeShape_(nullptr),
allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr)
size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
elementType_(JSVAL_TYPE_MAGIC)
{}
bool initProperties(const PropertyVector& properties, size_t size) {
@ -108,6 +114,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
return properties_.appendAll(properties);
}
void initArray(JSValueType elementType) {
elementType_ = elementType;
}
~UnboxedLayout() {
if (newScript_)
newScript_->clear();
@ -120,6 +130,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_.init(nullptr);
}
bool isArray() const {
return elementType_ != JSVAL_TYPE_MAGIC;
}
void detachFromCompartment();
const PropertyVector& properties() const {
@ -187,6 +201,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_ = code;
}
JSValueType elementType() const {
return elementType_;
}
inline gc::AllocKind getAllocKind() const;
void trace(JSTracer* trc);
@ -306,6 +324,193 @@ UnboxedLayout::getAllocKind() const
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 {