TypeScript風に書いてWASMファイルを出力する。
成果物
情報源
前提
手順
sudo npm install -g assemblyscript
バージョン確認
asc --version
Version 0.20.3
asc
コマンドが遅い。10秒以上かかる。これはひどい。バージョン値を返すのに10秒もかかったのはお前がはじめてだ。ノロマ大賞をくれてやる。本当にこれを使ったら速くなるのか? すでにお前が遅い件。説得力なさすぎィ!
$ time asc --version Version 0.20.3 real 0m10.888s user 0m36.016s sys 0m1.884s
ヘルプ確認
asc --help
ヘルプ内容
SYNTAX asc [entryFile ...] [options] EXAMPLES asc hello.ts asc hello.ts -o hello.wasm -t hello.wat asc hello1.ts hello2.ts -o -O > hello.wasm asc --config asconfig.json --target release OPTIONS General --version, -v Prints just the compiler's version and exits. --help, -h Prints this message and exits. --config Configuration file to apply. CLI arguments take precedence. --target Configuration file target to use. Defaults to 'release'. Optimization --optimize, -O Optimizes the module. Typical shorthands are: Default optimizations -O Make a release build -O --noAssert Make a debug build --debug Optimize for speed -Ospeed Optimize for size -Osize --optimizeLevel How much to focus on optimizing code. [0-3] --shrinkLevel How much to focus on shrinking code size. [0-2, s=1, z=2] --converge Re-optimizes until no further improvements can be made. --noAssert Replaces assertions with just their value without trapping. Output --outFile, -o Specifies the WebAssembly output file (.wasm). --textFile, -t Specifies the WebAssembly text output file (.wat). --bindings, -b Specifies the bindings to generate (.js + .d.ts). esm JavaScript bindings & typings for ESM integration. raw Like esm, but exports just the instantiate function. Useful where modules are meant to be instantiated multiple times or non-ESM imports must be provided. Debugging --sourceMap Enables source map generation. Optionally takes the URL used to reference the source map from the binary file. --debug Enables debug information in emitted binaries. Features --importMemory Imports the memory from 'env.memory'. --noExportMemory Does not export the memory as 'memory'. --initialMemory Sets the initial memory size in pages. --maximumMemory Sets the maximum memory size in pages. --sharedMemory Declare memory as shared. Requires maximumMemory. --zeroFilledMemory Assume imported memory is zeroed. Requires importMemory. --importTable Imports the function table from 'env.table'. --exportTable Exports the function table as 'table'. --exportStart Exports the start function using the specified name instead of calling it implicitly. Useful for WASI or to obtain the exported memory before executing any code accessing it. --runtime Specifies the runtime variant to include in the program. incremental TLSF + incremental GC (default) minimal TLSF + lightweight GC invoked externally stub Minimal runtime stub (never frees) ... Path to a custom runtime implementation --exportRuntime Exports the runtime helpers (__new, __collect etc.). --stackSize Overrides the stack size. Only relevant for incremental GC or when using a custom runtime that requires stack space. Defaults to 0 without and to 16384 with incremental GC. --enable Enables WebAssembly features being disabled by default. threads Threading and atomic operations. simd SIMD types and operations. reference-types Reference types and operations. gc Garbage collection (WIP). --disable Disables WebAssembly features being enabled by default. mutable-globals Mutable global imports and exports. sign-extension Sign-extension operations nontrapping-f2i Non-trapping float to integer ops. bulk-memory Bulk memory operations. --use, -u Aliases a global object under another name, e.g., to switch the default 'Math' implementation used: --use Math=JSMath Can also be used to introduce an integer constant. --lowMemoryLimit Enforces very low (<64k) memory constraints. Linking --memoryBase Sets the start offset of emitted memory segments. --tableBase Sets the start offset of emitted table elements. API --transform Specifies the path to a custom transform to load. Binaryen --trapMode Sets the trap mode to use. allow Allow trapping operations. This is the default. clamp Replace trapping operations with clamping semantics. js Replace trapping operations with JS semantics. --runPasses Specifies additional Binaryen passes to run after other optimizations, if any. See: Binaryen/src/passes/pass.cpp --noValidate Skips validating the module using Binaryen. Other --baseDir Specifies the base directory of input and output files. --noColors Disables terminal colors. --noUnsafe Disallows the use of unsafe features in user code. Does not affect library files and external modules. --noEmit Performs compilation as usual but does not emit code. --showConfig Print computed compiler options and exit. --stats Prints statistics on I/O and compile times. --pedantic Make yourself sad for no good reason. --lib Adds one or multiple paths to custom library components and uses exports of all top-level files at this path as globals. --path Adds one or multiple paths to package resolution, similar to node_modules. Prefers an 'ascMain' entry in a package's package.json and falls back to an inner 'assembly/' folder. --wasm Uses the specified Wasm binary of the compiler. -- ... Specifies node.js options (CLI only). See: node --help
コードを書く
hello.ts
export function increment(num: u32) : u32 { return num + 1; }
ビルドする
テキスト形式とバイナリ形式の二種類ある。それぞれ以下のように出力する。
asc hello.ts >| hello.wat asc -o hello.wasm hello.ts
出力内容を確認してみる。
cat hello.wasm
(module (type $i32_=>_i32 (func (param i32) (result i32))) (global $~lib/memory/__data_end i32 (i32.const 8)) (global $~lib/memory/__stack_pointer (mut i32) (i32.const 16392)) (global $~lib/memory/__heap_base i32 (i32.const 16392)) (memory $0 0) (table $0 1 funcref) (elem $0 (i32.const 1)) (export "increment" (func $hello-ok/increment)) (export "memory" (memory $0)) (func $hello-ok/increment (param $0 i32) (result i32) local.get $0 i32.const 1 i32.add ) )
cat hello.wasm
asm`pA A�� A�� incrementmemory A Aj
遅い。上記2ビルドするのに30秒くらいかかった。
$ time ./build.sh real 0m24.133s user 1m15.491s sys 0m4.085s
つぎはwasmファイルをjsから呼び出す。
wasmをjsから呼出
- hello.wasm
- load-wasm.js
- index.html
load-wasm.js
WebAssembly.instantiateStreaming(fetch("hello.wasm"), {}).then(mod => { const increment = mod.instance.exports.increment; const result = increment(999); console.log(result); console.assert(result === 1000); document.querySelector('body').innerText = result; });
実装したincrement
関数を呼び出していることがわかる。
index.html
<script src="load-wasm.js"></script>
これを実行すのだが、fetch APIを使っている。こいつがあるせいでローカルサーバが必要。
index.htmlがあるパスまでカレントディレクトリを移動させ、以下のコマンドを実行する。
server.sh
#!/bin/bash python3 -m http.server 8000 & sleep 1 URL=http://0.0.0.0:8000/ echo "$URL" | xsel -bi chromium-browser "$URL"
つぎにブラウザでhttp://0.0.0.0:8000/
にアクセスする。HTMLに1000
と表示されたらOK。DEMOをみればいいよ。
蛇足
エラー
ちなみに前回のTypeScriptのコードのままビルドしようとするとエラーになった。
ERROR TS1110: Type expected. function increment(num: number) { ^ in hello-ok.ts(1,32) FAILURE 1 parse error(s) ERROR TS1110: Type expected. function increment(num: number) { ^ in hello-ng.ts(1,32) FAILURE 1 parse error(s)
コードを見比べてみると別物だということがよくわかる。
TypeScript
function increment(num: number) { return num + 1; } console.log(increment(999));
AssemblyScript
export function increment(num: u32) : u32 { return num + 1; }
騙された
「は? AsseblyScriptってTypeScriptからwasmファイルにビルドするコンパイラじゃなかったのかよ!」とキレそうになった。騙された。だれだよ、こんな嘘ついたやつ。全然コード別ものやんけ!
まーた簡単詐欺かよ。「簡単だよ〜」という甘い言葉に誘われて騙されたのは何度目だろうか。いいかげんにしてくれ。はい、勝手に勘違いした私が悪いんですよね。知ってます。
所感
ビルドに時間がかかる。これはしょうがない。
TypeScriptとは別の型名である。つまり新たな学習コストがかかるということだ。
なんにせよ、複雑な計算をするときはAssemblyScriptでコードを書いてバイナリ化すれば高速化できる。それをJSで呼び出す処理を書かねばならないのが面倒だし、fetch APIのせいでローカルサーバも必要だから動作確認も面倒だが。
対象環境
- Raspbierry pi 4 Model B
- Raspberry Pi OS buster 10.0 2020-08-20 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 5.10.63-v7l+ #1496 SMP Wed Dec 1 15:58:56 GMT 2021 armv7l GNU/Linux