存取記憶體


存取記憶體的指令基本上區分為 load 系列與、store 系列。對於整數的 load 指令,都會從堆疊中取出一個 i32 數值作為位元組偏移量(也就是定址之用),名稱上可以識別從記憶體取出的位元長度,以及擴充方式,無論是整數或浮點數,若只有 load 字樣,依指令前的型態決定位元長度,取出的數值會置入堆疊。

  • i32.load8_s:載入 8 個位元,視為有號整數擴充為 i32
  • i32.load8_u:載入 8 個位元,視為無號整數擴充為 i32(左邊補 0)。
  • i32.load16_s:載入 16 個位元,視為有號整數擴充為 i32
  • i32.load16_u:載入 16 個位元,視為無號整數擴充為 i32(左邊補 0)。
  • i32.load:載入 32 個位元,以 i32 放入堆疊。
  • i64.load8_s:載入 8 個位元,視為有號整數擴充為 i64
  • i64.load8_u:載入 8 個位元,視為無號整數擴充為 i64(左邊補 0)。
  • i64.load16_s:載入 16 個位元,視為有號整數擴充為 i64
  • i64.load16_u:載入 16 個位元,視為無號整數擴充為 i64(左邊補 0)。
  • i64.load32_s:載入 32 個位元,視為有號整數擴充為 i64
  • i64.load32_u:載入 32 個位元,視為無號整數擴充為 i64(左邊補 0)。
  • i64.load:載入 64 個位元,以 i64 放入堆疊。
  • f32.load:載入 32 個位元,以 f32 放入堆疊。
  • f64.load:載入 64 個位元,以 f64 放入堆疊。

底下這個模組,使用 i32.load8_s 每次讀取 8 個位元,共讀取五次:

(module
    (import "env" "log" (func $log (param i32)))
    (memory (data "Hello"))
    (func $main
        (local $i i32)
        loop
            (i32.load8_s (get_local $i))
            call $log
            (set_local $i 
                (i32.add
                    (get_local $i)
                    (i32.const 1)
                )
            )
            (br_if 0 
                (i32.ne
                    (get_local $i)
                    (i32.const 5)
                ) 
            )  
        end
    )
    (start $main)
)

匯入的 $log 函式,會依指定的數值以 String.fromCharCode 轉為字串,因此會顯示 Hello:

const importObj = {
    env: {
        log(n) {
            console.log(String.fromCharCode(n));
        }
    }
};

WebAssembly.instantiateStreaming(fetch('program.wasm'), importObj);

指定位元組偏移量若超出了記憶體的位元組長度,會發生超出邊界的錯誤!

在儲存方面,對於整數的 store 指令,名稱上可以識別要儲存的位元長度,它會從堆疊中取出一個數值,接著再從堆疊中取得一個 i32 作為位元組偏移量,例如從記憶體開頭讀寫 72 這個數值:

(module
    (import "env" "log" (func $log (param i32)))
    (memory 1)
    (func $main
        i32.const 0   ;; 先指定偏移量
        i32.const 72  ;; 再指定要存放的數值
        i32.store8
        i32.const 0
        i32.load8_s
        call $log
    )
    (start $main)
)

這樣看來很不直覺,不過,若寫成以下就容易閱讀了:

(module
    (import "env" "log" (func $log (param i32)))
    (memory 1)
    (func $main
        (i32.store8 (i32.const 0) (i32.const 72))
        (i32.load8_s (i32.const 0))
        call $log
    )
    (start $main)
)

可以使用的儲存指令有:

  • i32.store8:將 i32 數值取 8 位元存入,多的位元捨去。
  • i32.store16:將 i32 數值取 16 位元存入,多的位元捨去。
  • i32.store:(不轉換)將 i32 數值的位元存入。
  • i64.store8:將 i64 數值取 8 位元存入,多的位元捨去。
  • i64.store16:將 i32 數值取 16 位元存入,多的位元捨去。
  • i64.store32:將 i32 數值取 32 位元存入,多的位元捨去。
  • i64.store:(不轉換)將 i64 數值的位元存入。
  • f32.store:(不轉換)將 f32 數值的位元存入。
  • f64.store:(不轉換)將 f64 數值的位元存入。