この記事は虎の穴ラボ Advent Calendar 2024、Deno Advent Calendar 2024の5日目の記事です
去る11月21日公開になったDeno 2.1で、wasmファイル(WebAssembly)を直接 import できるようになりました。
今回は、この「wasmのimport」にフォーカスしてご紹介します。
参考
Wasm is 何?
Wasm及びWebAssemblyは、 MDN 曰く次のように記されています。
WebAssembly は現代のウェブブラウザーで実行できる新しい種類のコードです。ネイティブに近いパフォーマンスで動作する、コンパクトなバイナリー形式の低レベルなアセンブリー風言語です。さらに、 C/C++、C# や Rust などの言語のコンパイル先となり、それらの言語をウェブ上で実行することができます。 WebAssembly は JavaScript と並行して動作するように設計されているため、両方を連携させることができます。
https://developer.mozilla.org/ja/docs/WebAssembly
WebAssembly を用いて、WordPressのバックエンドを構築し動作させる「Client-side WebAssembly WordPress」や、WebAssembly にコンパイルしたPostgres など、多数の実用例を探すことができます。 make.wordpress.org
また本サイトでも、過去にWebAssembly 連載企画が行われていました。よければこちらもご覧ください。
wasmファイルの作り方
docs.deno.com では、wasmファイルの作成例が掲載されています。
wabtを使った方法です。 簡単に内容を示します。(wabt自体の導入については、READMEを参照ください。)
multi.watを用意します。
;; multi.wat (module (func (export "multi") (param $a i32) (param $b i32) (result i32) local.get $a local.get $b i32.mul ) )
multi.watをコンパイルします
$ wat2wasm multi.wat -o multi.wasm
このように、wasmを作成することができます。
Deno 2.1 で導入されたファーストクラス Wasm サポート
本リリース前での、wasmの利用方法として、リリースノートには以下の方法が示されています。
const wasmInstance = WebAssembly.instantiateStreaming(fetch("./add.wasm")); const { add } = wasmInstance; console.log(add(1, 2));
また、次のような利用もできました。 先に作ったmulti.wasmを読み込む場合、以下のように記述できます。
const wasmCode = Deno.readFileSync("./wasm/multi.wasm"); const wasmModule = new WebAssembly.Module(wasmCode); const wasmInstance = new WebAssembly.Instance(wasmModule); const wasm = wasmInstance.exports console.log(wasm.mul(2, 2));
筆者は、後者の方法でRustで提供されるライブラリをベースにwasm化し、ソースコードに展開することでJavaScriptのimportのみでwasmを使えるようにし、deno.land/xでモジュール公開をしていた経験もあります。
そして、Deno 2.1から以下のように読み込むことができます。
import { mul } from "./wasm/multi.wasm"; console.log(mul(2, 2));
非常に簡便な記述ができるようになりました。
wasm import された wasm モジュールは、モジュールグラフの一部となり、キャッシュし高速な動作がされるようになります。
さらに、型チェックの対象になり、deno check
を実行するとエラーを返します。
//usewasm.ts const wasmCode = Deno.readFileSync("./wasm/multi.wasm"); const wasmModule = new WebAssembly.Module(wasmCode); const wasmInstance = new WebAssembly.Instance(wasmModule); const wasm = wasmInstance.exports console.log(wasm.mul(2, "A")); import { mul } from "./wasm/multi.wasm"; console.log(mul(2, "A"));
$ deno check usewasm.ts Check file:///hoge/usewasm.ts error: TS2349 [ERROR]: This expression is not callable. No constituent of type 'ExportValue' is callable. console.log(wasm.mul(2, "A")); ~~~ at file:///Users/1068/dev/deno-test/usewasm.ts:12:18 TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'. console.log(mul(2, "A")); ~~~ at file:///Users/1068/dev/deno-test/usewasm.ts:18:20 Found 2 errors.
既存の書き方では、型の解釈が望ましいものではなく、関数mulが呼び出しできない可能性を指摘します。 新しいimportでは、wasmからインポートした関数の型の不整合まで指摘をしてくれるようになっています。
また、これらの指摘は、deno の language serverにおいても指摘の対象になっており、VScodeでは拡張機能を導入していれば以下のキャプチャのようにヒントを出します。
wasmファイルが公開する関数とその型を解釈できているのは、開発の効率にも寄与するように感じます。
Deno 2.1 から、wasm importについてフォーカスし紹介してきました。 最近ですとduckdb-wasmが話題になるなど、これまでwasmに縁がなかった方も触る機会が増えてくるのかもしれません。 「wasmを使うクライアントにDenoを使ってみる」という体験、いかがでしょうか?
追記:Deno 2.1.2より、wasm インポートについて機能追加がありました。
- wasmからの非関数のエクスポートをのサポート追加
- lsp 言語サーバーが、import 宣言の候補にwasm追加
Fantia開発採用情報
虎の穴ラボでは現在、一緒にFantiaを開発していく仲間を積極募集中です!
多くのユーザーに使っていただけるtoCサービスの開発をやってみたい方は、ぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp