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:
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);