WebGL Tutorial
and more

Wasm Memories

撰写时间:2025-01-17

修订时间:2025-02-08

概述

本章集中阐述Wasm memories

Wasmmemory是一块可扩展的线性内存区域,是原始字节数据 (raw uniterpreted bytes) 的集合。可用于存储字符串、或一系列的字节数据。

Wasm当前规范,Safari及Chrome目前在一个Wasm模块中均只支持一块内存区域。

基本用法

声明

(module (memory 1) )

代码memory 1声明了一个memory内存区域,其初始尺寸为1page size),即64k字节,也即64 × 1024 = 65536个字节。

可使用第2个立即数来声明内存的最大尺寸。

(module (memory 1 5) )

上面所声明的内存,其容量最小1页,最大5页。最大容量的字节数不能超过4G,因此最大值应小于等于65536页。

查看memory的状态

可使用memorysize指令返回内存区域的大小。

let watSrc = ` (module (memory 2) (func $sum (export "memsize") (result i32) memory.size ) ) `; const { memsize } = await WabtUtils.RunWat(watSrc); pc.log(memsize());

也可直接导出memoryJavaScript读写其内容。

let watSrc = ` (module (memory $mem1 1 3) (export "memory" (memory $mem1)) ) `; const { memory } = await WabtUtils.RunWat(watSrc); pc.log(memory); let typeObj = memory.type(); pc.log(typeObj); let buffer = memory.buffer; let byteOffset = 0; let length = 20; let tarr = new Uint8Array(buffer, byteOffset, length); for (let i = byteOffset; i < length; i++) { tarr[i] = i * 2; } pc.log(tarr);

Wasmmemory对应于JavaScript的WebAssembly.Memory类,其buffer属性的类型为ArrayBuffer,因此可通过相应的TypedArrayDataView等视图来读写其数据。

由于Wasmmemory的字长为65536个字节,比较大,故上面代码只设置前20个字节的内容。

存储与读取单个数值

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory $mem1 (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 15)) ) (func (export "load") (result i32) (i32.load (i32.const 0)) ) (start $store) ) `; const { memory, load } = await WabtUtils.RunWat(watSrc); let num = load(); pc.log(num); let tarr = new Uint8Array(memory.buffer, 0, 8); pc.log('%s', getHexAddrStrFromTypedArray(tarr));

函数$store$mem1内存区域第0个字节的值设置为常数15。函数$load取出该内存区域第0个字节的数值。

start指令在Wasm模块完成初始化、且内存区域也已初始化后,自动调用$store函数,用于设置其值。

而在客户端中,调用所导出的load函数来加载该内存区域第0个字节的数值并打印出来,并打印内存前8个字节的状态。

alignment

操作数在内存中对齐仅是一种优化机制,而不是强制性的。

当在store中指定align=n时,编译器将确保内存地址的边界满足该需求。但如果指定不当,则可能会导致抛出异常,或导致应用效能损失。

当在load中指定align=n时,不会影响实际加载的结果。但如果指定不当,则可能会导致触发陷阱,或导致应用效能损失。

对齐参数为2n次幂,值为1, 2, 4, 8, 16等等。

对齐参数不能大于操作数的位数。例如,i64的字长为8字节,则对齐参数可以为1, 2, 4, 8

i32的默认对齐参数为align=4i64的默认对齐参数为align=8v128的默认对齐参数为align=16

const { getHexAddrStrFromTypedArray, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (v128.store offset=4 align=16 (i32.const 0) (v128.const i32x4 1 2 3 4)) (v128.store offset=4 align=16 (i32.const 16) (v128.const i32x4 5 6 7 8)) ) (func (export "load") (result i32) (i32.load (i32.const 4)) ) (start $store) ) `; const { memory, load } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 64); pc.log(tarr); let str = getHexAddrStrFromTypedArray(tarr, 16); pc.log('%s', str); let num = load(); pc.log(num); pc.log('%s', getBinStr(num));

最佳实践是使用默认对齐。但有时在强调地址边界时,可使用align=n来明示边界对齐的宽度。

初始化内存

可以使用data段将内存内容使用字符串进行初始化。

以字符串字面符初始化

(module (memory 1) (data (i32.const 0) "Hello") )

因为目前的实现只支持1memory,因此i32.const 0并非指定使用第几个内存,而是指定从默认唯一的内存的第0个字节的偏移处开始,将其内容设置为Hello的内容。此时memory的大小不能为0

在 JS 中查看字符串

let watSrc = ` (module (memory (export "memory") 1) (data (i32.const 0) "Hello") ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 5); pc.log(tarr); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

JavaScript环境中查看memory的内容时,由于memory的尺寸过大,因此我们需要知道在该内存中有内容的起始字节处,以及终止字节处。Wasm并未提供自动计算在内存中有实际内容大小的指令。因此上面的代码以手工的方式计算了Hello共计5个字节。

我们可以在JavaScript中寻求解决方案。思路是通过类型化数组的findIndex方法来找到第一个NUL字符的位置,根据此位置,再调用slice方法来切割出有实际内容的区域。

let watSrc = ` (module (memory (export "memory") 1) (data (i32.const 0) "Hello") ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer); let nulCharIndex = tarr.findIndex(value => value === 0); pc.log(nulCharIndex); tarr = tarr.slice(0, nulCharIndex); pc.log(tarr); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

findIndex方法一旦找到符合条件的第一个元素,将立即返回该元素的索引值,因此这种算法较为高效。

同时初始化多个字节处的内容

可以同时声明多个data段来初始化不同偏移处的内容。

let watSrc = ` (module (memory (export "memory") 1) (data (i32.const 0) "JavaScript") (data (i32.const 15) "Python") (data (i32.const 30) "Wasm") ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer); let lastCharIndex = tarr.findLastIndex(value => value !== 0); tarr = tarr.slice(0, lastCharIndex + 1); pc.log(tarr); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

使用3data段,在不同偏移值的地方分别声明了3个字符串。

在自动计算有效内容大小时,因多个字符串之间可能夹有NUL字符,因此只能调用findLastIndex方法来找到最后一个不为NUL字符的字符位置。

理论上讲,这种算法在实际内容较少时,迭代次数将较多,效率也较为低下。此时只能寄希望于JavaScriptfindLastIndex方法在其内部有优化算法。

但从实际运行效果来看,速度还是较快的。

此外,内存中间所混杂的NUL字符,经TextDecoder解码后均消失了。

被动数据段的初始化

在声明data段时,如果不指定偏移值,则属于被动 (passive) 的数据段,其数据不会自动填充memory。此时,可调用memoryinit指令,将指定数据段的相应数据,复制到memory中。

let watSrc = ` (module (memory (export "memory") 1) (data "Hello, World!") ;; the 0th data segment (func $init_memory (memory.init 0 ;; from 0th data segment (i32.const 2) ;; memory target offset (i32.const 7) ;; data segment offset (i32.const 5) ;; bytes ) ) (start $init_memory) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 10); pc.log(tarr); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

memory.init指令的第1个参数为立即数,指定填充数据来自哪个数据段;第2个参数指定要复制到memory的偏移值;第3个参数指定从数据段开始复制的偏移值;第4个参数指定要复制的字节数量。

最后,取出memory最前面的10个字节,按utf-8来解码为并打印出来。

可以同时使用多个被动数据段的数据来填充同一默认内存区域。

let watSrc = ` (module (memory (export "memory") 1) (data "Hello, World!") ;; the 0th data segment (data "Python is good.") ;; the 1st data segment (func $init_memory (memory.init 0 ;; from 0th data segment (i32.const 0) ;; memory target offset (i32.const 0) ;; data segment offset (i32.const 7) ;; bytes ) (memory.init 1 ;; from 1st data segment (i32.const 8) ;; memory target offset (i32.const 0) ;; data segment offset (i32.const 6) ;; bytes ) ) (start $init_memory) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 14); pc.log(tarr); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

这种方式,可灵活、自由地组合使用多个数据段中的数据。

被动数据段数据的舍弃

let watSrc = ` (module (memory (export "memory") 1) (data "Hello") ;; the 0th data segment (func $init_memory (memory.init 0 ;; from 0th data segment (i32.const 0) ;; memory target offset (i32.const 0) ;; data segment offset (i32.const 5) ;; bytes ) data.drop 0 ;; drop the data ;; of the 0th data segment ) (start $init_memory) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer); let str = new TextDecoder("utf-8").decode(tarr); pc.log(str);

0data段是被动数据段,其内容用以初始化memory后,可显式调用data.drop 0,将第0data段的数据舍弃。

其作用有二:一是其后该段数据不能再被使用。二是起到优化作用。在内存紧张时,Wasm可随时清理该数据段的内存。

数值的初始化

memory的内容为数值类型时,可通过相应数据类型的store指令予以初始化。

const { getHexAddrStrFromTypedArray, getHexStr, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 1)) (i32.store (i32.const 1) (i32.const 2)) (i32.store (i32.const 2) (i32.const 3)) (i32.store (i32.const 3) (i32.const 4)) ) (func (export "load") (result i32) (i32.load (i32.const 0)) ) (start $store) ) `; const { memory, load } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 8); pc.log(tarr); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num = load(); pc.log(num); pc.log('%s', getHexStr(num)); pc.log('%s', getBinStr(num));

4个连续的字节处,分别存储了4个数值;读取数据时,则连续读取了4个字节的内容,并解读为一个整数值。

上例可看到,Wasmmemory的存储及解读是按小尾来进行的。

i32虽为4个字节的指令,但由于其store指令需指定一个字节偏移处,而上面的代码每次只偏移了一个字节,因此实际上均覆盖了前一个i32在内存中的高位内容。

下面代码偏移2个字节再存储另一个数值,则得到了不同的数值。

const { getHexAddrStrFromTypedArray, getHexStr, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 1)) (i32.store (i32.const 2) (i32.const 2)) ) (func (export "load") (result i32) (i32.load (i32.const 0)) ) (start $store) ) `; const { memory, load } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 4); pc.log(tarr); let str = getHexAddrStrFromTypedArray(tarr, 4); pc.log('%s', str); let num = load(); pc.log(num); pc.log('%s', getHexStr(num)); pc.log('%s', getBinStr(num));

整数数值存储

整数的存储

调用各个数据类型的store指令以在内存中存储数据。

const { getHexAddrStrFromTypedArray, getHexStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 0x10)) ;; 4 bytes (i32.store (i32.const 4) (i32.const 0x20)) ;; 4 bytes (i64.store (i32.const 8) (i64.const 0x30)) ;; 8 bytes (i64.store (i32.const 16) (i64.const 0x40)) ;; 8 bytes ) (func (export "load32") (param $idx i32) (result i32) (i32.load (local.get $idx)) ) (func (export "load64") (param $idx i32) (result i64) (i64.load (local.get $idx)) ) (start $store) ) `; const { memory, load32, load64 } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 32); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num1 = load32(0); pc.log('%d, %s', num1, getHexStr(num1)); let num2 = load32(4); pc.log('%d, %s', num2, getHexStr(num2)); let num3 = load64(8); pc.log('%d, %s', num3, getHexStr(num3)); let num4 = load64(16); pc.log('%d, %s', num4, getHexStr(num4));

首先,与设置各种数据类型单个值不同,由于是在一连串的内存区域中设置值,因此store指令的参数需同时指定在内存中的偏移值以及数值。

其次,从内存存储状态来看,Wasm小尾方式来存储数据。

第三,在加载数据时,load指令也需指定在内存中的偏移值。

未正确指定偏移值的存储

在存储内存数值时,如果未能正确指定偏移值,则会引发不易觉察的错误。

const { getHexAddrStrFromTypedArray, getHexStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 0x10)) ;; 4 bytes (i32.store (i32.const 1) (i32.const 0x20)) ;; 4 bytes ) (func (export "load32") (param $idx i32) (result i32) (i32.load (local.get $idx)) ) (start $store) ) `; const { memory, load32} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num1 = load32(0); pc.log('%d, %s', num1, getHexStr(num1)); let num2 = load32(1); pc.log('%d, %s', num2, getHexStr(num2)); let num3 = load32(4); pc.log('%d, %s', num3, getHexStr(num3));

先在偏移值为0的地方存储了一个32位的数值0x10,而在存储第232位的数值时,本应在偏移值为4的地方存储,实际上却在偏移值为1的地方存储,结果第1个数值的内存区域被覆盖了,从而导致加载第1个数值时,其值不再是0x10

而第2个数值若在偏移值为1的地方加载,则没有问题。但如果在偏移值本应为4的地方加载,也会出现错误。

只存储部分位域的数值

i32的总位数为32位,但也可调用其store8指令来存储8位的整数, 调用其store16指令来存储16位的整数。

const { getHexAddrStrFromTypedArray, getHexStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store8 (i32.const 0) (i32.const 0x11223344)) (i32.store16 (i32.const 4) (i32.const 0x11223344)) (i32.store (i32.const 8) (i32.const 0x11223344)) ) (func (export "load32") (param $idx i32) (result i32) (i32.load (local.get $idx)) ) (start $store) ) `; const { memory, load32} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 12); let str = getHexAddrStrFromTypedArray(tarr, 4); pc.log('%s', str); let num1 = load32(0); pc.log('%d, %s', num1, getHexStr(num1)); let num2 = load32(4); pc.log('%d, %s', num2, getHexStr(num2)); let num3 = load32(8); pc.log('%d, %s', num3, getHexStr(num3));

这种只存储部分位域的指令,只保留相应低权重位部分的数值,高权重位的部分直接被舍弃。

i64的情况也类似于此。可调用其store8指令来存储8位的整数, 调用其store16指令来存储16位的整数, 调用其store32指令来存储32位的整数。

const { getHexAddrStrFromTypedArray, getHexStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i64.store8 (i32.const 0) (i64.const 0x1122334455667788)) (i64.store16 (i32.const 8) (i64.const 0x1122334455667788)) (i64.store32 (i32.const 16) (i64.const 0x1122334455667788)) (i64.store (i32.const 24) (i64.const 0x1122334455667788)) ) (func (export "load64") (param $idx i32) (result i64) (i64.load (local.get $idx)) ) (start $store) ) `; const { memory, load64} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 32); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num1 = load64(0); pc.log('%d, %s', num1, getHexStr(num1)); let num2 = load64(8); pc.log('%d, %s', num2, getHexStr(num2)); let num3 = load64(16); pc.log('%d, %s', num3, getHexStr(num3)); let num4 = load64(24); pc.log('%d, %s', num4, getHexStr(num4));

整数数值的加载

分为两大类,一类为load指令,另一类为loadN_sx指令。

load

使用i64进行存储,若使用i32读取同一偏移值的数值,由于Wasm小尾存储,将取出低权重位的数值。

const { getHexAddrStrFromTypedArray, getHexStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i64.store (i32.const 0) (i64.const 0x1122334455667788)) ) (func (export "load32") (param $idx i32) (result i32) (i32.load (local.get $idx)) ) (start $store) ) `; const { memory, load32} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 8); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num = load32(0); pc.log('%d, %s', num, getHexStr(num));

load指令返回有符号的整数数值。

const { getHexAddrStrFromTypedArray, getHexStr, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const 0x00112233)) (i32.store (i32.const 4) (i32.const 0x80112233)) ) (func (export "load32") (param $idx i32) (result i32) (i32.load (local.get $idx)) ) (func (export "load32s") (param $idx i32) (result i64) (i64.load32_s (local.get $idx)) ) (func (export "load32u") (param $idx i32) (result i64) (i64.load32_u (local.get $idx)) ) (start $store) ) `; const { memory, load32, load32s, load32u} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 8); let str = getHexAddrStrFromTypedArray(tarr, 4); pc.log('%s', str); pc.group('using i32.load'); let num1 = load32(0); pc.log('%d, %s', num1, getHexStr(num1, 8, true)); pc.log('%d, %s', 0x00, getBinStr(0x00)); let num2 = load32(4); pc.log('%d, %s', num2, getHexStr(num2, 8, true)); pc.log('%d, %s', 0x80, getBinStr(0x80)); pc.groupEnd(); pc.group('specifying sign or unsigned'); pc.group('signed'); let num3 = load32s(4); pc.log('%d, %s', num3, getHexStr(num3, 8, true)); pc.groupEnd(); pc.group('unsigned'); let num4 = load32u(4); pc.log('%d, %s', num4, getHexStr(num4, 8, true)); pc.groupEnd(); pc.groupEnd();

先存储了2个整数数值:0x001122330x80112233。使用load指令加载时,第1个数值与原来数值一样,但第2个数值与原来数值不一样。

两数的区别在于第1个数的最高权重位为0x00,其二进制最高权重位的值为0;而第2个数的最高权重位为0x80,其二进制最高权重位的值为1。对于无符号数值来讲,这个部位的数值表示正常的数值。对于有符号数值来讲,如果该位的值为1,表示该数为负数;如果该位的值为0,表示该数为正数。

接着,对于第2个数值,分别调用i64load32_s加载为有符号整数,调用i64load32_u加载为无符号整数。当加载为无符号整数时,取出的数值与原来的数值一样。从而说明了load指令返回有符号的整数数值。

loadN_sx

i32i64操作数的loadN_sx系列指令用以明示加载有符号或无符号的整数数值。其中N指定小于该操作数位数的位数,sx用于指定有符号s或无符号u

i32可用以加载8位及16位的子域数据。

指令名称符号标记含义
i32.load8_ssigned32位的数据中取出8个低权重位的数据,解释为有符号整数
i32.load8_uunsigned32位的数据中取出8个低权重位的数据,解释为无符号整数
i32.load16_ssigned32位的数据中取出16个低权重位的数据,解释为有符号整数
i32.load16_uunsigned32位的数据中取出16个低权重位的数据,解释为无符号整数

i64可用以加载8位、16位及32位的子域数据。

指令名称符号标记含义
i64.load8_ssigned64位的数据中取出8个低权重位的数据,解释为有符号整数
i64.load8_uunsigned64位的数据中取出8个低权重位的数据,解释为无符号整数
i64.load16_ssigned64位的数据中取出16个低权重位的数据,解释为有符号整数
i64.load16_uunsigned64位的数据中取出16个低权重位的数据,解释为无符号整数
i64.load32_ssigned64位的数据中取出32个低权重位的数据,解释为有符号整数
i64.load32_uunsigned64位的数据中取出32个低权重位的数据,解释为无符号整数
const { getHexAddrStrFromTypedArray, getHexStr, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i32.store (i32.const 0) (i32.const -30)) ) (func (export "load8") (param $idx i32) (result i32 i32) (i32.load8_s (local.get $idx)) (i32.load8_u (local.get $idx)) ) (start $store) ) `; const { memory, load8} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); const [ signedNum, unsignedNum ] = load8(0); pc.log('%d, %s, %s', signedNum, getHexStr(signedNum), getBinStr(signedNum)); pc.log('%d, %s', unsignedNum, getHexStr(unsignedNum), getBinStr(unsignedNum));

浮点数值的存储与加载

浮点数的指令比较简单,只有storeload

const { getHexAddrStrFromTypedArray, getHexStr, getBinStr } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (f32.store (i32.const 0) (f32.const 1.23)) ) (func (export "load") (param $idx i32) (result f32) (f32.load (local.get $idx)) ) (start $store) ) `; const { memory, load} = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str); let num = load(0); pc.log(num); pc.log('%s', getHexStr(num)); pc.log('%s', getBinStr(num));

memory的指令

fill

fill指令在指定的内存区域填充指定的单个字节的数值。

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (memory.fill (i32.const 2) ;; start offset (i32.const 3) ;; byte value (i32.const 5) ;; bytes length ) ) (start $store) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str);

fill指令共有3个参数,第1个参数指定内存区域偏移值,第2个参数指定要填充的数值,第3个参数指定共填充多少个字节。这3个参数的类型都只能为i32

上面代码从第2个字节开始,将各字节的值填充为3,共填充5个字节。

2个参数要填充的数值中,如果有效位域超过1个字节,则只取最低权重位的1个字节的数值。

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (memory.fill (i32.const 2) (i32.const 0x66778899) ;; take only the byte value ;; from the lowest significant‌ byte (i32.const 5) ) ) (start $store) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str);

copy

copy指令将内存特定区域中的数据复制到另一区域。

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (memory.fill (i32.const 0) (i32.const 0x11) (i32.const 8)) (memory.copy (i32.const 9) ;; target offset (i32.const 0) ;; source offset (i32.const 5) ;; byte length ) ) (start $store) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str);

copy指令共有3个参数,第1个参数指定目标偏移值,第2个参数指定源偏移值,第3个参数指定要复制字节数量。这3个参数的类型都只能为i32

目标区域与源区域可以相互重叠。

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let watSrc = ` (module (memory (export "memory") 1) (func $store (i64.store (i32.const 0) (i64.const 0x1122334455667788)) (i64.store (i32.const 8) (i64.const 0x1122334455667788)) (memory.copy (i32.const 0) ;; target offset (i32.const 3) ;; source offset (i32.const 5) ;; byte length ) ) (start $store) ) `; const { memory } = await WabtUtils.RunWat(watSrc); let tarr = new Uint8Array(memory.buffer, 0, 16); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str);

从第3个字节开始,复制5个字节至第0个字节处,目标区域与源区域有部分重叠。复制完成后,重叠的源区域的内容被覆盖。

size

size指令返回表示内存大小的数值。

let watSrc = ` (module (memory (export "memory") 1) (func (export "memsize") (result i32) memory.size ) ) `; const { memory, memsize } = await WabtUtils.RunWat(watSrc); pc.log('Memory size: %d', memsize());

JavaScript中,可通过导出的memory来查看其字节总数。

let watSrc = ` (module (memory (export "memory") 1) (func (export "memsize") (result i32) memory.size ) ) `; const { memory, memsize } = await WabtUtils.RunWat(watSrc); pc.log('Memory size: %d', memsize()); pc.log('Buffer bytes length: %d', memory.buffer.byteLength); pc.assert(memsize() * 64 * 1024 === memory.buffer.byteLength);

grow

grow指令将内存大小按指定的页数予以扩展,返回表示原来内存大小的数值。

let watSrc = ` (module (memory (export "memory") 1) (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0)) ) (func (export "memsize") (result i32) memory.size ) ) `; const { memory, grow, memsize } = await WabtUtils.RunWat(watSrc); pc.log('Memory size before growing: %d', memsize()); let result = grow(5); pc.log('Value returned from grow(): %d', result); pc.log('Memory size after growing: %d', memsize());

JavaScript与Wasm的联动

const { getHexAddrStrFromTypedArray } = await import('/js/esm/BinUtils.js'); let memory = new WebAssembly.Memory({ initial: 1, maximum: 5, shared: false }); pc.log(WebAssembly); pc.log(typeof WebAssembly.Memory); let buffer = memory.buffer; let tarr = new Uint8Array(buffer, 0, 16); tarr.forEach((element, index) => { tarr[index] = index; }); const importObject = { js: { memory: memory } }; let watSrc = ` (module (import "js" "memory" (memory $mem1 1 5)) (func $alt (memory.fill (i32.const 0) ;; offset (i32.const 5) ;; value (i32.const 8) ;; bytes ) ) (start $alt) ) `; await WabtUtils.RunWat(watSrc, importObject); let str = getHexAddrStrFromTypedArray(tarr, 8); pc.log('%s', str);

JavaScript中,WebAssemblyMemory是一个构造函数,先用它来创建一个实例memory变量,再初始化前16个字节的内容,再导入进Wasm中。

Wasm中,对前8个字节的内容均填充为数值5

最后,在JavaScript中,通过打印memory,我们发现其内存数据已被修改。说明JavaScriptWasm均引用了同一对象,无论在哪一端修改该对象的值,都会影响到另一端。

参考资源

WebAssembly

  1. Memory Instructions
  2. Module Memory
  3. Memory.wast
  4. Memory Instructions
  5. W3C version (single page)
  6. Doc version (webassembly.org)
  7. Bulk Memory Operations and Conditional Segment Initialization
  8. WebAssembly简介