tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 1884530df58f24261c97eb68fece5a7bab56e36e
parent 4c2e113134d42e178451726a11c1d789ac937813
Author: Jon Coppeard <jcoppeard@mozilla.com>
Date:   Mon,  6 Oct 2025 09:51:10 +0000

Bug 1978645 - Part 2: Use buffer allocator for wasm array data r=jseward

Differential Revision: https://phabricator.services.mozilla.com/D265784

Diffstat:
Mjs/src/wasm/WasmGcObject-inl.h | 46+++++++++-------------------------------------
Mjs/src/wasm/WasmGcObject.cpp | 191+++++++++++++++----------------------------------------------------------------
2 files changed, 44 insertions(+), 193 deletions(-)

diff --git a/js/src/wasm/WasmGcObject-inl.h b/js/src/wasm/WasmGcObject-inl.h @@ -193,32 +193,23 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL( // arrays use createArrayIL. MOZ_ASSERT(storageBytes > WasmArrayObject_MaxInlineBytes); - // Allocate the outline data before allocating the object so that we can - // infallibly initialize the pointer on the array object after it is - // allocated. - Nursery& nursery = cx->nursery(); - PointerAndUint7 outlineAlloc(nullptr, 0); - outlineAlloc = nursery.mallocedBlockCache().alloc(storageBytes); - if (MOZ_UNLIKELY(!outlineAlloc.pointer())) { + auto* arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>( + allocKind, initialHeap, typeDefData->clasp, allocSite); + if (MOZ_UNLIKELY(!arrayObj)) { ReportOutOfMemory(cx); return nullptr; } - // It's unfortunate that `arrayObj` has to be rooted, since this is a hot - // path and rooting costs around 15 instructions. It is the call to - // registerTrailer that makes it necessary. - Rooted<WasmArrayObject*> arrayObj(cx); - arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>( - allocKind, initialHeap, typeDefData->clasp, allocSite); - if (MOZ_UNLIKELY(!arrayObj)) { + uint8_t* outlineAlloc = AllocateCellBuffer<uint8_t>( + cx, arrayObj, storageBytes, MaxNurseryTrailerSize); + if (MOZ_UNLIKELY(!outlineAlloc)) { + arrayObj->numElements_ = 0; + arrayObj->data_ = nullptr; ReportOutOfMemory(cx); - if (outlineAlloc.pointer()) { - nursery.mallocedBlockCache().free(outlineAlloc); - } return nullptr; } - DataHeader* outlineHeader = (DataHeader*)outlineAlloc.pointer(); + DataHeader* outlineHeader = (DataHeader*)outlineAlloc; uint8_t* outlineData = (uint8_t*)(outlineHeader + 1); *outlineHeader = DataIsOOL; @@ -234,25 +225,6 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL( MOZ_ASSERT(!arrayObj->isDataInline()); - if (MOZ_LIKELY(js::gc::IsInsideNursery(arrayObj))) { - // We need to register the OOL area with the nursery, so it will be freed - // after GCing of the nursery if `arrayObj_` doesn't make it into the - // tenured heap. Note, the nursery will keep a running total of the - // current trailer block sizes, so it can decide to do a (minor) - // collection if that becomes excessive. - if (MOZ_UNLIKELY(!nursery.registerTrailer(outlineAlloc, storageBytes))) { - nursery.mallocedBlockCache().free(outlineAlloc); - ReportOutOfMemory(cx); - return nullptr; - } - } else { - MOZ_ASSERT(arrayObj->isTenured()); - // Register the trailer size with the major GC mechanism, so that can also - // is able to decide if that space use warrants a (major) collection. - AddCellMemory(arrayObj, storageBytes + wasm::TrailerBlockOverhead, - MemoryUse::WasmTrailerBlock); - } - MOZ_ASSERT(typeDefData->clasp->shouldDelayMetadataBuilder()); cx->realm()->setObjectPendingMetadata(arrayObj); diff --git a/js/src/wasm/WasmGcObject.cpp b/js/src/wasm/WasmGcObject.cpp @@ -8,7 +8,7 @@ #include <algorithm> -#include "gc/Marking.h" +#include "gc/Tracer.h" #include "js/CharacterEncoding.h" #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/PropertySpec.h" @@ -36,126 +36,21 @@ using namespace wasm; // [SMDOC] Management of OOL storage areas for Wasm{Array,Struct}Object. // -// WasmArrayObject always has its payload data stored in a block the C++-heap, -// which is pointed to from the WasmArrayObject. The same is true for -// WasmStructObject in the case where the fields cannot fit in the object -// itself. These C++ blocks are in some places referred to as "trailer blocks". +// WasmArrayObject always has its payload data stored in a block which is +// pointed to from the WasmArrayObject. The same is true for WasmStructObject in +// the case where the fields cannot fit in the object itself. These blocks are +// in some places referred to as "trailer blocks". // -// The presence of trailer blocks complicates the use of generational GC (that -// is, Nursery allocation) of Wasm{Array,Struct}Object. In particular: +// These blocks are allocated in the same way as JSObects slots and element +// buffers, either using the GC's buffer allocator or directly in the nursery if +// they are small enough. // -// (1) For objects which do not get tenured at minor collection, there must be -// a way to free the associated trailer, but there is no way to visit -// non-tenured blocks during minor collection. +// They require the use of WasmArrayObject/WasmStructObject::obj_moved hooks to +// update the pointer and mark the allocation when the object gets tenured, and +// also update the pointer in case it is an internal pointer when the object is +// moved. // -// (2) Even if (1) were solved, calling js_malloc/js_free for every object -// creation-death cycle is expensive, possibly around 400 machine -// instructions, and we expressly want to avoid that in a generational GC -// scenario. -// -// The following scheme is therefore employed. -// -// (a) gc::Nursery maintains a pool of available C++-heap-allocated blocks -- -// a js::MallocedBlockCache -- and the intention is that trailers are -// allocated from this pool and freed back into it whenever possible. -// -// (b) WasmArrayObject::createArrayNonEmpty and -// WasmStructObject::createStructOOL always request trailer allocation -// from the nursery's cache (a). If the cache cannot honour the request -// directly it will allocate directly from js_malloc; we hope this happens -// only infrequently. -// -// (c) The allocated block is returned as a js::PointerAndUint7, a pair that -// holds the trailer block pointer and an auxiliary tag that the -// js::MallocedBlockCache needs to see when the block is freed. -// -// The raw trailer block pointer (a `void*`) is stored in the -// Wasm{Array,Struct}Object OOL data field. These objects are not aware -// of and do not interact with js::PointerAndUint7, and nor does any -// JIT-generated code. -// -// (d) Still in WasmArrayObject::createArrayNonEmpty and -// WasmStructObject::createStructOOL, if the object was allocated in the -// nursery, then the resulting js::PointerAndUint7 is "registered" with -// the nursery by handing it to Nursery::registerTrailer. -// -// (e) When a minor collection happens (Nursery::doCollection), we are -// notified of objects that are moved by calls to the ::obj_moved methods -// in this file. For those objects that have been tenured, the raw -// trailer pointer is "unregistered" with the nursery by handing it to -// Nursery::unregisterTrailer. -// -// (f) Still during minor collection: The nursery now knows both the set of -// trailer blocks added, and those removed because the corresponding -// object has been tenured. The difference between these two sets (that -// is, `added - removed`) is the set of trailer blocks corresponding to -// blocks that didn't get tenured. That set is computed and freed (back -// to the nursery's js::MallocedBlockCache) by Nursery::freeTrailerBlocks. -// -// (g) At the end of minor collection, the added and removed sets are made -// empty, and the cycle begins again. -// -// (h) Also at the end of minor collection, a call to -// `mallocedBlockCache_.preen` hands a few blocks in the cache back to -// js_free. This mechanism exists so as to ensure that unused blocks do -// not remain in the cache indefinitely. -// -// (i) In order that the tenured heap is collected "often enough" in the case -// where the trailer blocks are large (relative to their owning objects), -// we have to tell the tenured heap about the sizes of trailers entering -// and leaving it. This is done via calls to AddCellMemory and -// GCContext::removeCellMemory. -// -// (j) For objects that got tenured, we are eventually notified of their death -// by a call to the ::obj_finalize methods below. At that point we hand -// their block pointers to js_free. -// -// (k) When the nursery is eventually destroyed, all blocks in its block cache -// are handed to js_free. Hence, at process exit, provided all nurseries -// are first collected and then their destructors run, no C++ heap blocks -// are leaked. -// -// As a result of this scheme, trailer blocks associated with what we hope is -// the frequent case -- objects that are allocated but never make it out of -// the nursery -- are cycled through the nursery's block cache. -// -// Trailers associated with tenured blocks cannot participate though; they are -// always returned to js_free. Making them participate is difficult: it would -// require changing their owning object's OOL data pointer to be a -// js::PointerAndUint7 rather than a raw `void*`, so that then the blocks -// could be released to the cache in the ::obj_finalize methods. This would -// however require changes in the generated code for array element and OOL -// struct element accesses. -// -// It would also lead to threading difficulties, because the ::obj_finalize -// methods run on a background thread, whilst allocation from the cache -// happens on the main thread, but the MallocedBlockCache is not thread safe. -// Making it thread safe would entail adding a locking mechanism, but that's -// potentially slow and so negates the point of having a cache at all. -// -// Here's a short summary of the trailer block life cycle: -// -// * allocated: -// -// - in WasmArrayObject::createArrayNonEmpty -// and WasmStructObject::createStructOOL -// -// - by calling the nursery's MallocBlockCache alloc method -// -// * deallocated: -// -// - for non-tenured objects, in the collector itself, -// in Nursery::doCollection calling Nursery::freeTrailerBlocks, -// releasing to the nursery's block cache -// -// - for tenured objects, in the ::obj_finalize methods, releasing directly -// to js_free -// -// If this seems confusing ("why is it ok to allocate from the cache but -// release to js_free?"), remember that the cache holds blocks previously -// obtained from js_malloc but which are *not* currently in use. Hence it is -// fine to give them back to js_free; that just makes the cache a bit emptier -// but has no effect on correctness. +// The blocks are freed by the GC when no longer referenced. //========================================================================= // WasmGcObject @@ -371,6 +266,15 @@ void WasmArrayObject::obj_trace(JSTracer* trc, JSObject* object) { WasmArrayObject& arrayObj = object->as<WasmArrayObject>(); uint8_t* data = arrayObj.data_; + if (!arrayObj.isDataInline()) { + uint8_t* outlineAlloc = (uint8_t*)dataHeaderFromDataPointer(arrayObj.data_); + uint8_t* prior = outlineAlloc; + TraceBufferEdge(trc, &arrayObj, &outlineAlloc, "WasmArrayObject storage"); + if (outlineAlloc != prior) { + arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1); + } + } + const auto& typeDef = arrayObj.typeDef(); const auto& arrayType = typeDef.arrayType(); if (!arrayType.elementType().isRefRepr()) { @@ -386,34 +290,6 @@ void WasmArrayObject::obj_trace(JSTracer* trc, JSObject* object) { } /* static */ -void WasmArrayObject::obj_finalize(JS::GCContext* gcx, JSObject* object) { - // This method, and also ::obj_moved and the WasmStructObject equivalents, - // assumes that the object's TypeDef (as reachable via its SuperTypeVector*) - // stays alive at least as long as the object. - WasmArrayObject& arrayObj = object->as<WasmArrayObject>(); - if (!arrayObj.isDataInline()) { - // Free the trailer block. Unfortunately we can't give it back to the - // malloc'd block cache because we might not be running on the main - // thread, and the cache isn't thread-safe. - js_free(arrayObj.dataHeader()); - // And tell the tenured-heap accounting machinery that the trailer has - // been freed. - const TypeDef& typeDef = arrayObj.typeDef(); - MOZ_ASSERT(typeDef.isArrayType()); - // arrayObj.numElements_ was validated to not overflow when constructing the - // array - size_t trailerSize = calcStorageBytesUnchecked( - typeDef.arrayType().elementType().size(), arrayObj.numElements_); - // Ensured by WasmArrayObject::createArrayNonEmpty. - MOZ_RELEASE_ASSERT(trailerSize <= size_t(MaxArrayPayloadBytes)); - gcx->removeCellMemory(&arrayObj, trailerSize + TrailerBlockOverhead, - MemoryUse::WasmTrailerBlock); - // For safety - arrayObj.data_ = nullptr; - } -} - -/* static */ size_t WasmArrayObject::obj_moved(JSObject* obj, JSObject* old) { // Moving inline arrays requires us to update the data pointer. WasmArrayObject& arrayObj = obj->as<WasmArrayObject>(); @@ -437,9 +313,13 @@ size_t WasmArrayObject::obj_moved(JSObject* obj, JSObject* old) { typeDef.arrayType().elementType().size(), arrayObj.numElements_); // Ensured by WasmArrayObject::createArrayOOL. MOZ_RELEASE_ASSERT(trailerSize <= size_t(MaxArrayPayloadBytes)); - nursery.trackTrailerOnPromotion(arrayObj.dataHeader(), obj, trailerSize, - TrailerBlockOverhead, - MemoryUse::WasmTrailerBlock); + uint8_t* outlineAlloc = + (uint8_t*)dataHeaderFromDataPointer(arrayObj.data_); + uint8_t* prior = outlineAlloc; + nursery.maybeMoveBufferOnPromotion(&outlineAlloc, obj, trailerSize); + if (outlineAlloc != prior) { + arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1); + } } } @@ -471,11 +351,11 @@ static const JSClassOps WasmArrayObjectClassOps = { nullptr, /* delProperty */ nullptr, /* enumerate */ WasmGcObject::obj_newEnumerate, - nullptr, /* resolve */ - nullptr, /* mayResolve */ - WasmArrayObject::obj_finalize, /* finalize */ - nullptr, /* call */ - nullptr, /* construct */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* construct */ WasmArrayObject::obj_trace, }; static const ClassExtension WasmArrayObjectClassExt = { @@ -483,8 +363,7 @@ static const ClassExtension WasmArrayObjectClassExt = { }; const JSClass WasmArrayObject::class_ = { "WasmArrayObject", - JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER | - JSCLASS_BACKGROUND_FINALIZE | JSCLASS_SKIP_NURSERY_FINALIZE, + JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER, &WasmArrayObjectClassOps, JS_NULL_CLASS_SPEC, &WasmArrayObjectClassExt,