Hi everyone!
I'm currently working on running Wasmtime using the new Pulley bytecode interpreter on a Raspberry Pi Pico 2.
I've successfully built a no_std host that runs a component precompiled to pulley32. The guest uses a custom SPI WIT to read a BME280 sensor and display data on an OLED screen. (https://github.com/idlab-discover/masters-wasi-spi)
While I've got a single component running successfully, I'm hitting a hard wall regarding linear memory management and the strict constraints of an MMU-less device. After digging into the memory behavior these are the memory roadblocks i came across:
64KiB Page Size: Standard 64KiB pages are very big for the Pico 2's 512KiB RAM. I tried the 1-byte custom page proposal using -C link-arg=--page-size=1, but it seemed the guest totally ignored this.
Forced memory.grow: Compiling to wasm32-unknown-unknown makes the default allocator treat all initial memory as the Wasm image. It immediately calls memory.grow to get heap space, forcing a minimum of 2 pages (128KiB) for any component handling dynamic data. (source: https://github.com/wasm-bindgen/wasm-bindgen/issues/1389#issuecomment-476224477)
No-MMU Reallocation Overhead: without virtual memory, linear memory must be physically contiguous. Growing from 1 to 2 pages requires the host to allocate 2 new pages before copying the original, temporarily spiking the memory requirement to 3 pages.
wac Multiplier: When gluing components with wac, they retain separate linear memories. This means the overheads mentioned above multiply for every internal sub-component.
This is my first project with wasmtime and embedded systems so I'd appreciate any insights, workarounds, or pointers to Wasmtime configuration flags I might have missed! Thanks! :D
this is a GREAT project, @Lukas Barragan Torres, and we're thrilled you're chewing on it. I'll leave these questions to the engineers here, but I note about the "wac multiplier": yes, that's a feature -- unless you do not want it.
for the seriously embedded use case, you'd only want a new sandbox to encapsulate code you do not want the entire component to access freely. If you're going to make the assumption that the component boundary -- that is, the parent component that wasmtime started -- is the boundary, then you would have the ability to compile all code together.
if there's a situation in which you MUST use different languages, for example, then the sandbox between those two things are going to be there, regardless whether they're components or modules.
so that one's a choice to make. If you trust your parent sandbox boundary as THE boundary, then just compile all the code together. The only issue is if for some reason you cannot.
I would imagine that typically in a pico sized scenario, you'd control the components executing, right? If you're not, then you'd need the security boundary in any case.....
Ralph said:
if there's a situation in which you MUST use different languages, for example, then the sandbox between those two things are going to be there, regardless whether they're components or modules.
I have developed a way to mix languages inside the same sandbox using WIT between the boundaries. See https://github.com/cpetig/wit-bindgen and https://wasmcon24.sched.com/event/1qvIG/component-model-in-software-defined-vehicles-christof-petig-aptiv , I should really make it an official variant (mostly documentation is missing) and support more languages, but it works well and can be exchanged for the canonical ABI without changing the source code.
there you go: a solution that casts all code into one sandbox -- which implies all code is trusted within that sandbox. @Christof Petig you really should make that official.
Thanks Ralph and Christof!
@Ralph I'm compiling everything together right now, which works okay for my current setup. However, it still feels a bit suboptimal compared to the broader Wasm philosophy, where you should be able to just plug and play with pre-compiled driver Wasm blobs using WIT.
@Christof Petig Just to make sure I understand: does your fork essentially generate standard, statically linkable bindings from the WIT file instead of the usual Component Model Canonical ABI bindings? Meaning it lets us use WIT purely as an interface design tool, but under the hood, everything still compiles down into one single Wasm module with a shared memory?
For now I'll stick to keeping it all compiled together until I can find a way to keep the memory overhead and individual component usage down on the Pulley/Wasmtime side. Thanks again for the pointers!
Lukas Barragan Torres said:
Christof Petig Just to make sure I understand: does your fork essentially generate standard, statically linkable bindings from the WIT file instead of the usual Component Model Canonical ABI bindings? Meaning it lets us use WIT purely as an interface design tool, but under the hood, everything still compiles down into one single Wasm module with a shared memory?
Yes, exactly. We use it to compile native applications with a single memory instance, thus no sandbox between components.
Last updated: Mar 23 2026 at 16:19 UTC