diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index a6adc122c7..e61cd065d1 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -1464,6 +1464,12 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_ res.isNumber() && !TypedArrayGetElemStubExists(stub, obj)) { + if (obj->is() && + obj->as().hasResizableOrGrowableBuffer()) + { + return true; + } + if (!cx->runtime()->jitSupportsFloatingPoint && (TypedThingRequiresFloatingPoint(obj) || rhs.isDouble())) { @@ -2154,6 +2160,8 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm) Register obj = masm.extractObject(R0, ExtractTemp0); masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg); masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); + if (layout_ == Layout_TypedArray) + GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure); // Ensure the index is an integer. if (cx->runtime()->jitSupportsFloatingPoint) { @@ -2625,6 +2633,9 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_ bool expectOutOfBounds; double idx = index.toNumber(); if (obj->is()) { + if (obj->as().hasResizableOrGrowableBuffer()) + return true; + expectOutOfBounds = (idx < 0 || idx >= double(obj->as().length())); } else { // Typed objects throw on out of bounds accesses. Don't attach @@ -3215,6 +3226,8 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm) Register obj = masm.extractObject(R0, ExtractTemp0); masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg); masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); + if (layout_ == Layout_TypedArray) + GuardResizableOrGrowableTypedArray(masm, obj, scratchReg, &failure); // Ensure the index is an integer. if (cx->runtime()->jitSupportsFloatingPoint) { diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 3aa6fba49d..c7d6feed05 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -8889,9 +8889,17 @@ CodeGenerator::branchIfNotEmptyObjectElements(Register obj, Label* target) Address(obj, NativeObject::offsetOfElements()), ImmPtr(js::emptyObjectElements), &emptyObj); - masm.branchPtr(Assembler::NotEqual, + masm.branchPtr(Assembler::Equal, Address(obj, NativeObject::offsetOfElements()), ImmPtr(js::emptyObjectElementsShared), + &emptyObj); + masm.branchPtr(Assembler::Equal, + Address(obj, NativeObject::offsetOfElements()), + ImmPtr(js::emptyObjectElementsResizableOrGrowable), + &emptyObj); + masm.branchPtr(Assembler::NotEqual, + Address(obj, NativeObject::offsetOfElements()), + ImmPtr(js::emptyObjectElementsSharedResizableOrGrowable), target); masm.bind(&emptyObj); } diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index b0fd03538d..2027ce2df4 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -535,8 +535,7 @@ class CodeGenerator final : public CodeGeneratorSpecific Label* ifDoesntEmulateUndefined, Register scratch, OutOfLineTestObject* ool); - // Branch to target unless obj has an emptyObjectElements or emptyObjectElementsShared - // elements pointer. + // Branch to target unless obj has one of the empty elements pointers. void branchIfNotEmptyObjectElements(Register obj, Label* target); void emitStoreElementTyped(const LAllocation* value, MIRType valueType, MIRType elementType, diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 5a7e437285..095dc14175 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -1245,6 +1245,7 @@ GenerateTypedArrayLength(JSContext* cx, MacroAssembler& masm, IonCache::StubAtta masm.branchPtr(Assembler::AboveOrEqual, tmpReg, ImmPtr(&TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]), failures); + GuardResizableOrGrowableTypedArray(masm, object, tmpReg, failures); // Load length. masm.loadTypedOrValue(Address(object, TypedArrayObject::lengthOffset()), output); @@ -1644,6 +1645,9 @@ GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript if (!JSID_IS_ATOM(id, cx->names().length)) return true; + if (obj->as().hasResizableOrGrowableBuffer()) + return true; + if (hasTypedArrayLengthStub(obj)) return true; @@ -4038,6 +4042,12 @@ GetPropertyIC::canAttachTypedOrUnboxedArrayElement(JSObject* obj, const Value& i if (!obj->is() && !obj->is()) return false; + if (obj->is() && + obj->as().hasResizableOrGrowableBuffer()) + { + return false; + } + MOZ_ASSERT(idval.isInt32() || idval.isString()); // Don't emit a stub if the access is out of bounds. We make to make @@ -4092,6 +4102,9 @@ GenerateGetTypedOrUnboxedArrayElement(JSContext* cx, MacroAssembler& masm, // Decide to what type index the stub should be optimized Register tmpReg = output.scratchReg().gpr(); MOZ_ASSERT(tmpReg != InvalidReg); + if (array->is()) + GuardResizableOrGrowableTypedArray(masm, object, tmpReg, &failures); + Register indexReg = tmpReg; if (idval.isString()) { MOZ_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX); @@ -4575,6 +4588,7 @@ GenerateSetTypedArrayElement(JSContext* cx, MacroAssembler& masm, IonCache::Stub if (!shape) return false; masm.branchTestObjShape(Assembler::NotEqual, object, shape, &failures); + GuardResizableOrGrowableTypedArray(masm, object, temp, &failures); // Ensure the index is an int32. Register indexReg; @@ -4661,6 +4675,9 @@ SetPropertyIC::tryAttachTypedArrayElement(JSContext* cx, HandleScript outerScrip if (!IsTypedArrayElementSetInlineable(obj, idval, val)) return true; + if (obj->as().hasResizableOrGrowableBuffer()) + return true; + *emitted = true; MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 0f78ef3f2f..4585a838b4 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -365,13 +365,9 @@ IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target) // Try to optimize typed array lengths. if (TypedArrayObject::isOriginalLengthGetter(native)) { - Scalar::Type type = thisTypes->getTypedArrayType(constraints()); - if (type == Scalar::MaxTypedArrayViewType) - return InliningStatus_NotInlined; - - MInstruction* length = addTypedArrayLength(thisArg); - current->push(length); - return InliningStatus_Inlined; + // RAB/GSAB views can have dynamic length or temporarily become + // out-of-bounds. Let the property IC/VM path handle the getter. + return InliningStatus_NotInlined; } // Try to optimize RegExp getters. @@ -2480,21 +2476,10 @@ IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def) IonBuilder::InliningStatus IonBuilder::inlinePossiblyWrappedTypedArrayLength(CallInfo& callInfo) { - MOZ_ASSERT(!callInfo.constructing()); - MOZ_ASSERT(callInfo.argc() == 1); - if (callInfo.getArg(0)->type() != MIRType::Object) - return InliningStatus_NotInlined; - if (getInlineReturnType() != MIRType::Int32) - return InliningStatus_NotInlined; + (void) callInfo; - if (!IsTypedArrayObject(constraints(), callInfo.getArg(0))) - return InliningStatus_NotInlined; - - MInstruction* length = addTypedArrayLength(callInfo.getArg(0)); - current->push(length); - - callInfo.setImplicitlyUsedUnchecked(); - return InliningStatus_Inlined; + // RAB/GSAB views require dynamic length semantics. + return InliningStatus_NotInlined; } IonBuilder::InliningStatus @@ -3251,45 +3236,14 @@ bool IonBuilder::atomicsMeetsPreconditions(CallInfo& callInfo, Scalar::Type* arrayType, bool* requiresTagCheck, AtomicCheckResult checkResult) { - if (!JitSupportsAtomics()) - return false; + (void) callInfo; + (void) arrayType; + (void) requiresTagCheck; + (void) checkResult; - if (callInfo.getArg(0)->type() != MIRType::Object) - return false; - - if (callInfo.getArg(1)->type() != MIRType::Int32) - return false; - - // Ensure that the first argument is a TypedArray that maps shared - // memory. - // - // Then check both that the element type is something we can - // optimize and that the return type is suitable for that element - // type. - - TemporaryTypeSet* arg0Types = callInfo.getArg(0)->resultTypeSet(); - if (!arg0Types) - return false; - - TemporaryTypeSet::TypedArraySharedness sharedness; - *arrayType = arg0Types->getTypedArrayType(constraints(), &sharedness); - *requiresTagCheck = sharedness != TemporaryTypeSet::KnownShared; - switch (*arrayType) { - case Scalar::Int8: - case Scalar::Uint8: - case Scalar::Int16: - case Scalar::Uint16: - case Scalar::Int32: - return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Int32; - case Scalar::Uint32: - // Bug 1077305: it would be attractive to allow inlining even - // if the inline return type is Int32, which it will frequently - // be. - return checkResult == DontCheckAtomicResult || getInlineReturnType() == MIRType::Double; - default: - // Excludes floating types and Uint8Clamped. - return false; - } + // Atomics bounds checks through addTypedArrayLengthAndData assume stable + // SharedArrayBuffer lengths. Avoid this path now that growable SAB exists. + return false; } void diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index e7cb0e55f0..e602212c6f 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -5505,25 +5505,15 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints, MDefinition* obj, MDefinition* id, Scalar::Type* arrayType) { - if (obj->mightBeType(MIRType::String)) - return false; + (void) constraints; + (void) obj; + (void) id; + (void) arrayType; - if (id->type() != MIRType::Int32 && id->type() != MIRType::Double) - return false; - - TemporaryTypeSet* types = obj->resultTypeSet(); - if (!types) - return false; - - *arrayType = types->getTypedArrayType(constraints); - - // FIXME: https://bugzil.la/1536699 - if (*arrayType == Scalar::MaxTypedArrayViewType || - Scalar::isBigIntType(*arrayType)) { - return false; - } - - return true; + // Resizable ArrayBuffer and growable SharedArrayBuffer views need dynamic + // length/out-of-bounds handling. This older MIR path assumes stable + // typed-array length/data slots, so keep element accesses on IC/VM paths. + return false; } bool diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index d337534768..c23fe60d74 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -3608,6 +3608,17 @@ CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Labe masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure); } +void +GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch, + Label* failure) +{ + masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); + masm.branchTest32(Assembler::NonZero, + Address(scratch, ObjectElements::offsetOfFlags()), + Imm32(ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER), + failure); +} + void LoadTypedThingData(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result) { diff --git a/js/src/jit/SharedIC.h b/js/src/jit/SharedIC.h index d259ebf0bc..ba85db660f 100644 --- a/js/src/jit/SharedIC.h +++ b/js/src/jit/SharedIC.h @@ -2302,6 +2302,10 @@ CheckDOMProxyExpandoDoesNotShadow(JSContext* cx, MacroAssembler& masm, Register void CheckForTypedObjectWithDetachedStorage(JSContext* cx, MacroAssembler& masm, Label* failure); +void +GuardResizableOrGrowableTypedArray(MacroAssembler& masm, Register obj, Register scratch, + Label* failure); + MOZ_MUST_USE bool DoCallNativeGetter(JSContext* cx, HandleFunction callee, HandleObject obj, MutableHandleValue result); diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index c463019dac..2697cb5337 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1748,6 +1748,9 @@ JS_IsFloat64Array(JSObject* obj); extern JS_FRIEND_API(bool) JS_GetTypedArraySharedness(JSObject* obj); +extern JS_FRIEND_API(uint32_t) +JS_GetTypedArrayLength(JSObject* obj); + /* * Test for specific typed array types (ArrayBufferView subtypes) and return * the unwrapped object if so, else nullptr. Never throws. @@ -1814,8 +1817,7 @@ inline void \ Get ## Type ## ArrayLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, type** data) \ { \ MOZ_ASSERT(GetObjectClass(obj) == detail::Type ## ArrayClassPtr); \ - const JS::Value& lenSlot = GetReservedSlot(obj, detail::TypedArrayLengthSlot); \ - *length = mozilla::AssertedCast(lenSlot.toInt32()); \ + *length = JS_GetTypedArrayLength(obj); \ *isSharedMemory = JS_GetTypedArraySharedness(obj); \ *data = static_cast(GetObjectPrivate(obj)); \ } diff --git a/js/src/tests/non262/ArrayBuffer/resizable-views.js b/js/src/tests/non262/ArrayBuffer/resizable-views.js index 32d2e0e342..94a409f637 100644 --- a/js/src/tests/non262/ArrayBuffer/resizable-views.js +++ b/js/src/tests/non262/ArrayBuffer/resizable-views.js @@ -66,5 +66,58 @@ assertEq(sharedTracking.length, 8); sharedTracking[6] = 33; assertEq(new Uint8Array(gsab)[6], 33); +function readLength(view) { + return view.length; +} + +function readElement(view, index) { + return view[index]; +} + +function writeElement(view, index, value) { + view[index] = value; +} + +var normal = new Uint8Array(4); +normal[0] = 7; +for (var i = 0; i < 2000; i++) { + assertEq(readLength(normal), 4); + assertEq(readElement(normal, 0), 7); + writeElement(normal, 1, 8); +} + +var icRab = new ArrayBuffer(4, { maxByteLength: 8 }); +var icTracking = new Uint8Array(icRab); +icTracking[0] = 9; +assertEq(readLength(icTracking), 4); +assertEq(readElement(icTracking, 0), 9); +icRab.resize(0); +assertEq(readLength(icTracking), 0); +assertEq(readElement(icTracking, 0), undefined); +writeElement(icTracking, 0, 1); +icRab.resize(4); +assertEq(readLength(icTracking), 4); +assertEq(readElement(icTracking, 0), 0); +writeElement(icTracking, 0, 12); +assertEq(readElement(icTracking, 0), 12); + +var icFixed = new Uint8Array(icRab, 1, 2); +assertEq(readLength(icFixed), 2); +icRab.resize(2); +assertEq(readLength(icFixed), 0); +assertEq(readElement(icFixed, 0), undefined); +writeElement(icFixed, 0, 55); +icRab.resize(4); +assertEq(readLength(icFixed), 2); +assertEq(readElement(icFixed, 0), 0); + +var icGsab = new SharedArrayBuffer(4, { maxByteLength: 8 }); +var icSharedTracking = new Uint8Array(icGsab); +assertEq(readLength(icSharedTracking), 4); +icGsab.grow(8); +assertEq(readLength(icSharedTracking), 8); +writeElement(icSharedTracking, 5, 77); +assertEq(readElement(icSharedTracking, 5), 77); + if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index cde86fb829..b34a20c91b 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -43,6 +43,22 @@ static const ObjectElements emptyElementsHeaderShared(0, 0, ObjectElements::Shar HeapSlot* const js::emptyObjectElementsShared = reinterpret_cast(uintptr_t(&emptyElementsHeaderShared) + sizeof(ObjectElements)); +static const ObjectElements emptyElementsHeaderResizableOrGrowable( + 0, 0, ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER); + +/* Objects with no elements share one empty set of elements. */ +HeapSlot* const js::emptyObjectElementsResizableOrGrowable = + reinterpret_cast(uintptr_t(&emptyElementsHeaderResizableOrGrowable) + + sizeof(ObjectElements)); + +static const ObjectElements emptyElementsHeaderSharedResizableOrGrowable( + 0, 0, ObjectElements::SHARED_MEMORY | ObjectElements::RESIZABLE_OR_GROWABLE_BUFFER); + +/* Objects with no elements share one empty set of elements. */ +HeapSlot* const js::emptyObjectElementsSharedResizableOrGrowable = + reinterpret_cast(uintptr_t(&emptyElementsHeaderSharedResizableOrGrowable) + + sizeof(ObjectElements)); + #ifdef DEBUG @@ -61,10 +77,12 @@ ObjectElements::ConvertElementsToDoubles(JSContext* cx, uintptr_t elementsPtr) * This function is infallible, but has a fallible interface so that it can * be called directly from Ion code. Only arrays can have their dense * elements converted to doubles, and arrays never have empty elements. - */ + */ HeapSlot* elementsHeapPtr = (HeapSlot*) elementsPtr; MOZ_ASSERT(elementsHeapPtr != emptyObjectElements && - elementsHeapPtr != emptyObjectElementsShared); + elementsHeapPtr != emptyObjectElementsShared && + elementsHeapPtr != emptyObjectElementsResizableOrGrowable && + elementsHeapPtr != emptyObjectElementsSharedResizableOrGrowable); ObjectElements* header = ObjectElements::fromElements(elementsHeapPtr); MOZ_ASSERT(!header->shouldConvertDoubleElements()); diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 86977d109f..67fd3a7a46 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -185,6 +185,11 @@ class ObjectElements // These elements are set to integrity level "frozen". FROZEN = 0x10, + + // For TypedArrays only: this TypedArray views a resizable + // ArrayBuffer or growable SharedArrayBuffer. JIT fast paths with + // cached length/data assumptions must fall back for these objects. + RESIZABLE_OR_GROWABLE_BUFFER = 0x20, }; private: @@ -255,6 +260,10 @@ class ObjectElements : flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length) {} + constexpr ObjectElements(uint32_t capacity, uint32_t length, uint32_t flags) + : flags(flags), initializedLength(0), capacity(capacity), length(length) + {} + HeapSlot* elements() { return reinterpret_cast(uintptr_t(this) + sizeof(ObjectElements)); } @@ -269,6 +278,10 @@ class ObjectElements return flags & SHARED_MEMORY; } + bool hasResizableOrGrowableBuffer() const { + return flags & RESIZABLE_OR_GROWABLE_BUFFER; + } + GCPtrNativeObject& ownerObject() const { MOZ_ASSERT(isCopyOnWrite()); return *(GCPtrNativeObject*)(&elements()[initializedLength]); @@ -326,6 +339,8 @@ static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(Obj */ extern HeapSlot* const emptyObjectElements; extern HeapSlot* const emptyObjectElementsShared; +extern HeapSlot* const emptyObjectElementsResizableOrGrowable; +extern HeapSlot* const emptyObjectElementsSharedResizableOrGrowable; struct Class; class GCMarker; @@ -480,6 +495,12 @@ class NativeObject : public ShapedObject elements_ = emptyObjectElementsShared; } + void setHasResizableOrGrowableBuffer() { + MOZ_ASSERT(elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared); + elements_ = isSharedMemory() ? emptyObjectElementsSharedResizableOrGrowable + : emptyObjectElementsResizableOrGrowable; + } + bool isInWholeCellBuffer() const { const gc::TenuredCell* cell = &asTenured(); gc::ArenaCellSet* cells = cell->arena()->bufferedCells; @@ -1254,7 +1275,10 @@ class NativeObject : public ShapedObject } inline bool hasEmptyElements() const { - return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared; + return elements_ == emptyObjectElements || + elements_ == emptyObjectElementsShared || + elements_ == emptyObjectElementsResizableOrGrowable || + elements_ == emptyObjectElementsSharedResizableOrGrowable; } /* diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 552102dc78..7259f242fd 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -509,12 +509,19 @@ class TypedArrayObjectTemplate : public TypedArrayObject return nullptr; bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get()); + bool hasResizableOrGrowableBuffer = + buffer && + ((buffer->is() && buffer->as().isResizable()) || + (buffer->is() && + buffer->as().isGrowable())); obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer)); // This is invariant. Self-hosting code that sets BUFFER_SLOT // (if it does) must maintain it, should it need to. if (isSharedMemory) obj->setIsSharedMemory(); + if (hasResizableOrGrowableBuffer) + obj->setHasResizableOrGrowableBuffer(); if (buffer) { obj->initViewData(buffer->dataPointerEither() + byteOffset);