LoginSignup
3
0

More than 1 year has passed since last update.

WebAssembly + C/C++: Reference Types を使いこなす

Last updated at Posted at 2022-02-15

動作確認環境

ホストのオブジェクトを関数経由で受け渡しする

関数引数に 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 を格納できないため。

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