WebAssembly は多くのプログラム言語からライブラリの様に呼び出す事が出来る。
WebAssembly のコードを書く方法は多くの記事で書かれているが、その使い方はフレームワーク等のツールに頼っている事が多い様だ。
本記事では、そのような便利ツールに出来るだけ頼らず JavaScript, Rust, Python, Ruby から WebAssembly を実行する方法を記載する。
WebAssembly は新しい技術である。
目先の最先端ツールに飛びつくのもよいが、その基礎を学んで長く使える知識を身に着けないか?
本記事はシリーズの第6回である。シリーズ記事の一覧は 第1回 の #シリーズ記事一覧 に載せている。シリーズの記事を追加する時は 第1回 の一覧を更新して通知を行うので興味の有る人は 第1回 をストックして欲しい。
本記事の概要と過去記事の復習
第6回 では 3 種類の WebAssembly モジュール(クラスの様な物) を作成し、オブジェクト指向の「委譲の様な事を行った。」
具体的には WebAssembly インスタンスの持つ抽象ポインターテーブルに移譲先のインスタンスに紐づくメソッドを保持する事で委譲先のメソッドを呼び出した。
また、登場するインスタンスは全て WebAssembly インスタンスであった。
今回は 第6回 と同様のプログラムを別の方法で実行してみる。
具体的には、WebAssembly インスタンスの持つグローバル変数に委譲先のオブジェクトの抽象ポインターを保持する。
また、委譲先のオブジェクトは外部プログラムのオブジェクトである。
イメージとしては、JavaScript の下記の様なコードを想定する。
(このコードは 第6回 と全く同じ物である。)
class EvenCounter {
constructor() {
this.counter = 0;
}
increment() {
this.counter += 2;
}
get() {
return this.counter;
}
}
class OddCounter {
constructor() {
this.counter = 1;
}
increment() {
this.counter += 2;
}
get() {
return this.counter;
}
}
class Counter {
constructor() {
this.counter = null;
}
increment() {
this.counter.increment();
}
get() {
return this.counter.get();
}
}
let evenCounter = new EvenCounter();
let oddCounter = new OddCounter();
let counter = new Counter();
counter.counter = evenCounter;
console.log(counter.get());
counter.increment();
console.log(counter.get());
counter.counter = oddCounter;
console.log(counter.get());
counter.increment();
console.log(counter.get());
EvenCounter
や OddCounter
は、それぞれ counter
というプロパティを持っている。
Counter
は上記のどちらかのインスタンスをプロパティに持ち、メソッド increment
や get
を呼ばれた時にその処理を委譲する。
本記事では上記の様な WebAssembly のコードを実行する下記の外部プログラムを作成する。
- Vanilla JS (フレームワークやライブラリを使わない、そのままの JavaScript)から WebAssembly を実行
- Rust, Python, Ruby から低レイヤーのライブラリ wasmtime だけを使って WebAssembly を実行
なお、説明の都合上 Vanilla JS から実行 の章は JavaScript に興味が無くとも読んでほしい。
(そんなに難しい事はやっていないので JavaScript を知らなくとも本質的な部分は理解できるはずだ。)
それ以外の興味の無い言語の章については読み飛ばして問題無い。
ところで「外部プログラムへの参照を行う抽象ポインター」には、「これが無ければ WebAssembly の価値が半減する」と言える程の重要な役割が存在する。
そこについては最後の まとめ 少し書いたので気が向いたら読んで欲しい。
WebAssembly コードの準備
過去記事同様、WebAssembly Text Format で動作確認をする WebAssembly のコードを作成し、wat2wasm でコンパイルする。
wabt のインストール
github の README.md を読んでコンパイルする。
C++ のコンパイル環境が必要なので注意。
wat2wasm というコマンドがコンパイルされるので、必要に応じて Path を通しておくと良いだろう。
wasm ファイルの準備
wasm ファイルとは、WebAssembly の規格に沿ったバイナリファイルの事である。
最初に各種言語から動かして動作確認するための wasm ファイルを用意しよう。
最初にテキストエディタで以下の様な内容のファイルを作成し /tmp/counter.wat
という名前で保存する。
(module
(import "host" "get" (func $delegate_get (param externref) (result i64)))
(import "host" "increment" (func $delegate_increment (param externref)))
(global $delegated (import "host" "delegated") externref)
(func (export "get") (result i64)
global.get $delegated
call $delegate_get)
(func (export "increment")
global.get $delegated
call $delegate_increment))
この wat ファイルは以下の事を行っている。
なお、下記の i64
とは符号付き 64 bit 整数、externref
とは外部プログラムのオブジェクトを指す抽象ポインターの事である。
- 外部プログラムから
host.get
という物を import する。これは関数であり、引数にexternref
を取り、i64
を返す。この関数に、この wasm ファイル内部から$delegate_get
という名前でアクセスできるようにする。 - 外部プログラムから
host.increment
という物を import する。これは関数であり、引数にexternref
を取り、値を返さない。値も無い。この関数に、この wasm ファイル内部からdelegate_increment
という名前でアクセスできるようにする。 - グローバル変数(プロパティ)を宣言。このグローバル変数に、この wasm ファイル内部から
$delegated
という名前でアクセスできるようにする。このグローバル変数はexternref
で外部プログラムのhost.delegated
という物を import する。 - 関数を宣言。この関数には外部から
get
という名前でアクセス可能。この関数は引数を取らずi64
型の値を 1 個返す。この関数は$delegate_get
に$delegated
を渡して実行する。 - 関数を宣言。この関数には外部から
increment
という名前でアクセス可能。この関数は引数も戻り値も無い。この関数は$delegate_increment
に$delegated
を渡して実行する。
WebAssembly 内部から externref
のメソッドを直接実行する事は出来ない。
メソッド実行方法は外部プログラムの仕様によって異なるからだ。
そのため、メソッドを実行するラッパー関数を外部からインポートする必要がある。
また、$delegated
の型が mut externref
では無く externref
である事にも注意しよう。
つまり、この参照は「不変」なのだ。
関数 increment
を実行すると、 externref
の指し示すオブジェクトは変化する。
しかし、変更されるのはあくまで「指し示しているオブジェクト」であり externref
自身は変更されない。
(「 externref
を変更する」とは「 externref
の指し示す先を別のオブジェクトに変更する」という事である。)
この wat ファイルを以下の様に wat2wasm コマンドでコンパイルすると /tmp
以下に counter.wasm
というバイナリファイルが出来る。
$ wat2wasm -o /tmp/counter.wasm /tmp/counter.wat
Vanilla JS から実行
説明の都合上、本章は JavaScript に興味の無い人でも斜めに読んで欲しい。
筆者の JavaScript 環境
- nodejs v18.13.0
Vanilla JS のコード
nodejs から WebAssembly を実行するコードは、例えば下記の様になる。
(冗長なので、EvenCounter を使った例だけを表示する。)
// Declare delegated classes
class EvenCounter {
constructor() {
this.counter = BigInt(0);
}
increment() {
this.counter += BigInt(2);
}
get() {
return this.counter;
}
}
// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/counter.wasm');
const mod = new WebAssembly.Module(wasm);
// Build Instance holding EvenCounter
const innerCounter = new EvenCounter();
const importObject = {
host: {
get: (externref) => {
return externref.get();
},
increment: (externref) => {
externref.increment();
},
delegated: innerCounter,
}
};
const counter = new WebAssembly.Instance(mod, importObject);
// Operation Check
console.log(`The count is ${counter.exports.get()} now.`);
console.log("Increment the count.")
counter.exports.increment();
console.log(`The count is ${counter.exports.get()} now.`);
上記のファイルを call_wasm.js
という名前で保存し実行すると、下記の様な結果を得る。
$ node call_wasm.js
The count is 0 now.
Increment the count.
The count is 2 now.
JavaScript のコードを少しずつ読んでみる。
(以下の 太字リンク は JavaScript のクラス名(コンストラクター名)である事を示す。リンク先は MDN Web Docs 。)
最初に以下を読んでみる。
// Declare delegate classes
class EvenCounter {
constructor() {
this.counter = BigInt(0);
}
increment() {
this.counter += BigInt(2);
}
get() {
return this.counter;
}
}
これは委譲先の class を宣言している。
WebAssembly とは関係ないので説明を省略する。
次に、以下の行を読んでみる。
// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/counter.wasm');
const mod = new WebAssembly.Module(wasm);
// Build Instance holding EvenCounter
const innerCounter = new EvenCounter();
const importObject = {
host: {
get: (externref) => {
return externref.get();
},
increment: (externref) => {
externref.increment();
},
delegated: innerCounter,
}
};
const counter = new WebAssembly.Instance(mod, importObject);
ここは 第2回 とほとんど同じだ。
第2回 でも外部プログラムから関数を import したが、今回は EvenCounter
のオブジェクトも一緒に import している。
最後に、以下の行を読んでみる。
// Operation Check
console.log(`The count is ${counter.exports.get()} now.`);
console.log("Increment the count.")
counter.exports.increment();
console.log(`The count is ${counter.exports.get()} now.`);
ここでは 第1回 と同じ方法で関数 get
と increment
を実行している。
より詳細に説明すると、以下だ。
- 関数
get
を実行。(evenCounter
のグローバル変数$counter
の初期値0
が表示される。) - 関数
increment
を実行。(evenCounter
のグローバル変数$counter
の値が2
増加する。) - 関数
get
を実行。(evenCounter
のグローバル変数$counter
の現在値2
が表示される。)
wasmtime から実行
以降の章では wasmtime を使って Rust から実行, Python から実行, Ruby から実行 方法について、それぞれ説明している。
不要な章は読み飛ばしても大丈夫だ。
Rust から実行
筆者の Rust 環境
- cargo 1.73.0
- rustup 1.26.0
- rustc 1.73.0 stable-x86_64-unknown-linux-gnu
Rust のコード
下記の様に call_counter というプロジェクトを作成する。
$ cargo new call_counter
$ cd call_counter
Cargo.toml の [dependencies]
セクションに wasmtime をの設定を加える。
筆者の環境では Cargo.toml は下記の様になった。
[package]
name = "call_counter"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wasmtime = "13.0.0"
Rust から WebAssembly を実行するコードは、例えば下記の様になる。
use std::fs;
use std::sync::atomic::{AtomicI64, Ordering};
use wasmtime::*;
struct EvenCounter {
counter: AtomicI64,
}
impl EvenCounter {
fn new() -> Self {
Self {
counter: AtomicI64::new(0),
}
}
fn get(&self) -> i64 {
self.counter.load(Ordering::SeqCst)
}
fn increment(&self) {
self.counter.fetch_add(2, Ordering::SeqCst);
}
}
fn main() -> wasmtime::Result<()> {
// Build `Engine`
let engine = Engine::default();
// Build `Module`
let wasm = fs::read("/tmp/counter.wasm").unwrap();
let module = Module::new(&engine, wasm)?;
// Build `Store`
let mut store = Store::new(&engine, ());
// Create functions for wasm to import
let get = Func::wrap(&mut store, |externref: Option<ExternRef>| -> i64 {
let externref = externref.unwrap();
let even_counter = externref.data().downcast_ref::<EvenCounter>().unwrap();
even_counter.get()
});
let increment = Func::wrap(&mut store, |externref: Option<ExternRef>| {
let externref = externref.unwrap();
let even_counter = externref.data().downcast_ref::<EvenCounter>().unwrap();
even_counter.increment();
});
// Create instance for wasm to import
let inner_counter = EvenCounter::new();
let externref = ExternRef::new(inner_counter);
let ty = GlobalType::new(ValType::ExternRef, Mutability::Const);
let global = Global::new(&mut store, ty, Val::ExternRef(Some(externref)))?;
// Build `Instance`. (`Linker` is builder class.)
let mut linker = Linker::new(&engine);
linker.define(&store, "host", "get", get)?;
linker.define(&store, "host", "increment", increment)?;
linker.define(&store, "host", "delegated", global)?;
let instance = linker.instantiate(&mut store, &module)?;
// Operation check
let get = instance.get_typed_func::<(), i64>(&mut store, "get")?;
let increment = instance.get_typed_func::<(), ()>(&mut store, "increment")?;
println!("The count is {} now.", get.call(&mut store, ())?);
println!("Increment the count.");
increment.call(&mut store, ())?;
println!("The count is {} now.", get.call(&mut store, ())?);
Ok(())
}
上記の内容で src/main.rs
を上書きして実行すると、下記の様な結果を得る。
$ cargo run
Compiling call_counter v0.1.0 (/home/wbcchsyn/tmp/call_counter)
Finished dev [unoptimized + debuginfo] target(s) in 4.45s
Running `target/debug/call_counter`
The count is 0 now.
Increment the count.
The count is 2 now.
上記の Rust コードを少しずつ解説していく。
以下で 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Rust 版 wasmtime の公式ドキュメント 。
まずはコードの全体像を把握しよう。
use std::fs;
use wasmtime::*;
struct EvenCounter {
...
}
impl EvenCounter {
...
}
fn main() -> wasmtime::Result<()> {
...
}
見ての通り、本コードは main 関数の他に EvenCounter
が定義してある。
以下、コードを少しずつ読んでみる。
use std::fs;
use std::sync::atomic::{AtomicI64, Ordering};
use wasmtime::*;
struct EvenCounter {
counter: AtomicI64,
}
impl EvenCounter {
fn new() -> Self {
Self {
counter: AtomicI64::new(0),
}
}
fn get(&self) -> i64 {
self.counter.load(Ordering::SeqCst)
}
fn increment(&self) {
self.counter.fetch_add(2, Ordering::SeqCst);
}
}
これは委譲先の struct とその method を宣言している。
WebAssembly とは関係ないので詳しく説明しないが、1 点だけ補足が有る。
wasmtime は thread を考慮に入れている。
基本的に wasmtime の抽象ポインターが指し示す struct は Sync
trait を実装している必要が有る。
また、wasmtime は mutable な参照を得ることが出来ない。
なので、プロパティーの型に i64
や std::cell::Cell<i64>
をそのまま使う事は出来ない。
そのため、ここでは std::sync::atomic::AtomicI64
を使った。
続いて main 関数の中身を上から少しずつ読んでみる。
// Build `Engine`
let engine = Engine::default();
// Build `Module`
let wasm = fs::read("/tmp/counter.wasm").unwrap();
let module = Module::new(&engine, wasm)?;
// Build `Store`
let mut store = Store::new(&engine, ());
ここは 第1回 とほとんど同じだ。
続いて以下の行を読んでみる。
// Create functions for wasm to import
let get = Func::wrap(&mut store, |externref: Option<ExternRef>| -> i64 {
let externref = externref.unwrap();
let even_counter = externref.data().downcast_ref::<EvenCounter>().unwrap();
even_counter.get()
});
let increment = Func::wrap(&mut store, |externref: Option<ExternRef>| {
let externref = externref.unwrap();
let even_counter = externref.data().downcast_ref::<EvenCounter>().unwrap();
even_counter.increment();
});
ここでは 第2回 と同様に WebAssembly に import する関数を定義しようとしている。
続いて、以下の行を読んでみる。
// Create a global variable holding a reference to EvenCounter.
let inner_counter = EvenCounter::new();
let externref = ExternRef::new(inner_counter);
let ty = GlobalType::new(ValType::ExternRef, Mutability::Const);
let global = Global::new(&mut store, ty, Val::ExternRef(Some(externref)))?;
ここでは 第4回 と同様に externref
を含むグローバル変数と、その指し示す先の実態 (inner_counter
) を作成している。
wasm ファイルの準備
でも説明したように、このグローバル変数は不変である。
なので ty
作成時に Mutability::Const
を指定している事に注意しよう。
続いて以下の行を読んでみる。
// Build `Instance`. (`Linker` is builder class.)
let mut linker = Linker::new(&engine);
linker.define(&store, "host", "get", get)?;
linker.define(&store, "host", "increment", increment)?;
linker.define(&store, "host", "delegated", global)?;
let instance = linker.instantiate(&mut store, &module)?;
ここでは 第2回 と同様の方法で Instance を作成している。
最後に以下の行を読んでみる。
// Operation check
let get = counter.get_typed_func::<(), i32>(&mut store, "get")?;
let increment = counter.get_typed_func::<(), ()>(&mut store, "increment")?;
println!("{}", get.call(&mut store, ())?);
increment.call(&mut store, ())?;
println!("{}", get.call(&mut store, ())?);
ここでは 第1回 と同じ方法で関数 get
と increment
を実行している。
より詳細に説明すると、以下だ。
- 関数
get
を実行。(even_counter
のグローバル変数$counter
の初期値0
が表示される。) - 関数
increment
を実行。(even_counter
のグローバル変数$counter
の値が2
増加する。) - 関数
get
を実行。(even_counter
のグローバル変数$counter
の現在値2
が表示される。)
Python から実行
筆者の Python 環境
- Python 3.11.2
- pip 23.0.1
Python のコード
最初に Python の version と環境に応じた方法で wasmtime をインストールする。
$ pip install wasmtime
Collecting wasmtime
Downloading wasmtime-13.0.2-py3-none-manylinux1_x86_64.whl (7.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.1/7.1 MB 1.7 MB/s eta 0:00:00
Installing collected packages: wasmtime
Successfully installed wasmtime-13.0.2
Python から WebAssembly を実行するコードは、例えば下記の様になる。
from wasmtime import Engine, Func, FuncType, Linker, Module, Store, ValType, Val, Global, GlobalType
class EvenCounter:
def __init__(self):
self.count = 0
def get(self):
return self.count
def increment(self):
self.count += 2
# Build Engine.
engine = Engine()
# Build Module.
wasm = open('/tmp/counter.wasm', 'rb').read()
module = Module(engine, wasm)
# Build Store.
store = Store(engine)
# Create functions to import.
i64 = ValType.i64()
externref = ValType.externref()
get = Func(store, FuncType([externref], [i64]), lambda exref: exref.get())
increment = Func(store, FuncType([externref], []), lambda exref: exref.increment())
# Create a global variable holding a reference to EvenCounter.
innerCounter = EvenCounter()
exref = Val.externref(innerCounter)
ty = GlobalType(externref, False)
exref_global = Global(store, ty, exref)
# Build Instance. (Linker is builder class.)
linker = Linker(engine)
linker.define(store, "host", "get", get)
linker.define(store, "host", "increment", increment)
linker.define(store, "host", "delegated", exref_global)
instance = linker.instantiate(store, module)
# Operation check.
print("The count is %d now" % instance.exports(store)['get'](store))
print("Increment the count")
instance.exports(store)['increment'](store)
print("The count is %d now" % instance.exports(store)['get'](store))
上記のファイルを call_wasm.py
という名前で保存し実行すると、下記の様な結果を得る。
$ python call_wasm.py
The count is 0 now
Increment the count
The count is 2 now
上記の Python コードを少しずつ解説していく。
以下で 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Python 版 wasmtime の公式ドキュメント 。
最初は少しまとめて読んで見る。
from wasmtime import Engine, Func, FuncType, Linker, Module, Store, ValType, Val, Global, GlobalType
class EvenCounter:
def __init__(self):
self.count = 0
def get(self):
return self.count
def increment(self):
self.count += 2
ここでは委譲先の class を宣言している。
WebAssembly とはあまり関係無いので説明を省略する。
続いて以下の行を読んでみる。
# Build Engine.
engine = Engine()
# Build Module.
wasm = open('/tmp/counter.wasm', 'rb').read()
module = Module(engine, wasm)
# Build Store.
store = Store(engine)
ここは 第1回 とほとんど同じだ。
続いて以下の行を読んでみる。
# Create functions to import.
i64 = ValType.i64()
externref = ValType.externref()
get = Func(store, FuncType([externref], [i64]), lambda exref: exref.get())
increment = Func(store, FuncType([externref], []), lambda exref: exref.increment())
ここでは 第2回 と同様に WebAssembly に import する関数を定義しようとしている。
続いて以下の行を読んでみる。
# Create a global variable holding a reference to EvenCounter.
inner_counter = EvenCounter()
exref = Val.externref(inner_counter)
ty = GlobalType(externref, False)
exref_global = Global(store, ty, exref)
ここでは 第4回 と同様に externref
を含むグローバル変数と、その指し示す先の実態 (inner_counter
) を作成している。
wasm ファイルの準備
でも説明したように、このグローバル変数は不変である。
なので GlobalType の引数の 2 個目が False
である事に注意をしよう。
続いて以下の行を読んでみる。
# Build Instance. (Linker is builder class.)
linker = Linker(engine)
linker.define(store, "host", "get", get)
linker.define(store, "host", "increment", increment)
linker.define(store, "host", "delegated", exref_global)
instance = linker.instantiate(store, module)
ここでは 第2回 と同様の方法で Instance を作成している。
最後に以下の行を読んでみる。
# Operation check.
print("The count is %d now" % instance.exports(store)['get'](store))
print("Increment the count")
instance.exports(store)['increment'](store)
print("The count is %d now" % instance.exports(store)['get'](store))
ここでは 第1回 と同じ方法で関数 get
と increment
を実行している。
より詳細に説明すると、以下だ。
- 関数
get
を実行。(even_counter
のグローバル変数$counter
の初期値0
が表示される。) - 関数
increment
を実行。(even_counter
のグローバル変数$counter
の値が2
増加する。) - 関数
get
を実行。(even_counter
のグローバル変数$counter
の現在値2
が表示される。)
Ruby から実行
筆者の Ruby 環境
- ruby 3.1.2p20
Ruby のコード
最初に gem で wasmtime をインストールする。
(必要に応じて bundler 等を使うと良いだろう。)
$ gem install wasmtime
Fetching wasmtime-13.0.0-x86_64-linux.gem
Successfully installed wasmtime-13.0.0-x86_64-linux
Parsing documentation for wasmtime-13.0.0-x86_64-linux
Installing ri documentation for wasmtime-13.0.0-x86_64-linux
Done installing documentation for wasmtime after 0 seconds
1 gem installed
Ruby から WebAssembly を実行するコードは、例えば下記の様になる。
require 'wasmtime'
class EvenCounter
def initialize
@count = 0
end
def get
@count
end
def increment
@count += 2
end
end
# Build Engine
engine = Wasmtime::Engine.new
# Build Module
wasm = open('/tmp/counter.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)
# Build Store
store = Wasmtime::Store.new(engine)
# Create functions to import from WebAssembly
get = Wasmtime::Func.new(store, [:externref], [:i64]) { |_caller, extref|
extref.get
}
increment = Wasmtime::Func.new(store, [:externref], []) { |_caller, extref|
extref.increment
}
# Create a global variable holding a reference to EvenCounter.
inner_counter = EvenCounter.new
exref_global = Wasmtime::Global.const(store, :externref, inner_counter)
# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "get", get)
linker.define(store, "host", "increment", increment)
linker.define(store, "host", "delegated", exref_global)
counter = linker.instantiate(store, mod)
# Operation check
puts("The count is %d now." % counter.export('get').to_func.call)
puts("Increment the count.")
counter.export('increment').to_func.call
puts("The count is %d now." % counter.export('get').to_func.call)
上記の Ruby コードを少しずつ解説していく。
以下で 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Ruby 版 wasmtime の公式ドキュメント 。
最初は少しまとめて読んで見る。
require 'wasmtime'
class EvenCounter
def initialize
@count = 0
end
def get
@count
end
def increment
@count += 2
end
end
ここでは委譲先の class を宣言している。
WebAssembly とはあまり関係無いので説明を省略する。
続いて以下の行を読んでみる。
# Build Engine
engine = Wasmtime::Engine.new
# Build Module
wasm = open('/tmp/counter.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)
# Build Store
store = Wasmtime::Store.new(engine)
ここは 第1回 とほとんど同じだ。
続いて以下の行を読んでみる。
# Create functions to import from WebAssembly
get = Wasmtime::Func.new(store, [:externref], [:i64]) { |_caller, extref|
extref.get
}
increment = Wasmtime::Func.new(store, [:externref], []) { |_caller, extref|
extref.increment
}
ここでは 第2回 と同様に WebAssembly に import する関数を定義しようとしている。
続いて以下の行を読んでみる。
# Create a global variable holding a reference to EvenCounter.
inner_counter = EvenCounter.new
exref_global = Wasmtime::Global.const(store, :externref, inner_counter)
ここでは 第4回 と同様に externref
を含むグローバル変数と、その指し示す先の実態( inner_counter
)を作成している。
wasm ファイルの準備
でも説明したように、このグローバル変数は不変である。
なので Wasmtime::Global.const を使ってグローバル変数を作成している事に注意しよう。
続いて以下の行を読んでみる。
# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "get", get)
linker.define(store, "host", "increment", increment)
linker.define(store, "host", "delegated", exref_global)
counter = linker.instantiate(store, mod)
ここでは 第2回 と同様の方法で Instance を作成している。
最後に以下の行を読んでみる。
# Operation check
puts("The count is %d now." % counter.export('get').to_func.call)
puts("Increment the count.")
counter.export('increment').to_func.call
puts("The count is %d now." % counter.export('get').to_func.call)
ここでは 第1回 と同じ方法で関数 get
と increment
を実行している。
より詳細に説明すると、以下だ。
- 関数
get
を実行。(even_counter
のグローバル変数$counter
の初期値0
が表示される。) - 関数
increment
を実行。(even_counter
のグローバル変数$counter
の値が2
増加する。) - 関数
get
を実行。(even_counter
のグローバル変数$counter
の現在値2
が表示される。)
まとめ
今回は 第6回 と同様の事を、別の方法で行った。
そして 第6回 では「説明のために書いたが、あまり乱用しない方が良い」と戒めるような事を書いたと思う。
今回紹介した「外部プログラムのオブジェクトを指す抽象ポインター」(以下、 externref
)も、使わずに済むならば使わない方が良いと思う。
ただ、現実問題として externref
は使わざるを得ないだろう。
本シリーズでは、説明のために意図的に「簡単な例」のみを挙げている。
扱うデータは整数がほとんどで、時々 String が出てくるくらいだ。
しかし実用的なプログラムを作る場合は、そうはいかないだろう。
例えばファイルを扱う場合を考えてみよう。
ファイルを扱うためには、WebAssembly は外部プログラムの力を借りる必要がある。
なぜならファイルを扱う際は「システムコール」を実行する必要が有り、WebAssembly では禁止されているのだ。
( 第1回 の冒頭に WebAssembly の制限を簡潔に記載したので、忘れた人は読んで欲しい。)
その際は(一部の例外を除き、)ほとんどの外部プログラムは「ファイルハンドル」や「ファイルポインター」と呼ばれるオブジェクトを扱うはずだ。
ならば WebAssembly は、それらのオブジェクトを扱う必要が有る。
その際には externref
を使うしかない。
筆者は externref
は「避けては通れない物」と認識している。