0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

WebAssembly は多くのプログラム言語からライブラリの様に呼び出す事が出来る。
WebAssembly のコードを書く方法は多くの記事で書かれているが、その使い方はフレームワーク等のツールに頼っている事が多い様だ。
本記事では、そのような便利ツールに出来るだけ頼らず JavaScript, Rust, Python, Ruby から WebAssembly を実行する方法を記載する。

WebAssembly は新しい技術である。
目先の最先端ツールに飛びつくのもよいが、その基礎を学んで長く使える知識を身に着けないか?

本記事はシリーズの第3回である。シリーズ記事の一覧は 第1回#シリーズ記事一覧 に載せている。シリーズの記事を追加する時は 第1回 の一覧を更新して通知を行うので興味の有る人は 第1回 をストックして欲しい。

本記事の概要と過去記事の復習

第3回第4回 は WebAssembly のコードに状態を持たせる方法について記載した。
今回は WebAssembly に「クラス変数」の様な物を追加するコードを紹介する。

第4回 の最後に「文字列を保持する可変長配列」について予告の様な事をしましたが、その記事は説明の都合上後回しにします。)

「クラス変数」とは「そのクラスの名前空間に保存された変数」の様な物だ。
多くの言語で「そのクラスのインスタンス間で共有する変数」の様な扱いが出来る。

筆者の知る限り、JavaScript にはクラス変数に相当する物は無い。
しかしモジュールの中で定義されたグローバル変数はクラス変数と少し似ているかもしれない。

例えば、以下の様な JavaScript のモジュールを考えてみる。

let next_id = 0;
const FIRST_ID = 0;

class Counter {
    constructor() {
        this.__id = next_id;
        next_id += 1;
    }

    get_id() {
        return this.__id;
    }

    is_first() {
        return this.__id === FIRST_ID;
    }
}

Counter というクラスの全インスタンスは next_id, FIRST_ID という 2 個の変数を共有している。

WebAssembly も JavaScript と同様にクラス変数に相当する物は存在しない。
しかし、工夫次第で「クラス変数の様な物」を作る事は可能である。

本記事では上記の様な WebAssembly のコードを実行する下記の外部プログラムを作成する。

  • Vanilla JS (フレームワークやライブラリを使わない、そのままの JavaScript)から WebAssembly を実行
  • Rust, Python, Ruby から低レイヤーのライブラリ wasmtime だけを使って WebAssembly を実行

なお、説明の都合上 Vanilla JS から実行 の章は JavaScript に興味が無くとも読んでほしい。
(そんなに難しい事はやっていないので JavaScript を知らなくとも本質的な部分は理解できるはずだ。)
それ以外の興味の無い言語の章については読み飛ばして問題無い。

WebAssembly コードの準備

過去記事同様、WebAssembly Text Format で動作確認をする WebAssembly のコードを作成し、wat2wasm でコンパイルする。

wabt のインストール

github の README.md を読んでコンパイルする。
C++ のコンパイル環境が必要なので注意。

wat2wasm というコマンドがコンパイルされるので、必要に応じて Path を通しておくと良いだろう。

wasm ファイルの準備

wasm ファイルとは、WebAssembly の規格に沿ったバイナリファイルの事である。
最初に各種言語から動かして動作確認するための wasm ファイルを用意しよう。

テキストエディタで以下の様な内容のファイルを作成し /tmp/counter.wat という名前で保存する。

(module
    (import "host" "next_id" (global $next_id (mut i64)))
    (import "host" "FIRST_ID" (global $FIRST_ID i64))

    (global $id (mut i64) (i64.const -1))

    (func $init 
        ;; Initialize the 
        global.get $next_id
        global.set $id

        ;; Increment $next_id
        global.get $next_id
        i64.const 1
        i64.add
        global.set $next_id)

    (func (export "get_id") (result i64)
        global.get $id)

    (func (export "is_first") (result i32)
        global.get $FIRST_ID
        global.get $id
        i64.eq)

    (start $init))

上記は WebAssembly Text Format という wasm ファイルのソースコードだ。
多くの読者に内容を何となく理解してもらうには、これが良いと思って選択した。

上記コードは大まかに以下の事を行っている。
(以下で i64 とは「符号付 64 bit 整数」、 i32 とは「符号付 32 bit 整数」の事である。)

  • 外部プログラムから "host.next_id" というグローバル変数を import。このグローバル変数は可変の i64 型である。この WebAssembly 内部からは $next_id という名前でアクセス可能にしている。
  • 外部プログラムから "host.FIRST_ID" というグローバル変数を import。このグローバル変数は不変の i64 型である。この WebAssembly 内部からは $FIRST_ID という名前でアクセス可能にしている。
  • グローバル変数を宣言。このグローバル変数は可変の i64 型であり、 -1 で初期化される。この WebAssembly 内部からは $id という名前でアクセス可能にしている。
  • 関数を定義。この関数は引数をとらず、戻り値も無い。この WebAssembly 内部からは $init という名前でアクセス可能にしている。
  • 関数を定義。この関数は引数を取らず、 i64 型を 1 個返す。外部プログラムから get_id という名前でアクセス可能にしている。
  • 関数を定義。この関数は引数をとらず、 i32 型を 1 個返す。外部プログラムから is_first という名前でアクセス可能にしている。
  • インスタンス作成時に関数 $init を実行する。

補足すると、WebAssembly には「Boolean 型」と言われる型は存在しない。 i32 型の 0False の様に、 1True の様に使用する。

関数 $init$id$next_id の値を代入し、その後 $next_id の値を 1 増加させる。 関数 get_id は変数 $id の値を返す。関数 is_first は、 $id$FIRST_ID と等しければ 1 を、異なれば 0 を返す。

この wat ファイルを以下の様に wat2wasm コマンドでコンパイルすると /tmp/counter.wasm というバイナリファイルが出来る。

$ wat2wasm -o /tmp/counter.wasm /tmp/counter.wat
$ ls /tmp/counter.wasm
/tmp/counter.wasm

Vanilla JS から実行

説明の都合上、本章は JavaScript に興味の無い人でも斜めに読んで欲しい。

筆者の JavaScript 環境

  • nodejs v18.16.1

Vanilla JS のコード

nodejs から WebAssembly を実行するコードは、例えば下記の様になる。

// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/counter.wasm');
const mod = new WebAssembly.Module(wasm);

// Build Instance
const importObject = {
    host: {
        next_id: new WebAssembly.Global({value: "i64", mutable: true}, BigInt(0)),
        FIRST_ID: new WebAssembly.Global({value: "i64", mutable: false}, BigInt(0)),
    }
};

const counter0 = new WebAssembly.Instance(mod, importObject);
const counter1 = new WebAssembly.Instance(mod, importObject);

// Show the id of counter0 and counter1.
console.log(`The id of counter0 is ${counter0.exports.get_id()}`);
console.log(`The id of counter1 is ${counter1.exports.get_id()}`);

// Show the first instance
if (counter0.exports.is_first()) {
    console.log("counter0 is the first instance.");
}
if (counter1.exports.is_first()) {
    console.log("counter1 is the first instance.");
}

上記のファイルを call_wasm.js という名前で保存し実行すると、下記の様な結果を得る。

$ node call_wasm.js
The id of counter0 is 0
The id of counter1 is 1
counter0 is the first instance.

JavaScript のコードを少しずつ読んでみる。
(以下の 太字リンク は JavaScript のクラス名(コンストラクター名)である事を示す。リンク先は MDN Web Docs 。)

最初に以下を読んでみる。

// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/counter.wasm');
const mod = new WebAssembly.Module(wasm);

ここまでは 第1回 と wasm ファイルのパス以外同じだ。

続いて以下の行を読んでみる。

// Build Instance
const importObject = {
    host: {
        next_id: new WebAssembly.Global({value: "i64", mutable: true}, BigInt(0)),
        FIRST_ID: new WebAssembly.Global({value: "i64", mutable: false}, BigInt(0)),
    }
};

const counter0 = new WebAssembly.Instance(mod, importObject);
const counter1 = new WebAssembly.Instance(mod, importObject);

ここも importObjectGlobal を持たせている事以外は 第2回 と同じだ。

私達の作成した wasm ファイルは next_id, FIRST_ID, id という 3 個のグローバル変数を持っている。
ここでは counter0counter1 の 2 個のインスタンスを作成しているが、どちらも同じ importObject を使用しているので、 next_idFIRST_ID は共通の物を参照する。( id は、それぞれ別の変数となる。)
そのため counter0 の初期化時に next_id を更新するが、その更新は counter1 にも反映される。

例えばこの部分を下記の様なコードにすると、 counter0 による next_id の更新が counter1 に反映されず、結果として両者の id0 となるだろう。

// Build Instance
const importObject = {
    host: {
        next_id: new WebAssembly.Global({value: "i64", mutable: true}, BigInt(0)),
        FIRST_ID: new WebAssembly.Global({value: "i64", mutable: false}, BigInt(0)),
    }
};
const counter0 = new WebAssembly.Instance(mod, importObject);

importObject.host.next_id = new WebAssembly.Global({value: "i64", mutable: true}, BigInt(0));
const counter1 = new WebAssembly.Instance(mod, importObject1);

次に以下の行を読んでみる。

// Show the id of counter0 and counter1.
console.log(`The id of counter0 is ${counter0.exports.get_id()}`);
console.log(`The id of counter1 is ${counter1.exports.get_id()}`);

// Show the first instance
if (counter0.exports.is_first()) {
    console.log("counter0 is the first instance.");
}
if (counter1.exports.is_first()) {
    console.log("counter1 is the first instance.");
}

ここは 第1回 と同様の方法で関数 get_idis_first を実行している。

wasmtime から実行

以降の章では wasmtime を使って Rust から実行, Python から実行, Ruby から実行 方法について、それぞれ説明している。
不要な章は読み飛ばしても大丈夫だ。

Rust から実行

筆者の Rust 環境

  • cargo 1.70.0
  • rustup 1.26.0
  • rustc 1.70.0 stable-x86_64-unknown-linux-gnu

Rust のコード

下記の様に call_person というプロジェクトを作成する。

$ cargo new call_counter
$ cd call_counter

Cargo.toml の [dependencies] セクションに wasmtime をの設定を加える。
筆者の環境では Cargo.toml は下記の様になった。

[package]
name = "call_person"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmtime = "10.0.1"

Rust から WebAssembly を実行するコードは、例えば下記の様になる。

use std::fs;
use wasmtime::*;

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 Global `next_id` and `first_id`
    let next_id_ty = GlobalType::new(ValType::I64, Mutability::Var);
    let next_id = Global::new(&mut store, next_id_ty, Val::I64(0))?;
    let first_id_ty = GlobalType::new(ValType::I64, Mutability::Const);
    let first_id = Global::new(&mut store, first_id_ty, Val::I64(0))?;

    // Build `Instance`. (`Linker` is builder class.)
    let mut linker = Linker::new(&engine);
    linker.define(&store, "host", "next_id", next_id)?;
    linker.define(&store, "host", "FIRST_ID", first_id)?;
    let counter0 = linker.instantiate(&mut store, &module)?;
    let counter1 = linker.instantiate(&mut store, &module)?;

    // Show the `id` of `counter0` and `counter1`.
    let get_id = counter0.get_typed_func::<(), i64>(&mut store, "get_id")?;
    println!("The id of counter0 is {}", &get_id.call(&mut store, ())?);
    let get_id = counter1.get_typed_func::<(), i64>(&mut store, "get_id")?;
    println!("The id of counter1 is {}", &get_id.call(&mut store, ())?);

    // Show the first instance.
    let is_first = counter0.get_typed_func::<(), i32>(&mut store, "is_first")?;
    if is_first.call(&mut store, ())? == 1 {
        println!("counter0 is the first instance.");
    }
    let is_first = counter1.get_typed_func::<(), i32>(&mut store, "is_first")?;
    if is_first.call(&mut store, ())? == 1 {
        println!("counter1 is the first instance.");
    }

    Ok(())
}

上記の内容で src/main.rs を上書きして実行すると、下記の様な結果を得る。

$ cargo run
   Compiling call_counter v0.1.0 (/home/wbcchsyn/tmp/class_variable/call_counter)
    Finished dev [unoptimized + debuginfo] target(s) in 3.44s
     Running `target/debug/call_counter`
The id of counter0 is 0
The id of counter1 is 1
counter0 is the first instance.

上記の Rust コードを少しずつ解説していく。
以下で 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Rust 版 wasmtime の公式ドキュメント

まずはコードの全体像を把握しよう。

use std::fs;
use wasmtime::*;

fn main() -> wasmtime::Result<()> {
...
}

見ての通り、本コードは main 関数が定義してあるだけである。

以下、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回 と wasm ファイルのパス以外同じだ。

続いて以下の行を読んでみる。

    // Create Global `next_id` and `first_id`
    let next_id_ty = GlobalType::new(ValType::I64, Mutability::Var);
    let next_id = Global::new(&mut store, next_id_ty, Val::I64(0))?;
    let first_id_ty = GlobalType::new(ValType::I64, Mutability::Const);
    let first_id = Global::new(&mut store, first_id_ty, Val::I64(0))?;

    // Build `Instance`. (`Linker` is builder class.)
    let mut linker = Linker::new(&engine);
    linker.define(&store, "host", "next_id", next_id)?;
    linker.define(&store, "host", "FIRST_ID", first_id)?;
    let counter0 = linker.instantiate(&mut store, &module)?;
    let counter1 = linker.instantiate(&mut store, &module)?;

ここも 第2回 とほとんど同じだ。
違いは linker.define の引数に Global を渡している事だけである。

first_id を大文字では無く小文字にした事に大きな理由は無い。大文字にすると Rust のコンパイラーが警告を出したので小文字にした。)

JavaScript の所でも説明したが、 counter0counter1 は同じ Global を参照している。そのため counter0 の初期化時に next_id の値を更新するが、その更新は counter1 にも反映される。

ところで、ここで Store に関する制限が有る。

GlobalInstance の各インスタンスは many-to-many の関係で紐づいている。
それとは別に、 StoreGlobalInstance は one-to-many の関係で紐づいている。

ここで関連する(紐づいている) GlobalInstance の各インスタンスは、全て同じ Store に紐づいている必要がある。

つまり、今回作成した next_id, first_id, counter0, counter1 は、同一の Store に紐づいている必要が有る。

続いて以下の行を読んでみる。

    // Show the `id` of `counter0` and `counter1`.
    let get_id = counter0.get_typed_func::<(), i64>(&mut store, "get_id")?;
    println!("The id of counter0 is {}", &get_id.call(&mut store, ())?);
    let get_id = counter1.get_typed_func::<(), i64>(&mut store, "get_id")?;
    println!("The id of counter1 is {}", &get_id.call(&mut store, ())?);

    // Show the first instance.
    let is_first = counter0.get_typed_func::<(), i32>(&mut store, "is_first")?;
    if is_first.call(&mut store, ())? == 1 {
        println!("counter0 is the first instance.");
    }
    let is_first = counter1.get_typed_func::<(), i32>(&mut store, "is_first")?;
    if is_first.call(&mut store, ())? == 1 {
        println!("counter1 is the first instance.");
    }

ここでは 第1回 と同じ方法で関数 get_idis_first を実行している。

Python から実行

筆者の Python 環境

  • Python 3.11.2
  • pip 23.0.1

Python のコード

最初に Python の version と環境に応じた方法で wasmtime をインストールする。

$ pip install wasmtime
Collecting wasmtime
  Downloading wasmtime-9.0.0-py3-none-manylinux1_x86_64.whl (6.6 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.6/6.6 MB 2.1 MB/s eta 0:00:00
Installing collected packages: wasmtime
Successfully installed wasmtime-9.0.0

Python から WebAssembly を実行するコードは、例えば下記の様になる。

from wasmtime import (Engine, Global, GlobalType, Linker, Module, Store, Val,
                      ValType)

# Build Engine.
engine = Engine()

# Build Module.
wasm = open('/tmp/counter.wasm', 'rb').read()
module = Module(engine, wasm)

# Build Store.
store = Store(engine)

# Create Global next_id and FIRST_ID.
next_id_ty = GlobalType(ValType.i64(), True)
next_id = Global(store, next_id_ty, Val.i64(0))
FIRST_ID_ty = GlobalType(ValType.i64(), False)
FIRST_ID = Global(store, FIRST_ID_ty, Val.i64(0))

# Build Instance. (Linker is builder class.)
linker = Linker(engine)
linker.define(store, "host", "next_id", next_id)
linker.define(store, "host", "FIRST_ID", FIRST_ID)
counter0 = linker.instantiate(store, module)
counter1 = linker.instantiate(store, module)

# Show the id of counter0 and counter1.
print("The id of counter0 is %d" % counter0.exports(store)['get_id'](store))
print("The id of counter1 is %d" % counter1.exports(store)['get_id'](store))

# Show the first instance.
if counter0.exports(store)['is_first'](store):
    print("counter0 is the first instance.")
if counter1.exports(store)['is_first'](store):
    print("counter1 is the first instance.")

上記の内容を call_wasm.py というファイル名で保存すると、実行結果は以下の様になる。

$ python call_wasm.py
The id of counter0 is 0
The id of counter1 is 1
counter0 is the first instance.

上記の Python コードを少しずつ解説していく。
以下では 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Python 版 wasmtime の公式ドキュメント である。

最初に以下の行を読んでみる。

from wasmtime import (Engine, Global, GlobalType, Linker, Module, Store, Val,
                      ValType)

# Build Engine.
engine = Engine()

# Build Module.
wasm = open('/tmp/counter.wasm', 'rb').read()
module = Module(engine, wasm)

# Build Store.
store = Store(engine)

ここでは 第1回 と wasm ファイルのパス以外は同じだ。

続いて以下の行を読んでみる。

# Create Global next_id and FIRST_ID.
next_id_ty = GlobalType(ValType.i64(), True)
next_id = Global(store, next_id_ty, Val.i64(0))
FIRST_ID_ty = GlobalType(ValType.i64(), False)
FIRST_ID = Global(store, FIRST_ID_ty, Val.i64(0))

# Build Instance. (Linker is builder class.)
linker = Linker(engine)
linker.define(store, "host", "next_id", next_id)
linker.define(store, "host", "FIRST_ID", FIRST_ID)
counter0 = linker.instantiate(store, module)
counter1 = linker.instantiate(store, module)

ここも 第2回 とほとんど同じだ。
違いは linker.define() の引数に Global を渡している事だけである。

JavaScript の所でも説明したが、 counter0counter1 は同じ Global を参照している。そのため counter0 の初期化時に next_id の値を更新するが、その更新は counter1 にも反映される。

ところで、ここで Store に関する制限が有る。

GlobalInstance の各インスタンスは many-to-many の関係で紐づいている。
それとは別に、 StoreGlobalInstance は one-to-many の関係で紐づいている。

ここで関連する(紐づいている) GlobalInstance の各インスタンスは、全て同じ Store に紐づいている必要がある。

つまり、今回作成した next_id, FIRST_ID, counter0, counter1 は、同一の Store に紐づいている必要が有る。

続いて以下の行を読んでみる。

# Show the id of counter0 and counter1.
print("The id of counter0 is %d" % counter0.exports(store)['get_id'](store))
print("The id of counter1 is %d" % counter1.exports(store)['get_id'](store))

# Show the first instance.
if counter0.exports(store)['is_first'](store):
    print("counter0 is the first instance.")
if counter1.exports(store)['is_first'](store):
    print("counter1 is the first instance.")

ここでは 第1回 と同じ方法で関数 get_idis_first を実行している。

Ruby から実行

筆者の Ruby 環境

  • ruby 3.1.2p20

Ruby のコード

最初に gem で wasmtime をインストールする。
(必要に応じて bundler 等を使うと良いだろう。)

$ gem install wasmtime
Fetching wasmtime-9.0.1-x86_64-linux.gem
Successfully installed wasmtime-9.0.1-x86_64-linux
Parsing documentation for wasmtime-9.0.1-x86_64-linux
Installing ri documentation for wasmtime-9.0.1-x86_64-linux
Done installing documentation for wasmtime after 0 seconds
1 gem installed

Ruby から WebAssembly を実行するコードは、例えば下記の様になる。

require 'wasmtime'

# 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 Global next_id and FIRST_ID.
next_id = Wasmtime::Global.var(store, :i64, 0)
FIRST_ID = Wasmtime::Global.const(store, :i64, 0)

# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "next_id", next_id)
linker.define(store, "host", "FIRST_ID", FIRST_ID)
counter0 = linker.instantiate(store, mod)
counter1 = linker.instantiate(store, mod)

# Show the id of counter0 and counter1.
puts("The id of counter0 is %d" % counter0.export('get_id').to_func.call)
puts("The id of counter1 is %d" % counter1.export('get_id').to_func.call)

# Show the first instance.
if counter0.export('is_first').to_func.call == 1 then 
    puts("counter0 is the first instance.")
end
if counter1.export('is_first').to_func.call == 1 then
    puts("counter1 is the first instance.")
end

上記の内容を call_wasm.rb というファイル名で保存すると、実行結果は以下の様になる。

$ ruby call_wasm.rb
The id of counter0 is 0
The id of counter1 is 1
counter0 is the first instance.

上記の Ruby コードを少しずつ解説していく。
以下では 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Ruby 版 wasmtime の公式ドキュメント である。

最初に以下の行を読んでみる。

require 'wasmtime'

# 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回 と wasm ファイルのパス以外は同じだ。

続いて以下の行を読んでみる。

# Create Global next_id and FIRST_ID.
next_id = Wasmtime::Global.var(store, :i64, 0)
FIRST_ID = Wasmtime::Global.const(store, :i64, 0)

# Build instance (Linker is builder Class)
linker = Wasmtime::Linker.new(engine)
linker.define(store, "host", "next_id", next_id)
linker.define(store, "host", "FIRST_ID", FIRST_ID)
counter0 = linker.instantiate(store, mod)
counter1 = linker.instantiate(store, mod)

ここも 第2回 とほとんど同じだ。
違いは linker.define の引数に Global を渡している事だけである。

JavaScript の所でも説明したが、 counter0counter1 は同じ Global を参照している。そのため counter0 の初期化時に next_id の値を更新するが、その更新は counter1 にも反映される。

ところで、ここで Store に関する制限が有る。

GlobalInstance の各インスタンスは many-to-many の関係で紐づいている。
それとは別に、 StoreGlobalInstance は one-to-many の関係で紐づいている。

ここで関連する(紐づいている) GlobalInstance の各インスタンスは、全て同じ Store に紐づいている必要がある。

つまり、今回作成した next_id, FIRST_ID, counter0, counter1 は、同一の Store に紐づいている必要が有る。

続いて以下の行を読んでみる。

# Show the id of counter0 and counter1.
puts("The id of counter0 is %d" % counter0.export('get_id').to_func.call)
puts("The id of counter1 is %d" % counter1.export('get_id').to_func.call)

# Show the first instance.
if counter0.export('is_first').to_func.call == 1 then 
    puts("counter0 is the first instance.")
end
if counter1.export('is_first').to_func.call == 1 then
    puts("counter1 is the first instance.")
end

ここでは 第1回 と同じ方法で関数 get_idis_first を実行している。

まとめ

今回は 2 個以上の wasm ファイルのインスタンス間でグローバル変数を共有する方法について説明した。
WebAssembly はグローバル変数の他にも関数、抽象ポインター、線形メモリを import できる。
つまり、これらの値は複数のインスタンス間で共有できる。
第2回 では関数の import を、 第4回 では線形メモリの import を紹介した。)

これらを上手く活用すれば「メタプログラミング」と呼ばれる様な強力なコーディングを行う事が可能だ。
フレームワークの作成などで便利だろう。

一方で import や export を多用すると、それは WebAssembly と外部プログラムが密結合になってしまう。
一般的に平凡でシンプルな方法で実装できるなら、その方が良いと筆者は個人的には考えている。
「知識をひけらかす」事を目的にコーディングすると、悪い結果につながるかもしれない。

次回以降はメタプログラミング的な方法を紹介した後で、 第4回 の続きとして線形メモリの実践的な使い方を紹介する予定だ。

0
0
1

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?