WebAssemblyでバックエンドAPIを動かそうとしてダメだった話

# この記事は何？
WebAssembly(wasm)初学者による「wasmやってみた」系の記事です。
実際にやってみて得られた気づきを共有します。

# モチベーション
- いくつかの言語で書いた実装をwasmに変換してみたい
- wasmがdockerコンテナイメージとして扱えるらしいのでやってみたい
- wasmをコンテナイメージとして扱えるなら、Google CloudRunやAWS ECSで動かせたりするのでは？ -> やってみたい

# WebAssembly？

https://developer.mozilla.org/ja/docs/WebAssembly

> WebAssembly は現代のウェブブラウザーで実行できる新しい種類のコードです。ネイティブに近いパフォーマンスで動作する、コンパクトなバイナリー形式の低レベルなアセンブリー風言語です。さらに、 C/C++、C# や Rust などの言語のコンパイル先となり、それらの言語をウェブ上で実行することができます。 WebAssembly は JavaScript と並行して動作するように設計されているため、両方を連携させることができます。

https://webassembly.org/

> WebAssembly（略称Wasm）は、スタックベースの仮想マシン用のバイナリ命令フォーマットである。 Wasmはプログラミング言語の移植可能なコンパイル・ターゲットとして設計されており、クライアントおよびサーバー・アプリケーションのウェブ上での展開を可能にする。

https://www.publickey1.jp/programming-lang/webassembly/

# やってみたリポジトリ一覧
python, rust, typescript についてwasm化などを試しました。
リポジトリではdevcontainerを用意しているので
vscode + docker環境 + [Remove Development拡張](https://github.com/Microsoft/vscode-remote-release)があればすぐに試すことができます。

https://github.com/naritata/wasm-handson-python

https://github.com/naritata/wasm-handson-rust

https://github.com/naritata/wasm-handson-typescript

# Python -> wasm 編

## Pythonにおけるwasm生成
https://www.publickey1.jp/blog/24/pythonwebassemblypy2wasmwasmer.html

wasm化には↑で紹介されている[py2wasm](https://github.com/wasmerio/py2wasm)を使ってみましたが、
色々引っかかってしまいwasm化できるようになるまで時間が掛かりました。

## ハマりポイント
- pythonのバージョンは`3.11`にする(py2wasmが`3.12`に対応していない)
- py2wasm実行時に`patchelf`のインストールも必要
- wasm実行環境としてwasmerを使う場合は`glibc2.34`以上が必要なので`bullseye`ではなく`bookworm`を使う

## py2wasmの辛いポイント
↓のようなコマンドでwasmを生成するのですが、かなり時間が掛かります。

```sh
py2wasm main.py -o main.wasm
```

↓の画像の状態で10分以上待たされました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/c79d9ca6-3407-adb3-0255-92e23f326b84.png)

wasm出力をGithub ActionsのCDスクリプトで行う場合は実行時間をかなり取られることになるため、コスト増が懸念点となるかもしれないと感じました。

## FastAPI + wasm の実行時エラー
Google CloudRunやAWS ECSでwebサーバとして実行するwasmを作ってみたかったため、
FastAPIで実装したpythonコードをwasm化し、wasmtime及びwasmerで実行してみました。

```sh
wasmtime main.wasm
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/a24fc702-9e0b-0f96-3d66-d4882e95a47b.png)

```sh
wasmer run main.wasm
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/69151896-c1ca-e081-0f93-cd9a0ef01eca.png)

いずれも`textwrap`に対する`ModuleNotFoundError`に引っかかり、上手く実行できませんでした。

## Flask + wasm の実行時エラー
FastAPIでは上手くいかなかったためFlaskも試してみました。

### Flask + wasmtime
```sh
wasmtime main_flask.wasm
```
以下のエラーが出て上手く実行できませんでした。

<details>
<summary>Flask + wasmtime実行時エラーログ</summary>

```
Error: failed to run main module `main_flask.wasm`

Caused by:
    0: failed to invoke command default
    1: error while executing at wasm backtrace:
           0: 0x15a55 - <unknown>!binary_op1
           1: 0x16b24 - <unknown>!PyNumber_InPlaceAdd
           2: 0x13b78a9 - <unknown>!_nuitka_loader_find_spec
           3: 0x80fa5 - <unknown>!cfunction_call
           4: 0x318e8 - <unknown>!_PyObject_MakeTpCall
           5: 0x32016 - <unknown>!PyObject_Vectorcall
           6: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
           7: 0x12baa4 - <unknown>!_PyEval_Vector
           8: 0x32491 - <unknown>!_PyFunction_Vectorcall
           9: 0x336c7 - <unknown>!object_vacall
          10: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          11: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          12: 0x124deb - <unknown>!_PyEval_EvalFrameDefault
          13: 0x11e34b - <unknown>!PyEval_EvalCode
          14: 0x164986 - <unknown>!PyImport_ExecCodeModuleObject
          15: 0x164b54 - <unknown>!PyImport_ExecCodeModuleWithPathnames
          16: 0x164bd7 - <unknown>!PyImport_ExecCodeModuleEx
          17: 0x139b488 - <unknown>!_EXECUTE_EMBEDDED_MODULE
          18: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
          19: 0x80fa5 - <unknown>!cfunction_call
          20: 0x318e8 - <unknown>!_PyObject_MakeTpCall
          21: 0x32016 - <unknown>!PyObject_Vectorcall
          22: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
          23: 0x12baa4 - <unknown>!_PyEval_Vector
          24: 0x32491 - <unknown>!_PyFunction_Vectorcall
          25: 0x336c7 - <unknown>!object_vacall
          26: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          27: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          28: 0x124deb - <unknown>!_PyEval_EvalFrameDefault
          29: 0x11e34b - <unknown>!PyEval_EvalCode
          30: 0x164986 - <unknown>!PyImport_ExecCodeModuleObject
          31: 0x164b54 - <unknown>!PyImport_ExecCodeModuleWithPathnames
          32: 0x164bd7 - <unknown>!PyImport_ExecCodeModuleEx
          33: 0x139b488 - <unknown>!_EXECUTE_EMBEDDED_MODULE
          34: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
          35: 0x80fa5 - <unknown>!cfunction_call
          36: 0x318e8 - <unknown>!_PyObject_MakeTpCall
          37: 0x32016 - <unknown>!PyObject_Vectorcall
          38: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
          39: 0x12baa4 - <unknown>!_PyEval_Vector
          40: 0x32491 - <unknown>!_PyFunction_Vectorcall
          41: 0x336c7 - <unknown>!object_vacall
          42: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          43: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          44: 0x124deb - <unknown>!_PyEval_EvalFrameDefault
          45: 0x11e34b - <unknown>!PyEval_EvalCode
          46: 0x164986 - <unknown>!PyImport_ExecCodeModuleObject
          47: 0x164b54 - <unknown>!PyImport_ExecCodeModuleWithPathnames
          48: 0x164bd7 - <unknown>!PyImport_ExecCodeModuleEx
          49: 0x139b488 - <unknown>!_EXECUTE_EMBEDDED_MODULE
          50: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
          51: 0x80fa5 - <unknown>!cfunction_call
          52: 0x318e8 - <unknown>!_PyObject_MakeTpCall
          53: 0x32016 - <unknown>!PyObject_Vectorcall
          54: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
          55: 0x12baa4 - <unknown>!_PyEval_Vector
          56: 0x32491 - <unknown>!_PyFunction_Vectorcall
          57: 0x336c7 - <unknown>!object_vacall
          58: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          59: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          60: 0x119d63 - <unknown>!builtin___import__
          61: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
          62: 0x31ece - <unknown>!_PyVectorcall_Call
          63: 0x31e77 - <unknown>!PyVectorcall_Call
          64: 0x80f50 - <unknown>!cfunction_call
          65: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
          66: 0x389790 - <unknown>!IMPORT_MODULE5
          67: 0x125c05d - <unknown>!modulecode_werkzeug$serving
          68: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
          69: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
          70: 0x80fa5 - <unknown>!cfunction_call
          71: 0x318e8 - <unknown>!_PyObject_MakeTpCall
          72: 0x32016 - <unknown>!PyObject_Vectorcall
          73: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
          74: 0x12baa4 - <unknown>!_PyEval_Vector
          75: 0x32491 - <unknown>!_PyFunction_Vectorcall
          76: 0x336c7 - <unknown>!object_vacall
          77: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          78: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          79: 0x119d63 - <unknown>!builtin___import__
          80: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
          81: 0x31ece - <unknown>!_PyVectorcall_Call
          82: 0x31e77 - <unknown>!PyVectorcall_Call
          83: 0x80f50 - <unknown>!cfunction_call
          84: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
          85: 0x389790 - <unknown>!IMPORT_MODULE5
          86: 0xe50403 - <unknown>!modulecode_werkzeug
          87: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
          88: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
          89: 0x80fa5 - <unknown>!cfunction_call
          90: 0x318e8 - <unknown>!_PyObject_MakeTpCall
          91: 0x32016 - <unknown>!PyObject_Vectorcall
          92: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
          93: 0x12baa4 - <unknown>!_PyEval_Vector
          94: 0x32491 - <unknown>!_PyFunction_Vectorcall
          95: 0x336c7 - <unknown>!object_vacall
          96: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
          97: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
          98: 0x119d63 - <unknown>!builtin___import__
          99: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         100: 0x31ece - <unknown>!_PyVectorcall_Call
         101: 0x3223c - <unknown>!_PyObject_Call
         102: 0x322fc - <unknown>!PyObject_Call
         103: 0x129777 - <unknown>!_PyEval_EvalFrameDefault
         104: 0x12baa4 - <unknown>!_PyEval_Vector
         105: 0x32491 - <unknown>!_PyFunction_Vectorcall
         106: 0x336c7 - <unknown>!object_vacall
         107: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
         108: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
         109: 0x119d63 - <unknown>!builtin___import__
         110: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         111: 0x31ece - <unknown>!_PyVectorcall_Call
         112: 0x31e77 - <unknown>!PyVectorcall_Call
         113: 0x80f50 - <unknown>!cfunction_call
         114: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
         115: 0x7bf629 - <unknown>!modulecode_flask$globals
         116: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
         117: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
         118: 0x80fa5 - <unknown>!cfunction_call
         119: 0x318e8 - <unknown>!_PyObject_MakeTpCall
         120: 0x32016 - <unknown>!PyObject_Vectorcall
         121: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
         122: 0x12baa4 - <unknown>!_PyEval_Vector
         123: 0x32491 - <unknown>!_PyFunction_Vectorcall
         124: 0x336c7 - <unknown>!object_vacall
         125: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
         126: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
         127: 0x119d63 - <unknown>!builtin___import__
         128: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         129: 0x31ece - <unknown>!_PyVectorcall_Call
         130: 0x31e77 - <unknown>!PyVectorcall_Call
         131: 0x80f50 - <unknown>!cfunction_call
         132: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
         133: 0x389790 - <unknown>!IMPORT_MODULE5
         134: 0x7d64c6 - <unknown>!modulecode_flask$json
         135: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
         136: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
         137: 0x80fa5 - <unknown>!cfunction_call
         138: 0x318e8 - <unknown>!_PyObject_MakeTpCall
         139: 0x32016 - <unknown>!PyObject_Vectorcall
         140: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
         141: 0x12baa4 - <unknown>!_PyEval_Vector
         142: 0x32491 - <unknown>!_PyFunction_Vectorcall
         143: 0x336c7 - <unknown>!object_vacall
         144: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
         145: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
         146: 0x119d63 - <unknown>!builtin___import__
         147: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         148: 0x31ece - <unknown>!_PyVectorcall_Call
         149: 0x3223c - <unknown>!_PyObject_Call
         150: 0x322fc - <unknown>!PyObject_Call
         151: 0x129777 - <unknown>!_PyEval_EvalFrameDefault
         152: 0x12baa4 - <unknown>!_PyEval_Vector
         153: 0x32491 - <unknown>!_PyFunction_Vectorcall
         154: 0x336c7 - <unknown>!object_vacall
         155: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
         156: 0x166000 - <unknown>!PyImport_ImportModuleLevelObject
         157: 0x119d63 - <unknown>!builtin___import__
         158: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         159: 0x31ece - <unknown>!_PyVectorcall_Call
         160: 0x31e77 - <unknown>!PyVectorcall_Call
         161: 0x80f50 - <unknown>!cfunction_call
         162: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
         163: 0x389790 - <unknown>!IMPORT_MODULE5
         164: 0x72c151 - <unknown>!modulecode_flask
         165: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
         166: 0x13b7cd4 - <unknown>!_nuitka_loader_exec_module
         167: 0x80fa5 - <unknown>!cfunction_call
         168: 0x318e8 - <unknown>!_PyObject_MakeTpCall
         169: 0x32016 - <unknown>!PyObject_Vectorcall
         170: 0x129c61 - <unknown>!_PyEval_EvalFrameDefault
         171: 0x12baa4 - <unknown>!_PyEval_Vector
         172: 0x32491 - <unknown>!_PyFunction_Vectorcall
         173: 0x336c7 - <unknown>!object_vacall
         174: 0x3344a - <unknown>!PyObject_CallMethodObjArgs
         175: 0x165cb5 - <unknown>!PyImport_ImportModuleLevelObject
         176: 0x119d63 - <unknown>!builtin___import__
         177: 0x80736 - <unknown>!cfunction_vectorcall_FASTCALL_KEYWORDS
         178: 0x31ece - <unknown>!_PyVectorcall_Call
         179: 0x31e77 - <unknown>!PyVectorcall_Call
         180: 0x80f50 - <unknown>!cfunction_call
         181: 0x37538a - <unknown>!CALL_FUNCTION_WITH_ARGS5
         182: 0x372a74 - <unknown>!modulecode___main__
         183: 0x139b4e1 - <unknown>!_EXECUTE_EMBEDDED_MODULE
         184: 0x139ac3d - <unknown>!EXECUTE_MAIN_MODULE
         185: 0x1399c5f - <unknown>!main
         186: 0x344330 - <unknown>!__main_void
         187: 0x12ba5 - <unknown>!_start
       note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information
    2: wasm trap: indirect call type mismatch
```

</details>

### Flask + wasmer
実行時エラーは出なかったものの、ブラウザで`localhost:5000`を開いてもローディングが終わらず...

```sh
wasmer run main_flask.wasm
```

# Rust -> wasm 編

## Rustにおけるwasm生成
↓のコマンドを1回だけ実行しておき、ビルドターゲットに`wasm32-wasip1`を指定できるようにします。

```sh
rustup target add wasm32-wasip1
```

↓のコマンドでwasmを生成します。

```sh
cargo build --target wasm32-wasip1 --release
```

`target/wasm32-wasip1/release/wasm-handson-rust.wasm`が生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/710ef3e0-bef9-2726-5a91-e9909bf258e9.png)

`cargo new`しただけのhello-worldなコードのwasmファイルは63KBでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/ac63e60e-e214-5eed-b2f6-1142f9d8f170.png)

## wasmとして実行する
```sh
wasmer target/wasm32-wasip1/release/wasm-handson-rust.wasm
```

```sh
wasmtime target/wasm32-wasip1/release/wasm-handson-rust.wasm
```

コードはただのhello-worldなので、普通に実行できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/6ad8f875-630d-04e7-3135-b051c397aab2.png)

## RustコードでwebAPIを実装する

```sh
cargo add actix-web
```

```rs:main.rs
use actix_web::{get, App, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    "Hello, world!"
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(hello))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}
```

## wasm化する前にRustとしてwebAPIを実行する

```sh
cargo run
```

ブラウザで`localhost:8080`を開き、webAPIとして動作していることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/3c65f6b8-d709-1a2f-586b-b654283f164c.png)

## webAPI化したRustコードをwasm化する

```sh
cargo build --target wasm32-wasip1 --release
```

以下のエラーが出てwasm生成に失敗しました。

<details>

<summary>ビルド時のエラーログ</summary>

```
error[E0308]: mismatched types
    --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/socket.rs:2351:15
     |
2351 | from!(Socket, net::UdpSocket);
     |               ^^^^^^^^^^^^^^ expected `UdpSocket`, found `()`
     |
    ::: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/lib.rs:109:16
     |
109  |             fn from(socket: $from) -> $for {
     |                ---- implicitly returns `()` as its body has no tail or `return` expression

error[E0599]: the method `ok` exists for enum `Result<SockAddr, Error>`, but its trait bounds were not satisfied
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/sockref.rs:119:60
    |
119 |             .field("local_addr", &self.socket.local_addr().ok())
    |                                                            ^^ method cannot be called on `Result<SockAddr, Error>` due to unsatisfied trait bounds
    |
   ::: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/sockaddr.rs:21:1
    |
21  | pub struct SockAddr {
    | ------------------- doesn't satisfy `SockAddr: Sized`
    |
    = note: the following trait bounds were not satisfied:
            `{type error}: Sized`
            which is required by `SockAddr: Sized`

error[E0599]: the method `ok` exists for enum `Result<SockAddr, Error>`, but its trait bounds were not satisfied
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/sockref.rs:120:58
    |
120 |             .field("peer_addr", &self.socket.peer_addr().ok())
    |                                                          ^^ method cannot be called on `Result<SockAddr, Error>` due to unsatisfied trait bounds
    |
   ::: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/sockaddr.rs:21:1
    |
21  | pub struct SockAddr {
    | ------------------- doesn't satisfy `SockAddr: Sized`
    |
    = note: the following trait bounds were not satisfied:
            `{type error}: Sized`
            which is required by `SockAddr: Sized`

error[E0599]: the method `as_mut_ptr` exists for mutable reference `&mut [MaybeUninitSlice<'_>]`, but its trait bounds were not satisfied
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/lib.rs:710:51
    |
390 | pub struct MaybeUninitSlice<'a>(sys::MaybeUninitSlice<'a>);
    | ------------------------------- doesn't satisfy `MaybeUninitSlice<'_>: Sized`
...
710 |         sys::set_msghdr_iov(&mut self.inner, bufs.as_mut_ptr().cast(), bufs.len());
    |                                                   ^^^^^^^^^^ method cannot be called on `&mut [MaybeUninitSlice<'_>]` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `{type error}: Sized`
            which is required by `MaybeUninitSlice<'_>: Sized`

error[E0599]: the method `len` exists for mutable reference `&mut [MaybeUninitSlice<'_>]`, but its trait bounds were not satisfied
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/socket2-0.5.8/src/lib.rs:710:77
    |
390 | pub struct MaybeUninitSlice<'a>(sys::MaybeUninitSlice<'a>);
    | ------------------------------- doesn't satisfy `MaybeUninitSlice<'_>: Sized`
...
710 |         sys::set_msghdr_iov(&mut self.inner, bufs.as_mut_ptr().cast(), bufs.len());
    |                                                                             ^^^ method cannot be called on `&mut [MaybeUninitSlice<'_>]` due to unsatisfied trait bounds
    |
    = note: the following trait bounds were not satisfied:
            `[MaybeUninitSlice<'_>]: ExactSizeIterator`
            which is required by `&mut [MaybeUninitSlice<'_>]: ExactSizeIterator`
            `{type error}: Sized`
            which is required by `MaybeUninitSlice<'_>: Sized`

Some errors have detailed explanations: E0061, E0308, E0412, E0422, E0425, E0432, E0433, E0583, E0599.
For more information about an error, try `rustc --explain E0061`.
error: could not compile `socket2` (lib) due to 223 previous errors
error: could not compile `tokio` (lib) due to 1 previous error
```
</details>

# Typescript -> wasm 編
## Typescriptにおけるwasm生成
### [Porffor](https://porffor.dev/)
- [Publickey | JavaScript/TypeScriptからWebAssemblyやネイティブバイナリを生成するコンパイラ「Porffor」の開発が加速へ、開発者がフルタイムで取り組み](https://www.publickey1.jp/blog/24/javascripttypescriptwebassemblyporffor.html)
- 現在はpre-alpha状態で2025年に利用可能になる模様なので様子見です

### [AssemblyScript](https://www.assemblyscript.org/getting-started.html#setting-up-a-new-project)
- [Zenn | TypeScriptでWebAssemblyに入門する](https://zenn.dev/is_ryo/articles/d75db3ad37bea0#assemblyscript)
- 現状だとこれが選択肢になりそうな感じでした。

## パッケージのインストール
```sh
bun add -d assemblyscript
```

`assembly/`配下に以下のような`index.ts`が生成されます。

```ts:assembly/index.ts
// The entry file of your WebAssembly module.

export function add(a: i32, b: i32): i32 {
	return a + b;
}
```

`package.json`にはAssemblyScript用のコマンドが追加されます。

```json:package.json
  "scripts": {
    "asbuild:debug": "asc assembly/index.ts --target debug",
    "asbuild:release": "asc assembly/index.ts --target release",
    "asbuild": "npm run asbuild:debug && npm run asbuild:release",
  }
```

## wasm化するTypescriptコードにエントリポイントを追加する
初期化したままだとwasmファイル自体が実行可能バイナリにならないため、
エントリポイントとなる関数定義を`_start()`として追加します。

```ts:assembly/index.ts
export function add(a: i32, b: i32): i32 {
	return a + b;
}

export function _start(): i32 {
	return add(10, 20);
}
```

## wasmビルド
以下のコマンドでassmebly/index.tsを元にwasmを生成します

```sh
bun run asbuild
```

build配下にdebug.wasmとrelease.wasmが生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/28489976-d0d6-2161-0a57-1a951f476788.png)


wasmファイルサイズは311Bと112Bでした。軽量です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/09708eab-432a-3edf-5776-06ee882faba1.png)

## wasmを実行

```sh
wasmer build/release.wasm
```

_start()の戻り値がコンソールに出力されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/6af5dad9-d317-59c3-7560-7db265eee85a.png)

```sh
wasmtime build/release.wasm
```

wasmtimeではwarningが出たもののwasmer同様に実行できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/9a8a4e9e-c421-872b-1534-6797eae90f76.png)

## wasmのdockerコンテナ化

```docker:Dockerfile
FROM scratch
COPY release.wasm /release.wasm
ENTRYPOINT [ "/release.wasm" ]
```

```sh
docker buildx build --platform wasi/wasm -t username/wasm-handson-ts .
```

`--platform`に指定した`wasi/wasm`でビルドするための要件を満たせずに以下のエラーが出ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/44966014-e7c0-b251-f843-24bb1aa157fe.png)


### DockerDesktopでwasmを利用可能にする
[Docker | Wasm workloads (Beta)](https://docs.docker.com/desktop/features/wasm/)を参考にDockerDesktopの設定を変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/7b90d38c-5807-5b06-1a48-780ee0b2c7b5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/738b82b4-10e4-e1f0-65d1-57b4faa5589c.png)


### 再びwasmのdockerコンテナ化
DockerDesktopを再起動した後、docker imageのビルドを実行します。

```sh
docker buildx build --platform wasi/wasm -t username/wasm-handson-ts .
```

`docker images`で確認するとimageが生成されていました。
サイズは僅か11.3KBと軽量です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/35f5e720-9093-b39b-67d4-1cc1a1a7719b.png)

### コンテナ化したwasmをdocker runで実行する
実行時には--runtimeと--platformの指定が必要です。
DockerDesktopで`Enable Wasm`にチェックを入れて再起動した時に wasm実行用のランタイムがインストールされ、`--runtime`で指定できるようになります。

```sh
docker run --runtime=io.containerd.wasmedge.v1 --platform=wasi/wasm username/wasm-handson-ts
```

### wasm + docker のまとめ
- 現状だとwasmをdocker imageとしてビルドするにも、それをコンテナとして実行するにもDockerDesktopの環境が必要です
- GithubActionsなどのCI環境で手軽にwasm製のdocker imageをdocker runするには手間が掛かりそうに感じました


# 言語毎のwasm化やってみた感想
- Python : 人類には早すぎた気がします、py2wasmの今後に期待
- Rust : wasm生成が簡単・高速なのがgood
- Typescript : 型(`i32`など)や作法が通常のtsと異なる点で戸惑うかも

# webサーバを立てるタイプのコードのwasm化が難しい理由
ChatGPTやGithubCopilotに聞いてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/1b373ba9-f9fa-7798-fe40-78191f5be805.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/72e3b551-51dd-e50e-ebaf-ae82f975d552.png)

- wasmは基本的にOSのファイルシステムやネットワークにはアクセスできない仕様(だからセキュア)
- OSの機能にアクセスするためにWASI(WebAssemblySystemInterface)の仕様がある
- wasmer等の各種wasm実行環境がWASIの実装を提供していれば、wasmからWASIを通してOSの機能にアクセスできるようになる
- 現状ではネットワークソケットや非同期処理、ファイルシステム周りの制約がクリアされておらず、`actix-web`等を使ったwebサーバ構築コードはwasm化できない
- これらの制約に対応するためのWASI仕様が策定され、各種wasm実行環境が実装を提供するまで待つ必要がある

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3942145/9331049c-9643-d51a-1a42-389c60bc9763.png)

# まとめ
- CloudRunやAWS ECSでwasmをバックエンドAPIとして動かすにはまだ早い...かも
- WASIの仕様とwasm実行環境の実装が追いつき、webサーバ構築系のパッケージごとwasm化できるようになるまでは難しそう
- wasmをコンテナとして動かすことができたのはあくまでも**DockerDesktop環境において**なので、その他の環境(例えばGithubActionsのCI環境など)で特にカスタムせずとも暗黙に実行できるようになるまでは用途が限定されそう