From 62ffe147dc7bb4b7d8a2c8533ce8369942a05363 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Wed, 26 Nov 2014 22:45:11 -0700 Subject: [PATCH] Bug 1100173, bug 1102510 - Add baseline caches for typed object scalar element accesses and scalar/reference property accesses, r=jandem. --- js/src/builtin/TypedObject.h | 8 + js/src/jit/BaselineIC.cpp | 598 +++++++++++++++++++++++++++----- js/src/jit/BaselineIC.h | 215 +++++++++++- js/src/jscompartment.cpp | 1 + js/src/jscompartment.h | 3 + js/src/vm/ArrayBufferObject.cpp | 1 + 6 files changed, 744 insertions(+), 82 deletions(-) diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index a91e7e929c5..b6a4583781e 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -154,6 +154,10 @@ class TypedProto : public NativeObject } inline type::Kind kind() const; + + static int32_t offsetOfTypeDescr() { + return getFixedSlotOffset(JS_TYPROTO_SLOT_DESCR); + } }; class TypeDescr : public NativeObject @@ -432,6 +436,10 @@ class ArrayTypeDescr : public ComplexTypeDescr int32_t length() const { return getReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH).toInt32(); } + + static int32_t offsetOfLength() { + return getFixedSlotOffset(JS_DESCR_SLOT_ARRAY_LENGTH); + } }; /* diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 402660f9e04..77b033a2da9 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -366,6 +366,11 @@ ICStub::trace(JSTracer *trc) } break; } + case ICStub::GetProp_TypedObject: { + ICGetProp_TypedObject *propStub = toGetProp_TypedObject(); + MarkShape(trc, &propStub->shape(), "baseline-getprop-typedobject-stub-shape"); + break; + } case ICStub::GetProp_CallDOMProxyNative: case ICStub::GetProp_CallDOMProxyWithGenerationNative: { ICGetPropCallDOMProxyNativeStub *propStub; @@ -435,6 +440,12 @@ ICStub::trace(JSTracer *trc) } break; } + case ICStub::SetProp_TypedObject: { + ICSetProp_TypedObject *propStub = toSetProp_TypedObject(); + MarkShape(trc, &propStub->shape(), "baseline-setprop-typedobject-stub-shape"); + MarkTypeObject(trc, &propStub->type(), "baseline-setprop-typedobject-stub-type"); + break; + } case ICStub::SetProp_CallScripted: { ICSetProp_CallScripted *callStub = toSetProp_CallScripted(); MarkShape(trc, &callStub->shape(), "baseline-setpropcallscripted-stub-shape"); @@ -1545,6 +1556,25 @@ DoTypeUpdateFallback(JSContext *cx, BaselineFrame *frame, ICUpdatedStub *stub, H types::AddTypePropertyId(cx, obj, id, value); break; } + case ICStub::SetProp_TypedObject: { + MOZ_ASSERT(obj->is()); + jsbytecode *pc = stub->getChainFallback()->icEntry()->pc(script); + id = NameToId(script->getName(pc)); + if (stub->toSetProp_TypedObject()->isObjectReference()) { + // Ignore all values being written except plain objects. Null + // is included implicitly in type information for this property, + // and non-object non-null values will cause the stub to fail to + // match shortly and we will end up doing the assignment in the VM. + if (value.isObject()) + types::AddTypePropertyId(cx, obj, id, value); + } else { + // Ignore undefined values, which are included implicitly in type + // information for this property. + if (!value.isUndefined()) + types::AddTypePropertyId(cx, obj, id, value); + } + break; + } default: MOZ_CRASH("Invalid stub"); } @@ -3893,9 +3923,35 @@ static bool TryAttachNativeGetElemStub(JSContext *cx, HandleScript script, jsbyt } static bool -TypedArrayRequiresFloatingPoint(HandleObject obj) +IsPrimitiveArrayTypedObject(JSObject *obj) { - uint32_t type = AnyTypedArrayType(obj); + if (!obj->is()) + return false; + TypeDescr &descr = obj->as().typeDescr(); + return descr.is() && + descr.as().elementType().is(); +} + +static Scalar::Type +PrimitiveArrayTypedObjectType(JSObject *obj) +{ + MOZ_ASSERT(IsPrimitiveArrayTypedObject(obj)); + TypeDescr &descr = obj->as().typeDescr(); + return descr.as().elementType().as().type(); +} + +static Scalar::Type +TypedThingElementType(JSObject *obj) +{ + return IsAnyTypedArray(obj) + ? AnyTypedArrayType(obj) + : PrimitiveArrayTypedObjectType(obj); +} + +static bool +TypedThingRequiresFloatingPoint(JSObject *obj) +{ + Scalar::Type type = TypedThingElementType(obj); return type == Scalar::Uint32 || type == Scalar::Float32 || type == Scalar::Float64; @@ -3987,8 +4043,10 @@ TryAttachGetElemStub(JSContext *cx, JSScript *script, jsbytecode *pc, ICGetElem_ } } - // Check for TypedArray[int] => Number accesses. - if (IsAnyTypedArray(obj.get()) && rhs.isNumber() && res.isNumber() && + // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses. + if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) && + rhs.isNumber() && + res.isNumber() && !TypedArrayGetElemStubExists(stub, obj)) { // Don't attach CALLELEM stubs for accesses on typed array expected to yield numbers. @@ -3998,15 +4056,19 @@ TryAttachGetElemStub(JSContext *cx, JSScript *script, jsbytecode *pc, ICGetElem_ #endif if (!cx->runtime()->jitSupportsFloatingPoint && - (TypedArrayRequiresFloatingPoint(obj) || rhs.isDouble())) + (TypedThingRequiresFloatingPoint(obj) || rhs.isDouble())) { return true; } + // Don't attach typed object stubs if they might be neutered, as the + // stub will always bail out. + if (IsPrimitiveArrayTypedObject(obj) && cx->compartment()->neuteredTypedObjects) + return true; + JitSpew(JitSpew_BaselineIC, " Generating GetElem(TypedArray[Int32]) stub"); - ICGetElem_TypedArray::Compiler compiler(cx, - AnyTypedArrayShape(obj.get()), - AnyTypedArrayType(obj.get())); + ICGetElem_TypedArray::Compiler compiler(cx, obj->lastProperty(), + TypedThingElementType(obj)); ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); if (!typedArrayStub) return false; @@ -4614,10 +4676,60 @@ ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm) // GetElem_TypedArray // +static void +LoadTypedThingLength(MacroAssembler &masm, TypedThingLayout layout, Register obj, Register result) +{ + switch (layout) { + case Layout_TypedArray: + masm.unboxInt32(Address(obj, TypedArrayLayout::lengthOffset()), result); + break; + case Layout_OutlineTypedObject: + case Layout_InlineTypedObject: + masm.loadObjProto(obj, result); + masm.unboxObject(Address(result, TypedProto::offsetOfTypeDescr()), result); + masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result); + break; + default: + MOZ_CRASH(); + } +} + +static void +LoadTypedThingData(MacroAssembler &masm, TypedThingLayout layout, Register obj, Register result) +{ + switch (layout) { + case Layout_TypedArray: + masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), result); + break; + case Layout_OutlineTypedObject: + masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), result); + break; + case Layout_InlineTypedObject: + masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), result); + break; + default: + MOZ_CRASH(); + } +} + +static void +CheckForNeuteredTypedObject(JSContext *cx, MacroAssembler &masm, Label *failure) +{ + // All stubs which manipulate typed objects need to check the compartment + // wide flag indicating whether the objects are neutered, and bail out in + // this case. + int32_t *address = &cx->compartment()->neuteredTypedObjects; + masm.branch32(Assembler::NotEqual, AbsoluteAddress(address), Imm32(0), failure); +} + bool ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) { Label failure; + + if (layout_ != Layout_TypedArray) + CheckForNeuteredTypedObject(cx, masm, &failure); + masm.branchTestObject(Assembler::NotEqual, R0, &failure); GeneralRegisterSet regs(availableGeneralRegs(2)); @@ -4650,11 +4762,11 @@ ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) Register key = masm.extractInt32(R1, ExtractTemp1); // Bounds check. - masm.unboxInt32(Address(obj, TypedArrayLayout::lengthOffset()), scratchReg); + LoadTypedThingLength(masm, layout_, obj, scratchReg); masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); // Load the elements vector. - masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), scratchReg); + LoadTypedThingData(masm, layout_, obj, scratchReg); // Load the value. BaseIndex source(scratchReg, key, ScaleFromElemWidth(Scalar::byteSize(type_))); @@ -5113,31 +5225,45 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_ return true; } - if (IsAnyTypedArray(obj.get()) && index.isNumber() && rhs.isNumber()) { + if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) && + index.isNumber() && + rhs.isNumber()) + { if (!cx->runtime()->jitSupportsFloatingPoint && - (TypedArrayRequiresFloatingPoint(obj) || index.isDouble())) + (TypedThingRequiresFloatingPoint(obj) || index.isDouble())) { return true; } - uint32_t len = AnyTypedArrayLength(obj.get()); + bool expectOutOfBounds; double idx = index.toNumber(); - bool expectOutOfBounds = (idx < 0 || idx >= double(len)); + if (IsAnyTypedArray(obj)) { + expectOutOfBounds = (idx < 0 || idx >= double(AnyTypedArrayLength(obj))); + } else { + // Typed objects throw on out of bounds accesses. Don't attach + // a stub in this case. + if (idx < 0 || idx >= double(obj->as().length())) + return true; + expectOutOfBounds = false; + + // Don't attach stubs if typed objects in the compartment might be + // neutered, as the stub will always bail out. + if (cx->compartment()->neuteredTypedObjects) + return true; + } if (!TypedArraySetElemStubExists(stub, obj, expectOutOfBounds)) { // Remove any existing TypedArraySetElemStub that doesn't handle out-of-bounds if (expectOutOfBounds) RemoveExistingTypedArraySetElemStub(cx, stub, obj); + Shape *shape = obj->lastProperty(); + Scalar::Type type = TypedThingElementType(obj); + JitSpew(JitSpew_BaselineIC, " Generating SetElem_TypedArray stub (shape=%p, type=%u, oob=%s)", - AnyTypedArrayShape(obj.get()), - AnyTypedArrayType(obj.get()), - expectOutOfBounds ? "yes" : "no"); - ICSetElem_TypedArray::Compiler compiler(cx, - AnyTypedArrayShape(obj.get()), - AnyTypedArrayType(obj.get()), - expectOutOfBounds); + shape, type, expectOutOfBounds ? "yes" : "no"); + ICSetElem_TypedArray::Compiler compiler(cx, shape, type, expectOutOfBounds); ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); if (!typedArrayStub) return false; @@ -5521,10 +5647,80 @@ ICSetElemDenseAddCompiler::generateStubCode(MacroAssembler &masm) // SetElem_TypedArray // +// Write an arbitrary value to a typed array or typed object address at dest. +// If the value could not be converted to the appropriate format, jump to +// failure or failureModifiedScratch. +template +static void +StoreToTypedArray(JSContext *cx, MacroAssembler &masm, Scalar::Type type, Address value, T dest, + Register scratch, Label *failure, Label *failureModifiedScratch) +{ + Label done; + + if (type == Scalar::Float32 || type == Scalar::Float64) { + masm.ensureDouble(value, FloatReg0, failure); + if (type == Scalar::Float32) { + masm.convertDoubleToFloat32(FloatReg0, ScratchFloat32Reg); + masm.storeToTypedFloatArray(type, ScratchFloat32Reg, dest); + } else { + masm.storeToTypedFloatArray(type, FloatReg0, dest); + } + } else if (type == Scalar::Uint8Clamped) { + Label notInt32; + masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); + masm.unboxInt32(value, scratch); + masm.clampIntToUint8(scratch); + + Label clamped; + masm.bind(&clamped); + masm.storeToTypedIntArray(type, scratch, dest); + masm.jump(&done); + + // If the value is a double, clamp to uint8 and jump back. + // Else, jump to failure. + masm.bind(¬Int32); + if (cx->runtime()->jitSupportsFloatingPoint) { + masm.branchTestDouble(Assembler::NotEqual, value, failure); + masm.unboxDouble(value, FloatReg0); + masm.clampDoubleToUint8(FloatReg0, scratch); + masm.jump(&clamped); + } else { + masm.jump(failure); + } + } else { + Label notInt32; + masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); + masm.unboxInt32(value, scratch); + + Label isInt32; + masm.bind(&isInt32); + masm.storeToTypedIntArray(type, scratch, dest); + masm.jump(&done); + + // If the value is a double, truncate and jump back. + // Else, jump to failure. + masm.bind(¬Int32); + if (cx->runtime()->jitSupportsFloatingPoint) { + masm.branchTestDouble(Assembler::NotEqual, value, failure); + masm.unboxDouble(value, FloatReg0); + masm.branchTruncateDouble(FloatReg0, scratch, failureModifiedScratch); + masm.jump(&isInt32); + } else { + masm.jump(failure); + } + } + + masm.bind(&done); +} + bool ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) { Label failure; + + if (layout_ != Layout_TypedArray) + CheckForNeuteredTypedObject(cx, masm, &failure); + masm.branchTestObject(Assembler::NotEqual, R0, &failure); GeneralRegisterSet regs(availableGeneralRegs(2)); @@ -5558,12 +5754,12 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) // Bounds check. Label oobWrite; - masm.unboxInt32(Address(obj, TypedArrayLayout::lengthOffset()), scratchReg); + LoadTypedThingLength(masm, layout_, obj, scratchReg); masm.branch32(Assembler::BelowOrEqual, scratchReg, key, expectOutOfBounds_ ? &oobWrite : &failure); // Load the elements vector. - masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), scratchReg); + LoadTypedThingData(masm, layout_, obj, scratchReg); BaseIndex dest(scratchReg, key, ScaleFromElemWidth(Scalar::byteSize(type_))); Address value(BaselineStackReg, ICStackValueOffset); @@ -5576,64 +5772,15 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) regs.take(scratchReg); Register secondScratch = regs.takeAny(); - if (type_ == Scalar::Float32 || type_ == Scalar::Float64) { - masm.ensureDouble(value, FloatReg0, &failure); - if (type_ == Scalar::Float32) - { - masm.convertDoubleToFloat32(FloatReg0, ScratchFloat32Reg); - masm.storeToTypedFloatArray(type_, ScratchFloat32Reg, dest); - } else { - masm.storeToTypedFloatArray(type_, FloatReg0, dest); - } - EmitReturnFromIC(masm); - } else if (type_ == Scalar::Uint8Clamped) { - Label notInt32; - masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); - masm.unboxInt32(value, secondScratch); - masm.clampIntToUint8(secondScratch); - - Label clamped; - masm.bind(&clamped); - masm.storeToTypedIntArray(type_, secondScratch, dest); - EmitReturnFromIC(masm); - - // If the value is a double, clamp to uint8 and jump back. - // Else, jump to failure. - masm.bind(¬Int32); - if (cx->runtime()->jitSupportsFloatingPoint) { - masm.branchTestDouble(Assembler::NotEqual, value, &failure); - masm.unboxDouble(value, FloatReg0); - masm.clampDoubleToUint8(FloatReg0, secondScratch); - masm.jump(&clamped); - } else { - masm.jump(&failure); - } - } else { - Label notInt32; - masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); - masm.unboxInt32(value, secondScratch); - - Label isInt32; - masm.bind(&isInt32); - masm.storeToTypedIntArray(type_, secondScratch, dest); - EmitReturnFromIC(masm); - - // If the value is a double, truncate and jump back. - // Else, jump to failure. - Label failureRestoreRegs; - masm.bind(¬Int32); - if (cx->runtime()->jitSupportsFloatingPoint) { - masm.branchTestDouble(Assembler::NotEqual, value, &failure); - masm.unboxDouble(value, FloatReg0); - masm.branchTruncateDouble(FloatReg0, secondScratch, &failureRestoreRegs); - masm.jump(&isInt32); - } else { - masm.jump(&failure); - } + Label failureModifiedSecondScratch; + StoreToTypedArray(cx, masm, type_, value, dest, + secondScratch, &failure, &failureModifiedSecondScratch); + EmitReturnFromIC(masm); + if (failureModifiedSecondScratch.used()) { // Writing to secondScratch may have clobbered R0 or R1, restore them // first. - masm.bind(&failureRestoreRegs); + masm.bind(&failureModifiedSecondScratch); masm.tagValue(JSVAL_TYPE_OBJECT, obj, R0); masm.tagValue(JSVAL_TYPE_INT32, key, R1); } @@ -5643,6 +5790,7 @@ ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler &masm) EmitStubGuardFailure(masm); if (expectOutOfBounds_) { + MOZ_ASSERT(layout_ == Layout_TypedArray); masm.bind(&oobWrite); EmitReturnFromIC(masm); } @@ -6604,6 +6752,46 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, return true; } +static bool +TryAttachTypedObjectGetPropStub(JSContext *cx, HandleScript script, + ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val, + bool *attached) +{ + MOZ_ASSERT(!*attached); + + if (!cx->runtime()->jitSupportsFloatingPoint) + return true; + + if (!val.isObject() || !val.toObject().is()) + return true; + Rooted obj(cx, &val.toObject().as()); + + if (!obj->typeDescr().is()) + return true; + Rooted structDescr(cx, &obj->typeDescr().as()); + + size_t fieldIndex; + if (!structDescr->fieldIndex(NameToId(name), &fieldIndex)) + return true; + + Rooted fieldDescr(cx, &structDescr->fieldDescr(fieldIndex)); + if (!fieldDescr->is()) + return true; + + uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); + ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); + + ICGetProp_TypedObject::Compiler compiler(cx, monitorStub, obj->lastProperty(), + fieldOffset, &fieldDescr->as()); + ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + stub->addNewStub(newStub); + + *attached = true; + return true; +} + static bool TryAttachPrimitiveGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val, @@ -6811,6 +6999,11 @@ DoGetPropFallback(JSContext *cx, BaselineFrame *frame, ICGetProp_Fallback *stub_ if (attached) return true; + if (!TryAttachTypedObjectGetPropStub(cx, script, stub, name, val, &attached)) + return false; + if (attached) + return true; + if (val.isString() || val.isNumber() || val.isBoolean()) { if (!TryAttachPrimitiveGetPropStub(cx, script, pc, stub, name, val, res, &attached)) return false; @@ -7706,6 +7899,82 @@ ICGetProp_Generic::Compiler::generateStubCode(MacroAssembler &masm) return true; } +bool +ICGetProp_TypedObject::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + + CheckForNeuteredTypedObject(cx, masm, &failure); + + GeneralRegisterSet regs(availableGeneralRegs(1)); + + Register scratch1 = regs.takeAnyExcluding(BaselineTailCallReg); + Register scratch2 = regs.takeAnyExcluding(BaselineTailCallReg); + + // Object and shape guard. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + Register object = masm.extractObject(R0, ExtractTemp0); + masm.loadPtr(Address(BaselineStubReg, ICGetProp_TypedObject::offsetOfShape()), scratch1); + masm.branchTestObjShape(Assembler::NotEqual, object, scratch1, &failure); + + // Get the object's data pointer. + LoadTypedThingData(masm, layout_, object, scratch1); + + // Get the address being written to. + masm.load32(Address(BaselineStubReg, ICGetProp_TypedObject::offsetOfFieldOffset()), scratch2); + masm.addPtr(scratch2, scratch1); + + // Only monitor the result if the type produced by this stub might vary. + bool monitorLoad; + + if (fieldDescr_->is()) { + Scalar::Type type = fieldDescr_->as().type(); + monitorLoad = type == Scalar::Uint32; + + masm.loadFromTypedArray(type, Address(scratch1, 0), R0, /* allowDouble = */ true, + scratch2, nullptr); + } else { + ReferenceTypeDescr::Type type = fieldDescr_->as().type(); + monitorLoad = type != ReferenceTypeDescr::TYPE_STRING; + + switch (type) { + case ReferenceTypeDescr::TYPE_ANY: + masm.loadValue(Address(scratch1, 0), R0); + break; + + case ReferenceTypeDescr::TYPE_OBJECT: { + Label notNull, done; + masm.loadPtr(Address(scratch1, 0), scratch1); + masm.branchTestPtr(Assembler::NonZero, scratch1, scratch1, ¬Null); + masm.moveValue(NullValue(), R0); + masm.jump(&done); + masm.bind(¬Null); + masm.tagValue(JSVAL_TYPE_OBJECT, scratch1, R0); + masm.bind(&done); + break; + } + + case ReferenceTypeDescr::TYPE_STRING: + masm.loadPtr(Address(scratch1, 0), scratch1); + masm.tagValue(JSVAL_TYPE_STRING, scratch1, R0); + break; + + default: + MOZ_CRASH(); + } + } + + if (monitorLoad) + EmitEnterTypeMonitorIC(masm); + else + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + + return true; +} + void BaselineScript::noteAccessedGetter(uint32_t pcOffset) { @@ -7884,6 +8153,48 @@ TryAttachSetAccessorPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, return true; } +static bool +TryAttachTypedObjectSetPropStub(JSContext *cx, HandleScript script, + ICSetProp_Fallback *stub, HandleId id, + HandleObject obj, HandleValue rhs, bool *attached) +{ + MOZ_ASSERT(!*attached); + + if (!cx->runtime()->jitSupportsFloatingPoint) + return true; + + if (!obj->is()) + return true; + + if (!obj->as().typeDescr().is()) + return true; + Rooted structDescr(cx); + structDescr = &obj->as().typeDescr().as(); + + size_t fieldIndex; + if (!structDescr->fieldIndex(id, &fieldIndex)) + return true; + + Rooted fieldDescr(cx, &structDescr->fieldDescr(fieldIndex)); + if (!fieldDescr->is()) + return true; + + uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); + + ICSetProp_TypedObject::Compiler compiler(cx, obj->lastProperty(), obj->type(), fieldOffset, + &fieldDescr->as()); + ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) + return false; + + stub->addNewStub(newStub); + + *attached = true; + return true; +} + static bool DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_, HandleValue lhs, HandleValue rhs, MutableHandleValue res) @@ -7985,6 +8296,15 @@ DoSetPropFallback(JSContext *cx, BaselineFrame *frame, ICSetProp_Fallback *stub_ if (attached) return true; + if (!attached && + lhs.isObject() && + !TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached)) + { + return false; + } + if (attached) + return true; + MOZ_ASSERT(!attached); if (!isTemporarilyUnoptimizable) stub->noteUnoptimizableAccess(); @@ -8264,6 +8584,128 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler &masm) return true; } +bool +ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + + CheckForNeuteredTypedObject(cx, masm, &failure); + + // Guard input is an object. + masm.branchTestObject(Assembler::NotEqual, R0, &failure); + + GeneralRegisterSet regs(availableGeneralRegs(2)); + Register scratch = regs.takeAny(); + + // Unbox and shape guard. + Register object = masm.extractObject(R0, ExtractTemp0); + masm.loadPtr(Address(BaselineStubReg, ICSetProp_TypedObject::offsetOfShape()), scratch); + masm.branchTestObjShape(Assembler::NotEqual, object, scratch, &failure); + + // Guard that the type object matches. + masm.loadPtr(Address(BaselineStubReg, ICSetProp_TypedObject::offsetOfType()), scratch); + masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfType()), scratch, + &failure); + + if (needsUpdateStubs()) { + // Stow both R0 and R1 (object and value). + masm.push(object); + masm.push(BaselineStubReg); + EmitStowICValues(masm, 2); + + // Move RHS into R0 for TypeUpdate check. + masm.moveValue(R1, R0); + + // Call the type update stub. + if (!callTypeUpdateIC(masm, sizeof(Value))) + return false; + + // Unstow R0 and R1 (object and key) + EmitUnstowICValues(masm, 2); + masm.pop(BaselineStubReg); + masm.pop(object); + + // Trigger post barriers here on the values being written. Descriptors + // which can write objects also need update stubs. + GeneralRegisterSet saveRegs; + saveRegs.add(R0); + saveRegs.add(R1); + saveRegs.addUnchecked(object); + saveRegs.add(BaselineStubReg); + emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs); + } + + // Save the rhs on the stack so we can get a second scratch register. + Label failurePopRHS; + masm.pushValue(R1); + regs = availableGeneralRegs(1); + regs.takeUnchecked(object); + regs.take(scratch); + Register secondScratch = regs.takeAny(); + + // Get the object's data pointer. + LoadTypedThingData(masm, layout_, object, scratch); + + // Compute the address being written to. + masm.load32(Address(BaselineStubReg, ICSetProp_TypedObject::offsetOfFieldOffset()), secondScratch); + masm.addPtr(secondScratch, scratch); + + Address dest(scratch, 0); + Address value(BaselineStackReg, 0); + + if (fieldDescr_->is()) { + Scalar::Type type = fieldDescr_->as().type(); + StoreToTypedArray(cx, masm, type, value, dest, + secondScratch, &failurePopRHS, &failurePopRHS); + masm.popValue(R1); + EmitReturnFromIC(masm); + } else { + ReferenceTypeDescr::Type type = fieldDescr_->as().type(); + + masm.popValue(R1); + + switch (type) { + case ReferenceTypeDescr::TYPE_ANY: + EmitPreBarrier(masm, dest, MIRType_Value); + masm.storeValue(R1, dest); + break; + + case ReferenceTypeDescr::TYPE_OBJECT: { + EmitPreBarrier(masm, dest, MIRType_Object); + Label notObject; + masm.branchTestObject(Assembler::NotEqual, R1, ¬Object); + Register rhsObject = masm.extractObject(R1, ExtractTemp0); + masm.storePtr(rhsObject, dest); + EmitReturnFromIC(masm); + masm.bind(¬Object); + masm.branchTestNull(Assembler::NotEqual, R1, &failure); + masm.storePtr(ImmWord(0), dest); + break; + } + + case ReferenceTypeDescr::TYPE_STRING: { + EmitPreBarrier(masm, dest, MIRType_String); + masm.branchTestString(Assembler::NotEqual, R1, &failure); + Register rhsString = masm.extractString(R1, ExtractTemp0); + masm.storePtr(rhsString, dest); + break; + } + + default: + MOZ_CRASH(); + } + + EmitReturnFromIC(masm); + } + + masm.bind(&failurePopRHS); + masm.popValue(R1); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + bool ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) { diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 6e2de5c545c..172bc280b93 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -14,9 +14,11 @@ #include "jsgc.h" #include "jsopcode.h" +#include "builtin/TypedObject.h" #include "jit/BaselineJIT.h" #include "jit/BaselineRegisters.h" #include "vm/ArrayObject.h" +#include "vm/TypedArrayCommon.h" namespace js { @@ -424,6 +426,7 @@ class ICEntry _(GetProp_Native) \ _(GetProp_NativeDoesNotExist) \ _(GetProp_NativePrototype) \ + _(GetProp_TypedObject) \ _(GetProp_CallScripted) \ _(GetProp_CallNative) \ _(GetProp_CallNativePrototype)\ @@ -437,6 +440,7 @@ class ICEntry _(SetProp_Fallback) \ _(SetProp_Native) \ _(SetProp_NativeAdd) \ + _(SetProp_TypedObject) \ _(SetProp_CallScripted) \ _(SetProp_CallNative) \ \ @@ -3409,6 +3413,26 @@ class ICGetElem_Dense : public ICMonitoredStub }; }; +// Enum for stubs handling a combination of typed arrays and typed objects. +enum TypedThingLayout { + Layout_TypedArray, + Layout_OutlineTypedObject, + Layout_InlineTypedObject +}; + +static inline TypedThingLayout +GetTypedThingLayout(const Class *clasp) +{ + if (IsAnyTypedArrayClass(clasp)) + return Layout_TypedArray; + if (IsOutlineTypedObjectClass(clasp)) + return Layout_OutlineTypedObject; + if (IsInlineTypedObjectClass(clasp)) + return Layout_InlineTypedObject; + MOZ_CRASH("Bad object class"); +} + +// Accesses scalar elements of a typed array or typed object. class ICGetElem_TypedArray : public ICStub { friend class ICStubSpace; @@ -3438,19 +3462,23 @@ class ICGetElem_TypedArray : public ICStub class Compiler : public ICStubCompiler { RootedShape shape_; Scalar::Type type_; + TypedThingLayout layout_; protected: bool generateStubCode(MacroAssembler &masm); virtual int32_t getKey() const { - return static_cast(kind) | (static_cast(type_) << 16); + return static_cast(kind) | + (static_cast(type_) << 16) | + (static_cast(layout_) << 24); } public: Compiler(JSContext *cx, Shape *shape, Scalar::Type type) : ICStubCompiler(cx, ICStub::GetElem_TypedArray), shape_(cx, shape), - type_(type) + type_(type), + layout_(GetTypedThingLayout(shape->getObjectClass())) {} ICStub *getStub(ICStubSpace *space) { @@ -3724,6 +3752,7 @@ class ICSetElemDenseAddCompiler : public ICStubCompiler { ICUpdatedStub *getStub(ICStubSpace *space); }; +// Accesses scalar elements of a typed array or typed object. class ICSetElem_TypedArray : public ICStub { friend class ICStubSpace; @@ -3763,14 +3792,17 @@ class ICSetElem_TypedArray : public ICStub class Compiler : public ICStubCompiler { RootedShape shape_; Scalar::Type type_; + TypedThingLayout layout_; bool expectOutOfBounds_; protected: bool generateStubCode(MacroAssembler &masm); virtual int32_t getKey() const { - return static_cast(kind) | (static_cast(type_) << 16) | - (static_cast(expectOutOfBounds_) << 24); + return static_cast(kind) | + (static_cast(type_) << 16) | + (static_cast(layout_) << 24) | + (static_cast(expectOutOfBounds_) << 28); } public: @@ -3778,6 +3810,7 @@ class ICSetElem_TypedArray : public ICStub : ICStubCompiler(cx, ICStub::SetElem_TypedArray), shape_(cx, shape), type_(type), + layout_(GetTypedThingLayout(shape->getObjectClass())), expectOutOfBounds_(expectOutOfBounds) {} @@ -4574,6 +4607,84 @@ class ICGetPropNativeDoesNotExistCompiler : public ICStubCompiler ICStub *getStub(ICStubSpace *space); }; +static uint32_t +SimpleTypeDescrKey(SimpleTypeDescr *descr) +{ + if (descr->is()) + return uint32_t(descr->as().type()) << 1; + return (uint32_t(descr->as().type()) << 1) | 1; +} + +class ICGetProp_TypedObject : public ICMonitoredStub +{ + friend class ICStubSpace; + + HeapPtrShape shape_; + uint32_t fieldOffset_; + + ICGetProp_TypedObject(JitCode *stubCode, ICStub *firstMonitorStub, HandleShape shape, + uint32_t fieldOffset) + : ICMonitoredStub(ICStub::GetProp_TypedObject, stubCode, firstMonitorStub), + shape_(shape), fieldOffset_(fieldOffset) + { + (void) fieldOffset_; // Silence clang warning + } + + public: + static inline ICGetProp_TypedObject *New(ICStubSpace *space, JitCode *code, + ICStub *firstMonitorStub, HandleShape shape, + uint32_t fieldOffset) + { + if (!code) + return nullptr; + return space->allocate(code, firstMonitorStub, shape, fieldOffset); + } + + HeapPtrShape &shape() { + return shape_; + } + + static size_t offsetOfShape() { + return offsetof(ICGetProp_TypedObject, shape_); + } + static size_t offsetOfFieldOffset() { + return offsetof(ICGetProp_TypedObject, fieldOffset_); + } + + class Compiler : public ICStubCompiler { + protected: + ICStub *firstMonitorStub_; + RootedShape shape_; + uint32_t fieldOffset_; + TypedThingLayout layout_; + Rooted fieldDescr_; + + bool generateStubCode(MacroAssembler &masm); + + virtual int32_t getKey() const { + return static_cast(kind) | + (static_cast(SimpleTypeDescrKey(fieldDescr_)) << 16) | + (static_cast(layout_) << 24); + } + + public: + Compiler(JSContext *cx, ICStub *firstMonitorStub, + Shape *shape, uint32_t fieldOffset, SimpleTypeDescr *fieldDescr) + : ICStubCompiler(cx, ICStub::GetProp_TypedObject), + firstMonitorStub_(firstMonitorStub), + shape_(cx, shape), + fieldOffset_(fieldOffset), + layout_(GetTypedThingLayout(shape->getObjectClass())), + fieldDescr_(cx, fieldDescr) + {} + + ICStub *getStub(ICStubSpace *space) { + return ICGetProp_TypedObject::New(space, getStubCode(), firstMonitorStub_, + shape_, fieldOffset_); + } + }; +}; + class ICGetPropCallGetter : public ICMonitoredStub { friend class ICStubSpace; @@ -5416,6 +5527,102 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler ICUpdatedStub *getStub(ICStubSpace *space); }; +class ICSetProp_TypedObject : public ICUpdatedStub +{ + friend class ICStubSpace; + + HeapPtrShape shape_; + HeapPtrTypeObject type_; + uint32_t fieldOffset_; + bool isObjectReference_; + + ICSetProp_TypedObject(JitCode *stubCode, HandleShape shape, HandleTypeObject type, + uint32_t fieldOffset, bool isObjectReference) + : ICUpdatedStub(ICStub::SetProp_TypedObject, stubCode), + shape_(shape), + type_(type), + fieldOffset_(fieldOffset), + isObjectReference_(isObjectReference) + { + (void) fieldOffset_; // Silence clang warning + } + + public: + static inline ICSetProp_TypedObject *New(ICStubSpace *space, JitCode *code, + HandleShape shape, HandleTypeObject type, + uint32_t fieldOffset, bool isObjectReference) + { + if (!code) + return nullptr; + return space->allocate(code, shape, type, + fieldOffset, isObjectReference); + } + + HeapPtrShape &shape() { + return shape_; + } + HeapPtrTypeObject &type() { + return type_; + } + bool isObjectReference() { + return isObjectReference_; + } + + static size_t offsetOfShape() { + return offsetof(ICSetProp_TypedObject, shape_); + } + static size_t offsetOfType() { + return offsetof(ICSetProp_TypedObject, type_); + } + static size_t offsetOfFieldOffset() { + return offsetof(ICSetProp_TypedObject, fieldOffset_); + } + + class Compiler : public ICStubCompiler { + protected: + RootedShape shape_; + RootedTypeObject type_; + uint32_t fieldOffset_; + TypedThingLayout layout_; + Rooted fieldDescr_; + + bool generateStubCode(MacroAssembler &masm); + + virtual int32_t getKey() const { + return static_cast(kind) | + (static_cast(SimpleTypeDescrKey(fieldDescr_)) << 16) | + (static_cast(layout_) << 24); + } + + public: + Compiler(JSContext *cx, Shape *shape, types::TypeObject *type, uint32_t fieldOffset, + SimpleTypeDescr *fieldDescr) + : ICStubCompiler(cx, ICStub::SetProp_TypedObject), + shape_(cx, shape), + type_(cx, type), + fieldOffset_(fieldOffset), + layout_(GetTypedThingLayout(shape->getObjectClass())), + fieldDescr_(cx, fieldDescr) + {} + + ICUpdatedStub *getStub(ICStubSpace *space) { + bool isObjectReference = + fieldDescr_->is() && + fieldDescr_->as().type() == ReferenceTypeDescr::TYPE_OBJECT; + ICUpdatedStub *stub = ICSetProp_TypedObject::New(space, getStubCode(), shape_, type_, + fieldOffset_, isObjectReference); + if (!stub || !stub->initUpdatingChain(cx, space)) + return nullptr; + return stub; + } + + bool needsUpdateStubs() { + return fieldDescr_->is() && + fieldDescr_->as().type() != ReferenceTypeDescr::TYPE_STRING; + } + }; +}; + // Base stub for calling a setters on a native object. class ICSetPropCallSetter : public ICStub { diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 762f555e264..6dbdcc67b12 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -56,6 +56,7 @@ JSCompartment::JSCompartment(Zone *zone, const JS::CompartmentOptions &options = lastAnimationTime(0), regExps(runtime_), globalWriteBarriered(false), + neuteredTypedObjects(0), propertyTree(thisForCtor()), selfHostingScriptSource(nullptr), lazyArrayBuffers(nullptr), diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index df76c4543b6..e13b0f4c644 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -241,6 +241,9 @@ struct JSCompartment */ bool globalWriteBarriered; + // Non-zero if any typed objects in this compartment might be neutered. + int32_t neuteredTypedObjects; + public: void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *tiAllocationSiteTables, diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 52b42196f4e..8d918575ccd 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -529,6 +529,7 @@ ArrayBufferObject::neuter(JSContext *cx, Handle buffer, if (!cx->global()->getType(cx)) CrashAtUnhandlableOOM("ArrayBufferObject::neuter"); types::MarkTypeObjectFlags(cx, cx->global(), types::OBJECT_FLAG_TYPED_OBJECT_NEUTERED); + cx->compartment()->neuteredTypedObjects = 1; } // Neuter all views on the buffer, clear out the list of views and the