diff --git a/js/src/jit/AliasAnalysisShared.cpp b/js/src/jit/AliasAnalysisShared.cpp index d33dc40e8b0b..f2a48842a751 100644 --- a/js/src/jit/AliasAnalysisShared.cpp +++ b/js/src/jit/AliasAnalysisShared.cpp @@ -90,6 +90,7 @@ GetObject(const MDefinition* ins) case MDefinition::Op_ArrayLength: case MDefinition::Op_SetArrayLength: case MDefinition::Op_StoreElementHole: + case MDefinition::Op_FallibleStoreElement: case MDefinition::Op_TypedObjectDescr: case MDefinition::Op_Slots: case MDefinition::Op_Elements: diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 8041f523e39d..884a35270091 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -7975,7 +7975,8 @@ class OutOfLineStoreElementHole : public OutOfLineCodeBase explicit OutOfLineStoreElementHole(LInstruction* ins) : ins_(ins) { - MOZ_ASSERT(ins->isStoreElementHoleV() || ins->isStoreElementHoleT()); + MOZ_ASSERT(ins->isStoreElementHoleV() || ins->isStoreElementHoleT() || + ins->isFallibleStoreElementV() || ins->isFallibleStoreElementT()); } void accept(CodeGenerator* codegen) { @@ -8069,9 +8070,12 @@ CodeGenerator::visitStoreElementV(LStoreElementV* lir) } } -void -CodeGenerator::visitStoreElementHoleT(LStoreElementHoleT* lir) +template void +CodeGenerator::emitStoreElementHoleT(T* lir) { + static_assert(std::is_same::value || std::is_same::value, + "emitStoreElementHoleT called with unexpected argument type"); + OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir); addOutOfLineCode(ool, lir->mir()); @@ -8120,15 +8124,24 @@ CodeGenerator::visitStoreElementHoleT(LStoreElementHoleT* lir) } void -CodeGenerator::visitStoreElementHoleV(LStoreElementHoleV* lir) +CodeGenerator::visitStoreElementHoleT(LStoreElementHoleT* lir) { + emitStoreElementHoleT(lir); +} + +template void +CodeGenerator::emitStoreElementHoleV(T* lir) +{ + static_assert(std::is_same::value || std::is_same::value, + "emitStoreElementHoleV called with unexpected parameter type"); + 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, LStoreElementHoleV::Value); + const ValueOperand value = ToValue(lir, T::Value); RegisterOrInt32Constant key = ToRegisterOrInt32Constant(index); JSValueType unboxedType = lir->mir()->unboxedType(); @@ -8170,6 +8183,66 @@ CodeGenerator::visitStoreElementHoleV(LStoreElementHoleV* lir) masm.bind(ool->rejoin()); } +void +CodeGenerator::visitStoreElementHoleV(LStoreElementHoleV* lir) +{ + emitStoreElementHoleV(lir); +} + +typedef bool (*ThrowReadOnlyFn)(JSContext*, HandleObject); +static const VMFunction ThrowReadOnlyInfo = + FunctionInfo(ThrowReadOnlyError, "ThrowReadOnlyError"); + +void +CodeGenerator::visitFallibleStoreElementT(LFallibleStoreElementT* lir) +{ + Register elements = ToRegister(lir->elements()); + + // Handle frozen objects + Label isFrozen; + Address flags(elements, ObjectElements::offsetOfFlags()); + if (!lir->mir()->strict()) { + masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), &isFrozen); + } else { + Register object = ToRegister(lir->object()); + OutOfLineCode* ool = oolCallVM(ThrowReadOnlyInfo, lir, + ArgList(object), StoreNothing()); + masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), ool->entry()); + // This OOL code should have thrown an exception, so will never return. + // So, do not bind ool->rejoin() anywhere, so that it implicitly (and without the cost + // of a jump) does a masm.assumeUnreachable(). + } + + emitStoreElementHoleT(lir); + + masm.bind(&isFrozen); +} + +void +CodeGenerator::visitFallibleStoreElementV(LFallibleStoreElementV* lir) +{ + Register elements = ToRegister(lir->elements()); + + // Handle frozen objects + Label isFrozen; + Address flags(elements, ObjectElements::offsetOfFlags()); + if (!lir->mir()->strict()) { + masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), &isFrozen); + } else { + Register object = ToRegister(lir->object()); + OutOfLineCode* ool = oolCallVM(ThrowReadOnlyInfo, lir, + ArgList(object), StoreNothing()); + masm.branchTest32(Assembler::NonZero, flags, Imm32(ObjectElements::FROZEN), ool->entry()); + // This OOL code should have thrown an exception, so will never return. + // So, do not bind ool->rejoin() anywhere, so that it implicitly (and without the cost + // of a jump) does a masm.assumeUnreachable(). + } + + emitStoreElementHoleV(lir); + + masm.bind(&isFrozen); +} + typedef bool (*SetDenseOrUnboxedArrayElementFn)(JSContext*, HandleObject, int32_t, HandleValue, bool strict); static const VMFunction SetDenseOrUnboxedArrayElementInfo = @@ -8196,7 +8269,16 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value)); unboxedType = store->mir()->unboxedType(); temp = store->getTemp(0); - } else { + } else if (ins->isFallibleStoreElementV()) { + LFallibleStoreElementV* store = ins->toFallibleStoreElementV(); + object = ToRegister(store->object()); + elements = ToRegister(store->elements()); + 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()); elements = ToRegister(store->elements()); @@ -8208,6 +8290,18 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) 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()); + elements = ToRegister(store->elements()); + index = store->index(); + valueType = store->mir()->value()->type(); + if (store->value()->isConstant()) + 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); @@ -8264,13 +8358,21 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) masm.bind(&dontUpdate); } - if (ins->isStoreElementHoleT() && unboxedType == JSVAL_TYPE_MAGIC && valueType != MIRType::Double) { - // The inline path for StoreElementHoleT does not always store the type tag, - // so we do the store on the OOL path. We use MIRType::None for the element type - // so that storeElementTyped will always store the type tag. - emitStoreElementTyped(ins->toStoreElementHoleT()->value(), valueType, MIRType::None, - elements, index, 0); - masm.jump(ool->rejoin()); + if ((ins->isStoreElementHoleT() || ins->isFallibleStoreElementT()) && + 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 + // type so that storeElementTyped will always store the type tag. + if (ins->isStoreElementHoleT()) { + emitStoreElementTyped(ins->toStoreElementHoleT()->value(), valueType, MIRType::None, + elements, index, 0); + masm.jump(ool->rejoin()); + } else if (ins->isFallibleStoreElementT()) { + emitStoreElementTyped(ins->toFallibleStoreElementT()->value(), valueType, + MIRType::None, elements, index, 0); + masm.jump(ool->rejoin()); + } } else { // Jump to the inline path where we will store the value. masm.jump(ool->rejoinStore()); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index f4d0ddb7634f..d173614e51b4 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -298,8 +298,12 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitUnboxObjectOrNull(LUnboxObjectOrNull* lir); void visitStoreElementT(LStoreElementT* lir); void visitStoreElementV(LStoreElementV* lir); + template void emitStoreElementHoleT(T* lir); + template void emitStoreElementHoleV(T* lir); void visitStoreElementHoleT(LStoreElementHoleT* lir); void visitStoreElementHoleV(LStoreElementHoleV* lir); + void visitFallibleStoreElementV(LFallibleStoreElementV* lir); + void visitFallibleStoreElementT(LFallibleStoreElementT* lir); void visitStoreUnboxedPointer(LStoreUnboxedPointer* lir); void visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative* lir); void emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, Register obj, diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 7f0feb3a6648..b7cde1e48f21 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -3563,6 +3563,13 @@ jit::AddKeepAliveInstructions(MIRGraph& graph) continue; } + if (use->isFallibleStoreElement()) { + // See StoreElementHole case above. + MOZ_ASSERT_IF(!use->toFallibleStoreElement()->object()->isUnbox() && !ownerObject->isUnbox(), + use->toFallibleStoreElement()->object() == ownerObject); + continue; + } + if (use->isInArray()) { // See StoreElementHole case above. MOZ_ASSERT_IF(!use->toInArray()->object()->isUnbox() && !ownerObject->isUnbox(), diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2f976c493e1e..73eacadfc04d 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -10459,7 +10459,9 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, // Writes which are on holes in the object do not have to bail out if they // cannot hit another indexed property on the object or its prototypes. - bool writeOutOfBounds = !ElementAccessHasExtraIndexedProperty(this, obj); + bool hasNoExtraIndexedProperty = !ElementAccessHasExtraIndexedProperty(this, obj); + + bool mayBeFrozen = ElementAccessMightBeFrozen(constraints(), obj); // Ensure id is an integer. MInstruction* idInt32 = MToInt32::New(alloc(), id); @@ -10505,20 +10507,31 @@ IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion, // Use MStoreElementHole if this SETELEM has written to out-of-bounds // indexes in the past. Otherwise, use MStoreElement so that we can hoist // the initialized length and bounds check. + // If an object may have been frozen, no previous expectation hold and we + // fallback to MFallibleStoreElement. MInstruction* store; MStoreElementCommon *common = nullptr; - if (writeHole && writeOutOfBounds) { + if (writeHole && hasNoExtraIndexedProperty && !mayBeFrozen) { MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue, unboxedType); store = ins; common = ins; + current->add(ins); + current->push(value); + } else if (hasNoExtraIndexedProperty && mayBeFrozen) { + bool strict = IsStrictSetPC(pc); + MFallibleStoreElement* ins = MFallibleStoreElement::New(alloc(), obj, elements, id, + newValue, unboxedType, strict); + store = ins; + common = ins; + current->add(ins); current->push(value); } else { MInstruction* initLength = initializedLength(obj, elements, unboxedType); id = addBoundsCheck(id, initLength); - bool needsHoleCheck = !packed && !writeOutOfBounds; + bool needsHoleCheck = !packed && !hasNoExtraIndexedProperty; if (unboxedType != JSVAL_TYPE_MAGIC) { store = storeUnboxedValue(obj, elements, 0, id, unboxedType, newValue); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 55dd27ffa3ea..913fa97a0f3f 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -3156,6 +3156,38 @@ LIRGenerator::visitStoreElementHole(MStoreElementHole* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitFallibleStoreElement(MFallibleStoreElement* ins) +{ + MOZ_ASSERT(ins->elements()->type() == MIRType::Elements); + MOZ_ASSERT(ins->index()->type() == MIRType::Int32); + + const LUse object = useRegister(ins->object()); + 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()), + tempDef); + break; + default: + const LAllocation value = useRegisterOrNonDoubleConstant(ins->value()); + lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value, tempDef); + break; + } + + add(lir, ins); + assignSafepoint(lir, ins); +} + + void LIRGenerator::visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 13a54dbbca46..7b5aad462497 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -223,6 +223,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitLoadUnboxedString(MLoadUnboxedString* ins); void visitStoreElement(MStoreElement* ins); void visitStoreElementHole(MStoreElementHole* ins); + void visitFallibleStoreElement(MFallibleStoreElement* ins); void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins); void visitStoreUnboxedString(MStoreUnboxedString* ins); void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 5376a3b0dc3c..a7269b22a99e 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -2917,6 +2917,7 @@ NeedNegativeZeroCheck(MDefinition* def) } case MDefinition::Op_StoreElement: case MDefinition::Op_StoreElementHole: + case MDefinition::Op_FallibleStoreElement: case MDefinition::Op_LoadElement: case MDefinition::Op_LoadElementHole: case MDefinition::Op_LoadUnboxedScalar: @@ -5691,6 +5692,13 @@ jit::ElementAccessMightBeCopyOnWrite(CompilerConstraintList* constraints, MDefin return !types || types->hasObjectFlags(constraints, OBJECT_FLAG_COPY_ON_WRITE); } +bool +jit::ElementAccessMightBeFrozen(CompilerConstraintList* constraints, MDefinition* obj) +{ + TemporaryTypeSet* types = obj->resultTypeSet(); + return !types || types->hasObjectFlags(constraints, OBJECT_FLAG_FROZEN); +} + bool jit::ElementAccessHasExtraIndexedProperty(IonBuilder* builder, MDefinition* obj) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index b1b79f3a28e6..35618fdac82f 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -9400,6 +9400,50 @@ class MStoreElementHole ALLOW_CLONE(MStoreElementHole) }; +// Try to store a value to a dense array slots vector. May fail due to the object being frozen. +// Cannot be used on an object that has extra indexed properties. +class MFallibleStoreElement + : public MAryInstruction<4>, + public MStoreElementCommon, + public MixPolicy >::Data +{ + JSValueType unboxedType_; + bool strict_; + + MFallibleStoreElement(MDefinition* object, MDefinition* elements, + MDefinition* index, MDefinition* value, + JSValueType unboxedType, bool strict) + : unboxedType_(unboxedType) + { + initOperand(0, object); + initOperand(1, elements); + initOperand(2, index); + initOperand(3, value); + strict_ = strict; + MOZ_ASSERT(elements->type() == MIRType::Elements); + MOZ_ASSERT(index->type() == MIRType::Int32); + } + + public: + INSTRUCTION_HEADER(FallibleStoreElement) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value)) + + JSValueType unboxedType() const { + return unboxedType_; + } + AliasSet getAliasSet() const override { + return AliasSet::Store(AliasSet::ObjectFields | + AliasSet::BoxedOrUnboxedElements(unboxedType())); + } + bool strict() const { + return strict_; + } + + ALLOW_CLONE(MFallibleStoreElement) +}; + + // Store an unboxed object or null pointer to a v\ector. class MStoreUnboxedObjectOrNull : public MAryInstruction<4>, @@ -13877,6 +13921,7 @@ bool ElementAccessIsTypedArray(CompilerConstraintList* constraints, Scalar::Type* arrayType); bool ElementAccessIsPacked(CompilerConstraintList* constraints, MDefinition* obj); bool ElementAccessMightBeCopyOnWrite(CompilerConstraintList* constraints, MDefinition* obj); +bool ElementAccessMightBeFrozen(CompilerConstraintList* constraints, MDefinition* obj); bool ElementAccessHasExtraIndexedProperty(IonBuilder* builder, MDefinition* obj); MIRType DenseNativeElementType(CompilerConstraintList* constraints, MDefinition* obj); BarrierKind PropertyReadNeedsTypeBarrier(JSContext* propertycx, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index ee1468ebb081..f6d079b77357 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -212,6 +212,7 @@ namespace jit { _(LoadUnboxedString) \ _(StoreElement) \ _(StoreElementHole) \ + _(FallibleStoreElement) \ _(StoreUnboxedScalar) \ _(StoreUnboxedObjectOrNull) \ _(StoreUnboxedString) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 8cb88e979a9b..c21c86eda88e 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1298,6 +1298,15 @@ ThrowRuntimeLexicalError(JSContext* cx, unsigned errorNumber) return false; } +bool +ThrowReadOnlyError(JSContext* cx, HandleObject handle) +{ + HandleNativeObject obj = handle.as(); + RootedValue val(cx, ObjectValue(*obj)); + ReportValueError(cx, JSMSG_READ_ONLY, JSDVG_IGNORE_STACK, val, nullptr); + return false; +} + bool ThrowBadDerivedReturn(JSContext* cx, HandleValue v) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 1b1b6fc09413..ce7a9a37883c 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -786,6 +786,8 @@ bool ObjectIsConstructor(JSObject* obj); MOZ_MUST_USE bool ThrowRuntimeLexicalError(JSContext* cx, unsigned errorNumber); MOZ_MUST_USE bool +ThrowReadOnlyError(JSContext* cx, HandleObject obj); +MOZ_MUST_USE bool BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame); MOZ_MUST_USE bool ThrowBadDerivedReturn(JSContext* cx, HandleValue v); diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index d171357bb3a6..e712d297a635 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -5734,6 +5734,71 @@ class LStoreElementHoleT : public LInstructionHelper<0, 4, 1> } }; +// Like LStoreElementV, but can just ignore assignment (for eg. frozen objects) +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 LDefinition& temp) { + setOperand(0, object); + setOperand(1, elements); + setOperand(2, index); + setBoxOperand(Value, value); + setTemp(0, temp); + } + + static const size_t Value = 3; + + const MFallibleStoreElement* mir() const { + return mir_->toFallibleStoreElement(); + } + const LAllocation* object() { + return getOperand(0); + } + const LAllocation* elements() { + return getOperand(1); + } + const LAllocation* index() { + return getOperand(2); + } +}; + +// Like LStoreElementT, but can just ignore assignment (for eg. frozen objects) +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 LDefinition& temp) { + setOperand(0, object); + setOperand(1, elements); + setOperand(2, index); + setOperand(3, value); + setTemp(0, temp); + } + + const MFallibleStoreElement* mir() const { + return mir_->toFallibleStoreElement(); + } + const LAllocation* object() { + return getOperand(0); + } + const LAllocation* elements() { + return getOperand(1); + } + const LAllocation* index() { + return getOperand(2); + } + const LAllocation* value() { + return getOperand(3); + } +}; + class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0> { public: diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 058e09efda26..f83d7e04e28c 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -294,6 +294,8 @@ _(ArrayJoin) \ _(StoreElementHoleV) \ _(StoreElementHoleT) \ + _(FallibleStoreElementV) \ + _(FallibleStoreElementT) \ _(LoadTypedArrayElementHole) \ _(LoadTypedArrayElementStatic) \ _(StoreTypedArrayElementHole) \