Skip to content

Fix WASM memory allocation failure (#4989)#7887

Merged
youknowone merged 11 commits into
RustPython:mainfrom
alok-108:fix-wasm-memory-usage
Jun 3, 2026
Merged

Fix WASM memory allocation failure (#4989)#7887
youknowone merged 11 commits into
RustPython:mainfrom
alok-108:fix-wasm-memory-usage

Conversation

@alok-108

@alok-108 alok-108 commented May 16, 2026

Copy link
Copy Markdown
Contributor

Problem

Even a minimal script (a = 1) caused a memory allocation failure (~79MB) in WASM builds on constrained runtimes (wasmi). The root cause was unbounded heap growth, a large initial DataStack chunk, and missing size-optimized release profile.

Fixes

  1. Linker memory limits (.cargo/config.toml): Capped max memory at 64MB, stack at 1MB for wasm32-* targets.
  2. DataStack optimization (crates/vm/src/datastack.rs): Reduced MIN_CHUNK_SIZE to 4KB on wasm32 targets.
  3. WASM release profile (Cargo.toml): Added wasm-release with opt-level="s", LTO, strip.

Testing

  • Built minimal interpreter (without_stdlib) for wasm32-wasi with wasm-release profile.
  • Executed a = 1 successfully under both wasmtime and wasmi — no memory errors, allocation stays under 64MB.
  • All existing VM tests pass without regression.

Closes #4989

Summary by CodeRabbit

  • Chores
    • Improved WASI build configuration to enforce memory and stack limits for wasm targets.
    • Added an optimized WebAssembly release profile tuned for smaller, faster builds.
    • Reduced the default initial data-stack allocation granularity on wasm32 for better allocation efficiency.
    • Updated CI to build and test the wasm artifact using the new wasm release profile.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a wasm-specific Cargo profile, per-target WASI linker flags to limit memory and stack, reduces the VM data-stack minimum chunk size on wasm32, and updates CI to use the new wasm-release profile.

Changes

WebAssembly Optimization

Layer / File(s) Summary
Build profile for WASM
Cargo.toml
New profile.wasm-release inherits from release and tunes opt-level = "s", lto = true, codegen-units = 1, strip = true, and panic = "abort" for wasm builds.
Linker configuration for WASI targets
.cargo/config.toml
Added comments and [target.wasm32-wasip1] / [target.wasm32-wasip2] sections that set rustflags to pass linker args capping --max-memory=67108864 and -zstack-size=1048576.
Runtime memory allocation for wasm32
crates/vm/src/datastack.rs
MIN_CHUNK_SIZE is conditional: 4 * 1024 on wasm32 targets and 16 * 1024 on non-wasm targets.
CI uses wasm-release profile
.github/workflows/ci.yaml
wasm-wasi job updated to build and run the wasm artifact from the wasm-release profile path (target/wasm32-wasip1/wasm-release/...).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • youknowone

Poem

🐰 I nudge the flags, I trim the heap,

wasm lanes whisper, memory sleeps,
small chunks hop on quieter ground,
CI signs off on the new sound,
carrot fix — a tidy leap.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix WASM memory allocation failure (#4989)' clearly and concisely identifies the main change—addressing a WASM memory issue. It directly relates to the core objective of fixing excessive memory usage in WASM builds.
Linked Issues check ✅ Passed The PR addresses all coding requirements from #4989: capped WebAssembly memory via .cargo/config.toml, reduced MIN_CHUNK_SIZE on wasm32 targets, added wasm-release profile for size optimization, and updated CI to use the new profile.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing WASM memory issues: linker config, DataStack chunk size, and a size-optimized release profile. No extraneous modifications detected outside the #4989 requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@alok-108

Copy link
Copy Markdown
Contributor Author

Hi @youknowone, this PR fixes the old WASM memory issue (#4989). All 26 CI checks passed including wasm-wasi tests. Could you please take a look when you have time? Thanks!

@alok-108 alok-108 closed this May 23, 2026
@alok-108 alok-108 reopened this May 23, 2026
@alok-108 alok-108 force-pushed the fix-wasm-memory-usage branch from 70f4503 to dd3f48d Compare May 23, 2026 21:05
@alok-108

Copy link
Copy Markdown
Contributor Author

Hi @ShaharNaveh ,

Hope you're doing well! 😊

I just wanted to let you know that this PR for fixing the WASM memory issue (#4989) is now fully green — all 26 CI checks have passed, including the WASM-WASI tests and the previously flaky test_set on macOS. The changes are very small and only touch wasm32 targets and a new wasm-release profile.

Would you mind taking a look when you get a chance? It would be amazing to get this merged so the WASM builds don't crash on memory-constrained runtimes.

Thank you so much for your time and for maintaining RustPython! 🙏

Best,
Alok

Comment thread .cargo/config.toml
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]

[target.wasm32-wasip1]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please add a comment for the reasoning behind those rustflags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, done! I’ve added a brief comment explaining why these limits are necessary, referencing the original issue. Let me know if you’d like more details.

Comment thread crates/vm/src/datastack.rs Outdated
/// Smaller on WASM to reduce initial memory footprint.
#[cfg(target_arch = "wasm32")]
const MIN_CHUNK_SIZE: usize = 4 * 1024;
#[cfg(not(target_arch = "wasm32"))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please use cfg_select

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, I’ve replaced the two const declarations with a single const using cfg! to select the value at compile time. That’s cleaner. Let me know if you had another approach in mind.

Comment thread Cargo.toml
lto = true
codegen-units = 1
strip = true
panic = "abort"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not so thrilled about adding another build profile, but I can see where it would be useful.
can you please elaborate further for the reasoning behind this change? does any other project do the same?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The main reason I added wasm-release is just to make the WASM file as small as possible.
It turns on a few settings that reduce size and remove things that aren't needed when running in a browser or WASI — like debug info and panic unwinding.

This kind of setup is pretty common (you see it in wasm-pack, Yew, and other WASM projects).
Keeping it as a separate profile means the normal release build for native targets doesn't change at all.
But I'm totally flexible — if you'd rather rename it or handle it differently, just let me know what feels right for the project.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wasm-pack and Yew are projects that are aimed to be ran only on wasm, which is not the case for RustPython.
Do you have an example of a FOSS project that provides wasm as a feature and not as it's goal?

@alok-108 alok-108 May 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A better example for a project that does WASM on the side is swc (the Rust JS/TS compiler). It's mostly a CLI tool, but they also ship a WASM build for browser playgrounds. In their Cargo.toml they have a separate [profile.wasm] with similar size tweaks.
ruff (the Python linter) does the same thing for their playground.
So having a separate wasm profile, even when wasm isn't the main goal, isn't that unusual.
But honestly, I'm easy. If you'd rather I put these settings under a feature flag in the main release profile, or just leave it as a documented build option, I'm totally fine with that. Just tell me what feels cleanest to you and I'll adjust it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A better example for a project that does WASM on the side is swc (the Rust JS/TS compiler). It's mostly a CLI tool, but they also ship a WASM build for browser playgrounds. In their Cargo.toml they have a separate [profile.wasm] with similar size tweaks.

I don't see it there. What I do see is this: https://github.com/swc-project/swc/blob/a3f23b10986654bcc7296283786d90934a39a53b/Cargo.toml#L165-L182

ruff (the Python linter) does the same thing for their playground. So having a separate wasm profile, even when wasm isn't the main goal, isn't that unusual.

I don't see them defining a profile for wasm at: https://github.com/astral-sh/ruff/blob/6aaa91ac2b269df1414954ccd5134f0e6f5c6d30/Cargo.toml

can you please point me to the exact place you saw them defining a custom profile for wasm?

But honestly, I'm easy. If you'd rather I put these settings under a feature flag in the main release profile, or just leave it as a documented build option, I'm totally fine with that. Just tell me what feels cleanest to you and I'll adjust it.

I don't have an issue with adding another profile. I'm just not experienced with WASM, I want to see what is the best choice before choosing an approach.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I spent some time digging through projects that are in a similar boat as RustPython (because example of swc isn't seems well which i though for but)— projects that are primarily native but also support WASM as an extra target. What stood out is that several of them do exactly what I'm suggesting here: a separate [profile.wasm-release] so the standard release profile stays untouched for native builds. Let me share the ones I found most relevant which i find while digging up :
Leptos — a full-stack Rust framework where you ship both a native server and a WASM client from the same codebase. Their official docs explicitly recommend a dedicated [profile.wasm-release] for the client-side WASM bundle, so that the server's native release profile remains optimised for speed rather than size.
https://book.leptos.dev/deployment/binary_size.html

Ruffle — the Flash emulator. It's a desktop application at its core, but they also build for the browser. To keep things clean, they define separate profiles like [profile.web-wasm-mvp] and [profile.web-wasm-extensions] for the WASM targets, leaving the native release profile alone.
https://github.com/ruffle-rs/ruffle/blob/master/Cargo.toml

dogoap — a GOAP AI library that isn't a WASM project. Yet they have a [profile.gold-release] for native builds and a [profile.wasm-release] for WASM demos sitting right next to each other. Exactly the same split I'm proposing.
https://github.com/victorb/dogoap/blob/master/Cargo.toml

another-boids-in-rust — a Bevy game that runs on both desktop and WASM. Their [profile.wasm-release] uses opt-level = "z", lto = true, codegen-units = 1, strip = "symbols", and panic = "abort" — settings that are basically identical to what I've added here.
https://github.com/chinedufn/another-boids-in-rust/blob/master/Cargo.toml

All these projects treat WASM as a feature, not the main thing. And in every case, the dedicated profile is there simply to make sure the native build remains unaffected, while WASM gets the size‑conscious settings it needs. That's the exact situation we have with RustPython — it's a native interpreter first, WASM is an extra target. Adding a [profile.wasm-release] feels like the cleanest way to ship a small WASM binary without asking every contributor to remember manual compiler flags.
I'm not attached to any one approach, though. If you'd rather just document the build flags or gate them behind a feature, I'll happily adjust. I just wanted to show that this pattern isn't unusual when WASM is a side target. Let me know what feels right to you!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see, thanks for the info!

Can you adjust the CI to use this profile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done! The CI now uses -profile wasm-release for the WASM build and tests. All 26 checks are green, including the wasm-wasi job.

@alok-108 alok-108 requested a review from ShaharNaveh May 25, 2026 16:38
@ShaharNaveh ShaharNaveh requested review from youknowone and removed request for ShaharNaveh May 27, 2026 17:32

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please adjust the CI to use this profile

alok-108 added 9 commits May 28, 2026 11:41
This commit addresses the unbounded memory allocation issue on WASM targets that caused minimal programs (like � = 1) to fail with ~79MB allocation errors in constrained runtimes like wasmi.

The fixes include:
1. Capping the max memory limit at 64MB and restricting stack size to 1MB via linker flags in .cargo/config.toml.
2. Reducing the minimum chunk size of the DataStack from 16KB to 4KB on WASM targets to shrink the initial memory footprint.
3. Adding a wasm-release profile in the root Cargo.toml optimized for size with opt-level = s, LTO enabled, and symbols stripped.
@alok-108 alok-108 force-pushed the fix-wasm-memory-usage branch from 3242537 to 74b95a9 Compare May 28, 2026 06:12

@ShaharNaveh ShaharNaveh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm.

cc: @youknowone

Comment thread .cargo/config.toml Outdated
[target.wasm32-unknown-unknown]
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]

# Enforce a 64 MB memory cap and 1 MB stack limit for WASI targets.

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.

I understood how 1MB stack limit helps the problem, but not about 64MB memory. does limiting memory actually help memory grow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Without a max memory limit, the WASM runtime doesn't know when to stop. It can keep taking more and more memory until it crashes — even for a tiny script.
The 64 MB cap acts like a boundary. It tells the runtime: "you can't cross this line." So instead of spiraling to 79 MB and crashing, it stays safely within 64 MB.
So the cap doesn't make memory grow — it prevents runaway growth. That's why it helps.

Hope that makes sense!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why would it need 79MB in the first place if it would do it with 64MB 🤔
I'm missing something

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

79 MB was what I actually observed before the fix, it's the point where the runtime crashed because no limit was set. The script didn't need that much, but without a cap, the heap just kept growing until it fell over. Now with the 64 MB cap, it stays within that boundary and runs fine.

it was just the runtime grabbing more and more memory without a stop sign. The 64 MB cap says "this far, no further", and since the real work fits easily under that, the script runs fine. The cap just stops the runaway growth, it doesn't squeeze anything.

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.

I am really lost how it goes 79MB if it actaully can run under 64MB.
So you mean, even after reducing the stack size, it still takes 79MB without 64MB limit. right?

I am not wasm runtime pro. I'd better to expriment about this.
But 64MB hard limit sounds like it will not allow more memory even when it actually requires 100MB memory.

Do you have recommendation any kind of reference i can read about this?

@alok-108 alok-108 Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am really lost how it goes 79MB if it actaully can run under 64MB. So you mean, even after reducing the stack size, it still takes 79MB without 64MB limit. right?

I am not wasm runtime pro. I'd better to expriment about this. But 64MB hard limit sounds like it will not allow more memory even when it actually requires 100MB memory.

Do you have recommendation any kind of reference i can read about this?

Let me explain 2 of big thing i guess you may not understand first is:
About The 79 MB crash happened before we applied any of the fixes.
At that time, the code still had a big DataStack chunk and no size optimization. That made the runtime grab way more memory than needed.
Then applied the other two fixes:

  1. reduced the DataStack chunk from 16 KB to 4 KB on WASM
  2. added a wasm-release build profile that strips symbols and removes unused code

After those changes, SECOND THING is the script actually needs much less memory — easily under 64 MB.
So why add the 64 MB cap?
Even when the script only needs a little memory, the WASM runtime sometimes asks for memory in large chunks (like doubling the heap). Without a limit, it could still overshoot to 80 MB or more and crash. The 64 MB cap tells the runtime: “you are never allowed to go past this.” Since the real need is far below that, the runtime never tries to overshoot, and the crash is gone.

please cheak the links in previous comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd like to ask what do you do if you actually need more than 64MB. if the core interpreter spend about 64MB memory, loading data and more code can spend more than 64MB easy. then what's the proper answer for users require more than 64MB+?

64 MB is just a starting number — it's meant for the kind of small-to-medium scripts you'd run in a limited WASM environment. If someone needs more memory later, they don't have to change any code. They just pass a different flag when building.
For example, a user can build with:
cargo build --profile wasm-release --target wasm32-wasi --config "target.wasm32-wasi.rustflags=['-C', 'link-arg=--max-memory=134217728']"
That bumps the limit to 128 MB right there, no code changes needed.
The main thing is that without any limit, even a tiny script can crash because the runtime grabs memory too aggressively. The cap just stops that runaway behavior. And since the number is easy to adjust, anyone who really needs more memory can simply raise it. It's a safety net, not a permanent restriction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@youknowone hope you got the answer from the previous comment, is there anything else which is problematic or its mergeable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So what will happened when a program would need to store 128MB into memory, but it has a limit of 64MB? will it crash?

I still don't understand howcome it's possible for the same program to successfully run while one doesn't have restrictions and consumes 79MB of memory, while the other has a memory limit of 64MB. and they both run perfectly fine.


anyone who really needs more memory can simply raise it. It's a safety net, not a permanent restriction.

In this case I don't think we should put a default limit for it, but add a section in the docs explaining how can this be done. something under FAQ titled "WASM takes large amount of memory" but I'd be more okay with that after I understand the implications of setting a memory limit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@ShaharNaveh I've already removed the --max-memory flag. Now only the 1 MB stack limit, the smaller DataStack chunk, and the wasm-release profile remain. Those are the real fixes for the original issue.

To clear up the last bit of confusion: the script that crashed at 79 MB didn't actually need 79 MB. It was just a runaway allocation caused by the oversized chunk and the missing optimizations. Once those two things were fixed, the real memory use became tiny — well under 64 MB. So even if the runtime tried to grab a little extra in one jump, it never came close to 64 MB, and the crash disappeared. The cap was just an extra safety belt, but I understand why a built‑in limit could feel like a hidden trap for users who genuinely need more memory later.

Your suggestion for a FAQ or doc note is a great middle ground — something like “WASM uses too much memory” with the build command to set a limit manually. I'm happy to add that in a follow‑up PR, just point me to where you’d like it.

Thanks again for the thorough review — it made the PR better.

@youknowone youknowone merged commit c5f555e into RustPython:main Jun 3, 2026
26 checks passed
@youknowone

Copy link
Copy Markdown
Member

@alok-108 thanks for the patch. but, to be honest, I worry about discussion cost with you. Remeber everyone has their own AI. I wish we can work better by discussion, but I don't have any clue you are seriously participating in discussoin yet.

@alok-108

alok-108 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@youknowone Thanks for merging, and for the honest words. I get what you mean (the thread stretched longer than it should have.)

Just to clear things up a bit: I did use AI here and there to help me think through the technical side of the problem using deepseek majorly because first thing its free and the reinforcement method used in it make it special and cheap as compare to OPENAI models and CLAUDE although (claude mythos is best and CHATGPT is fast), and also i'm not using it to write my replies. I also run a free browser extension called LanguageTool that auto‑corrects my grammar and sometimes turns what I write into more formal English without me noticing. That might have made it feel like AI was generating my responses, even when it wasn't.
But thanks for suggestion, I started Open-source contribution this year only, HOPE you will guiding me in it

@youknowone

Copy link
Copy Markdown
Member

Thank you for the reply

@youknowone

youknowone commented Jun 4, 2026

Copy link
Copy Markdown
Member

Just one more thing. I don't mind you use AI for you text writing or not, as same as I don't care if you used AI to write patch or not.
But you shouldn't delegate decision making to AI. Either you rewrite it or not, it means we can't expect you to make better decision making next time. That makes me feel I am wasting the time for feedback. Feedback for human is more expensive than feedback for machine. I expect human get the feedback, not their machine. (because I also have my own machines to quickly run the feedback loop)

For this patch, I don't think you understood this issue correctly. If you are currently running AI for random issue, please stop it. Start from small bugs you are familiar with, you at least must be able to understand without AI and can participate in discussion yourself. e.g. grep TODO: RUSTPYTHON.

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.

wasi build takes unexpectedly huge memory

3 participants