Skip to content

[wasm][r2r] Jit generates code with very large numbers of wasm locals #135139

Open

Description

@davidwrighton

The jit for WebAssembly generates methods with tremendous numbers of wasm locals. For instance, the System.TimeZoneInfo.CacheTransitionsForYear function has 848 declared locals, and wasm-opt is able to take that down to 9 locals. While we probably don't need to capture all of those wins, its important to capture some of them, since 848 locals causes all sorts of problems.

There are 2 risks.

  1. If functions exceed 50,000 locals+parameters combined, most WebAssembly runtimes will refuse to compile.
  2. If functions have a large number of locals then functions will consume excessive resources on compilation backends which do not perform optimization.

Notably, initial tier compilation generally doesn't optimize locals at all, so it requires all of the locals to actually exist and be compiled and dealt with, so that's 4,676 bytes of stack frame that the WebAssembly frame actually uses, and the wasm-opt optimized variant only requires 60 bytes. Since we are very concerned about initial process startup behavior, I'd like us to focus on optimizations which should pay off nicely for this initial tier. Additionally, V8 has implemented speculative devirtualization for indirect calls. This implementation is based on a deoptimization strategy, and so requires the ability to reconstruct the state of any non-dead locals at speculative call sites. Looking at our typical implementation strategy, it appears that many locals are dead at nearly all points in the codebase, but de-opt may still be negatively impacted by the presence of a great many locals.

Cranelift, which is relevant for Wasmtime and other standalone/wasi runtimes simply has the 50,000 local limit, and appears to optimize without any issues with large numbers of locals (when most of them are dead.)

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Oct 2, 2026
  2. dotnet-policy-service commented on Oct 2, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Oct 2, 2026
  4. added this to the 12.0.0 milestone on Oct 2, 2026
  5. dotnet-policy-service commented on Oct 2, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  6. JulieLeeMSFT commented on Oct 2, 2026

    @JulieLeeMSFT
    Member
  7. adamperlin commented on Oct 16, 2026

    @adamperlin
    Contributor

    Following up on this one as I realize I never responded to this issue. Right now, our mapping of register candidate jit local <-> wasm local is essentially 1:1 for an entire method. The bottom line is, if two JIT locals that are candidates have non-overlapping lifetimes then they can be assigned the same wasm local as a home. I would guess that we could implement some kind of scheme that looks more like traditional register allocation with LSRA, where we construct live intervals and do a scan over them. Conflicting intervals can be assigned their own locals, but we should only really need to allocate an interval to a new wasm local if there isn't a free one available. This is a big change to how the current RA system works though, so this may take a bit longer to implement.

  8. AndyAyersMS commented on Nov 2, 2026

    @AndyAyersMS
    Member

    Yeah I think this plus similar issues like #136662 are pointing us at implementing a better RA for Wasm.

    We might first look into enabling linear scan before considering alternatives.

  9. adamperlin commented on Nov 6, 2026

    @adamperlin
    Contributor

    I've been reading more about speculative inlining in V8 here: https://v8.dev/blog/wasm-speculative-optimizations; It looks like having the deoptimization approach allows for more surrounding optimizations when speculative inlining happens. Interestingly, it looks like V8 has a limit of 500 locals for doing deopt: https://github.com/v8/v8/blob/41b0e42f7f004cdf8a17d10bc085792efbf9cfd4/src/wasm/turboshaft-graph-interface.cc#L7163. I think V8 can still do speculative inlining without deopt, but this is interesting to know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

arch-wasmWebAssembly architecturearea-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions