Bug 1103273 - Fix uses of forwarding pointers in inline typed objects, r=sfink.

This commit is contained in:
Brian Hackett 2014-11-23 11:09:41 -07:00
parent 96b7a29c5f
commit b8bc67a4f9
10 changed files with 199 additions and 79 deletions

View File

@ -1511,6 +1511,11 @@ PrototypeForTypeDescr(JSContext *cx, HandleTypeDescr descr)
void
OutlineTypedObject::setOwnerAndData(JSObject *owner, uint8_t *data)
{
// Make sure we don't associate with array buffers whose data is from an
// inline typed object, see obj_trace.
MOZ_ASSERT_IF(owner && owner->is<ArrayBufferObject>(),
!owner->as<ArrayBufferObject>().forInlineTypedObject());
// Typed objects cannot move from one owner to another, so don't worry
// about pre barriers during this initialization.
owner_ = owner;
@ -1554,6 +1559,14 @@ OutlineTypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t off
MOZ_ASSERT(offset >= 0);
MOZ_ASSERT((size_t) (offset + size()) <= buffer.byteLength());
// If the owner's data is from an inline typed object, associate this with
// the inline typed object instead, to simplify tracing.
if (buffer.forInlineTypedObject()) {
InlineTypedObject &realOwner = buffer.firstView()->as<InlineTypedObject>();
attach(cx, realOwner, offset);
return;
}
buffer.setHasTypedObjectViews();
if (!buffer.addView(cx, this))
@ -1683,22 +1696,29 @@ OutlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
gc::MarkObjectUnbarriered(trc, &typedObj.owner_, "typed object owner");
JSObject *owner = typedObj.owner_;
uint8_t *mem = typedObj.outOfLineTypedMem();
uint8_t *oldData = typedObj.outOfLineTypedMem();
uint8_t *newData = oldData;
// Update the data pointer if the owner moved and the owner's data is
// inline with it.
// inline with it. Note that an array buffer pointing to data in an inline
// typed object will never be used as an owner for another outline typed
// object. In such cases, the owner will be the inline typed object itself.
MOZ_ASSERT_IF(owner->is<ArrayBufferObject>(),
!owner->as<ArrayBufferObject>().forInlineTypedObject());
if (owner != oldOwner &&
(owner->is<InlineTypedObject>() ||
owner->as<ArrayBufferObject>().hasInlineData()))
{
mem += reinterpret_cast<uint8_t *>(owner) - reinterpret_cast<uint8_t *>(oldOwner);
typedObj.setData(mem);
newData += reinterpret_cast<uint8_t *>(owner) - reinterpret_cast<uint8_t *>(oldOwner);
typedObj.setData(newData);
trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, /* direct = */ false);
}
if (!descr.opaque() || !typedObj.maybeForwardedIsAttached())
return;
descr.traceInstances(trc, mem, 1);
descr.traceInstances(trc, newData, 1);
}
bool
@ -2260,6 +2280,25 @@ InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
descr.traceInstances(trc, typedObj.inlineTypedMem(), 1);
}
/* static */ void
InlineTypedObject::objectMovedDuringMinorGC(JSTracer *trc, JSObject *dst, JSObject *src)
{
// Inline typed object element arrays can be preserved on the stack by Ion
// and need forwarding pointers created during a minor GC. We can't do this
// in the trace hook because we don't have any stale data to determine
// whether this object moved and where it was moved from.
TypeDescr &descr = dst->as<InlineTypedObject>().typeDescr();
if (descr.kind() == type::Array) {
// The forwarding pointer can be direct as long as there is enough
// space for it. Other objects might point into the object's buffer,
// but they will not set any direct forwarding pointers.
uint8_t *oldData = reinterpret_cast<uint8_t *>(src) + offsetOfDataStart();
uint8_t *newData = dst->as<InlineTypedObject>().inlineTypedMem();
trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData,
descr.size() >= sizeof(uintptr_t));
}
}
ArrayBufferObject *
InlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx)
{

View File

@ -741,6 +741,7 @@ class InlineTypedObject : public TypedObject
}
static void obj_trace(JSTracer *trace, JSObject *object);
static void objectMovedDuringMinorGC(JSTracer *trc, JSObject *dst, JSObject *src);
static size_t offsetOfDataStart() {
return offsetof(InlineTypedObject, data_);

View File

@ -393,8 +393,8 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj)
// Proxies have finalizers and are not nursery allocated.
MOZ_ASSERT(!IsProxy(obj));
// Inlined opaque typed objects are followed by their data, so make sure we
// copy it all over to the new object.
// Inlined typed objects are followed by their data, so make sure we copy
// it all over to the new object.
if (obj->is<InlineTypedObject>()) {
// Figure out the size of this object, from the prototype's TypeDescr.
// The objects we are traversing here are all tenured, so we don't need
@ -429,28 +429,41 @@ js::Nursery::allocateFromTenured(Zone *zone, AllocKind thingKind)
}
void
js::Nursery::setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots)
Nursery::setForwardingPointer(void *oldData, void *newData, bool direct)
{
MOZ_ASSERT(nslots > 0);
MOZ_ASSERT(isInside(oldSlots));
MOZ_ASSERT(!isInside(newSlots));
*reinterpret_cast<HeapSlot **>(oldSlots) = newSlots;
MOZ_ASSERT(isInside(oldData));
MOZ_ASSERT(!isInside(newData));
if (direct) {
*reinterpret_cast<void **>(oldData) = newData;
} else {
if (!forwardedBuffers.initialized() && !forwardedBuffers.init())
CrashAtUnhandlableOOM("Nursery::setForwardingPointer");
#ifdef DEBUG
if (ForwardedBufferMap::Ptr p = forwardedBuffers.lookup(oldData))
MOZ_ASSERT(p->value() == newData);
#endif
if (!forwardedBuffers.put(oldData, newData))
CrashAtUnhandlableOOM("Nursery::setForwardingPointer");
}
}
void
js::Nursery::setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader,
uint32_t nelems)
Nursery::setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots)
{
MOZ_ASSERT(isInside(oldHeader));
MOZ_ASSERT(!isInside(newHeader));
if (nelems - ObjectElements::VALUES_PER_HEADER < 1) {
if (!forwardedBuffers.initialized() && !forwardedBuffers.init())
CrashAtUnhandlableOOM("Nursery::setElementsForwardingPointer");
if (!forwardedBuffers.put(oldHeader->elements(), newHeader->elements()))
CrashAtUnhandlableOOM("Nursery::setElementsForwardingPointer");
return;
}
*reinterpret_cast<HeapSlot **>(oldHeader->elements()) = newHeader->elements();
// Slot arrays always have enough space for a forwarding pointer, since the
// number of slots is never zero.
MOZ_ASSERT(nslots > 0);
setForwardingPointer(oldSlots, newSlots, /* direct = */ true);
}
void
Nursery::setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader,
uint32_t nelems)
{
// Only use a direct forwarding pointer if there is enough space for one.
setForwardingPointer(oldHeader->elements(), newHeader->elements(),
nelems > ObjectElements::VALUES_PER_HEADER);
}
#ifdef DEBUG
@ -590,7 +603,7 @@ js::Nursery::moveToTenured(MinorCollectionTracer *trc, JSObject *src)
if (!dst)
CrashAtUnhandlableOOM("Failed to allocate object while tenuring.");
trc->tenuredSize += moveObjectToTenured(dst, src, dstKind);
trc->tenuredSize += moveObjectToTenured(trc, dst, src, dstKind);
RelocationOverlay *overlay = RelocationOverlay::fromCell(src);
overlay->forwardTo(dst);
@ -601,7 +614,8 @@ js::Nursery::moveToTenured(MinorCollectionTracer *trc, JSObject *src)
}
MOZ_ALWAYS_INLINE size_t
js::Nursery::moveObjectToTenured(JSObject *dst, JSObject *src, AllocKind dstKind)
js::Nursery::moveObjectToTenured(MinorCollectionTracer *trc,
JSObject *dst, JSObject *src, AllocKind dstKind)
{
size_t srcSize = Arena::thingSize(dstKind);
size_t tenuredSize = srcSize;
@ -625,8 +639,8 @@ js::Nursery::moveObjectToTenured(JSObject *dst, JSObject *src, AllocKind dstKind
tenuredSize += moveElementsToTenured(ndst, nsrc, dstKind);
}
if (src->is<TypedArrayObject>())
forwardTypedArrayPointers(&dst->as<TypedArrayObject>(), &src->as<TypedArrayObject>());
if (src->is<InlineTypedObject>())
InlineTypedObject::objectMovedDuringMinorGC(trc, dst, src);
/* The shape's list head may point into the old object. */
if (&src->shape_ == dst->shape_->listp)
@ -635,35 +649,6 @@ js::Nursery::moveObjectToTenured(JSObject *dst, JSObject *src, AllocKind dstKind
return tenuredSize;
}
void
js::Nursery::forwardTypedArrayPointers(TypedArrayObject *dst, TypedArrayObject *src)
{
/*
* Typed array data may be stored inline inside the object's fixed slots. If
* so, we need update the private pointer and leave a forwarding pointer at
* the start of the data.
*/
if (src->buffer()) {
MOZ_ASSERT(!isInside(src->getPrivate()));
return;
}
void *srcData = src->fixedData(TypedArrayObject::FIXED_DATA_START);
void *dstData = dst->fixedData(TypedArrayObject::FIXED_DATA_START);
MOZ_ASSERT(src->getPrivate() == srcData);
dst->setPrivate(dstData);
/*
* We don't know the number of slots here, but
* TypedArrayObject::AllocKindForLazyBuffer ensures that it's always at
* least one.
*/
size_t nslots = 1;
setSlotsForwardingPointer(reinterpret_cast<HeapSlot*>(srcData),
reinterpret_cast<HeapSlot*>(dstData),
nslots);
}
MOZ_ALWAYS_INLINE size_t
js::Nursery::moveSlotsToTenured(NativeObject *dst, NativeObject *src, AllocKind dstKind)
{

View File

@ -137,6 +137,11 @@ class Nursery
static void forwardBufferPointer(JSTracer* trc, HeapSlot **pSlotsElems);
void maybeSetForwardingPointer(JSTracer *trc, void *oldData, void *newData, bool direct) {
if (IsMinorCollectionTracer(trc) && isInside(oldData))
setForwardingPointer(oldData, newData, direct);
}
size_t sizeOfHeapCommitted() const {
return numActiveChunks_ * gc::ChunkSize;
}
@ -295,12 +300,14 @@ class Nursery
MOZ_ALWAYS_INLINE void markSlots(gc::MinorCollectionTracer *trc, HeapSlot *vp, HeapSlot *end);
MOZ_ALWAYS_INLINE void markSlot(gc::MinorCollectionTracer *trc, HeapSlot *slotp);
void *moveToTenured(gc::MinorCollectionTracer *trc, JSObject *src);
size_t moveObjectToTenured(JSObject *dst, JSObject *src, gc::AllocKind dstKind);
size_t moveObjectToTenured(gc::MinorCollectionTracer *trc, JSObject *dst, JSObject *src,
gc::AllocKind dstKind);
size_t moveElementsToTenured(NativeObject *dst, NativeObject *src, gc::AllocKind dstKind);
size_t moveSlotsToTenured(NativeObject *dst, NativeObject *src, gc::AllocKind dstKind);
void forwardTypedArrayPointers(TypedArrayObject *dst, TypedArrayObject *src);
/* Handle relocation of slots/elements pointers stored in Ion frames. */
void setForwardingPointer(void *oldData, void *newData, bool direct);
void setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots);
void setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader,
uint32_t nelems);

View File

@ -0,0 +1,12 @@
gczeal(2);
var Vec3u16Type = TypedObject.uint16.array(3);
function foo_u16(n) {
if (n == 0)
return;
var i = 0;
var vec = new Vec3u16Type([i, i+1, i+2]);
var sum = vec[0] + vec[1] + vec[(/[]/g )];
foo_u16(n - 1);
}
foo_u16(100);

View File

@ -0,0 +1,20 @@
var Vec3u16Type = TypedObject.uint16.array(3);
function foo() {
var x = 0;
for (var i = 0; i < 3; i++) {
var obj = new Vec3u16Type;
var buf = TypedObject.storage(obj).buffer;
var arr = new Uint8Array(buf, 3);
arr[0] = i + 1;
arr[1] = i + 2;
arr[2] = i + 3;
for (var j = 0; j < arr.length; j++) {
minorgc();
x += arr[j];
}
}
assertEq(x, 27);
}
foo();

View File

@ -1187,13 +1187,36 @@ ArrayBufferViewObject::trace(JSTracer *trc, JSObject *objArg)
HeapSlot &bufSlot = obj->getReservedSlotRef(TypedArrayLayout::BUFFER_SLOT);
MarkSlot(trc, &bufSlot, "typedarray.buffer");
// Update obj's data pointer if the array buffer moved. Note that during
// initialization, bufSlot may still contain |undefined|.
// Update obj's data pointer if it moved.
if (bufSlot.isObject()) {
ArrayBufferObject &buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
int32_t offset = obj->getReservedSlot(TypedArrayLayout::BYTEOFFSET_SLOT).toInt32();
MOZ_ASSERT(buf.dataPointer() != nullptr);
obj->initPrivate(buf.dataPointer() + offset);
if (buf.forInlineTypedObject()) {
// The data is inline with an InlineTypedObject associated with the
// buffer. Get a new address for the typed object if it moved.
JSObject *view = buf.firstView();
// Mark the object to move it into the tenured space.
MarkObjectUnbarriered(trc, &view, "typed array nursery owner");
MOZ_ASSERT(view->is<InlineTypedObject>() && view != obj);
void *srcData = obj->getPrivate();
void *dstData = view->as<InlineTypedObject>().inlineTypedMem() + offset;
obj->setPrivate(dstData);
// We can't use a direct forwarding pointer here, as there might
// not be enough bytes available, and other views might have data
// pointers whose forwarding pointers would overlap this one.
trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, srcData, dstData,
/* direct = */ false);
} else {
// 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.
obj->initPrivate(buf.dataPointer() + offset);
}
}
}

View File

@ -56,6 +56,22 @@ class ArrayBufferViewObject;
// As ArrayBufferObject and SharedArrayBufferObject are separated, so are the
// TypedArray hierarchies below the two. However, the TypedArrays have the
// same layout (see TypedArrayObject.h), so there is little code duplication.
//
// The possible data ownership and reference relationships with array buffers
// and related classes are enumerated below. These are the possible locations
// for typed data:
//
// (1) malloc'ed or mmap'ed data owned by an ArrayBufferObject.
// (2) Data allocated inline with an ArrayBufferObject.
// (3) Data allocated inline with a TypedArrayObject.
// (4) Data allocated inline with an InlineTypedObject.
//
// An ArrayBufferObject may point to any of these sources of data, except (3).
// All array buffer views may point to any of these sources of data, except
// that (3) may only be pointed to by the typed array the data is inline with.
//
// During a minor GC, (3) and (4) may move. During a compacting GC, (2), (3),
// and (4) may move.
class ArrayBufferObjectMaybeShared;
@ -329,6 +345,8 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
setFlags(flags() | TYPED_OBJECT_VIEWS);
}
bool forInlineTypedObject() const { return flags() & FOR_INLINE_TYPED_OBJECT; }
protected:
void setDataPointer(BufferContents contents, OwnsState ownsState);
void setByteLength(size_t length);
@ -341,7 +359,6 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
}
bool forInlineTypedObject() const { return flags() & FOR_INLINE_TYPED_OBJECT; }
bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
void setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); }

View File

@ -125,13 +125,25 @@ TypedArrayObject::ensureHasBuffer(JSContext *cx, Handle<TypedArrayObject *> tarr
}
/* static */ void
TypedArrayObject::ObjectMoved(JSObject *dstArg, const JSObject *srcArg)
TypedArrayObject::trace(JSTracer *trc, JSObject *objArg)
{
const TypedArrayObject &src = srcArg->as<TypedArrayObject>();
TypedArrayObject &dst = dstArg->as<TypedArrayObject>();
if (!src.hasBuffer()) {
MOZ_ASSERT(src.getPrivate() == src.fixedData(FIXED_DATA_START));
dst.setPrivate(dst.fixedData(FIXED_DATA_START));
// Handle all tracing required when the object has a buffer.
ArrayBufferViewObject::trace(trc, objArg);
// If the typed array doesn't have a buffer, it must have a lazy buffer and
// its data pointer must point to its inline data. Watch for cases where
// the GC moved this object and fix up its data pointer.
TypedArrayObject &obj = objArg->as<TypedArrayObject>();
if (!obj.hasBuffer() && obj.getPrivate() != obj.fixedData(FIXED_DATA_START)) {
void *oldData = obj.getPrivate();
void *newData = obj.fixedData(FIXED_DATA_START);
obj.setPrivateUnbarriered(newData);
// If this is a minor GC, set a forwarding pointer for the array data.
// This can always be done inline, as AllocKindForLazyBuffer ensures
// there is at least a pointer's worth of inline data.
trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, true);
}
}
@ -354,6 +366,12 @@ class TypedArrayObjectTemplate : public TypedArrayObject
if (buffer) {
obj->initPrivate(buffer->dataPointer() + byteOffset);
// If the buffer is for an inline typed object, the data pointer
// may be in the nursery, so include a barrier to make sure this
// object is updated if that typed object moves.
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(buffer->dataPointer()))
cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(obj);
} else {
void *data = obj->fixedData(FIXED_DATA_START);
obj->initPrivate(data);
@ -967,6 +985,11 @@ DataViewObject::create(JSContext *cx, uint32_t byteOffset, uint32_t byteLength,
dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset);
MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
// Include a barrier if the data view's data pointer is in the nursery, as
// is done for typed arrays.
if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer()))
cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(obj);
// Verify that the private slot is at the expected place
MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayLayout::DATA_SLOT);
@ -1747,15 +1770,8 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double)
nullptr, /* call */ \
nullptr, /* hasInstance */ \
nullptr, /* construct */ \
ArrayBufferViewObject::trace, /* trace */ \
TYPED_ARRAY_CLASS_SPEC(_typedArray), \
{ \
nullptr, /* outerObject */ \
nullptr, /* innerObject */ \
false, /* isWrappedNative */ \
nullptr, /* weakmapKeyDelegateOp */ \
TypedArrayObject::ObjectMoved \
} \
TypedArrayObject::trace, /* trace */ \
TYPED_ARRAY_CLASS_SPEC(_typedArray) \
}
const Class TypedArrayObject::classes[Scalar::TypeMax] = {

View File

@ -182,7 +182,7 @@ class TypedArrayObject : public NativeObject
return layout_;
}
static void ObjectMoved(JSObject *obj, const JSObject *old);
static void trace(JSTracer *trc, JSObject *obj);
/* Initialization bits */