Skip to content

Commit 1884530

Browse files
committed
Bug 1978645 - Part 2: Use buffer allocator for wasm array data r=jseward
Differential Revision: https://phabricator.services.mozilla.com/D265784
1 parent 4c2e113 commit 1884530

File tree

2 files changed

+44
-193
lines changed

2 files changed

+44
-193
lines changed

js/src/wasm/WasmGcObject-inl.h

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -193,32 +193,23 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL(
193193
// arrays use createArrayIL.
194194
MOZ_ASSERT(storageBytes > WasmArrayObject_MaxInlineBytes);
195195

196-
// Allocate the outline data before allocating the object so that we can
197-
// infallibly initialize the pointer on the array object after it is
198-
// allocated.
199-
Nursery& nursery = cx->nursery();
200-
PointerAndUint7 outlineAlloc(nullptr, 0);
201-
outlineAlloc = nursery.mallocedBlockCache().alloc(storageBytes);
202-
if (MOZ_UNLIKELY(!outlineAlloc.pointer())) {
196+
auto* arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>(
197+
allocKind, initialHeap, typeDefData->clasp, allocSite);
198+
if (MOZ_UNLIKELY(!arrayObj)) {
203199
ReportOutOfMemory(cx);
204200
return nullptr;
205201
}
206202

207-
// It's unfortunate that `arrayObj` has to be rooted, since this is a hot
208-
// path and rooting costs around 15 instructions. It is the call to
209-
// registerTrailer that makes it necessary.
210-
Rooted<WasmArrayObject*> arrayObj(cx);
211-
arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>(
212-
allocKind, initialHeap, typeDefData->clasp, allocSite);
213-
if (MOZ_UNLIKELY(!arrayObj)) {
203+
uint8_t* outlineAlloc = AllocateCellBuffer<uint8_t>(
204+
cx, arrayObj, storageBytes, MaxNurseryTrailerSize);
205+
if (MOZ_UNLIKELY(!outlineAlloc)) {
206+
arrayObj->numElements_ = 0;
207+
arrayObj->data_ = nullptr;
214208
ReportOutOfMemory(cx);
215-
if (outlineAlloc.pointer()) {
216-
nursery.mallocedBlockCache().free(outlineAlloc);
217-
}
218209
return nullptr;
219210
}
220211

221-
DataHeader* outlineHeader = (DataHeader*)outlineAlloc.pointer();
212+
DataHeader* outlineHeader = (DataHeader*)outlineAlloc;
222213
uint8_t* outlineData = (uint8_t*)(outlineHeader + 1);
223214
*outlineHeader = DataIsOOL;
224215

@@ -234,25 +225,6 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL(
234225

235226
MOZ_ASSERT(!arrayObj->isDataInline());
236227

237-
if (MOZ_LIKELY(js::gc::IsInsideNursery(arrayObj))) {
238-
// We need to register the OOL area with the nursery, so it will be freed
239-
// after GCing of the nursery if `arrayObj_` doesn't make it into the
240-
// tenured heap. Note, the nursery will keep a running total of the
241-
// current trailer block sizes, so it can decide to do a (minor)
242-
// collection if that becomes excessive.
243-
if (MOZ_UNLIKELY(!nursery.registerTrailer(outlineAlloc, storageBytes))) {
244-
nursery.mallocedBlockCache().free(outlineAlloc);
245-
ReportOutOfMemory(cx);
246-
return nullptr;
247-
}
248-
} else {
249-
MOZ_ASSERT(arrayObj->isTenured());
250-
// Register the trailer size with the major GC mechanism, so that can also
251-
// is able to decide if that space use warrants a (major) collection.
252-
AddCellMemory(arrayObj, storageBytes + wasm::TrailerBlockOverhead,
253-
MemoryUse::WasmTrailerBlock);
254-
}
255-
256228
MOZ_ASSERT(typeDefData->clasp->shouldDelayMetadataBuilder());
257229
cx->realm()->setObjectPendingMetadata(arrayObj);
258230

js/src/wasm/WasmGcObject.cpp

Lines changed: 35 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#include <algorithm>
1010

11-
#include "gc/Marking.h"
11+
#include "gc/Tracer.h"
1212
#include "js/CharacterEncoding.h"
1313
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
1414
#include "js/PropertySpec.h"
@@ -36,126 +36,21 @@ using namespace wasm;
3636

3737
// [SMDOC] Management of OOL storage areas for Wasm{Array,Struct}Object.
3838
//
39-
// WasmArrayObject always has its payload data stored in a block the C++-heap,
40-
// which is pointed to from the WasmArrayObject. The same is true for
41-
// WasmStructObject in the case where the fields cannot fit in the object
42-
// itself. These C++ blocks are in some places referred to as "trailer blocks".
39+
// WasmArrayObject always has its payload data stored in a block which is
40+
// pointed to from the WasmArrayObject. The same is true for WasmStructObject in
41+
// the case where the fields cannot fit in the object itself. These blocks are
42+
// in some places referred to as "trailer blocks".
4343
//
44-
// The presence of trailer blocks complicates the use of generational GC (that
45-
// is, Nursery allocation) of Wasm{Array,Struct}Object. In particular:
44+
// These blocks are allocated in the same way as JSObects slots and element
45+
// buffers, either using the GC's buffer allocator or directly in the nursery if
46+
// they are small enough.
4647
//
47-
// (1) For objects which do not get tenured at minor collection, there must be
48-
// a way to free the associated trailer, but there is no way to visit
49-
// non-tenured blocks during minor collection.
48+
// They require the use of WasmArrayObject/WasmStructObject::obj_moved hooks to
49+
// update the pointer and mark the allocation when the object gets tenured, and
50+
// also update the pointer in case it is an internal pointer when the object is
51+
// moved.
5052
//
51-
// (2) Even if (1) were solved, calling js_malloc/js_free for every object
52-
// creation-death cycle is expensive, possibly around 400 machine
53-
// instructions, and we expressly want to avoid that in a generational GC
54-
// scenario.
55-
//
56-
// The following scheme is therefore employed.
57-
//
58-
// (a) gc::Nursery maintains a pool of available C++-heap-allocated blocks --
59-
// a js::MallocedBlockCache -- and the intention is that trailers are
60-
// allocated from this pool and freed back into it whenever possible.
61-
//
62-
// (b) WasmArrayObject::createArrayNonEmpty and
63-
// WasmStructObject::createStructOOL always request trailer allocation
64-
// from the nursery's cache (a). If the cache cannot honour the request
65-
// directly it will allocate directly from js_malloc; we hope this happens
66-
// only infrequently.
67-
//
68-
// (c) The allocated block is returned as a js::PointerAndUint7, a pair that
69-
// holds the trailer block pointer and an auxiliary tag that the
70-
// js::MallocedBlockCache needs to see when the block is freed.
71-
//
72-
// The raw trailer block pointer (a `void*`) is stored in the
73-
// Wasm{Array,Struct}Object OOL data field. These objects are not aware
74-
// of and do not interact with js::PointerAndUint7, and nor does any
75-
// JIT-generated code.
76-
//
77-
// (d) Still in WasmArrayObject::createArrayNonEmpty and
78-
// WasmStructObject::createStructOOL, if the object was allocated in the
79-
// nursery, then the resulting js::PointerAndUint7 is "registered" with
80-
// the nursery by handing it to Nursery::registerTrailer.
81-
//
82-
// (e) When a minor collection happens (Nursery::doCollection), we are
83-
// notified of objects that are moved by calls to the ::obj_moved methods
84-
// in this file. For those objects that have been tenured, the raw
85-
// trailer pointer is "unregistered" with the nursery by handing it to
86-
// Nursery::unregisterTrailer.
87-
//
88-
// (f) Still during minor collection: The nursery now knows both the set of
89-
// trailer blocks added, and those removed because the corresponding
90-
// object has been tenured. The difference between these two sets (that
91-
// is, `added - removed`) is the set of trailer blocks corresponding to
92-
// blocks that didn't get tenured. That set is computed and freed (back
93-
// to the nursery's js::MallocedBlockCache) by Nursery::freeTrailerBlocks.
94-
//
95-
// (g) At the end of minor collection, the added and removed sets are made
96-
// empty, and the cycle begins again.
97-
//
98-
// (h) Also at the end of minor collection, a call to
99-
// `mallocedBlockCache_.preen` hands a few blocks in the cache back to
100-
// js_free. This mechanism exists so as to ensure that unused blocks do
101-
// not remain in the cache indefinitely.
102-
//
103-
// (i) In order that the tenured heap is collected "often enough" in the case
104-
// where the trailer blocks are large (relative to their owning objects),
105-
// we have to tell the tenured heap about the sizes of trailers entering
106-
// and leaving it. This is done via calls to AddCellMemory and
107-
// GCContext::removeCellMemory.
108-
//
109-
// (j) For objects that got tenured, we are eventually notified of their death
110-
// by a call to the ::obj_finalize methods below. At that point we hand
111-
// their block pointers to js_free.
112-
//
113-
// (k) When the nursery is eventually destroyed, all blocks in its block cache
114-
// are handed to js_free. Hence, at process exit, provided all nurseries
115-
// are first collected and then their destructors run, no C++ heap blocks
116-
// are leaked.
117-
//
118-
// As a result of this scheme, trailer blocks associated with what we hope is
119-
// the frequent case -- objects that are allocated but never make it out of
120-
// the nursery -- are cycled through the nursery's block cache.
121-
//
122-
// Trailers associated with tenured blocks cannot participate though; they are
123-
// always returned to js_free. Making them participate is difficult: it would
124-
// require changing their owning object's OOL data pointer to be a
125-
// js::PointerAndUint7 rather than a raw `void*`, so that then the blocks
126-
// could be released to the cache in the ::obj_finalize methods. This would
127-
// however require changes in the generated code for array element and OOL
128-
// struct element accesses.
129-
//
130-
// It would also lead to threading difficulties, because the ::obj_finalize
131-
// methods run on a background thread, whilst allocation from the cache
132-
// happens on the main thread, but the MallocedBlockCache is not thread safe.
133-
// Making it thread safe would entail adding a locking mechanism, but that's
134-
// potentially slow and so negates the point of having a cache at all.
135-
//
136-
// Here's a short summary of the trailer block life cycle:
137-
//
138-
// * allocated:
139-
//
140-
// - in WasmArrayObject::createArrayNonEmpty
141-
// and WasmStructObject::createStructOOL
142-
//
143-
// - by calling the nursery's MallocBlockCache alloc method
144-
//
145-
// * deallocated:
146-
//
147-
// - for non-tenured objects, in the collector itself,
148-
// in Nursery::doCollection calling Nursery::freeTrailerBlocks,
149-
// releasing to the nursery's block cache
150-
//
151-
// - for tenured objects, in the ::obj_finalize methods, releasing directly
152-
// to js_free
153-
//
154-
// If this seems confusing ("why is it ok to allocate from the cache but
155-
// release to js_free?"), remember that the cache holds blocks previously
156-
// obtained from js_malloc but which are *not* currently in use. Hence it is
157-
// fine to give them back to js_free; that just makes the cache a bit emptier
158-
// but has no effect on correctness.
53+
// The blocks are freed by the GC when no longer referenced.
15954

16055
//=========================================================================
16156
// WasmGcObject
@@ -371,6 +266,15 @@ void WasmArrayObject::obj_trace(JSTracer* trc, JSObject* object) {
371266
WasmArrayObject& arrayObj = object->as<WasmArrayObject>();
372267
uint8_t* data = arrayObj.data_;
373268

269+
if (!arrayObj.isDataInline()) {
270+
uint8_t* outlineAlloc = (uint8_t*)dataHeaderFromDataPointer(arrayObj.data_);
271+
uint8_t* prior = outlineAlloc;
272+
TraceBufferEdge(trc, &arrayObj, &outlineAlloc, "WasmArrayObject storage");
273+
if (outlineAlloc != prior) {
274+
arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1);
275+
}
276+
}
277+
374278
const auto& typeDef = arrayObj.typeDef();
375279
const auto& arrayType = typeDef.arrayType();
376280
if (!arrayType.elementType().isRefRepr()) {
@@ -385,34 +289,6 @@ void WasmArrayObject::obj_trace(JSTracer* trc, JSObject* object) {
385289
}
386290
}
387291

388-
/* static */
389-
void WasmArrayObject::obj_finalize(JS::GCContext* gcx, JSObject* object) {
390-
// This method, and also ::obj_moved and the WasmStructObject equivalents,
391-
// assumes that the object's TypeDef (as reachable via its SuperTypeVector*)
392-
// stays alive at least as long as the object.
393-
WasmArrayObject& arrayObj = object->as<WasmArrayObject>();
394-
if (!arrayObj.isDataInline()) {
395-
// Free the trailer block. Unfortunately we can't give it back to the
396-
// malloc'd block cache because we might not be running on the main
397-
// thread, and the cache isn't thread-safe.
398-
js_free(arrayObj.dataHeader());
399-
// And tell the tenured-heap accounting machinery that the trailer has
400-
// been freed.
401-
const TypeDef& typeDef = arrayObj.typeDef();
402-
MOZ_ASSERT(typeDef.isArrayType());
403-
// arrayObj.numElements_ was validated to not overflow when constructing the
404-
// array
405-
size_t trailerSize = calcStorageBytesUnchecked(
406-
typeDef.arrayType().elementType().size(), arrayObj.numElements_);
407-
// Ensured by WasmArrayObject::createArrayNonEmpty.
408-
MOZ_RELEASE_ASSERT(trailerSize <= size_t(MaxArrayPayloadBytes));
409-
gcx->removeCellMemory(&arrayObj, trailerSize + TrailerBlockOverhead,
410-
MemoryUse::WasmTrailerBlock);
411-
// For safety
412-
arrayObj.data_ = nullptr;
413-
}
414-
}
415-
416292
/* static */
417293
size_t WasmArrayObject::obj_moved(JSObject* obj, JSObject* old) {
418294
// Moving inline arrays requires us to update the data pointer.
@@ -437,9 +313,13 @@ size_t WasmArrayObject::obj_moved(JSObject* obj, JSObject* old) {
437313
typeDef.arrayType().elementType().size(), arrayObj.numElements_);
438314
// Ensured by WasmArrayObject::createArrayOOL.
439315
MOZ_RELEASE_ASSERT(trailerSize <= size_t(MaxArrayPayloadBytes));
440-
nursery.trackTrailerOnPromotion(arrayObj.dataHeader(), obj, trailerSize,
441-
TrailerBlockOverhead,
442-
MemoryUse::WasmTrailerBlock);
316+
uint8_t* outlineAlloc =
317+
(uint8_t*)dataHeaderFromDataPointer(arrayObj.data_);
318+
uint8_t* prior = outlineAlloc;
319+
nursery.maybeMoveBufferOnPromotion(&outlineAlloc, obj, trailerSize);
320+
if (outlineAlloc != prior) {
321+
arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1);
322+
}
443323
}
444324
}
445325

@@ -471,20 +351,19 @@ static const JSClassOps WasmArrayObjectClassOps = {
471351
nullptr, /* delProperty */
472352
nullptr, /* enumerate */
473353
WasmGcObject::obj_newEnumerate,
474-
nullptr, /* resolve */
475-
nullptr, /* mayResolve */
476-
WasmArrayObject::obj_finalize, /* finalize */
477-
nullptr, /* call */
478-
nullptr, /* construct */
354+
nullptr, /* resolve */
355+
nullptr, /* mayResolve */
356+
nullptr, /* finalize */
357+
nullptr, /* call */
358+
nullptr, /* construct */
479359
WasmArrayObject::obj_trace,
480360
};
481361
static const ClassExtension WasmArrayObjectClassExt = {
482362
WasmArrayObject::obj_moved, /* objectMovedOp */
483363
};
484364
const JSClass WasmArrayObject::class_ = {
485365
"WasmArrayObject",
486-
JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER |
487-
JSCLASS_BACKGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE,
366+
JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER,
488367
&WasmArrayObjectClassOps,
489368
JS_NULL_CLASS_SPEC,
490369
&WasmArrayObjectClassExt,

0 commit comments

Comments
 (0)