環境準備
最近購入したTarget Board for RX671でWebAssemblyインタプリタの実装であるwasm3を使用して、C/C++ + WebAssemblyを動かす手順を確認してみました。
- WebAssemblyはC言語やRust言語等の様々なプログラミング言語をコンパイルターゲットにして、ウェブブラウザ等の様々な環境で実行されるプログラミング言語のことです。
準備した環境は以下の通りです。
- Target Board for RX671 (評価ボード)
- e2 studio 2021-07 (統合開発環境)
- GCC for Renesas 8.3.0.202102-GNURX Windows (RXマイコン用コンパイラ)
- wasm3 v0.5.0 (https://github.com/wasm3/wasm3)
- Emscripten (WebAssemblyへのコンパイラツールチェーン)
- Git (Emscriptenダウンロード用)
- MSYS2 64-bit (xxdコマンド用)
Emscriptenのインストール
WebAssemblyへのコンパイラツールチェーンであるEmscriptenをインストールするためにコマンドプロンプトで下記コマンドを実行します。
- Emscriptenのインストールには別途Pythonのインストールが必要です。
- インストールが完了したらemcmdprompt.batをダブルクリックで実行しておきます。
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git pull
emsdk install latest
emsdk activate latest
C/C++のソースコードをWebAssemblyバイナリに変換する
Emscriptenを使用して、wasm3上で実行するWebAssemblyバイナリを作成します。
今回はC/C++のソースコードにTarget Board for RX671に実装されているLEDをチカチカさせるための処理を実装します。
以下の内容のCソースファイルを作成します。
#include <stdint.h>
void delay (int ms);
void led_low (void);
void led_high (void);
#define WASM_EXPORT __attribute__((used)) __attribute__((visibility ("default")))
#define WASM_EXPORT_AS(NAME) WASM_EXPORT __attribute__((export_name(NAME)))
#define WASM_IMPORT(MODULE,NAME) __attribute__((import_module(MODULE))) __attribute__((import_name(NAME)))
#define WASM_CONSTRUCTOR __attribute__((constructor))
WASM_IMPORT("function", "delay") void delay (int ms);
WASM_IMPORT("function", "led_low") void led_low (void);
WASM_IMPORT("function", "led_high") void led_high (void);
void setup() {
led_high();
}
// the loop function runs over and over again forever
void loop() {
led_low();
delay(100);
led_high();
delay(900);
}
/*
* Entry point
*/
WASM_EXPORT
void _start() {
setup();
while (1) { loop(); }
}
上記のCソースファイルを下記コマンドでWebAssemblyバイナリに変換します。
- ワーニングが表示されますが今回は無視してください。
- test_wasm.cと同じディレクトリにtest_wasm.wasmが作成されていれば成功です。
emcc test_wasm.c -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s WASM=1 -o test_wasm.html
WebAssemblyバイナリをC言語で読み込める配列に変換する
WebAssemblyバイナリに変換したら、下記コマンドでMSYS2にVimをインストールします(xxd
コマンドを使用するためです)。
pacman -S vim
次にxxd
コマンドを使用してC言語で読み込める配列に変換します。
- 下記コマンドを実行すると
test_wasm.h
が生成されます。
xxd -i test_wasm.wasm > test_wasm.h
e2 studioで新規プロジェクトを作成する
e2 studioを起動して上記URLからダウンロードしたwasm3を動かすための新規プロジェクトを作成します。
-
新規プロジェクトで使用するコンパイラはGCC for Renesas RX 8.3.0202102を選択しておきます。
-
Target BoardはTargetBoardRX671を選択しておいてください(クロックがTargetBoard用に自動的に設定されます)
-
今回はRXファミリMCU向けのデバイスドライバであるFITモジュールを使用するのでUse Smart Configuratorにチェックを入れて新規プロジェクトを作成します。
-
新規プロジェクトを作成したら、プロジェクトの
src
フォルダ以下にwasm3
フォルダを作成します。 -
追加した
wasm3
のソースコードに対して下記のインクルードパスを設定します。"${workspace_loc:/${ProjName}/src/wasm3}"
"${workspace_loc:/${ProjName}/src/wasm3/extensions}"
"${workspace_loc:/${ProjName}/src/wasm3/extra}"
メインルーチンを追加してプログラムを実行する
wasm3
のソースコードはRXマイコン用GCCコンパイラでビルドするとワーニングが表示されるので、wasm3_defs.h
を開いて下記定義を追加しておきます。
# elif defined(__arc__)
# define M3_ARCH "arc32"
# elif defined(__AVR__)
# define M3_ARCH "avr"
# elif defined(__RX__)
# define M3_ARCH "Renesas RX"
# endif
# endif
次にmain関数が定義されているソースコード(作成したプロジェクト名と同じ名前)を開き、下記処理を追加します。
-
m3_LinkRawFunction
関数でWebAssemblyバイナリに変換したC言語の関数とのリンクを実施します(つまり、wasm3
がWebAssemblyバイナリを実行してdelay
関数をコールするとメインルーチンにあるm3_delay_ms
関数がコールされます)。 - 今回は
Target Board for RX671
に実装されているLED0とLED1を点滅するプログラムを作成しました、そのためdelay
関数の他にもled_low
とled_high
関数をそれぞれm3_led_low
とm3_led_high
関数にリンクさせています。
#include "r_smc_entry.h"
#include "platform.h"
#include <wasm3.h>
#include <m3_env.h>
#define WASM_STACK_SLOTS 1024
#define NATIVE_STACK_SIZE (32*1024)
#define WASM_MEMORY_LIMIT 4096
m3ApiRawFunction(m3_delay_ms)
{
m3ApiGetArg (uint32_t, ms)
R_BSP_SoftwareDelay(ms, BSP_DELAY_MILLISECS);
m3ApiSuccess();
}
m3ApiRawFunction(m3_led_low)
{
PORT3.PODR.BIT.B2 = 1;
PORT3.PODR.BIT.B3 = 1;
m3ApiSuccess();
}
m3ApiRawFunction(m3_led_high)
{
PORT3.PODR.BIT.B2 = 0;
PORT3.PODR.BIT.B3 = 0;
m3ApiSuccess();
}
M3Result LinkFunction (IM3Runtime runtime)
{
IM3Module module = runtime->modules;
const char* function = "function";
m3_LinkRawFunction (module, function, "delay", "v(i)", &m3_delay_ms);
m3_LinkRawFunction (module, function, "led_low", "v()", &m3_led_low);
m3_LinkRawFunction (module, function, "led_high", "v()", &m3_led_high);
return m3Err_none;
}
void wasm_task(void *param)
{
M3Result result = m3Err_none;
IM3Environment env = m3_NewEnvironment ();
if (!env)
{
while(1);
}
IM3Runtime runtime = m3_NewRuntime (env, WASM_STACK_SLOTS, NULL);
if (!runtime)
{
while(1);
}
#ifdef WASM_MEMORY_LIMIT
runtime->memoryLimit = WASM_MEMORY_LIMIT;
#endif
IM3Module module;
result = m3_ParseModule (env, &module, test_wasm_wasm, test_wasm_wasm_len);
if (result)
{
while(1);
}
result = m3_LoadModule (runtime, module);
if (result)
{
while(1);
}
result = LinkFunction (runtime);
if (result)
{
while(1);
}
IM3Function f;
result = m3_FindFunction (&f, runtime, "_start");
if (result)
{
while(1);
}
result = m3_CallV (f);
}
void main(void);
void main(void)
{
PORT3.PDR.BIT.B2 = 1;
PORT3.PDR.BIT.B3 = 1;
wasm_task(NULL);
while(1);
}
次に先ほど作成したtest_wasm.h
をテキストエディタ等で開いて、その内容をmain関数が定義されているソースコードにペーストします。
unsigned char test_wasm_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x9f, 0x80, 0x80,
0x80, 0x00, 0x06, 0x60, 0x00, 0x00, 0x60, 0x00, 0x01, 0x7f, 0x60, 0x01,
/* ... */
};
unsigned int test_wasm_wasm_len = 744;
wasm3
はcallocで動的にメモリを確保します。
しかし、プロジェクト新規作成時デフォルトのヒープサイズでは足りないため、下記のようにヒープサイズを変更します。
プログラムを実行するので、下記のようにTarget Board for RX671
用にデバッガ設定を変更して、デバッグを開始します。
デバッグ開始後にプログラムを実行するとTarget Board for RX671
のLED0とLED1が点滅していることを確認します。
さいごに
WebAssemblyインタプリタ実装であるwasm3
をRXマイコンMCU上で動作させていました。
今回はC/C++のソースコードを変換して実行しましたが、wasm3
は他にもRust
やGo
言語にも対応しているので試してみたいです。
また、C言語をネイティブに実行させたときに比べて、どの程度パフォーマンスに差が出るのか確認してみたいです。