2
3

開発ツールに頼らず 様々な言語から WebAssembly(第7回)

Last updated at Posted at 2023-10-16

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());

EvenCounterOddCounter は、それぞれ counter というプロパティを持っている。
Counter は上記のどちらかのインスタンスをプロパティに持ち、メソッド incrementget を呼ばれた時にその処理を委譲する。

本記事では上記の様な 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回 と同じ方法で関数 getincrement を実行している。
より詳細に説明すると、以下だ。

  1. 関数 get を実行。( evenCounter のグローバル変数 $counter の初期値 0 が表示される。)
  2. 関数 increment を実行。( evenCounter のグローバル変数 $counter の値が 2 増加する。)
  3. 関数 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 な参照を得ることが出来ない。

なので、プロパティーの型に i64std::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回 と同じ方法で関数 getincrement を実行している。
より詳細に説明すると、以下だ。

  1. 関数 get を実行。( even_counter のグローバル変数 $counter の初期値 0 が表示される。)
  2. 関数 increment を実行。( even_counter のグローバル変数 $counter の値が 2 増加する。)
  3. 関数 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回 と同じ方法で関数 getincrement を実行している。
より詳細に説明すると、以下だ。

  1. 関数 get を実行。( even_counter のグローバル変数 $counter の初期値 0 が表示される。)
  2. 関数 increment を実行。( even_counter のグローバル変数 $counter の値が 2 増加する。)
  3. 関数 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回 と同じ方法で関数 getincrement を実行している。
より詳細に説明すると、以下だ。

  1. 関数 get を実行。( even_counter のグローバル変数 $counter の初期値 0 が表示される。)
  2. 関数 increment を実行。( even_counter のグローバル変数 $counter の値が 2 増加する。)
  3. 関数 get を実行。( even_counter のグローバル変数 $counter の現在値 2 が表示される。)

まとめ

今回は 第6回 と同様の事を、別の方法で行った。
そして 第6回 では「説明のために書いたが、あまり乱用しない方が良い」と戒めるような事を書いたと思う。

今回紹介した「外部プログラムのオブジェクトを指す抽象ポインター」(以下、 externref )も、使わずに済むならば使わない方が良いと思う。
ただ、現実問題として externref は使わざるを得ないだろう。

本シリーズでは、説明のために意図的に「簡単な例」のみを挙げている。
扱うデータは整数がほとんどで、時々 String が出てくるくらいだ。

しかし実用的なプログラムを作る場合は、そうはいかないだろう。
例えばファイルを扱う場合を考えてみよう。

ファイルを扱うためには、WebAssembly は外部プログラムの力を借りる必要がある。
なぜならファイルを扱う際は「システムコール」を実行する必要が有り、WebAssembly では禁止されているのだ。

第1回 の冒頭に WebAssembly の制限を簡潔に記載したので、忘れた人は読んで欲しい。)

その際は(一部の例外を除き、)ほとんどの外部プログラムは「ファイルハンドル」や「ファイルポインター」と呼ばれるオブジェクトを扱うはずだ。

ならば WebAssembly は、それらのオブジェクトを扱う必要が有る。
その際には externref を使うしかない。

筆者は externref は「避けては通れない物」と認識している。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3