Skip to content

Compile WebAssembly to native MicroPython modules

License

Notifications You must be signed in to change notification settings

vshymanskyy/wasm2mpy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

wasm2mpy

StandWithUkraine Build status GitHub license Support vshymanskyy

wasm2mpy enables developers to write code in statically compiled languages and run it on MicroPython-based embedded systems (such as ESP32, Raspberry Pi Pico, STM32, and nRF52) with near-native performance. Since MicroPython is relatively slow for computationally intensive applications, wasm2mpy provides the tools necessary to run demanding software, such as AI models and signal processing algorithms, more efficiently.

Status

App \ Target x86/x64 armv6m armv7m/+s/+d esp82661 esp32 rv32imc
🚀 TypeScript2 ✅✅ ✅✅✅ ⚠️3 🚧
🤩 C++ ✅✅ ✅✅✅ 🟡 🚧
🦀 Rust ✅✅ 🟡🟡✅ ⚠️3 🚧
🤖 TinyGo ✅✅ 🟡🟡✅ ⚠️3 🚧
⚡ Zig ✅✅ ✅✅✅ ⚠️3 🚧
✨ Virgil ✅✅ ✅✅✅ 🟡 🚧
⚙ WAT ✅✅ ✅✅✅ 🟡 🚧
🇨 Coremark ✅✅ 🚧 ✅✅✅ 🟡 🚧

✅ builds and runs OK
🟡 builds OK, doesn't run
🚧 work in progress

CoreMark results

  • STM32F405 168MHz: 233.918
  • ESP32 240MHz: 228.363
  • ESP32-S3 240MHz: 271.573
  • iMXRT1062 600MHz: 1911.437
  • i5-8250U 1.6GHz: 18696.248

Compile

Important

This is a Proof-of-Concept, not optimized or ready for actual use.

You'll need:

  • Python 3
    • pip install --upgrade pyelftools ar
  • wasm2c from WABT
  • Latest MicroPython source code
  • Target architecture toolchain

Set up the environment and build the .mpy module from .wasm:

export MPY_DIR=/path/to/micropython
export PATH=/opt/wabt/bin:$PATH
export PATH=/opt/xtensa-lx106-elf/bin:$PATH
export PATH=/opt/xtensa-esp32-elf/bin:$PATH
pip install -U pyelftools
make ARCH=xtensawin APP=zig   # x86, x64, armv6m, armv7m, armv7emsp, armv7emdp, xtensa, xtensawin

Output:

W2C test/zig.wasm
GEN build/zig.config.h
CC runtime/runtime.c
CC runtime/wasm-rt-mem-impl.c
CC runtime/wasm-rt-impl.c
CC .wasm/wasm.c
LINK build/runtime/runtime.o
arch:         EM_XTENSA
text size:    3524
rodata size:  850
bss size:     144
GOT entries:  57
GEN zig.mpy

Upload and Run

mpremote cp zig.mpy :lib/
mpremote exec "import zig; zig.setup()"
⚡ Zig is running!

Run any exported function

Note

This requires adding some glue code to the runtime.
Glue code can be auto-generated, but for now it's a manual process.

For example, test/simple.wasm just adds 2 numbers:

(module
    (func (export "add") (param i32 i32) (result i32)
        (i32.add (local.get 0) (local.get 1))
    )
)
MicroPython v1.24.0-preview.224.g6c3dc0c0b on 2024-08-22; Raspberry Pi Pico W with RP2040
Type "help()" for more information.
>>> import simple
>>> simple.add(3, 4)
7
>>> simple.add(10, 6)
16

Access WASM module memory

>>> import cpp
>>> cpp.setup()
🤩 C++ is running!
>>> cpp._memory[4096:4096+32]
bytearray(b' Blink\x00\xf0\x9f\xa4\xa9 C++ is running!\x00\n\x00\x00\x00')
>>> new_data = b"Hello C++ world"
>>> cpp._memory[4096+12:4096+12+len(new_data)] = new_data
>>> cpp.setup()
🤩 Hello C++ world

How It Works?

The idea is very similar to embedded-wasm-apps:

image

TODO

Further reading

Footnotes

  1. esp8266 requires the use of esp.set_native_code_location, and setting WASM_PAGE_SIZE to 8192 (or need to wait for WASM Custom Page Sizes)

  2. AssemblyScript

  3. not enough memory to run, need to wait for WASM Custom Page Sizes 2 3 4