From 77138efc569500c81ef38618fe95529fcb22cf14 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 26 May 2015 16:29:19 -0600 Subject: [PATCH] Bug 1165392, Bug 1165463 - Various unboxed array fixes and optimizations, r=jandem. --- js/src/gc/Nursery-inl.h | 6 +- js/src/jit/BaselineIC.cpp | 80 ++++++--------- js/src/jit/CodeGenerator.cpp | 161 +++++++++++++++++++++-------- js/src/jit/IonBuilder.cpp | 20 ++-- js/src/jit/IonBuilder.h | 4 +- js/src/jit/IonCaches.cpp | 188 +++++++++++++++++++++++++--------- js/src/jit/IonCaches.h | 16 +-- js/src/jit/LIR-Common.h | 4 + js/src/jit/MCallOptimize.cpp | 58 ++++++++--- js/src/jit/MIR.h | 112 +++++++++++++------- js/src/jit/VMFunctions.cpp | 39 +++---- js/src/jit/VMFunctions.h | 2 +- js/src/jsarray.cpp | 98 ++++++++++++------ js/src/jsarray.h | 12 ++- js/src/jsobj.cpp | 6 ++ js/src/vm/UnboxedObject-inl.h | 44 ++++++-- js/src/vm/UnboxedObject.cpp | 21 ++-- js/src/vm/UnboxedObject.h | 2 +- 18 files changed, 594 insertions(+), 279 deletions(-) diff --git a/js/src/gc/Nursery-inl.h b/js/src/gc/Nursery-inl.h index ffd809db459d..bb3ba33a8312 100644 --- a/js/src/gc/Nursery-inl.h +++ b/js/src/gc/Nursery-inl.h @@ -41,7 +41,8 @@ AllocateObjectBuffer(ExclusiveContext* cx, uint32_t count) { if (cx->isJSContext()) { Nursery& nursery = cx->asJSContext()->runtime()->gc.nursery; - return static_cast(nursery.allocateBuffer(cx->zone(), count * sizeof(T))); + size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value)); + return static_cast(nursery.allocateBuffer(cx->zone(), nbytes)); } return cx->zone()->pod_malloc(count); } @@ -52,7 +53,8 @@ AllocateObjectBuffer(ExclusiveContext* cx, JSObject* obj, uint32_t count) { if (cx->isJSContext()) { Nursery& nursery = cx->asJSContext()->runtime()->gc.nursery; - return static_cast(nursery.allocateBuffer(obj, count * sizeof(T))); + size_t nbytes = JS_ROUNDUP(count * sizeof(T), sizeof(Value)); + return static_cast(nursery.allocateBuffer(obj, nbytes)); } return obj->zone()->pod_malloc(count); } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 74131da1de9d..8913aef58add 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -37,6 +37,7 @@ #include "vm/Interpreter-inl.h" #include "vm/ScopeObject-inl.h" #include "vm/StringObject-inl.h" +#include "vm/UnboxedObject-inl.h" using mozilla::BitwiseCast; using mozilla::DebugOnly; @@ -5154,29 +5155,13 @@ RemoveExistingTypedArraySetElemStub(JSContext* cx, ICSetElem_Fallback* stub, Han return false; } -static size_t -SetElemObjectInitializedLength(JSObject *obj) -{ - if (obj->isNative()) - return obj->as().getDenseInitializedLength(); - return obj->as().initializedLength(); -} - -static size_t -SetElemObjectCapacity(JSObject *obj) -{ - if (obj->isNative()) - return obj->as().getDenseCapacity(); - return obj->as().capacity(); -} - static bool CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index, Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength, bool* isAddingCaseOut, size_t* protoDepthOut) { - uint32_t initLength = SetElemObjectInitializedLength(obj); - uint32_t capacity = SetElemObjectCapacity(obj); + uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj); + uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj); *isAddingCaseOut = false; *protoDepthOut = 0; @@ -5263,9 +5248,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_ // Check the old capacity uint32_t oldCapacity = 0; uint32_t oldInitLength = 0; - if (obj->isNative() && index.isInt32() && index.toInt32() >= 0) { - oldCapacity = obj->as().getDenseCapacity(); - oldInitLength = obj->as().getDenseInitializedLength(); + if (index.isInt32() && index.toInt32() >= 0) { + oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj); + oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj); } if (op == JSOP_INITELEM) { @@ -5533,8 +5518,6 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) // Unbox key. Register key = masm.extractInt32(R1, ExtractTemp1); - Address valueAddr(BaselineStackReg, ICStackValueOffset); - if (unboxedType_ == JSVAL_TYPE_MAGIC) { // Set element on a native object. @@ -5569,6 +5552,8 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) regs.takeUnchecked(obj); regs.takeUnchecked(key); + Address valueAddr(BaselineStackReg, ICStackValueOffset); + // We need to convert int32 values being stored into doubles. In this case // the heap typeset is guaranteed to contain both int32 and double, so it's // okay to store a double. Note that double arrays are only created by @@ -5598,10 +5583,11 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg); // Compute the address being written to. - BaseIndex address(obj, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_))); + BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_))); EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_); + Address valueAddr(BaselineStackReg, ICStackValueOffset + sizeof(Value)); masm.Push(R0); masm.loadValue(valueAddr, R0); masm.storeUnboxedProperty(address, unboxedType_, @@ -5748,8 +5734,6 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm) // Unbox key. Register key = masm.extractInt32(R1, ExtractTemp1); - Address valueAddr(BaselineStackReg, ICStackValueOffset); - if (unboxedType_ == JSVAL_TYPE_MAGIC) { // Adding element to a native object. @@ -5793,6 +5777,9 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm) masm.branchTest32(Assembler::Zero, elementsFlags, Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), &dontConvertDoubles); + + Address valueAddr(BaselineStackReg, ICStackValueOffset); + // Note that double arrays are only created by IonMonkey, so if we have no // floating-point support Ion is disabled and there should be no double arrays. if (cx->runtime()->jitSupportsFloatingPoint) @@ -5815,31 +5802,31 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm) masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg); masm.branch32(Assembler::NotEqual, scratchReg, key, &failure); - Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength()); - // Capacity check. masm.checkUnboxedArrayCapacity(obj, Int32Key(key), scratchReg, &failure); - // Increment initLength before write. - masm.add32(Imm32(1), initLengthAddr); - - // If length is now <= key, increment length before write. - Label skipIncrementLength; - masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength); - masm.add32(Imm32(1), lengthAddr); - masm.bind(&skipIncrementLength); - // 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(BaselineStackReg, ICStackValueOffset + sizeof(Value)); masm.loadValue(valueAddr, R0); - - // Write the value. No need for pre-barrier since we're not overwriting an old value. - BaseIndex address(obj, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_))); + 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); @@ -9956,18 +9943,11 @@ GetTemplateObjectForNative(JSContext* cx, HandleScript script, jsbytecode* pc, } if (native == js::array_concat) { - if (args.thisv().isObject() && - args.thisv().toObject().is() && - !args.thisv().toObject().isSingleton() && - !args.thisv().toObject().group()->hasUnanalyzedPreliminaryObjects()) - { - RootedObject proto(cx, args.thisv().toObject().getProto()); - res.set(NewDenseEmptyArray(cx, proto, TenuredObject)); + if (args.thisv().isObject() && !args.thisv().toObject().isSingleton()) { + res.set(NewFullyAllocatedArrayTryReuseGroup(cx, &args.thisv().toObject(), 0, + TenuredObject, /* forceAnalyze = */ true)); if (!res) return false; - - res->setGroup(args.thisv().toObject().group()); - return true; } } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 6cf0fdd52957..b9bc80e8ee69 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2492,6 +2492,12 @@ CodeGenerator::visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite* lir) OutOfLineCode* ool = oolCallVM(CopyElementsForWriteInfo, lir, (ArgList(), object), StoreNothing()); + if (lir->mir()->checkNative()) { + masm.loadObjClass(object, temp); + masm.branchTest32(Assembler::NonZero, Address(temp, Class::offsetOfFlags()), + Imm32(Class::NON_NATIVE), ool->rejoin()); + } + masm.loadPtr(Address(object, NativeObject::offsetOfElements()), temp); masm.branchTest32(Assembler::NonZero, Address(temp, ObjectElements::offsetOfFlags()), @@ -6808,9 +6814,11 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) template static void -StoreUnboxedPointer(MacroAssembler& masm, T address, MIRType type, const LAllocation* value) +StoreUnboxedPointer(MacroAssembler& masm, T address, MIRType type, const LAllocation* value, + bool preBarrier) { - masm.patchableCallPreBarrier(address, type); + if (preBarrier) + masm.patchableCallPreBarrier(address, type); if (value->isConstant()) { Value v = *value->toConstant(); if (v.isMarkable()) { @@ -6829,12 +6837,15 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir) { MIRType type; int32_t offsetAdjustment; + bool preBarrier; if (lir->mir()->isStoreUnboxedObjectOrNull()) { type = MIRType_Object; offsetAdjustment = lir->mir()->toStoreUnboxedObjectOrNull()->offsetAdjustment(); + preBarrier = lir->mir()->toStoreUnboxedObjectOrNull()->preBarrier(); } else if (lir->mir()->isStoreUnboxedString()) { type = MIRType_String; offsetAdjustment = lir->mir()->toStoreUnboxedString()->offsetAdjustment(); + preBarrier = lir->mir()->toStoreUnboxedString()->preBarrier(); } else { MOZ_CRASH(); } @@ -6845,10 +6856,10 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir) if (index->isConstant()) { Address address(elements, ToInt32(index) * sizeof(uintptr_t) + offsetAdjustment); - StoreUnboxedPointer(masm, address, type, value); + StoreUnboxedPointer(masm, address, type, value, preBarrier); } else { BaseIndex address(elements, ToRegister(index), ScalePointer, offsetAdjustment); - StoreUnboxedPointer(masm, address, type, value); + StoreUnboxedPointer(masm, address, type, value, preBarrier); } } @@ -6889,14 +6900,22 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R // VM call if a write barrier is necessary. masm.branchTestNeedsIncrementalBarrier(Assembler::NonZero, ool->entry()); - // Load elements and length. - masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp); - masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp); - - // VM call if length != initializedLength. + // Load elements and length, and VM call if length != initializedLength. Int32Key key = Int32Key(lengthTemp); - Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength()); - masm.branchKey(Assembler::NotEqual, initLength, key, ool->entry()); + 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.branchKey(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.branchKey(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 @@ -6915,25 +6934,41 @@ CodeGenerator::emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, R masm.bumpKey(&key, -1); if (mir->mode() == MArrayPopShift::Pop) { - masm.loadElementTypedOrValue(BaseIndex(elementsTemp, lengthTemp, TimesEight), 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); - masm.loadElementTypedOrValue(Address(elementsTemp, 0), out, mir->needsHoleCheck(), - ool->entry()); + Address addr(elementsTemp, 0); + 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. @@ -6972,7 +7007,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(jit::ArrayPushDense); @@ -6982,25 +7017,51 @@ CodeGenerator::emitArrayPush(LInstruction* lir, const MArrayPush* mir, Register { OutOfLineCode* ool = oolCallVM(ArrayPushDenseInfo, lir, (ArgList(), obj, value), StoreRegisterTo(length)); - // Load elements and length. - masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp); - masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length); - Int32Key key = Int32Key(length); - Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength()); - Address capacity(elementsTemp, ObjectElements::offsetOfCapacity()); + if (mir->unboxedType() == JSVAL_TYPE_MAGIC) { + // Load elements and length. + masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp); + masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length); - // Guard length == initializedLength. - masm.branchKey(Assembler::NotEqual, initLength, key, ool->entry()); + // Guard length == initializedLength. + Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength()); + masm.branchKey(Assembler::NotEqual, initLength, key, ool->entry()); - // Guard length < capacity. - masm.branchKey(Assembler::BelowOrEqual, capacity, key, ool->entry()); + // Guard length < capacity. + Address capacity(elementsTemp, ObjectElements::offsetOfCapacity()); + masm.branchKey(Assembler::BelowOrEqual, capacity, key, ool->entry()); - masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight)); + // 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); + + // Guard length == initializedLength. + Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength()); + masm.branchKey(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.bumpKey(&key, 1); - masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfLength())); - masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfInitializedLength())); + + // Update length and initialized length. + 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()); } @@ -7044,13 +7105,23 @@ CodeGenerator::visitArrayConcat(LArrayConcat* lir) // inline and pass it to the stub. Else, we just pass nullptr and the stub falls // back to a slow path. Label fail, call; - masm.loadPtr(Address(lhs, NativeObject::offsetOfElements()), temp1); - masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2); - masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail); + if (lir->mir()->unboxedType() == JSVAL_TYPE_MAGIC) { + masm.loadPtr(Address(lhs, NativeObject::offsetOfElements()), temp1); + masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2); + masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail); - masm.loadPtr(Address(rhs, NativeObject::offsetOfElements()), temp1); - masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2); - masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail); + masm.loadPtr(Address(rhs, NativeObject::offsetOfElements()), temp1); + masm.load32(Address(temp1, ObjectElements::offsetOfInitializedLength()), temp2); + masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail); + } else { + masm.load32(Address(lhs, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), temp1); + masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp1); + masm.branch32(Assembler::NotEqual, Address(lhs, UnboxedArrayObject::offsetOfLength()), temp1, &fail); + + masm.load32(Address(rhs, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), temp1); + masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp1); + masm.branch32(Assembler::NotEqual, Address(rhs, UnboxedArrayObject::offsetOfLength()), temp1, &fail); + } // Try to allocate an object. masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index ab5ed294dedc..92729dd4561e 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -6619,7 +6619,7 @@ IonBuilder::jsop_initelem_array() if (unboxedType != JSVAL_TYPE_MAGIC) { // Note: storeUnboxedValue takes care of any post barriers on the value. - storeUnboxedValue(obj, elements, 0, id, unboxedType, value); + storeUnboxedValue(obj, elements, 0, id, unboxedType, value, /* preBarrier = */ false); MInstruction* increment = MIncrementUnboxedArrayInitializedLength::New(alloc(), obj); current->add(increment); @@ -7101,6 +7101,7 @@ static bool ClassHasEffectlessLookup(const Class* clasp) { return (clasp == &UnboxedPlainObject::class_) || + (clasp == &UnboxedArrayObject::class_) || IsTypedObjectClass(clasp) || (clasp->isNative() && !clasp->ops.lookupProperty); } @@ -9109,7 +9110,9 @@ IonBuilder::setElemTryCache(bool* emitted, MDefinition* object, bool guardHoles = ElementAccessHasExtraIndexedProperty(constraints(), object); // Make sure the object being written to doesn't have copy on write elements. - object = addMaybeCopyElementsForWrite(object); + const Class* clasp = object->resultTypeSet() ? object->resultTypeSet()->getKnownClass(constraints()) : nullptr; + bool checkNative = !clasp || !clasp->isNative(); + object = addMaybeCopyElementsForWrite(object, checkNative); if (NeedsPostBarrier(info(), value)) current->add(MPostWriteBarrier::New(alloc(), object, value)); @@ -9152,7 +9155,7 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, id = idInt32; // Copy the elements vector if necessary. - obj = addMaybeCopyElementsForWrite(obj); + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); // Get the elements vector. MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC); @@ -11503,7 +11506,7 @@ IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType un MInstruction* IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset, MDefinition* scaledOffset, JSValueType unboxedType, - MDefinition* value) + MDefinition* value, bool preBarrier /* = true */) { MInstruction* store; switch (unboxedType) { @@ -11523,12 +11526,13 @@ IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t e break; case JSVAL_TYPE_STRING: - store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value, elementsOffset); + store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value, + elementsOffset, preBarrier); break; case JSVAL_TYPE_OBJECT: store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, obj, - elementsOffset); + elementsOffset, preBarrier); break; default: @@ -12533,11 +12537,11 @@ IonBuilder::addConvertElementsToDoubles(MDefinition* elements) } MDefinition* -IonBuilder::addMaybeCopyElementsForWrite(MDefinition* object) +IonBuilder::addMaybeCopyElementsForWrite(MDefinition* object, bool checkNative) { if (!ElementAccessMightBeCopyOnWrite(constraints(), object)) return object; - MInstruction* copy = MMaybeCopyElementsForWrite::New(alloc(), object); + MInstruction* copy = MMaybeCopyElementsForWrite::New(alloc(), object, checkNative); current->add(copy); return copy; } diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 76b9d67fc145..47162241e02b 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -395,7 +395,7 @@ class IonBuilder MDefinition* walkScopeChain(unsigned hops); MInstruction* addConvertElementsToDoubles(MDefinition* elements); - MDefinition* addMaybeCopyElementsForWrite(MDefinition* object); + MDefinition* addMaybeCopyElementsForWrite(MDefinition* object, bool checkNative); MInstruction* addBoundsCheck(MDefinition* index, MDefinition* length); MInstruction* addShapeGuard(MDefinition* obj, Shape* const shape, BailoutKind bailoutKind); MInstruction* addGroupGuard(MDefinition* obj, ObjectGroup* group, BailoutKind bailoutKind); @@ -959,7 +959,7 @@ class IonBuilder MInstruction* storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset, MDefinition* scaledOffset, JSValueType unboxedType, - MDefinition* value); + MDefinition* value, bool preBarrier = true); bool checkPreliminaryGroups(MDefinition *obj); bool freezePropTypeSets(TemporaryTypeSet* types, JSObject* foundProto, PropertyName* name); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 79a0b5ff0778..3de5fc8d0d3b 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -628,6 +628,9 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher, } else { masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure); } + } else if (obj->is()) { + MOZ_ASSERT(failure); + masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure); } else if (obj->is()) { attacher.branchNextStubOrLabel(masm, Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), @@ -1154,6 +1157,40 @@ 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. @@ -1244,7 +1281,7 @@ CanAttachNativeGetProp(JSContext* cx, const GetPropCache& cache, // |length| is a non-configurable getter property on ArrayObjects. Any time this // check would have passed, we can install a getter stub instead. Allow people to // make that decision themselves with skipArrayLen - if (!skipArrayLen && cx->names().length == name && cache.allowArrayLength(cx, obj) && + if (!skipArrayLen && cx->names().length == name && cache.allowArrayLength(cx) && IsCacheableArrayLength(cx, obj, name, cache.output())) { // The array length property is non-configurable, which means both that @@ -1277,7 +1314,7 @@ CanAttachNativeGetProp(JSContext* cx, const GetPropCache& cache, } bool -GetPropertyIC::allowArrayLength(JSContext* cx, HandleObject obj) const +GetPropertyIC::allowArrayLength(JSContext* cx) const { if (!idempotent()) return true; @@ -1402,6 +1439,33 @@ GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando"); } +bool +GetPropertyIC::tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandlePropertyName name, + void* returnAddr, bool* emitted) +{ + MOZ_ASSERT(canAttachStub()); + MOZ_ASSERT(!*emitted); + MOZ_ASSERT(outerScript->ionScript() == ion); + + if (!obj->is()) + return true; + + if (cx->names().length != name) + return true; + + if (obj->as().length() > INT32_MAX) + return true; + + *emitted = true; + + MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); + + StubAttacher attacher(*this); + GenerateUnboxedArrayLength(cx, masm, attacher, obj, object(), output()); + return linkAndAttachStub(cx, masm, attacher, ion, "unboxed array length"); +} + bool GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandlePropertyName name, bool* emitted) @@ -1841,6 +1905,9 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, name, returnAddr, emitted)) return false; + if (!*emitted && !tryAttachUnboxedArrayLength(cx, outerScript, ion, obj, name, returnAddr, emitted)) + return false; + if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, name, emitted)) return false; @@ -3570,16 +3637,15 @@ GetElementIC::attachDenseElementHole(JSContext* cx, HandleScript outerScript, Io } /* static */ bool -GetElementIC::canAttachTypedArrayElement(JSObject* obj, const Value& idval, - TypedOrValueRegister output) +GetElementIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval, + TypedOrValueRegister output) { - if (!IsAnyTypedArray(obj)) + if (!IsAnyTypedArray(obj) && !obj->is()) return false; if (!idval.isInt32() && !idval.isString()) return false; - // Don't emit a stub if the access is out of bounds. We make to make // certain that we monitor the type coming out of the typed array when // we generate the stub. Out of bounds accesses will hit the fallback @@ -3592,34 +3658,42 @@ GetElementIC::canAttachTypedArrayElement(JSObject* obj, const Value& idval, if (index == UINT32_MAX) return false; } - if (index >= AnyTypedArrayLength(obj)) + + if (IsAnyTypedArray(obj)) { + if (index >= AnyTypedArrayLength(obj)) + return false; + + // The output register is not yet specialized as a float register, the only + // way to accept float typed arrays for now is to return a Value type. + uint32_t arrayType = AnyTypedArrayType(obj); + if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64) + return output.hasValue(); + + return output.hasValue() || !output.typedReg().isFloat(); + } + + if (index >= obj->as().initializedLength()) return false; - // The output register is not yet specialized as a float register, the only - // way to accept float typed arrays for now is to return a Value type. - uint32_t arrayType = AnyTypedArrayType(obj); - if (arrayType == Scalar::Float32 || arrayType == Scalar::Float64) + JSValueType elementType = obj->as().elementType(); + if (elementType == JSVAL_TYPE_DOUBLE) return output.hasValue(); return output.hasValue() || !output.typedReg().isFloat(); } static void -GenerateGetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher, - HandleObject tarr, const Value& idval, Register object, - ConstantOrRegister index, TypedOrValueRegister output, - bool allowDoubleResult) +GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm, + IonCache::StubAttacher& attacher, + HandleObject array, const Value& idval, Register object, + ConstantOrRegister index, TypedOrValueRegister output, + bool allowDoubleResult) { - MOZ_ASSERT(GetElementIC::canAttachTypedArrayElement(tarr, idval, output)); + MOZ_ASSERT(GetElementIC::canAttachTypedOrUnboxedArrayElement(array, idval, output)); Label failures; - // The array type is the object within the table of typed array classes. - Scalar::Type arrayType = AnyTypedArrayType(tarr); - - // Guard on the shape. - Shape* shape = AnyTypedArrayShape(tarr); - masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); + TestMatchingReceiver(masm, attacher, object, array, &failures); // Decide to what type index the stub should be optimized Register tmpReg = output.scratchReg().gpr(); @@ -3675,34 +3749,55 @@ GenerateGetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub } } - // Guard on the initialized length. - Address length(object, TypedArrayLayout::lengthOffset()); - masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures); + Label popObjectAndFail; - // Save the object register on the stack in case of failure. - Label popAndFail; - Register elementReg = object; - masm.push(object); + if (IsAnyTypedArray(array)) { + // Guard on the initialized length. + Address length(object, TypedArrayLayout::lengthOffset()); + masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures); - // Load elements vector. - masm.loadPtr(Address(object, TypedArrayLayout::dataOffset()), elementReg); + // Save the object register on the stack in case of failure. + Register elementReg = object; + masm.push(object); - // Load the value. We use an invalid register because the destination - // register is necessary a non double register. - int width = Scalar::byteSize(arrayType); - BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width)); - if (output.hasValue()) { - masm.loadFromTypedArray(arrayType, source, output.valueReg(), allowDoubleResult, - elementReg, &popAndFail); + // Load elements vector. + masm.loadPtr(Address(object, TypedArrayLayout::dataOffset()), elementReg); + + // Load the value. We use an invalid register because the destination + // register is necessary a non double register. + Scalar::Type arrayType = AnyTypedArrayType(array); + 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, &popAndFail); + // 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().elementType(); + BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(UnboxedTypeSize(elementType))); + masm.loadUnboxedProperty(source, elementType, output); } masm.pop(object); attacher.jumpRejoin(masm); // Restore the object before continuing to the next stub. - masm.bind(&popAndFail); + masm.bind(&popObjectAndFail); masm.pop(object); masm.bind(&failures); @@ -3710,13 +3805,14 @@ GenerateGetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub } bool -GetElementIC::attachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion, - HandleObject tarr, const Value& idval) +GetElementIC::attachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript, + IonScript* ion, HandleObject tarr, + const Value& idval) { MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); StubAttacher attacher(*this); - GenerateGetTypedArrayElement(cx, masm, attacher, tarr, idval, object(), index(), output(), - allowDoubleResult()); + GenerateGetTypedOrUnboxedArrayElement(cx, masm, attacher, tarr, idval, object(), index(), + output(), allowDoubleResult()); return linkAndAttachStub(cx, masm, attacher, ion, "typed array"); } @@ -3887,8 +3983,8 @@ GetElementIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex, return false; attachedStub = true; } - if (!attachedStub && canAttachTypedArrayElement(obj, idval, cache.output())) { - if (!cache.attachTypedArrayElement(cx, outerScript, ion, obj, idval)) + if (!attachedStub && canAttachTypedOrUnboxedArrayElement(obj, idval, cache.output())) { + if (!cache.attachTypedOrUnboxedArrayElement(cx, outerScript, ion, obj, idval)) return false; attachedStub = true; } diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index 492c69acae4a..86d47e88e1c1 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -467,7 +467,7 @@ class GetPropertyIC : public IonCache }; // Helpers for CanAttachNativeGetProp - bool allowArrayLength(JSContext* cx, HandleObject obj) const; + bool allowArrayLength(JSContext* cx) const; bool allowGetters() const { return monitoredResult() && !idempotent(); } @@ -503,6 +503,10 @@ class GetPropertyIC : public IonCache HandleObject obj, HandlePropertyName name, void* returnAddr, bool* emitted); + bool tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject obj, HandlePropertyName name, + void* returnAddr, bool* emitted); + bool tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, HandlePropertyName name, bool* emitted); @@ -668,7 +672,7 @@ class GetElementIC : public IonCache // Helpers for CanAttachNativeGetProp typedef JSContext * Context; bool allowGetters() const { MOZ_ASSERT(!idempotent()); return true; } - bool allowArrayLength(Context, HandleObject) const { return false; } + bool allowArrayLength(Context) const { return false; } bool canMonitorSingletonUndefinedSlot(HandleObject holder, HandleShape shape) const { return monitoredResult(); } @@ -677,8 +681,8 @@ class GetElementIC : public IonCache static bool canAttachDenseElement(JSObject* obj, const Value& idval); static bool canAttachDenseElementHole(JSObject* obj, const Value& idval, TypedOrValueRegister output); - static bool canAttachTypedArrayElement(JSObject* obj, const Value& idval, - TypedOrValueRegister output); + static bool canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& idval, + TypedOrValueRegister output); bool attachGetProp(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, const Value& idval, HandlePropertyName name); @@ -689,8 +693,8 @@ class GetElementIC : public IonCache bool attachDenseElementHole(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj, const Value& idval); - bool attachTypedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion, - HandleObject tarr, const Value& idval); + bool attachTypedOrUnboxedArrayElement(JSContext* cx, HandleScript outerScript, IonScript* ion, + HandleObject tarr, const Value& idval); bool attachArgumentsElement(JSContext* cx, HandleScript outerScript, IonScript* ion, HandleObject obj); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 2cb2bc4b5c5c..81163a18333f 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -4142,6 +4142,10 @@ class LMaybeCopyElementsForWrite : public LInstructionHelper<0, 1, 1> const LDefinition* temp() { return getTemp(0); } + + const MMaybeCopyElementsForWrite* mir() const { + return mir_->toMaybeCopyElementsForWrite(); + } }; // Load the initialized length from an elements header. diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 5237ad409137..87f68aa11e61 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -667,7 +667,10 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode) MDefinition* obj = callInfo.thisArg(); TemporaryTypeSet* thisTypes = obj->resultTypeSet(); - if (!thisTypes || thisTypes->getKnownClass(constraints()) != &ArrayObject::class_) + if (!thisTypes) + return InliningStatus_NotInlined; + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) return InliningStatus_NotInlined; if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) { trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); @@ -679,9 +682,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); + if (clasp == &ArrayObject::class_) + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); TemporaryTypeSet* returnTypes = getInlineReturnTypeSet(); bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_NON_PACKED); @@ -692,7 +703,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); @@ -795,7 +807,10 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo) return InliningStatus_NotInlined; TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet(); - if (!thisTypes || thisTypes->getKnownClass(constraints()) != &ArrayObject::class_) + if (!thisTypes) + return InliningStatus_NotInlined; + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) return InliningStatus_NotInlined; if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | OBJECT_FLAG_LENGTH_OVERFLOW)) @@ -816,6 +831,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo) return InliningStatus_NotInlined; } + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), callInfo.thisArg(), nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + } + callInfo.setImplicitlyUsedUnchecked(); value = callInfo.getArg(0); @@ -827,12 +849,13 @@ IonBuilder::inlineArrayPush(CallInfo& callInfo) value = valueDouble; } - obj = addMaybeCopyElementsForWrite(obj); + if (unboxedType == JSVAL_TYPE_MAGIC) + obj = addMaybeCopyElementsForWrite(obj, /* checkNative = */ false); if (NeedsPostBarrier(info(), 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); @@ -863,7 +886,8 @@ IonBuilder::inlineArrayConcat(CallInfo& callInfo) if (!thisTypes || !argTypes) return InliningStatus_NotInlined; - if (thisTypes->getKnownClass(constraints()) != &ArrayObject::class_) + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) return InliningStatus_NotInlined; if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | OBJECT_FLAG_LENGTH_OVERFLOW)) @@ -872,7 +896,7 @@ IonBuilder::inlineArrayConcat(CallInfo& callInfo) return InliningStatus_NotInlined; } - if (argTypes->getKnownClass(constraints()) != &ArrayObject::class_) + if (argTypes->getKnownClass(constraints()) != clasp) return InliningStatus_NotInlined; if (argTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | OBJECT_FLAG_LENGTH_OVERFLOW)) @@ -881,6 +905,15 @@ IonBuilder::inlineArrayConcat(CallInfo& callInfo) return InliningStatus_NotInlined; } + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), callInfo.thisArg(), nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + if (unboxedType != UnboxedArrayElementType(constraints(), callInfo.getArg(0), nullptr)) + return InliningStatus_NotInlined; + } + // Watch out for indexed properties on the prototype. if (ArrayPrototypeHasIndexedProperty(constraints(), script())) { trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); @@ -934,13 +967,14 @@ IonBuilder::inlineArrayConcat(CallInfo& callInfo) JSObject* templateObj = inspector->getTemplateObjectForNative(pc, js::array_concat); if (!templateObj || templateObj->group() != thisGroup) return InliningStatus_NotInlined; - MOZ_ASSERT(templateObj->is()); callInfo.setImplicitlyUsedUnchecked(); - MArrayConcat* ins = MArrayConcat::New(alloc(), constraints(), callInfo.thisArg(), callInfo.getArg(0), - &templateObj->as(), - templateObj->group()->initialHeap(constraints())); + MArrayConcat* ins = MArrayConcat::New(alloc(), constraints(), + callInfo.thisArg(), callInfo.getArg(0), + templateObj, + templateObj->group()->initialHeap(constraints()), + unboxedType); current->add(ins); current->push(ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 8d8ce807150b..74cd7c847ee3 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -360,6 +360,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; + } }; // An MDefinition is an SSA name. @@ -7758,8 +7761,10 @@ class MMaybeCopyElementsForWrite : public MUnaryInstruction, public SingleObjectPolicy::Data { - explicit MMaybeCopyElementsForWrite(MDefinition* object) - : MUnaryInstruction(object) + bool checkNative_; + + explicit MMaybeCopyElementsForWrite(MDefinition* object, bool checkNative) + : MUnaryInstruction(object), checkNative_(checkNative) { setGuard(); setMovable(); @@ -7770,15 +7775,19 @@ class MMaybeCopyElementsForWrite public: INSTRUCTION_HEADER(MaybeCopyElementsForWrite) - static MMaybeCopyElementsForWrite* New(TempAllocator& alloc, MDefinition* object) { - return new(alloc) MMaybeCopyElementsForWrite(object); + static MMaybeCopyElementsForWrite* New(TempAllocator& alloc, MDefinition* object, bool checkNative) { + return new(alloc) MMaybeCopyElementsForWrite(object, checkNative); } MDefinition* object() const { return getOperand(0); } + bool checkNative() const { + return checkNative_; + } bool congruentTo(const MDefinition* ins) const override { - return congruentIfOperandsEqual(ins); + return congruentIfOperandsEqual(ins) && + checkNative() == ins->toMaybeCopyElementsForWrite()->checkNative(); } AliasSet getAliasSet() const override { return AliasSet::Store(AliasSet::ObjectFields); @@ -8542,9 +8551,7 @@ class MLoadElementHole return congruentIfOperandsEqual(other); } AliasSet getAliasSet() const override { - return AliasSet::Load(unboxedType() == JSVAL_TYPE_MAGIC - ? AliasSet::Element - : AliasSet::UnboxedElement); + return AliasSet::Load(AliasSet::BoxedOrUnboxedElements(unboxedType())); } void collectRangeInfoPreTrunc() override; @@ -8804,9 +8811,7 @@ class MStoreElementHole // StoreElementHole can update the initialized length, the array length // or reallocate obj->elements. return AliasSet::Store(AliasSet::ObjectFields | - ((unboxedType() == JSVAL_TYPE_MAGIC) - ? AliasSet::Element - : AliasSet::UnboxedElement)); + AliasSet::BoxedOrUnboxedElements(unboxedType())); } ALLOW_CLONE(MStoreElementHole) @@ -8818,11 +8823,12 @@ class MStoreUnboxedObjectOrNull public StoreUnboxedObjectOrNullPolicy::Data { int32_t offsetAdjustment_; + bool preBarrier_; MStoreUnboxedObjectOrNull(MDefinition* elements, MDefinition* index, MDefinition* value, MDefinition* typedObj, - int32_t offsetAdjustment) - : offsetAdjustment_(offsetAdjustment) + int32_t offsetAdjustment, bool preBarrier) + : offsetAdjustment_(offsetAdjustment), preBarrier_(preBarrier) { initOperand(0, elements); initOperand(1, index); @@ -8839,9 +8845,10 @@ class MStoreUnboxedObjectOrNull static MStoreUnboxedObjectOrNull* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, MDefinition* value, MDefinition* typedObj, - int32_t offsetAdjustment = 0) { + int32_t offsetAdjustment = 0, + bool preBarrier = true) { return new(alloc) MStoreUnboxedObjectOrNull(elements, index, value, typedObj, - offsetAdjustment); + offsetAdjustment, preBarrier); } MDefinition* elements() const { return getOperand(0); @@ -8858,6 +8865,9 @@ class MStoreUnboxedObjectOrNull int32_t offsetAdjustment() const { return offsetAdjustment_; } + bool preBarrier() const { + return preBarrier_; + } AliasSet getAliasSet() const override { return AliasSet::Store(AliasSet::UnboxedElement); } @@ -8876,10 +8886,11 @@ class MStoreUnboxedString public MixPolicy >::Data { int32_t offsetAdjustment_; + bool preBarrier_; MStoreUnboxedString(MDefinition* elements, MDefinition* index, MDefinition* value, - int32_t offsetAdjustment) - : offsetAdjustment_(offsetAdjustment) + int32_t offsetAdjustment, bool preBarrier) + : offsetAdjustment_(offsetAdjustment), preBarrier_(preBarrier) { initOperand(0, elements); initOperand(1, index); @@ -8893,8 +8904,10 @@ class MStoreUnboxedString static MStoreUnboxedString* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index, - MDefinition* value, int32_t offsetAdjustment = 0) { - return new(alloc) MStoreUnboxedString(elements, index, value, offsetAdjustment); + MDefinition* value, int32_t offsetAdjustment = 0, + bool preBarrier = true) { + return new(alloc) MStoreUnboxedString(elements, index, value, + offsetAdjustment, preBarrier); } MDefinition* elements() const { return getOperand(0); @@ -8908,6 +8921,9 @@ class MStoreUnboxedString int32_t offsetAdjustment() const { return offsetAdjustment_; } + bool preBarrier() const { + return preBarrier_; + } AliasSet getAliasSet() const override { return AliasSet::Store(AliasSet::UnboxedElement); } @@ -8982,21 +8998,23 @@ class MArrayPopShift private: Mode mode_; + JSValueType unboxedType_; bool needsHoleCheck_; bool maybeUndefined_; - MArrayPopShift(MDefinition* object, Mode mode, bool needsHoleCheck, bool maybeUndefined) - : MUnaryInstruction(object), mode_(mode), needsHoleCheck_(needsHoleCheck), - maybeUndefined_(maybeUndefined) + MArrayPopShift(MDefinition* object, Mode mode, JSValueType unboxedType, + bool needsHoleCheck, bool maybeUndefined) + : MUnaryInstruction(object), mode_(mode), unboxedType_(unboxedType), + needsHoleCheck_(needsHoleCheck), maybeUndefined_(maybeUndefined) { } public: INSTRUCTION_HEADER(ArrayPopShift) static MArrayPopShift* New(TempAllocator& alloc, MDefinition* object, Mode mode, - bool needsHoleCheck, bool maybeUndefined) + JSValueType unboxedType, bool needsHoleCheck, bool maybeUndefined) { - return new(alloc) MArrayPopShift(object, mode, needsHoleCheck, maybeUndefined); + return new(alloc) MArrayPopShift(object, mode, unboxedType, needsHoleCheck, maybeUndefined); } MDefinition* object() const { @@ -9011,8 +9029,12 @@ class MArrayPopShift bool mode() const { return mode_; } + JSValueType unboxedType() const { + return unboxedType_; + } AliasSet getAliasSet() const override { - return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields); + return AliasSet::Store(AliasSet::ObjectFields | + AliasSet::BoxedOrUnboxedElements(unboxedType())); } ALLOW_CLONE(MArrayPopShift) @@ -9023,8 +9045,10 @@ class MArrayPush : public MBinaryInstruction, public MixPolicy >::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); } @@ -9032,8 +9056,9 @@ class MArrayPush public: INSTRUCTION_HEADER(ArrayPush) - static MArrayPush* New(TempAllocator& alloc, MDefinition* object, MDefinition* value) { - return new(alloc) MArrayPush(object, value); + static MArrayPush* New(TempAllocator& alloc, MDefinition* object, MDefinition* value, + JSValueType unboxedType) { + return new(alloc) MArrayPush(object, value, unboxedType); } MDefinition* object() const { @@ -9042,8 +9067,12 @@ class MArrayPush MDefinition* value() const { return getOperand(1); } + JSValueType unboxedType() const { + return unboxedType_; + } AliasSet getAliasSet() const override { - return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields); + return AliasSet::Store(AliasSet::ObjectFields | + AliasSet::BoxedOrUnboxedElements(unboxedType())); } void computeRange(TempAllocator& alloc) override; @@ -9055,14 +9084,16 @@ class MArrayConcat : public MBinaryInstruction, public MixPolicy, ObjectPolicy<1> >::Data { - AlwaysTenured templateObj_; + AlwaysTenuredObject templateObj_; gc::InitialHeap initialHeap_; + JSValueType unboxedType_; MArrayConcat(CompilerConstraintList* constraints, MDefinition* lhs, MDefinition* rhs, - ArrayObject* templateObj, gc::InitialHeap initialHeap) + JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType) : MBinaryInstruction(lhs, rhs), templateObj_(templateObj), - initialHeap_(initialHeap) + initialHeap_(initialHeap), + unboxedType_(unboxedType) { setResultType(MIRType_Object); setResultTypeSet(MakeSingletonTypeSet(constraints, templateObj)); @@ -9073,12 +9104,14 @@ class MArrayConcat static MArrayConcat* New(TempAllocator& alloc, CompilerConstraintList* constraints, MDefinition* lhs, MDefinition* rhs, - ArrayObject* templateObj, gc::InitialHeap initialHeap) + JSObject* templateObj, gc::InitialHeap initialHeap, + JSValueType unboxedType) { - return new(alloc) MArrayConcat(constraints, lhs, rhs, templateObj, initialHeap); + return new(alloc) MArrayConcat(constraints, lhs, rhs, templateObj, + initialHeap, unboxedType); } - ArrayObject* templateObj() const { + JSObject* templateObj() const { return templateObj_; } @@ -9086,8 +9119,13 @@ class MArrayConcat return initialHeap_; } + JSValueType unboxedType() const { + return unboxedType_; + } + AliasSet getAliasSet() const override { - return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields); + return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedType()) | + AliasSet::ObjectFields); } bool possiblyCalls() const override { return true; diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index d2191e5e6704..2f7ea6489a4a 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -269,7 +269,7 @@ ArraySpliceDense(JSContext* cx, HandleObject obj, uint32_t start, uint32_t delet bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) { - MOZ_ASSERT(obj->is()); + MOZ_ASSERT(obj->is() || obj->is()); AutoDetectInvalidation adi(cx, rval); @@ -288,21 +288,14 @@ ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) } bool -ArrayPushDense(JSContext* cx, HandleArrayObject obj, HandleValue v, uint32_t* length) +ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length) { - if (MOZ_LIKELY(obj->lengthIsWritable())) { - uint32_t idx = obj->length(); - DenseElementResult result = obj->ensureDenseElements(cx, idx, 1); - if (result == DenseElementResult::Failure) - return false; - - if (result == DenseElementResult::Success) { - obj->setDenseElement(idx, v); - MOZ_ASSERT(idx < INT32_MAX); - *length = idx + 1; - obj->setLengthInt32(*length); - return true; - } + *length = GetAnyBoxedOrUnboxedArrayLength(obj); + DenseElementResult result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, *length, v.address(), 1, DontUpdateTypes); + if (result != DenseElementResult::Incomplete) { + (*length)++; + return result == DenseElementResult::Success; } JS::AutoValueArray<3> argv(cx); @@ -319,7 +312,7 @@ ArrayPushDense(JSContext* cx, HandleArrayObject obj, HandleValue v, uint32_t* le bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) { - MOZ_ASSERT(obj->is()); + MOZ_ASSERT(obj->is() || obj->is()); AutoDetectInvalidation adi(cx, rval); @@ -340,21 +333,17 @@ ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval) JSObject* ArrayConcatDense(JSContext* cx, HandleObject obj1, HandleObject obj2, HandleObject objRes) { - Rooted arr1(cx, &obj1->as()); - Rooted arr2(cx, &obj2->as()); - Rooted arrRes(cx, objRes ? &objRes->as() : nullptr); - - if (arrRes) { + if (objRes) { // Fast path if we managed to allocate an object inline. - if (!js::array_concat_dense(cx, arr1, arr2, arrRes)) + if (!js::array_concat_dense(cx, obj1, obj2, objRes)) return nullptr; - return arrRes; + return objRes; } JS::AutoValueArray<3> argv(cx); argv[0].setUndefined(); - argv[1].setObject(*arr1); - argv[2].setObject(*arr2); + argv[1].setObject(*obj1); + argv[2].setObject(*obj2); if (!js::array_concat(cx, 1, argv.begin())) return nullptr; return &argv[0].toObject(); diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 25186b7e2da2..48bd686e4276 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -658,7 +658,7 @@ template bool StringsEqual(JSContext* cx, HandleString left, HandleString right, bool* res); bool ArrayPopDense(JSContext* cx, HandleObject obj, MutableHandleValue rval); -bool ArrayPushDense(JSContext* cx, HandleArrayObject obj, HandleValue v, uint32_t* length); +bool ArrayPushDense(JSContext* cx, HandleObject obj, HandleValue v, uint32_t* length); bool ArrayShiftDense(JSContext* cx, HandleObject obj, MutableHandleValue rval); JSObject* ArrayConcatDense(JSContext* cx, HandleObject obj1, HandleObject obj2, HandleObject res); JSString* ArrayJoin(JSContext* cx, HandleObject array, HandleString sep); diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 37d308697b2f..a0ac77868461 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2069,18 +2069,38 @@ js::array_pop(JSContext* cx, unsigned argc, Value* vp) return SetLengthProperty(cx, obj, index); } -void -js::ArrayShiftMoveElements(ArrayObject* obj) +template +static inline DenseElementResult +ShiftMoveBoxedOrUnboxedDenseElements(JSObject* obj) { - MOZ_ASSERT(obj->lengthIsWritable()); + MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(obj)); /* * At this point the length and initialized length have already been * decremented and the result fetched, so just shift the array elements * themselves. */ - uint32_t initlen = obj->getDenseInitializedLength(); - obj->moveDenseElementsNoPreBarrier(0, 1, initlen); + size_t initlen = GetBoxedOrUnboxedInitializedLength(obj); + if (Type == JSVAL_TYPE_MAGIC) { + obj->as().moveDenseElementsNoPreBarrier(0, 1, initlen); + } else { + uint8_t* data = obj->as().elements(); + size_t elementSize = UnboxedTypeSize(Type); + memmove(data, data + elementSize, initlen * elementSize); + } + + return DenseElementResult::Success; +} + +DefineBoxedOrUnboxedFunctor1(ShiftMoveBoxedOrUnboxedDenseElements, JSObject*); + +void +js::ArrayShiftMoveElements(JSObject* obj) +{ + MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); + + ShiftMoveBoxedOrUnboxedDenseElementsFunctor functor(obj); + JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success); } template @@ -2263,23 +2283,23 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp) // object. The resulting array will have the same boxed/unboxed elements // representation as the input object, and will either reuse the input // object's group or will have unknown property types. -static inline JSObject* -NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, JSObject* obj, size_t length) +JSObject* +js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, JSObject* obj, size_t length, + NewObjectKind newKind, bool forceAnalyze) { if (!obj->is() && !obj->is()) - return NewDenseFullyAllocatedArray(cx, length); + return NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); if (obj->getProto() != cx->global()->maybeGetArrayPrototype()) - return NewDenseFullyAllocatedArray(cx, length); + return NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); RootedObjectGroup group(cx, obj->getGroup(cx)); if (!group) return nullptr; if (group->maybePreliminaryObjects()) - group->maybePreliminaryObjects()->maybeAnalyze(cx, group); + group->maybePreliminaryObjects()->maybeAnalyze(cx, group, forceAnalyze); - NewObjectKind newKind = GenericObject; if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; @@ -2442,7 +2462,7 @@ js::array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueI if (!arr) return false; DebugOnly result = - CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, actualStart, actualDeleteCount); + CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount); MOZ_ASSERT(result.value == DenseElementResult::Success); } } else { @@ -2607,29 +2627,49 @@ js::array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueI return true; } -bool -js::array_concat_dense(JSContext* cx, Handle arr1, Handle arr2, - Handle result) +template +DenseElementResult +ArrayConcatDenseKernel(JSContext* cx, JSObject* obj1, JSObject* obj2, JSObject* result) { - uint32_t initlen1 = arr1->getDenseInitializedLength(); - MOZ_ASSERT(initlen1 == arr1->length()); + uint32_t initlen1 = GetBoxedOrUnboxedInitializedLength(obj1); + MOZ_ASSERT(initlen1 == GetAnyBoxedOrUnboxedArrayLength(obj1)); - uint32_t initlen2 = arr2->getDenseInitializedLength(); - MOZ_ASSERT(initlen2 == arr2->length()); + uint32_t initlen2 = GetBoxedOrUnboxedInitializedLength(obj2); + MOZ_ASSERT(initlen2 == GetAnyBoxedOrUnboxedArrayLength(obj2)); /* No overflow here due to nelements limit. */ uint32_t len = initlen1 + initlen2; - if (!result->ensureElements(cx, len)) - return false; + MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength(result) == 0); - MOZ_ASSERT(!result->getDenseInitializedLength()); - result->setDenseInitializedLength(len); + if (Type == JSVAL_TYPE_MAGIC) { + if (!result->as().ensureElements(cx, len)) + return DenseElementResult::Failure; + } else { + if (result->as().capacity() < len) { + if (!result->as().growElements(cx, len)) + return DenseElementResult::Failure; + } + } - result->initDenseElements(0, arr1->getDenseElements(), initlen1); - result->initDenseElements(initlen1, arr2->getDenseElements(), initlen2); - result->setLengthInt32(len); - return true; + CopyBoxedOrUnboxedDenseElements(cx, result, obj1, 0, 0, initlen1); + CopyBoxedOrUnboxedDenseElements(cx, result, obj2, initlen1, 0, initlen2); + + SetAnyBoxedOrUnboxedArrayLength(cx, result, len); + return DenseElementResult::Success; +} + +DefineBoxedOrUnboxedFunctor4(ArrayConcatDenseKernel, + JSContext*, JSObject*, JSObject*, JSObject*); + +bool +js::array_concat_dense(JSContext* cx, HandleObject obj1, HandleObject obj2, + HandleObject result) +{ + ArrayConcatDenseKernelFunctor functor(cx, obj1, obj2, result); + DenseElementResult rv = CallBoxedOrUnboxedSpecialization(functor, result); + MOZ_ASSERT(rv != DenseElementResult::Incomplete); + return rv == DenseElementResult::Success; } /* @@ -2661,7 +2701,7 @@ js::array_concat(JSContext* cx, unsigned argc, Value* vp) SetAnyBoxedOrUnboxedArrayLength(cx, narr, length); DebugOnly result = - CopyAnyBoxedOrUnboxedDenseElements(cx, narr, aobj, 0, initlen); + CopyAnyBoxedOrUnboxedDenseElements(cx, narr, aobj, 0, 0, initlen); MOZ_ASSERT(result.value == DenseElementResult::Success); args.rval().setObject(*narr); @@ -2917,7 +2957,7 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp) if (count) { DebugOnly result = - CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, begin, count); + CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, 0, begin, count); MOZ_ASSERT(result.value == DenseElementResult::Success); } args.rval().setObject(*narr); diff --git a/js/src/jsarray.h b/js/src/jsarray.h index e5cbbcfad1ae..24b6711a7c7e 100644 --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -95,6 +95,12 @@ NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSObject extern JSObject* NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap); +/* Create a dense or unboxed array, using the same group as |obj| if possible. */ +extern JSObject* +NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, JSObject* obj, size_t length, + NewObjectKind newKind = GenericObject, + bool forceAnalyze = false); + /* * Determines whether a write to the given element on |obj| should fail because * |obj| is an Array with a non-writable length, and writing that element would @@ -153,8 +159,8 @@ JSString* ArrayJoin(JSContext* cx, HandleObject obj, HandleLinearString sepstr, uint32_t length); extern bool -array_concat_dense(JSContext* cx, Handle arr1, Handle arr2, - Handle result); +array_concat_dense(JSContext* cx, HandleObject arr1, HandleObject arr2, + HandleObject result); bool array_join(JSContext* cx, unsigned argc, js::Value* vp); @@ -163,7 +169,7 @@ extern JSString* array_join_impl(JSContext* cx, HandleValue array, HandleString sep); extern void -ArrayShiftMoveElements(ArrayObject* obj); +ArrayShiftMoveElements(JSObject* obj); extern bool array_shift(JSContext* cx, unsigned argc, js::Value* vp); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 1d2eea591210..36d132b9625b 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2810,6 +2810,12 @@ js::LookupPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, JSObject** MarkNonNativePropertyFound(propp); return true; } + } else if (obj->is()) { + if (obj->as().containsProperty(cx, id)) { + *objp = obj; + MarkNonNativePropertyFound(propp); + return true; + } } else if (obj->is()) { if (obj->as().typeDescr().hasProperty(cx->names(), id)) { *objp = obj; diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h index 32e138408708..3934ab17071a 100644 --- a/js/src/vm/UnboxedObject-inl.h +++ b/js/src/vm/UnboxedObject-inl.h @@ -282,6 +282,16 @@ GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj) return 0; } +static inline size_t +GetAnyBoxedOrUnboxedCapacity(JSObject* obj) +{ + if (obj->isNative()) + return obj->as().getDenseCapacity(); + if (obj->is()) + return obj->as().capacity(); + return 0; +} + static inline Value GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index) { @@ -290,6 +300,14 @@ GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index) return obj->as().getElement(index); } +static inline size_t +GetAnyBoxedOrUnboxedArrayLength(JSObject* obj) +{ + if (obj->is()) + return obj->as().length(); + return obj->as().length(); +} + static inline void SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length) { @@ -493,24 +511,26 @@ MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, template static inline DenseElementResult CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, - uint32_t srcStart, uint32_t length) + uint32_t dstStart, uint32_t srcStart, uint32_t length) { MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(src)); MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(dst)); - MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength(dst) == 0); + MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength(dst) == dstStart); MOZ_ASSERT(GetBoxedOrUnboxedCapacity(dst) >= length); - SetBoxedOrUnboxedInitializedLength(cx, dst, length); + SetBoxedOrUnboxedInitializedLength(cx, dst, dstStart + length); if (Type == JSVAL_TYPE_MAGIC) { const Value* vp = src->as().getDenseElements() + srcStart; - dst->as().initDenseElements(0, vp, length); + dst->as().initDenseElements(dstStart, vp, length); } else { uint8_t* dstData = dst->as().elements(); uint8_t* srcData = src->as().elements(); size_t elementSize = UnboxedTypeSize(Type); - memcpy(dstData, srcData + srcStart * elementSize, length * elementSize); + memcpy(dstData + dstStart * elementSize, + srcData + srcStart * elementSize, + length * elementSize); // Add a post barrier if we might have copied a nursery pointer to dst. if (UnboxedTypeNeedsPostBarrier(Type) && !IsInsideNursery(dst)) @@ -554,6 +574,18 @@ CallBoxedOrUnboxedSpecialization(F f, JSObject* obj) #undef DEPENDENT_TEMPLATE_HINT +#define DefineBoxedOrUnboxedFunctor1(Signature, A) \ +struct Signature ## Functor { \ + A a; \ + explicit Signature ## Functor(A a) \ + : a(a) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a); \ + } \ +} + #define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \ struct Signature ## Functor { \ A a; B b; C c; \ @@ -613,7 +645,7 @@ MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, DenseElementResult CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, - uint32_t srcStart, uint32_t length); + uint32_t dstStart, uint32_t srcStart, uint32_t length); void SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen); diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index ae6abdc1814a..e46df9f086bf 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -1281,7 +1281,7 @@ UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap) } bool -UnboxedArrayObject::containsProperty(JSContext* cx, jsid id) +UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id) { if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength()) return true; @@ -1396,11 +1396,11 @@ UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id if (!CanonicalizeArrayLengthValue(cx, v, &len)) return false; UnboxedArrayObject* nobj = &obj->as(); - nobj->setLength(cx, len); if (len < nobj->initializedLength()) { nobj->setInitializedLength(len); nobj->shrinkElements(cx, len); } + nobj->setLength(cx, len); return result.succeed(); } @@ -1439,6 +1439,15 @@ UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { + if (obj->as().containsProperty(cx, id)) { + size_t initlen = obj->as().initializedLength(); + if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) { + obj->as().setInitializedLength(initlen - 1); + obj->as().shrinkElements(cx, initlen - 1); + return result.succeed(); + } + } + if (!convertToNative(cx, obj)) return false; return DeleteProperty(cx, obj, id, result); @@ -1959,14 +1968,14 @@ js::MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, return CallBoxedOrUnboxedSpecialization(functor, obj); } -DefineBoxedOrUnboxedFunctor5(CopyBoxedOrUnboxedDenseElements, - JSContext*, JSObject*, JSObject*, uint32_t, uint32_t); +DefineBoxedOrUnboxedFunctor6(CopyBoxedOrUnboxedDenseElements, + JSContext*, JSObject*, JSObject*, uint32_t, uint32_t, uint32_t); DenseElementResult js::CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, - uint32_t srcStart, uint32_t length) + uint32_t dstStart, uint32_t srcStart, uint32_t length) { - CopyBoxedOrUnboxedDenseElementsFunctor functor(cx, dst, src, srcStart, length); + CopyBoxedOrUnboxedDenseElementsFunctor functor(cx, dst, src, dstStart, srcStart, length); return CallBoxedOrUnboxedSpecialization(functor, dst); } diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h index 3651bbc7bfd2..39e48d754eb3 100644 --- a/js/src/vm/UnboxedObject.h +++ b/js/src/vm/UnboxedObject.h @@ -435,7 +435,7 @@ class UnboxedArrayObject : public JSObject return computeCapacity(capacityIndex(), length()); } - bool containsProperty(JSContext* cx, jsid id); + 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);