WebAssembly Micro RuntimeでRustアプリをマイコンで動かす!

はじめに

前回、もう一歩のところだったのですが、RAMが2MB搭載されていないと動かない状態でした。

tomo-wait-for-it-yuki.hatenablog.com

私はそんなマイコン持っていないため、今回は、256KB RAMが搭載されているマイコン (これも高性能品ですが) 上でRustアプリを動作させます。

問題点

データセグメントが0x100000番地に配置されていることが問題でした。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm 
...
Data[1]:
 - segment[0] size=16 - init i32=1048576
  - 0100000: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

リンカオプションによる解決

調べていると、次のissueに行き当たりました。

github.com

The large memory size here is because we default to asking LLD to allocate a 1MB stack for all Rust binaries. If you'd like to reduce that though you can pass -C link-arg=-zstack-size=16 to rustc, for an appropriate stack size for your application. That should allow you to shrink quite a bit!

LLDにスタックを1MB割り当てるようなデフォルトになっており、このサイズを減らせば良い、ということでした。

ということで、リンカにオプションを与えます。

|> .cargo/config

[target.wasm32-unknown-unknown]
rustflags = [
    "-C", "link-arg=-zstack-size=16"
]

ビルドします。

$ cargo build --target=wasm32-unknown-unknown --release
$ wasm-strip target/wasm32-unknown-unknown/release/hello_wasm.wasm

中身を確認してみましょう。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm

hello_wasm.wasm:        file format wasm 0x1

Section Details:

Type[3]:
 - type[0] (i32, i32) -> i32
 - type[1] () -> nil
 - type[2] () -> i32
...
Code[2]:
 - func[1] size=2
 - func[2] size=16 <main>
Data[1]:
 - segment[0] size=16 - init i32=16
  - 0000010: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

これでデータセグメントが0x10 (16) から置かれるようになりました。

QEMUでの動作確認

実機で試す前に、QEMUを使って、RAM 64KB搭載のCortex-M3をターゲットにして、動作確認します。 (無理矢理動かすために、WebAssembly Micro Runtimeのページサイズを4KBにしています)

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello from Rust

よし!

実機動作

手元に偶然あるnRF52840-DKで動かしてみます。 RAMも256KBあるので、なんとかなるはずです。

$ mkdir nrf52840 && cd $_
$ cmake -GNinja -DBOARD=nrf52840_pca10056 ..
$ ninja
[10/14] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       76436 B         1 MB      7.29%
            SRAM:      144328 B       256 KB     55.06%
        IDT_LIST:          56 B         2 KB      2.73%
[14/14] Linking C executable zephyr/zephyr.elf

ビルドはOKです。

minicomでUARTを受信できるようにして、ファームウェアを書き込みます。

ninja flash
Welcome to minicom 2.7.1

OPTIONS: I18n 
Compiled on Aug 13 2017, 15:25:34.
Port /dev/ttyUSB0, 20:35:14

Press CTRL-A Z for help on special keys

***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello from Rust

OK! 無事動きました!

WebAssembly Micro Runtimeの方は、次の通りです。

|> src/main.c

// デフォルトの512 KBでは実機のRAM 256KBに入らないため
static char global_heap_buf[128 * 1024] = { 0 };

|> iwasm/runtime/vmcore-wasm/wasm.h

// WASMは1ページ64KBとのことなので4KBから元に戻しています
#define NumBytesPerPage 65536
#define NumBytesPerPageLog2 16