RXファミリMCUでwasm3を使用してC/C++ + WebAssemblyを動かす

# 環境準備
最近購入したTarget Board for RX671でWebAssemblyインタプリタの実装であるwasm3を使用して、C/C++ + WebAssemblyを動かす手順を確認してみました。
* WebAssemblyはC言語やRust言語等の様々なプログラミング言語をコンパイルターゲットにして、ウェブブラウザ等の様々な環境で実行されるプログラミング言語のことです。

準備した環境は以下の通りです。

1. Target Board for RX671 (評価ボード)
2. e2 studio 2021-07 (統合開発環境)
3. GCC for Renesas 8.3.0.202102-GNURX Windows (RXマイコン用コンパイラ)
4. wasm3 v0.5.0 (https://github.com/wasm3/wasm3)
5. Emscripten (WebAssemblyへのコンパイラツールチェーン)
6. Git (Emscriptenダウンロード用)
6. MSYS2 64-bit (xxdコマンド用)

# Emscriptenのインストール
WebAssemblyへのコンパイラツールチェーンであるEmscriptenをインストールするためにコマンドプロンプトで下記コマンドを実行します。
* Emscriptenのインストールには別途Pythonのインストールが必要です。
* インストールが完了したら**emcmdprompt.bat**をダブルクリックで実行しておきます。

```bash
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ソースファイルを作成します。

```c:test_wasm.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が作成されていれば成功です。

```bash
emcc test_wasm.c -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s WASM=1 -o test_wasm.html
```

# WebAssemblyバイナリをC言語で読み込める配列に変換する
WebAssemblyバイナリに変換したら、下記コマンドでMSYS2にVimをインストールします(`xxd`コマンドを使用するためです)。

```bash
pacman -S vim
```

次に`xxd`コマンドを使用してC言語で読み込める配列に変換します。
* 下記コマンドを実行すると`test_wasm.h`が生成されます。

```bash
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用に自動的に設定されます)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/eba945a1-5089-6c92-850a-c15f74221d24.png)

* 今回はRXファミリMCU向けのデバイスドライバである**FITモジュール**を使用するので**Use Smart Configurator**にチェックを入れて新規プロジェクトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/5ccc742c-f660-e5d6-8034-e1b440010e73.png)

* 新規プロジェクトを作成したら、プロジェクトの`src`フォルダ以下に`wasm3`フォルダを作成します。
* 作成した`wasm3`フォルダにwasm3 v0.5.0の`source`フォルダ以下をドラッグアンドドロップでコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/948312d6-ffc4-cc62-065a-b36f900b2f99.png)

* 追加した`wasm3`のソースコードに対して下記のインクルードパスを設定します。
  * `"${workspace_loc:/${ProjName}/src/wasm3}"`
  * `"${workspace_loc:/${ProjName}/src/wasm3/extensions}"`
  * `"${workspace_loc:/${ProjName}/src/wasm3/extra}"`

# メインルーチンを追加してプログラムを実行する
`wasm3`のソースコードはRXマイコン用GCCコンパイラでビルドするとワーニングが表示されるので、`wasm3_defs.h`を開いて下記定義を追加しておきます。

```c: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`関数にリンクさせています。

```c:main.c
#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関数が定義されているソースコードにペーストします。

```c:test_wasm.h
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で動的にメモリを確保します。
しかし、プロジェクト新規作成時デフォルトのヒープサイズでは足りないため、下記のようにヒープサイズを変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/be7c8c25-1512-c676-b1d0-905132039001.png)

プログラムを実行するので、下記のように`Target Board for RX671`用にデバッガ設定を変更して、デバッグを開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/349987c0-b376-256e-27bb-7f5956e1a816.png)

デバッグ開始後にプログラムを実行すると`Target Board for RX671`のLED0とLED1が点滅していることを確認します。

# さいごに
WebAssemblyインタプリタ実装である`wasm3`をRXマイコンMCU上で動作させていました。
今回はC/C++のソースコードを変換して実行しましたが、`wasm3`は他にも`Rust`や`Go`言語にも対応しているので試してみたいです。
また、C言語をネイティブに実行させたときに比べて、どの程度パフォーマンスに差が出るのか確認してみたいです。