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 は「避けては通れない物」と認識している。