tor-browser

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

commit 990dfa57ae137a925d86886b7fdb0a23301d5621
parent ad3f3d0b8bdfa4e8c3fb7f915081b88f8a12efe2
Author: Ryan Hunt <rhunt@eqrion.net>
Date:   Thu, 18 Dec 2025 16:45:27 +0000

Bug 2002625 - wasm: Store a set of all suspenders, not just suspended. r=yury

This simplifies the logic when switching stacks to no longer
have to juggle a linked list. Suspender's also no longer need
to be linked list elements. It also removes the need to track
the suspender context for a given suspender.

I've been told that a better way to do this is to use a WeakCache,
so I may look into that in the future. For now this works.

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

Diffstat:
Mjs/src/wasm/WasmContext.cpp | 13+++++++------
Mjs/src/wasm/WasmContext.h | 11++++++-----
Mjs/src/wasm/WasmPI.cpp | 79+++++++++++++++++++++++++++++++++++++------------------------------------------
Mjs/src/wasm/WasmPI.h | 7+++----
4 files changed, 53 insertions(+), 57 deletions(-)

diff --git a/js/src/wasm/WasmContext.cpp b/js/src/wasm/WasmContext.cpp @@ -42,9 +42,7 @@ Context::Context() mainStackLimit(JS::NativeStackLimitMin) #ifdef ENABLE_WASM_JSPI , - activeSuspender_(nullptr), - suspendableStacksCount(0), - suspendedStacks_() + activeSuspender_(nullptr) #endif { } @@ -52,7 +50,7 @@ Context::Context() Context::~Context() { #ifdef ENABLE_WASM_JSPI MOZ_ASSERT(activeSuspender_ == nullptr); - MOZ_ASSERT(suspendedStacks_.isEmpty()); + MOZ_ASSERT(suspenders_.empty()); #endif } @@ -88,8 +86,11 @@ void Context::traceRoots(JSTracer* trc) { return; } gc::AssertRootMarkingPhase(trc); - for (const SuspenderObjectData& data : suspendedStacks_) { - TraceSuspendableStack(trc, data); + for (auto iter = suspenders_.iter(); !iter.done(); iter.next()) { + SuspenderObject* object = iter.get(); + if (object->state() == SuspenderState::Suspended) { + TraceSuspendableStack(trc, *object->data()); + } } } diff --git a/js/src/wasm/WasmContext.h b/js/src/wasm/WasmContext.h @@ -20,8 +20,6 @@ #define wasm_context_h #ifdef ENABLE_WASM_JSPI -# include "mozilla/DoublyLinkedList.h" - # include "gc/Barrier.h" #endif // ENABLE_WASM_JSPI @@ -32,6 +30,9 @@ namespace js::wasm { #ifdef ENABLE_WASM_JSPI class SuspenderObject; class SuspenderObjectData; +using SuspenderObjectSet = + HashSet<SuspenderObject*, PointerHasher<SuspenderObject*>, + SystemAllocPolicy>; #endif // ENABLE_WASM_JSPI // wasm::Context lives in JSContext and contains the wasm-related per-context @@ -88,9 +89,9 @@ class Context { // The currently active suspender object. Null if we're executing on the // system stack, otherwise we're on a wasm suspendable stack. HeapPtr<SuspenderObject*> activeSuspender_; - mozilla::Atomic<uint32_t> suspendableStacksCount; - // Using double-linked list to avoid allocation in the JIT code. - mozilla::DoublyLinkedList<SuspenderObjectData> suspendedStacks_; + + // All of the allocated suspender objects. + SuspenderObjectSet suspenders_; #endif }; diff --git a/js/src/wasm/WasmPI.cpp b/js/src/wasm/WasmPI.cpp @@ -195,51 +195,35 @@ void TraceSuspendableStack(JSTracer* trc, const SuspenderObjectData& data) { static_assert(JS_STACK_GROWTH_DIRECTION < 0, "JS-PI implemented only for native stacks that grows towards 0"); -static void DecrementSuspendableStacksCount(JSContext* cx) { - for (;;) { - uint32_t currentCount = cx->wasm().suspendableStacksCount; - MOZ_ASSERT(currentCount > 0); - if (cx->wasm().suspendableStacksCount.compareExchange(currentCount, - currentCount - 1)) { - break; - } - // Failed to decrement suspendableStacksCount, repeat. - } -} - SuspenderObject* SuspenderObject::create(JSContext* cx) { - for (;;) { - uint32_t currentCount = cx->wasm().suspendableStacksCount; - if (currentCount >= SuspendableStacksMaxCount) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_JSPI_SUSPENDER_LIMIT); - return nullptr; - } - if (cx->wasm().suspendableStacksCount.compareExchange(currentCount, - currentCount + 1)) { - break; - } - // Failed to increment suspendableStacksCount, repeat. + if (cx->wasm().suspenders_.count() >= SuspendableStacksMaxCount) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_JSPI_SUSPENDER_LIMIT); + return nullptr; } Rooted<SuspenderObject*> suspender( cx, NewBuiltinClassInstance<SuspenderObject>(cx)); if (!suspender) { - DecrementSuspendableStacksCount(cx); return nullptr; } void* stackMemory = js_malloc(SuspendableStackPlusRedZoneSize); if (!stackMemory) { - DecrementSuspendableStacksCount(cx); + ReportOutOfMemory(cx); + return nullptr; + } + + if (!cx->wasm().suspenders_.putNew(suspender)) { + js_free(stackMemory); ReportOutOfMemory(cx); return nullptr; } SuspenderObjectData* data = js_new<SuspenderObjectData>(stackMemory); if (!data) { + cx->wasm().suspenders_.remove(suspender); js_free(stackMemory); - DecrementSuspendableStacksCount(cx); ReportOutOfMemory(cx); return nullptr; } @@ -270,6 +254,8 @@ const JSClass SuspenderObject::class_ = { "SuspenderObject", JSCLASS_HAS_RESERVED_SLOTS(SlotCount) | JSCLASS_FOREGROUND_FINALIZE, &SuspenderObject::classOps_, + nullptr, + &SuspenderObject::classExt_, }; const JSClassOps SuspenderObject::classOps_ = { @@ -285,6 +271,10 @@ const JSClassOps SuspenderObject::classOps_ = { trace, // trace }; +const ClassExtension SuspenderObject::classExt_ = { + .objectMovedOp = SuspenderObject::moved, +}; + /* static */ void SuspenderObject::finalize(JS::GCContext* gcx, JSObject* obj) { SuspenderObject& suspender = obj->as<SuspenderObject>(); @@ -295,11 +285,10 @@ void SuspenderObject::finalize(JS::GCContext* gcx, JSObject* obj) { if (data->state() == SuspenderState::Moribund) { MOZ_RELEASE_ASSERT(!data->stackMemory()); } else { - // Cleaning stack memory and removing from suspendableStacks_. + // Cleaning stack memory and removing from suspenders_. data->releaseStackMemory(); - if (Context* scx = data->suspendedBy()) { - scx->suspendedStacks_.remove(data); - } + gcx->runtime()->mainContextFromOwnThread()->wasm().suspenders_.remove( + &suspender); } js_free(data); } @@ -320,14 +309,22 @@ void SuspenderObject::trace(JSTracer* trc, JSObject* obj) { TraceSuspendableStack(trc, data); } +/* static */ +size_t SuspenderObject::moved(JSObject* obj, JSObject* old) { + wasm::Context& context = + obj->runtimeFromMainThread()->mainContextFromOwnThread()->wasm(); + context.suspenders_.rekeyIfMoved(&old->as<SuspenderObject>(), + &obj->as<SuspenderObject>()); + return 0; +} + void SuspenderObject::setMoribund(JSContext* cx) { MOZ_ASSERT(state() == SuspenderState::Active); cx->wasm().leaveSuspendableStack(cx); SuspenderObjectData* data = this->data(); data->setState(SuspenderState::Moribund); data->releaseStackMemory(); - DecrementSuspendableStacksCount(cx); - MOZ_ASSERT(!cx->wasm().suspendedStacks_.ElementProbablyInList(data)); + cx->wasm().suspenders_.remove(this); } void SuspenderObject::setActive(JSContext* cx) { @@ -348,7 +345,6 @@ void SuspenderObject::enter(JSContext* cx) { void SuspenderObject::suspend(JSContext* cx) { MOZ_ASSERT(state() == SuspenderState::Active); setSuspended(cx); - cx->wasm().suspendedStacks_.pushFront(data()); data()->setSuspendedBy(&cx->wasm()); if (cx->realm()->isDebuggee()) { @@ -373,7 +369,6 @@ void SuspenderObject::resume(JSContext* cx) { // Use barrier because object is being removed from the suspendable stack // from roots. gc::PreWriteBarrier(this); - cx->wasm().suspendedStacks_.remove(data()); if (cx->realm()->isDebuggee()) { for (FrameIter iter(cx);; ++iter) { @@ -510,7 +505,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "x0", "x3", "x27", CALLER_SAVED_REGS, "cc", "memory") - INLINED_ASM(24, 32, 40, 48); + INLINED_ASM(8, 16, 24, 32); # elif defined(_WIN64) && defined(_M_X64) # define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ @@ -541,7 +536,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "rcx", "rax", "cc", "memory") - INLINED_ASM(24, 32, 40, 48); + INLINED_ASM(8, 16, 24, 32); # elif defined(__x86_64__) # define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ @@ -572,7 +567,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "rdi", "rax", "cc", "memory") - INLINED_ASM(24, 32, 40, 48); + INLINED_ASM(8, 16, 24, 32); # elif defined(__i386__) || defined(_M_IX86) # define CALLER_SAVED_REGS "eax", "ecx", "edx" # define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ @@ -601,7 +596,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : CALLER_SAVED_REGS, "cc", "memory") - INLINED_ASM(12, 16, 20, 24); + INLINED_ASM(4, 8, 12, 16); # elif defined(__arm__) # define INLINED_ASM(MAIN_FP, MAIN_SP, SUSPENDABLE_FP, SUSPENDABLE_SP) \ @@ -633,7 +628,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "r0", "r1", "r2", "r3", "cc", "memory") - INLINED_ASM(12, 16, 20, 24); + INLINED_ASM(4, 8, 12, 16); #elif defined(__loongarch_lp64) # define CALLER_SAVED_REGS \ @@ -670,7 +665,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "$a0", "$a3", CALLER_SAVED_REGS, "cc", "memory") - INLINED_ASM(24, 32, 40, 48); +INLINED_ASM(8, 16, 24, 32); # elif defined(__riscv) && defined(__riscv_xlen) && (__riscv_xlen == 64) # define CALLER_SAVED_REGS \ @@ -710,7 +705,7 @@ bool CallOnMainStack(JSContext* cx, CallOnMainStackFn fn, void* data) { : "=r"(res) \ : "r"(stacks), "r"(fn), "r"(data) \ : "ra", "a0", "a3", CALLER_SAVED_REGS, "cc", "memory") - INLINED_ASM(24, 32, 40, 48); +INLINED_ASM(8, 16, 24, 32); # elif defined(__mips64) # define CALLER_SAVED_REGS \ diff --git a/js/src/wasm/WasmPI.h b/js/src/wasm/WasmPI.h @@ -19,8 +19,6 @@ #ifndef wasm_pi_h #define wasm_pi_h -#include "mozilla/DoublyLinkedList.h" // for DoublyLinkedListElement - #include "js/TypeDecls.h" #include "vm/NativeObject.h" #include "vm/PromiseObject.h" @@ -123,8 +121,7 @@ enum SuspenderState { Suspended, }; -class SuspenderObjectData - : public mozilla::DoublyLinkedListElement<SuspenderObjectData> { +class SuspenderObjectData { void* stackMemory_; // Stored main stack FP register. @@ -287,9 +284,11 @@ class SuspenderObject : public NativeObject { private: static const JSClassOps classOps_; + static const ClassExtension classExt_; static void finalize(JS::GCContext* gcx, JSObject* obj); static void trace(JSTracer* trc, JSObject* obj); + static size_t moved(JSObject* obj, JSObject* old); }; using CallOnMainStackFn = bool (*)(void* data);