Skip to content

Sculk perf - #227

Closed
casperwtf wants to merge 7 commits into
bytecodealliance:mainfrom
casperwtf:sculk-perf
Closed

casperwtf wants to merge 7 commits into
bytecodealliance:mainfrom
casperwtf:sculk-perf

Conversation

@casperwtf

Copy link
Copy Markdown

No description provided.

John Universe added 7 commits October 4, 2026 22:50
Every direct call ran Shaded.checkInterruption, a static call that reads the
current thread's interrupt flag. Backward branches, call_indirect dispatch
and calls to imported functions keep their checks, and a direct call keeps
its check when the callee sits on a cycle of the direct call graph (found
with Tarjan's algorithm), so recursion such as power.c, which runs 2^n calls
at depth n, still stops when the thread is interrupted. Calls into acyclic
helpers, most of a compiled AssemblyScript or Rust module, no longer pay
for the check.
Instance.global(int) asked ImportValues for its global count twice and then
indexed either the import list or the module's own globals. Compiled code
calls it through Shaded.readGlobal and Shaded.writeGlobal on every
global.get and global.set, and AssemblyScript and Rust modules touch their
stack and heap pointers in most functions. Keep one array covering the
whole global index space, imports first, filled as globals are created.
…dules

ByteArrayMemory keeps memory in 64 KiB pages so a shared memory can grow
while other threads read it. Every load and store pays for that: it loads
the page from the page table, masks the offset, and checks whether the
access straddles two pages before the VarHandle access and its own bounds
check. A memory that is not shared has no concurrent readers, so it can live
in one byte array and grow by copying, leaving one bounds-checked access per
load or store.

FlatByteArrayMemory rejects shared limits and caps growth at
RUNTIME_MAX_PAGES, the largest size one Java array can hold. With it as the
default for every unshared memory, the runtime, compiler, spec and WASI
suites pass (96,560 tests). It is opt-in through Instance.Builder
withMemoryFactory(FlatByteArrayMemory::new).
A WasmModule keeps every function's parsed instructions for as long as the
module lives, and an Instance and its compiled MachineFactory keep the
module. Compiled machines never read those instructions; only the
interpreter does. For a host running many large modules this is the
largest single cost on the heap: 72 AssemblyScript modules kept 8.5
million AnnotatedInstruction objects and their operand arrays, about
740 MiB.

MachineFactoryCompiler.Builder#withReleasedFunctionBodies hands instances
a copy of the module (WasmModule#withCodeSection) whose compiled functions
keep only their local types. Functions left to the interpreter keep their
instructions. Modules loaded from a cache keep theirs, since the cache
does not record which functions were interpreted.
@casperwtf
casperwtf requested a review from andreaTP as a code owner October 5, 2026 07:04
@casperwtf casperwtf closed this Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant