Bug 1722674 - Use a reserved slot instead of a private slot for TypedArray and DataView objects. r=jonco

Differential Revision: https://phabricator.services.mozilla.com/D121068
This commit is contained in:
Jan de Mooij 2021-07-29 08:10:43 +00:00
parent d64a25c382
commit 25c3d08eef
17 changed files with 85 additions and 98 deletions

View File

@ -20,7 +20,7 @@
#include "jstypes.h" // JS_PUBLIC_API
#include "js/Object.h" // JS::GetClass, JS::GetPrivate, JS::GetReservedSlot
#include "js/Object.h" // JS::GetClass, JS::GetReservedSlot, JS::GetMaybePtrFromReservedSlot
#include "js/RootingAPI.h" // JS::Handle
#include "js/ScalarType.h" // js::Scalar::Type
@ -204,19 +204,21 @@ extern JS_PUBLIC_DATA const JSClass* const BigUint64ArrayClassPtr;
extern JS_PUBLIC_DATA const JSClass* const Float32ArrayClassPtr;
extern JS_PUBLIC_DATA const JSClass* const Float64ArrayClassPtr;
const size_t TypedArrayLengthSlot = 1;
constexpr size_t TypedArrayLengthSlot = 1;
constexpr size_t TypedArrayDataSlot = 3;
} // namespace detail
#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Type, type) \
inline void Get##Type##ArrayLengthAndData( \
JSObject* obj, size_t* length, bool* isSharedMemory, type** data) { \
MOZ_ASSERT(JS::GetClass(obj) == detail::Type##ArrayClassPtr); \
const JS::Value& lenSlot = \
JS::GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
*length = size_t(lenSlot.toPrivate()); \
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
*data = static_cast<type*>(JS::GetPrivate(obj)); \
#define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Type, type) \
inline void Get##Type##ArrayLengthAndData( \
JSObject* obj, size_t* length, bool* isSharedMemory, type** data) { \
MOZ_ASSERT(JS::GetClass(obj) == detail::Type##ArrayClassPtr); \
const JS::Value& lenSlot = \
JS::GetReservedSlot(obj, detail::TypedArrayLengthSlot); \
*length = size_t(lenSlot.toPrivate()); \
*isSharedMemory = JS_GetTypedArraySharedness(obj); \
*data = JS::GetMaybePtrFromReservedSlot<type>(obj, \
detail::TypedArrayDataSlot); \
}
JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(Int8, int8_t)

View File

@ -962,8 +962,7 @@ const ClassSpec DataViewObject::classSpec_ = {
const JSClass DataViewObject::class_ = {
"DataView",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(DataViewObject::RESERVED_SLOTS) |
JSCLASS_HAS_RESERVED_SLOTS(DataViewObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
&DataViewObjectClassOps, &DataViewObject::classSpec_};

View File

@ -767,24 +767,11 @@ static void FindStartOfUninitializedAndUndefinedSlots(
}
}
template <typename Src>
inline void MacroAssembler::storeObjPrivate(Src ptr, const Address& address) {
// The private pointer is stored as a PrivateValue in a JS::Value, so on 32
// bit systems we also need to zero the top word.
#ifdef JS_PUNBOX64
storePtr(ptr, address);
#else
storePtr(ptr, LowWord(address));
store32(Imm32(0), HighWord(address));
#endif
}
void MacroAssembler::initTypedArraySlots(Register obj, Register temp,
Register lengthReg,
LiveRegisterSet liveRegs, Label* fail,
TypedArrayObject* templateObj,
TypedArrayLength lengthKind) {
MOZ_ASSERT(templateObj->hasPrivate());
MOZ_ASSERT(!templateObj->hasBuffer());
constexpr size_t dataSlotOffset = ArrayBufferViewObject::dataOffset();
@ -811,7 +798,7 @@ void MacroAssembler::initTypedArraySlots(Register obj, Register temp,
// Store data elements inside the remaining JSObject slots.
computeEffectiveAddress(Address(obj, dataOffset), temp);
storeObjPrivate(temp, Address(obj, dataSlotOffset));
storePrivateValue(temp, Address(obj, dataSlotOffset));
// Write enough zero pointers into fixed data to zero every
// element. (This zeroes past the end of a byte count that's
@ -845,8 +832,8 @@ void MacroAssembler::initTypedArraySlots(Register obj, Register temp,
callWithABI<Fn, AllocateAndInitTypedArrayBuffer>();
PopRegsInMask(liveRegs);
// Fail when data elements is set to NULL.
branchPtr(Assembler::Equal, Address(obj, dataSlotOffset), ImmWord(0), fail);
// Fail when data slot is UndefinedValue.
branchTestUndefined(Assembler::Equal, Address(obj, dataSlotOffset), fail);
}
}
@ -936,6 +923,7 @@ void MacroAssembler::initGCThing(Register obj, Register temp,
const TemplateNativeObject& ntemplate =
templateObj.asTemplateNativeObject();
MOZ_ASSERT(!ntemplate.hasDynamicElements());
MOZ_ASSERT(!ntemplate.hasPrivate());
// If the object has dynamic slots, the slots member has already been
// filled in.
@ -977,12 +965,6 @@ void MacroAssembler::initGCThing(Register obj, Register temp,
Address(obj, NativeObject::offsetOfElements()));
initGCSlots(obj, temp, ntemplate, initContents);
if (ntemplate.hasPrivate() && !ntemplate.isTypedArrayObject()) {
uint32_t nfixed = ntemplate.numFixedSlots();
Address privateSlot(obj, NativeObject::getPrivateDataOffset(nfixed));
storeObjPrivate(ImmPtr(ntemplate.getPrivate()), privateSlot);
}
}
} else {
MOZ_CRASH("Unknown object");

View File

@ -4274,9 +4274,6 @@ class MacroAssembler : public MacroAssemblerSpecific {
inline void storeObjShape(Shape* shape, Register obj,
EmitPreBarrier emitPreBarrier);
template <typename T>
void storeObjPrivate(T src, const Address& address);
void loadObjProto(Register obj, Register dest) {
loadPtr(Address(obj, JSObject::offsetOfShape()), dest);
loadPtr(Address(dest, Shape::offsetOfBaseShape()), dest);

View File

@ -119,10 +119,6 @@ inline gc::Cell* TemplateNativeObject::regExpShared() const {
return regexp->getShared();
}
inline void* TemplateNativeObject::getPrivate() const {
return asNativeObject().getPrivate();
}
} // namespace jit
} // namespace js

View File

@ -67,10 +67,8 @@ class TemplateNativeObject : public TemplateObject {
inline bool hasDynamicElements() const;
inline const Value* getDenseElements() const;
// Reading private slots is safe.
inline bool hasPrivate() const;
inline gc::Cell* regExpShared() const;
inline void* getPrivate() const;
};
} // namespace jit

View File

@ -2250,7 +2250,9 @@ void AllocateAndInitTypedArrayBuffer(JSContext* cx, TypedArrayObject* obj,
int32_t count) {
AutoUnsafeCallWithABI unsafe;
obj->initPrivate(nullptr);
// Initialize the data slot to UndefinedValue to signal to our JIT caller that
// the allocation failed if the slot isn't overwritten below.
obj->initFixedSlot(TypedArrayObject::DATA_SLOT, UndefinedValue());
// Negative numbers or zero will bail out to the slow path, which in turn will
// raise an invalid argument exception or create a correct object with zero
@ -2270,7 +2272,8 @@ void AllocateAndInitTypedArrayBuffer(JSContext* cx, TypedArrayObject* obj,
void* buf = cx->nursery().allocateZeroedBuffer(obj, nbytes,
js::ArrayBufferContentsArena);
if (buf) {
InitObjectPrivate(obj, buf, nbytes, MemoryUse::TypedArrayElements);
InitReservedSlot(obj, TypedArrayObject::DATA_SLOT, buf, nbytes,
MemoryUse::TypedArrayElements);
}
}

View File

@ -1093,6 +1093,11 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM {
store32(temp, ToPayload(dest));
}
void storePrivateValue(Register src, const Address& dest) {
store32(Imm32(0), ToType(dest));
store32(src, ToPayload(dest));
}
void loadValue(Address src, ValueOperand val);
void loadValue(Operand dest, ValueOperand val) {
loadValue(dest.toAddress(), val);

View File

@ -322,6 +322,10 @@ class MacroAssemblerCompat : public vixl::MacroAssembler {
storePtr(temp, dest);
}
void storePrivateValue(Register src, const Address& dest) {
storePtr(src, dest);
}
void loadValue(Address src, Register val) {
Ldr(ARMRegister(val, 64), MemOperand(src));
}

View File

@ -289,6 +289,10 @@ class MacroAssemblerNone : public Assembler {
MOZ_CRASH();
}
template <typename T, typename S>
void storePrivateValue(const T&, const S&) {
MOZ_CRASH();
}
template <typename T, typename S>
void loadValue(T, S) {
MOZ_CRASH();
}

View File

@ -171,6 +171,9 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared {
loadPtr(src, temp);
storePtr(temp, dest);
}
void storePrivateValue(Register src, const Address& dest) {
storePtr(src, dest);
}
void loadValue(Operand src, ValueOperand val) { movq(src, val.valueReg()); }
void loadValue(Address src, ValueOperand val) {
loadValue(Operand(src), val);

View File

@ -180,6 +180,10 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared {
load32(ToPayload(src), temp);
store32(temp, ToPayload(dest));
}
void storePrivateValue(Register src, const Address& dest) {
store32(Imm32(0), ToType(dest));
store32(src, ToPayload(dest));
}
void loadValue(Operand src, ValueOperand val) {
Operand payload = ToPayload(src);
Operand type = ToType(src);

View File

@ -31,20 +31,19 @@ void ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg) {
TraceEdge(trc, &bufSlot, "ArrayBufferViewObject.buffer");
// Update obj's data pointer if it moved.
if (bufSlot.isObject()) {
if (gc::MaybeForwardedObjectIs<ArrayBufferObject>(&bufSlot.toObject())) {
ArrayBufferObject& buf =
gc::MaybeForwardedObjectAs<ArrayBufferObject>(&bufSlot.toObject());
size_t offset = obj->byteOffset();
if (bufSlot.isObject() &&
gc::MaybeForwardedObjectIs<ArrayBufferObject>(&bufSlot.toObject())) {
ArrayBufferObject& buf =
gc::MaybeForwardedObjectAs<ArrayBufferObject>(&bufSlot.toObject());
size_t offset = obj->byteOffset();
MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
// The data may or may not be inline with the buffer. The buffer
// can only move during a compacting GC, in which case its
// objectMoved hook has already updated the buffer's data pointer.
size_t nfixed = obj->numFixedSlotsMaybeForwarded();
obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset);
}
// The data may or may not be inline with the buffer. The buffer can only
// move during a compacting GC, in which case its objectMoved hook has
// already updated the buffer's data pointer.
void* data = buf.dataPointer() + offset;
obj->getFixedSlotRef(DATA_SLOT).unbarrieredSet(PrivateValue(data));
}
}
@ -59,8 +58,7 @@ void ArrayBufferViewObject::notifyBufferDetached() {
setFixedSlot(LENGTH_SLOT, PrivateValue(size_t(0)));
setFixedSlot(BYTEOFFSET_SLOT, PrivateValue(size_t(0)));
setPrivate(nullptr);
setFixedSlot(DATA_SLOT, UndefinedValue());
}
/* static */
@ -113,7 +111,7 @@ bool ArrayBufferViewObject::init(JSContext* cx,
MOZ_ASSERT(length * bytesPerElement <=
TypedArrayObject::INLINE_BUFFER_LIMIT);
void* data = fixedData(TypedArrayObject::FIXED_DATA_START);
initPrivate(data);
initReservedSlot(DATA_SLOT, PrivateValue(data));
memset(data, 0, length * bytesPerElement);
#ifdef DEBUG
if (length == 0) {
@ -135,9 +133,6 @@ bool ArrayBufferViewObject::init(JSContext* cx,
MOZ_ASSERT(bufferByteLength - viewByteOffset >= viewByteLength);
MOZ_ASSERT(viewByteOffset <= bufferByteLength);
}
// Verify that the private slot is at the expected place.
MOZ_ASSERT(numFixedSlots() == DATA_SLOT);
#endif
// ArrayBufferObjects track their views to support detaching.

View File

@ -36,18 +36,15 @@ class ArrayBufferViewObject : public NativeObject {
// Offset of view within underlying (Shared)ArrayBufferObject.
static constexpr size_t BYTEOFFSET_SLOT = 2;
static constexpr size_t RESERVED_SLOTS = 3;
// Pointer to raw buffer memory.
static constexpr size_t DATA_SLOT = 3;
static constexpr size_t RESERVED_SLOTS = 4;
#ifdef DEBUG
static const uint8_t ZeroLengthArrayData = 0x4A;
#endif
// The raw pointer to the buffer memory, the "private" value.
//
// This offset is exposed for performance reasons - so that it
// need not be looked up on accesses.
static constexpr size_t DATA_SLOT = 3;
static constexpr int bufferOffset() {
return NativeObject::getFixedSlotOffset(BUFFER_SLOT);
}
@ -58,14 +55,14 @@ class ArrayBufferViewObject : public NativeObject {
return NativeObject::getFixedSlotOffset(BYTEOFFSET_SLOT);
}
static constexpr int dataOffset() {
return NativeObject::getPrivateDataOffset(DATA_SLOT);
return NativeObject::getFixedSlotOffset(DATA_SLOT);
}
private:
void* dataPointerEither_() const {
// Note, do not check whether shared or not
// Keep synced with js::Get<Type>ArrayLengthAndData in jsfriendapi.h!
return static_cast<void*>(getPrivate(DATA_SLOT));
return maybePtrFromReservedSlot<void>(DATA_SLOT);
}
public:
@ -86,7 +83,8 @@ class ArrayBufferViewObject : public NativeObject {
// accessed only from jitted code and from the
// dataPointerEither_() accessor above; in neither case does the
// raw pointer escape untagged into C++ code.
initPrivate(viewData.unwrap(/*safe - see above*/));
void* data = viewData.unwrap(/*safe - see above*/);
initReservedSlot(DATA_SLOT, PrivateValue(data));
}
SharedMem<void*> dataPointerShared() const {

View File

@ -1545,9 +1545,6 @@ class NativeObject : public JSObject {
MOZ_ASSERT(slot < MAX_FIXED_SLOTS);
return sizeof(NativeObject) + slot * sizeof(Value);
}
static constexpr size_t getPrivateDataOffset(size_t nfixed) {
return getFixedSlotOffset(nfixed);
}
static constexpr size_t getFixedSlotIndexFromOffset(size_t offset) {
MOZ_ASSERT(offset >= sizeof(NativeObject));
offset -= sizeof(NativeObject);

View File

@ -138,8 +138,8 @@ bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements);
}
tarray->setPrivate(buffer->dataPointer());
tarray->setFixedSlot(TypedArrayObject::DATA_SLOT,
PrivateValue(buffer->dataPointer()));
tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer));
return true;
@ -249,7 +249,8 @@ size_t TypedArrayObject::objectMoved(JSObject* obj, JSObject* old) {
"Failed to allocate typed array elements while tenuring.");
}
MOZ_ASSERT(!nursery.isInside(data));
InitObjectPrivate(newObj, data, nbytes, MemoryUse::TypedArrayElements);
newObj->setReservedSlot(DATA_SLOT, PrivateValue(data));
AddCellMemory(newObj, nbytes, MemoryUse::TypedArrayElements);
}
mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes);
@ -435,10 +436,9 @@ class TypedArrayObjectTemplate : public TypedArrayObject {
initTypedArraySlots(tarray, len);
// Template objects do not need memory for its elements, since there
// won't be any elements to store. Therefore, we set the pointer to
// nullptr and avoid allocating memory that will never be used.
tarray->initPrivate(nullptr);
// Template objects don't need memory for their elements, since there
// won't be any elements to store.
MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
return tarray;
}
@ -450,9 +450,6 @@ class TypedArrayObjectTemplate : public TypedArrayObject {
tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
PrivateValue(size_t(0)));
// Verify that the private slot is at the expected place.
MOZ_ASSERT(tarray->numFixedSlots() == TypedArrayObject::DATA_SLOT);
#ifdef DEBUG
if (len == 0) {
uint8_t* output = tarray->fixedData(TypedArrayObject::FIXED_DATA_START);
@ -464,7 +461,8 @@ class TypedArrayObjectTemplate : public TypedArrayObject {
static void initTypedArrayData(TypedArrayObject* tarray, void* buf,
size_t nbytes, gc::AllocKind allocKind) {
if (buf) {
InitObjectPrivate(tarray, buf, nbytes, MemoryUse::TypedArrayElements);
InitReservedSlot(tarray, TypedArrayObject::DATA_SLOT, buf, nbytes,
MemoryUse::TypedArrayElements);
} else {
#ifdef DEBUG
constexpr size_t dataOffset = ArrayBufferViewObject::dataOffset();
@ -473,7 +471,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject {
#endif
void* data = tarray->fixedData(FIXED_DATA_START);
tarray->initPrivate(data);
tarray->initReservedSlot(DATA_SLOT, PrivateValue(data));
memset(data, 0, nbytes);
}
}
@ -2234,13 +2232,13 @@ static const ClassSpec
};
const JSClass TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = {
#define IMPL_TYPED_ARRAY_CLASS(NativeType, Name) \
{#Name "Array", \
JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \
JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \
JSCLASS_BACKGROUND_FINALIZE, \
&TypedArrayClassOps, &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
#define IMPL_TYPED_ARRAY_CLASS(NativeType, Name) \
{#Name "Array", \
JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \
JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \
JSCLASS_BACKGROUND_FINALIZE, \
&TypedArrayClassOps, &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
&TypedArrayClassExtension},
JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS)

View File

@ -48,6 +48,8 @@ class TypedArrayObject : public ArrayBufferViewObject {
public:
static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT,
"bad inlined constant in TypedData.h");
static_assert(js::detail::TypedArrayDataSlot == DATA_SLOT,
"bad inlined constant in TypedData.h");
static bool sameBuffer(Handle<TypedArrayObject*> a,
Handle<TypedArrayObject*> b) {
@ -78,7 +80,7 @@ class TypedArrayObject : public ArrayBufferViewObject {
return &protoClasses[type];
}
static constexpr size_t FIXED_DATA_START = DATA_SLOT + 1;
static constexpr size_t FIXED_DATA_START = RESERVED_SLOTS;
// For typed arrays which can store their data inline, the array buffer
// object is created lazily.
@ -111,7 +113,7 @@ class TypedArrayObject : public ArrayBufferViewObject {
bool hasInlineElements() const;
void setInlineElements();
uint8_t* elementsRaw() const {
return *(uint8_t**)((((char*)this) + ArrayBufferViewObject::dataOffset()));
return maybePtrFromReservedSlot<uint8_t>(DATA_SLOT);
}
uint8_t* elements() const {
assertZeroLengthArrayData();