建立記憶體


WebAssembly 預設的資料型態有 i32i64f32f64,那麼陣列呢?字串呢?這些在其他程式語言中,常見的基本元素在哪?嗯~必須自行實作!

正如〈堆疊與數值型態〉中談到,真正處理的都是位元,對於一組位元要以什麼觀點來看得,就是資料型態的概念,有了資料型態的概念,就可以用具體的概念來操作一組位元,而不是直接處理 0101 的運算。

因此對於什麼是陣列?何為字串?就視乎你如何看待、處理一組位元,WebAssembly 提供給你的,是可以儲存位元的線性空間。

可以使用 memory 來建立儲存位元的線性空間,一個模組只可以定義一個 memory

(module
    (memory 1)
    ...
)

memory 建立的線性空間,與變數、函式表等是隔離的,因此不會有記憶體溢位等安全問題。memory 的第一個數字是初始長度,以 page 為單位,一個 page 是 64 KiB(一個 KiB 是 1024 位元組),可以選擇性地加上第二個數字限制最大長度,若不加上,長度不受限。

記憶體建立之後,全部的位元都會是 0,如果要指定初始值,可以使用 data,指定從索引 0 開始儲存,儲存時以位元組為單位,例如:

(module
    (memory $mem 1)
    (data (i32.const 0) "\48\65\6C\6C\6F")
    (export "mem" (memory $mem))
    (func $nope)
)

模組的記憶體可以匯出,被匯出的記憶體會是 WebAssembly.Memory 的實例,它有個 buffer 特性,是 ArrayBuffer 實例,代表著 WebAssembly 中建立的線性空間。

上面的範例在記憶體中寫入的五個位元組,若以無號 8 位元整數來看,正好是 "Hello" 五個字元的 Unicode 號碼,因此可以如下顯示 Hello:

WebAssembly.instantiateStreaming(fetch('program.wasm'))
           .then(prog => {
               console.log(String.fromCharCode.apply(null, 
                   new Uint8Array(prog.instance.exports.mem.buffer, 0, 5)
               ));
           });

也可以在 JavaScript 中建立 WebAssembly.Memory,再匯入 WebAssembly 模組之中:

const mem = new WebAssembly.Memory({initial:1});

const importObj = {
    env: {mem}
};

WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj)
           .then(prog => {
               console.log(String.fromCharCode.apply(null, 
                   new Uint8Array(mem.buffer, 0, 5)
               ));
           });

底下的模組會在匯入的記憶體中寫入資料:

(module
    (import "env" "mem" (memory 1))
    (data (i32.const 0) "Hello")
    (func $nope)
)

可以使用 current_memory 取得記憶體長度置入堆疊,單位是 page,使用 grow_memory 可以試著增加長度,若成功,會將先前記憶體的長度置入堆疊,若失敗(像是已經定義長度上限),會將 -1 置入堆疊。

(module
    (import "env" "log" (func $log (param i32)))
    (memory $mem 1)
    (data (i32.const 0) "\48\65\6C\6C\6F")
    (func $main
        current_memory
        call $log  ;; 顯示 1
        (grow_memory (i32.const 2))
        call $log  ;; 顯示 1
    )
    (start $main)
)