動作確認環境
ホストのオブジェクトを関数経由で受け渡しする
関数引数に externref/funcref を指定することで、ホスト側で使用されるオブジェクト (JavaScript オブジェクト/関数) の受け渡しができる。
C/C++ では、アドレス空間 10 なポインタが externref、アドレス空間 20 なポインタが funcref に対応する。
WebAssembly | C/C++ 型宣言 |
---|---|
externref | char __attribute__((address_space(10)))* |
funcref | char __attribute__((address_space(20)))* |
Main.cpp
typedef char __attribute__((address_space(10)))* externref;
extern "C" externref create_empty_object(void);
int main() {
create_empty_object();
return 0;
}
Main.js
export function create_empty_object() {
return {};
}
Main.wasm
;; clang -O2 Main.cpp -mreference-types -o Main.wasm --target=wasm32-wasi --sysroot=<path to wasi-sdk>/share/wasi-sysroot/ -nostartfiles -Wl,-no-entry -Wl,--allow-undefined -Wl,--export=main
(module
(type $none_=>_externref (func (result externref)))
(type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
(import "env" "create_empty_object" (func $fimport$0 (result externref)))
(memory $0 2)
(export "memory" (memory $0))
(export "main" (func $0))
(func $0 (param $0 i32) (param $1 i32) (result i32)
(drop
(call $fimport$0)
)
(i32.const 0)
)
)
ホストのオブジェクトを WebAssembly.Global 経由で受け渡しする
アドレス空間 1 に所属するグローバル変数は、WebAssembly.Global
として格納される。
WebAssembly | C/C++ 型宣言 |
---|---|
i32 | int32_t __attribute__((address_space(1))) |
i64 | int64_t __attribute__((address_space(1))) |
f32 | float __attribute__((address_space(1))) |
f64 | double __attribute__((address_space(1))) |
externref | char __attribute__((address_space(10)))* __attribute__((address_space(1))) |
funcref | char __attribute__((address_space(20)))* __attribute__((address_space(1))) |
Main.cpp
char __attribute__((address_space(10)))* __attribute__((address_space(1))) wasm_global_externref;
__attribute__((export_name("receive_externref")))
void receive_externref(char __attribute__((address_space(10)))* ref) {
wasm_global_externref = ref;
}
Main.wasm
;; clang -O2 Main.cpp -mmutable-globals -mreference-types -o Main.wasm --target=wasm32-wasi --sysroot=<path to wasi-sdk>/share/wasi-sysroot/ -nostartfiles -Wl,-no-entry -Wl,--export=receive_externref -Wl,--export=wasm_global_externref
(module
(type $externref_=>_none (func (param externref)))
(global $global$0 (mut externref) (ref.null extern))
(memory $0 2)
(export "memory" (memory $0))
(export "wasm_global_externref" (global $global$0))
(export "receive_externref" (func $0))
(func $0 (param $0 externref)
(global.set $global$0
(local.get $0)
)
)
)
関数ポインタからホストの関数オブジェクトに変換する
ホストで関数ポインタを受け取って、WebAssembly.Table の要素を参照せずとも、関数ポインタから直接ホストの関数オブジェクトを取得でき、ホストに渡すことができる。
Main.cpp
typedef char __attribute__((address_space(20)))* funcref;
typedef void* funcref_ptr;
extern "C" {
// imported from javascript
int setTimeout(funcref callback, int timeout);
funcref funcptr_to_funcref(funcref_ptr);
}
void some_proc() {
}
int main() {
setTimeout(funcptr_to_funcref((funcref_ptr)&some_proc), 1000);
return 0;
}
Main.support.S
.globl __indirect_function_table
.tabletype __indirect_function_table, funcref
.globl funcptr_to_funcref
funcptr_to_funcref:
.functype funcptr_to_funcref(i32) -> (funcref)
local.get 0
table.get __indirect_function_table
end_function
Main.wasm
;; clang -O2 Main.cpp -mreference-types -o Main.wasm --target=wasm32-wasi --sysroot=<path to wasi-sdk>/share/wasi-sysroot/ -nostartfiles -Wl,-no-entry -Wl,--allow-undefined -Wl,--export=main
(module
(type $funcref_i32_=>_i32 (func (param funcref i32) (result i32)))
(type $none_=>_none (func))
(type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
(import "env" "setTimeout" (func $fimport$0 (param funcref i32) (result i32)))
(memory $0 2)
(table $0 2 2 funcref)
(elem (i32.const 1) $0)
(export "memory" (memory $0))
(export "main" (func $1))
(func $0
(nop)
)
(func $1 (param $0 i32) (param $1 i32) (result i32)
(drop
(call $fimport$0
(table.get $0
(i32.const 1)
)
(i32.const 1000)
)
)
(i32.const 0)
)
)
Q&A
ビルド時にコンパイラが落ちてしまう
- clang に
-mreference-types
オプションを付与する (筆者はこれで 1 時間ほど無駄にしたことがある) - clang の最適化レベルを
-O1
以上に設定する-
-O0
だと線形メモリ上の一時変数に externref/funcref を格納しようとしてしまい、対応する命令がないためにビルドに失敗してしまうことがある
-
どうして WebAssembly.Local を C/C++ 関数内で定義できないのか
アドレス空間が 1 な変数がコンテキストによって、 WebAssembly.Local または WebAssembly.Global になる。しかし、Clang はスタック変数をアドレス空間で修飾することを許可していないため。
どうして externref/funcref を構造体のメンバにできないのか
C/C++ の構造体は基本的に線形メモリに格納され、線形メモリには externref/funcref を格納できないため。