Skip to content

wasm2c: Save memory base locally as a perf optimization - #2804

Open
shravanrn wants to merge 1 commit into
WebAssembly:mainfrom
UT-Security:local-base
Open

wasm2c: Save memory base locally as a perf optimization#2804
shravanrn wants to merge 1 commit into
WebAssembly:mainfrom
UT-Security:local-base

Conversation

@shravanrn

@shravanrn shravanrn commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

For Wasm modules with a single unshared wasm-32 memory, and when using the MMap based memory allocation (which guarantees that the base won't move over the Wasm instance lifetime), this change caches the memory base in a local variable and uses that instead of fetching the value each time from the instance pointer.

While this seems like it really shouldn't do much, it unlocks a bunch of optimizations in C compilers, as they don't seem to be able to reason that the base pointer hasn't changed after most function calls with the Wasm code. The end result is some dramatic improvements.

As a reference, this approach is able to claw back a lot of the same performance overheads of segue (i.e., using the segment register) in wasm2c except without segment registers, meaning this will work on all platforms. This makes some intuitive sense, as I expect that segue is more important to classic SFI tools (like LFI) rather than a Wasm like system. (But there are other benchmarks that segue still is a huge win --- on average, segue still remains the best option if supported on the target platform)

Anyway, here are the performance numbers from two use cases in Firefox: Expat XML parsing, Graphite font rendering that use Wasm sandboxes. The number is the overhead over native code (so bigger is worse)

Note the "local base" row is the new row below

Expat XML parsing overhead

Baseline: native

Wasm2c (Mmap + guard pages, no segue): +37.6%
Wasm2c (Mmap + guard pages, segue): +13.7%
Wasm2c (Mmap + guard pages, local base): +18.7%

Wasm2c (Mmap + bounds checks, no segue): +50.7%
Wasm2c (Mmap + bounds checks, segue): +37.4%
Wasm2c (Mmap + bounds checks, local base): +32.8%

Graphite font overhead

Baseline: native

Wasm2c (Mmap + guard pages, no segue): +41.2%
Wasm2c (Mmap + guard pages, segue): +18.2%
Wasm2c (Mmap + guard pages, local base): +14.1%

Wasm2c (Mmap + bounds checks, no segue): +60.0%
Wasm2c (Mmap + bounds checks, segue): +43.3%
Wasm2c (Mmap + bounds checks, local base): +41.8%

I intend to enable this optimization in Firefox's builds asap once it is landed here.

@sbc100

sbc100 commented Aug 3, 2026

Copy link
Copy Markdown
Member

Can you update the PR description now that part 1 has landed?

@shravanrn

shravanrn commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Did some cleanup to simplify the code and reduce the code-diff and updated the PR description.

@sbc100

sbc100 commented Aug 3, 2026

Copy link
Copy Markdown
Member

In your benchmark results I guess +37.6% means 37% overhead compared to native? i.e. bigger is worse?

#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY
// We can only use Segue for this module if it uses a single unshared,
// default-page, 32-bit imported or exported memory.
#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_DEFAULT32_MEMORY

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about splitting out this change?

@shravanrn shravanrn Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this potentially belongs in this PR.

Reason: Segue doesn't need this change. This change is needed because this current PR (aka local-base optimization) has the same constrains as segue + some additional constraints. Rather than segue and local-base doing 2 different sets of constraints checks, I've changed things so segue and the local-base optimization use a superset of constraints. So I see this as part of the local-base optimization change.

Details
The local base optimization works only when the memory is not movable -- i.e., linear memory is allocated with mmap. However, wasm2c does not use mmap for memory types whose page size is a custom. So the full set of constraints are

  • Single memory
  • Unshared
  • Default32

Segue actually can support custom page size memories too, as it supports movable memories as long as the memory growth happens within the module and thus it only requires

  • Single memory
  • Unshared

But writing code to separately check segue's constraints and linear base's constraints adds more complexity than needed for a use case that presumably get's very little use. Hence this approach which just has segue and local-base check for Single memory, Unshared, Default32

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.

2 participants