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 */
417293size_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};
481361static const ClassExtension WasmArrayObjectClassExt = {
482362 WasmArrayObject::obj_moved, /* objectMovedOp */
483363};
484364const 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