變數


WebAssembly 中可定義區域變數與全域變數。區域變數定義在函式之中,必須使用 local 預先宣告,變數的預設值對整數來說是 0,對浮點數來說是 +0.

想讀出變數值置入堆疊,要使用 get_localset_local 取出堆疊頂端的值設定為變數值,tee_local 取出堆疊頂端的值設定為變數值,並傳回設定後的值置入堆疊。

來看看底下這個操作:

int a = 10;
int b = 20;
int c = a + b;

只使用 get_localset_local 的話,就必須寫為:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        (local $a i32) (local $b i32) (local $c i32)                                
        i32.const 10
        set_local $a
        i32.const 20
        set_local $b
        get_local $a
        get_local $b
        i32.add
        set_local $c
        get_local $c
        call $log
    )
    (start $main)
)

變數名稱只是增加可讀性,每個變數會使用以 0 開始的索引識別,上面的程式,使用 tee_local 會比較方便:

(module
    (import "env" "log" (func $log (param i32)))
    (func $main
        (local $a i32) (local $b i32) (local $c i32)                                
        i32.const 10
        tee_local $a
        i32.const 20
        tee_local $b
        i32.add
        tee_local $c
        call $log
    )
    (start $main)
)

全域變數使用 global 宣告,要在模組區段定義,預設是不可變動,因此可以在宣告全域變數時一併指定其值,可以使用 get_global 來讀出變數值置入堆疊:

(module
    (import "env" "log" (func $log (param f32)))
    (global $PI f32 (f32.const 3.14159))
    (func $main
        get_global $PI
        call $log
    )
    (start $main)
)

一樣地,全域變數也是使用從 0 開始的索引識別,名稱只是便於撰寫與閱讀。

如果想定義可變動的全域變數,必須加上 mut,之後可以使用 set_global 來設定變數:

(module
    (import "env" "log" (func $log (param f32)))
    (global $PI (mut f32) (f32.const 3.14159))
    (func $main
        get_global $PI
        call $log
        f32.const 3.14
        set_global $PI
        get_global $PI
        call $log
    )
    (start $main)
)

也可以將 JavaScript 中的變數匯入成為全域變數:

(module
    (import "env" "PI" (global $PI f32))
    (import "env" "log" (func $log (param f32)))
    (func $main
        get_global $PI
        call $log
    )
    (start $main)
)

JavaScript 的部份,匯入物件上有對應的特性就可以匯入:

const importObj = {
    env: {
        PI : 3.14159,
        log(n) {
            console.log(n);
        }
    }
};
WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj);

然而,若匯入之後要成為可變動的全域變數:

(module
  (import "env" "PI" (global $PI (mut f32)))
    (import "env" "log" (func $log (param f32)))
    (func $main
        get_global $PI
        call $log
        f32.const 3.14
        set_global $PI
        get_global $PI
        call $log       
    )
    (start $main)
)

JavaScript 的部份,匯入物件上對應的特性,必須是 WebAssembly.Global 實例:

const importObj = {
    env: {
        PI : new WebAssembly.Global({value : 'f32', mutable : true}, 3.14159),
        log(n) {
            console.log(n);
        }
    }
};
WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj)
           .then(_ => {
               console.log(importObj.env.PI.value);
           });

而且這麼一來,在 WebAssembly 中變動了全域變數的值,JavaScript 中對應的 WebAssembly.Global 實例,其 value 也會反映變動後的值。

若要從 WebAssembly 中匯出全域變數,可以使用 export

(module
    (global $PI f32 (f32.const 3.14159))
    (export "PI" (global $PI))
    (func $nop)
)

(在這個範例中,(func $nop) 是必須的,似乎要匯出東西的模組,至少得有一個函式的樣子,拿掉該行會發生錯誤!)

被匯出的變數,會成為 WebAssembly.Instance 實例上 exports 的特性,型態會是 WebAssembly.Global,可以透過 value 特性來取得,例如:

WebAssembly.instantiateStreaming(fetch('program.wasm'))
           .then(prog => console.log(prog.instance.exports.PI.value));

在 WebAssembly 中,只有不可變動的變數才可以匯出,否則會發生錯誤。