WebAssembly核心编程[4]: Memory
由于Memory存储的是单纯的二进制字节,所以原则上我们可以用来它作为媒介,在wasm模块和数组程序之间传递任何类型的数据。在JavaScript API中,Memory通过WebAssembly.Memory类型表示,我们一般将它内部的缓冲区映射相应类型的数组进行处理。WebAssembly也提供了相应的指令来提供针对Memory的读、写、扩容等操作(源代码从这里下载)。
一、容量限制与扩容
二、内容的读写
三、内容初始化
四、多Memory支持
五、批量内存处理
一、容量限制与扩容
Memory本质上一个可以扩容的内存缓冲区,在初始化的时候我们必需指定该缓冲器的初始大小,单位为Page(64K)。如果没有指定最大允许的大小,意味着它可以无限“扩容”。WebAssembly.Memory的实例方法grow用来扩容,作为参数的整数表示扩大的Page数量,其返回值表示扩容之前的容量。在如下这个演示实例中,我们在一个Web页面index.html初始化的时候创建了一个WebAssembly.Memory对象,并将其初始和最大尺寸设置为1和3。
<html>
<head></head>
<body>
<script>
var memory= new WebAssembly.Memory({ initial: 1, maximum: 3});
var grow = (size) => {
try{
console.log(`memory.grow(${size}) = ${memory.grow(size)}`);
}
catch(error){
console.log(error);
}
};
grow(1);
grow(1);
grow(1);
</script>
</body>
</html>
grow函数对这个WebAssembly.Memory试试扩容。我们先后3次调用次函数(增扩的容量为1),并将其返回值打印到控制台上。从如下的输出可以看出,创建的Memory的初始容量为1,经过两次扩容后,它的容量达到运行的最大容量3,进而导致第三次扩容失败。

针对Memory的扩容也利用利用wasm的memory.grow指令来完成,该指令的输入参数依然是扩大的容量,返回的依然是扩容前的大小。如果超过设定的最大容量,该指令会返回-1。wasm还提供了memory.size指令返回Memory当前的容量。在如下这个wat文件(app.wat)中,我们依然定义了一个初始和最大容量为1和3的Memory,两个导出的函数size和grow分别返回它当前容量和对它实施扩容。
(module
(memory 1 3)
(func (export "size") (result i32)
(memory.size)
)
(func (export "grow") (param $size i32) (result i32)
(memory.grow (local.get $size))
)
)
在作为宿主的index.html页面中,我们调用导出的grow函数(增扩的容量为1)对Memory实施3次扩容,并调用size函数输出它当前的容量。
<html>
<head></head>
<body>
<script>
var memory= new WebAssembly.Memory({ initial: 1, maximum: 3});
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then((results) => {
var exports = results.instance.exports;
var grow = (size)=> console.log(`memory.grow(${size}) = ${exports.grow(size)}`);
grow(1);
grow(1);
grow(1);
console.log(`memory.size() = ${exports.size()}`);
});
</script>
</body>
</html>
从如下的输出可以看出,前两次成功扩容将Memory的容量增扩到最大容量3,导致最后一次扩容失败,返回-1。

二、内容的读写
我们利用Memory对其管理的缓冲区按照纯字节的形式进行读写。WebAssembly针对具体的数据类型(i32/i64/f32/f64)提供一系列的load和store指令读写Memory的内容,具体的指令如下(8/16/32代表读写位数,s和u分别表示有符号和无符号整数):
- {i32|i64|f32|f64}.load
- {i32|i64}.load8_s
- {i32|i64}.load8_u
- {i32|i64}.load16_s
- {i32|i64}.load16_u
- {i32|i64}.load32_s
- {i32|i64}.load32_u
- {i32|i64|f32|f64}.store
- {i32|i64}}.store8
- {i32|i64}.store16
- i64.store32
如下所示的WAT程序(app.wat)文件利用两个导出的函数store和load对导入的Memory实施写入和读取。我们假设存储的数据类型均为i32,所以store函数在执行i32.store指令的时候,代表写入序号的第一个参数需要乘以4,作为指令的第一个参数(代表写入的起始位置)。load函数在执行i32.load指令的时候也需要做类似的处理。
(module
(memory (import "imports" "memory") 1)
(func (export "store") (param $index i32) (param $value i32)
(i32.store (i32.mul (local.get $index) (i32.const 4)) (local.get $value))
)
(func (export "load") (param $index i32) (result i32)
(i32.load (i32.mul (local.get $index) (i32.const 4)))
)
)
作为数组应用的JavaScript程序可以将Memory对象的缓冲区映射为指定元素类型的数组,并以数组的形式对其进行读写。在我们的演示实例中,作为宿主应用的index.html页面调用构造函数创建了一个WebAssembly.Memory对象,并将其buffer属性对应的缓冲区映射成一个Int32Array对象,并将前三个元素赋值为1、2和3。我们将Memory对象导入到加载的app.wasm模块中后,调用导出的load函数以i32类型将Memory中存储的12个字节读出来。
<html>
<head></head>
<body>
<script>
var memory= new WebAssembly.Memory({ initial: 1, maximum: 3});
var array = new Int32Array(memory.buffer); array[0] = 1;
array[1] = 2;
array[2] = 3;
WebAssembly
.instantiateStreaming(fetch("app.wasm"), {"imports":{"memory":memory}})
.then((results) => {
var exports = results.instance.exports; console.log(`load (0) = ${exports.load(0)}`);
console.log(`load (1) = ${exports.load(1)}`);
console.log(`load (2) = ${exports.load(2)}`);
});
</script>
</body>
</html>
从如下所示的三个输出结果可以看出,wasm模块中读取的内容与宿主应用设置的内容是一致的。

上面演示了wasm模块读取宿主应用写入Memory的内容,我们接下来通过修改index.html的内容调用导出的store函数往Memory中写入相同的内容,然后在宿主JavaScript程序中利用映射的数组将其读出来。
<html>
<head></head>
<body>
<script>
var memory= new WebAssembly.Memory({ initial: 1, maximum: 3});
var array = new Int32Array(memory.buffer);
WebAssembly
.instantiateStreaming(fetch("app.wasm"), {"imports":{"memory":memory}})
.then((results) => {
var exports = results.instance.exports;
exports.store(0, 1);
exports.store(1, 2);
exports.store(2, 3); console.log(`array[0] = ${array[0]}`);
console.log(`array[1] = ${array[0]}`);
console.log(`array[2] = ${array[0]}`);
});
</script>
</body>
</html>
宿主程序从Memory中读取的内容体现在如下的输出结果中。

三、内容初始化
store指令一次只能往Memory对象的缓存区写入指定数据对象承载的全部或者部分字节,如果需要在初始化一长串字节(比如一大段文本),可以将其存储到data section中,data section会与Memory对象自动关联。在如下所示的WAT程序中(app.wat),我们声明了一个data section,并用它来存储一段文本(Hello World!),文本经过UTF-8编码后的字节将存储在此区域中。data指令的第一个参数 (i32.const 0)表示存储的起始位置。
(module
(data (i32.const 0) "Hello, World!")
(memory (export "memory") 1)
)
上面的WAT程序还定义并导出了一个Memory对象,利用它与data section的自动映射机制,我们可以利用Memory来读取存储的文本。在如下所示作为宿主应用的index.html中,我们提取出导出的Memory对象,并将其缓冲区映射为一个Int8Array对象,然后利用TextDescorder将其解码成文本并输出。
<html>
<head></head>
<body>
<script>
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then((results) => {
var exports = results.instance.exports;
var array = new Int8Array(exports.memory.buffer, 0, 13); console.log(new TextDecoder().decode(array))
});
</script>
</body>
</html>
从如下所示的输出结果可以看出,我们利用Memory成功读取了存储在data section的文本。

四、多Memory支持
WebAssembly目前的正式版本只支持“单Memory模式”,也就是说一个wasm只维护一个单一的Memory对象。虽然“多Memory”目前还处于实验阶段,但是目前主流的浏览器还是支持的,WAT程序中针对多Memory的程序又如何编写呢?在如下这个演示程序中,我们定义了4个Memory,并分别将其命名为$m0、$m1、$m2和$m3,其中前两个为导入对象,后两个为导出对象。我们将这4个Memory对象的初始化容量分别设置为1、2、3、4,导出的size函数用来返回指定Memory对象当前的容量。
(module
(memory $m0 (import "imports" "memory1") 1)
(memory $m1 (import "imports" "memory2") 2)
(memory $m2 (export "memory3") 3)
(memory $m3 (export "memory4") 4) (func (export "size") (param $memory i32) (result i32)
(local $size i32)
(local.set $size (memory.size $m0)) (i32.eq (local.get $memory) (i32.const 1))
if
(local.set $size (memory.size $m1))
end (i32.eq (local.get $memory) (i32.const 2))
if
(local.set $size (memory.size $m2))
end (i32.eq (local.get $memory) (i32.const 3))
if
(local.set $size (memory.size $m3))
end (local.get $size)
)
)
size函数利用第一个参数(0、1、2、3)来确定具体的Memory对象,在执行memory.size的时候, 我们会附加上Memory的命名(默认为第一个Memory)。除了指定给定的别名,也可以按照如下的方式使用Memory的序号(0、1、2和3),其他指令的使用与之类似。
(module
(memory (import "imports" "memory1") 1)
(memory (import "imports" "memory2") 2)
(memory (export "memory3") 3)
(memory (export "memory4") 4) (func (export "size") (param $memory i32) (result i32)
(local $size i32)
(local.set $size (memory.size 0)) (i32.eq (local.get $memory) (i32.const 1))
if
(local.set $size (memory.size 1))
end (i32.eq (local.get $memory) (i32.const 2))
if
(local.set $size (memory.size 2))
end (i32.eq (local.get $memory) (i32.const 3))
if
(local.set $size (memory.size 3))
end (local.get $size)
)
)
在执行wat2wasm对app.wat进行编译的时候,我们需要手工添加命令行开关--enable-multi-memory以提供针对“多Memory”的支持(wat2wasm app.wat -o app.wasm --enable-multi-memory)。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
var memory1 = new WebAssembly.Memory({initial:1});
var memory2 = new WebAssembly.Memory({initial:2});
WebAssembly
.instantiateStreaming(fetch("app.wasm"), {"imports":{"memory1":memory1, "memory2":memory2}})
.then((results) => {
var exports = results.instance.exports; console.log(`memory1.size = ${exports.size(1)}`);
console.log(`memory2.size = ${exports.size(2)}`);
console.log(`memory3.size = ${exports.size(3)}`);
console.log(`memory4.size = ${exports.size(4)}`);
});
</script>
</body>
</html>
在如上所示的作为宿主的index.html中,我们利用调用导出的size函数将四个Memory的初始容量输出到控制台上,具体的输出结果如下所示。

利用data section对Memory的填充同样也支持多Memory模式。如下面的代码片段所示,我们在app.wat中定义并导出了三个Memory,随后定义的三个data section通过后面指定的序号(默认为0)。我们将三个data section填充为对应的文本“foo”、“bar”和“baz”。
(module
(memory (export "memory1") 1)
(memory (export "memory2") 1)
(memory (export "memory3") 1)
(data (i32.const 0) "foo")
(data 1 (i32.const 0) "bar")
(data 2 (i32.const 0) "baz")
)
作为宿主的index.html在获得导出的Memory对象后,同样将它们的缓冲区映射为Int8Array对象,并将其解码成字符串并输出到控制台上。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then((results) => {
var exports = results.instance.exports;
var decoder = new TextDecoder(); var array = new Int8Array(exports.memory1.buffer, 0, 3);
console.log(`memory1: ${decoder.decode(array)}`); array = new Int8Array(exports.memory2.buffer, 0, 3);
console.log(`memory2: ${decoder.decode(array)}`); array = new Int8Array(exports.memory3.buffer, 0, 3);
console.log(`memory3: ${decoder.decode(array)}`);
});
</script>
</body>
</html>
从三个导出的Memory中得到的字符串按照如下的形式输出到控制台上,可以看出它们与三个data section存储的内容是一致的。

五、批量缓冲处理
针对Memory的操作本质上就是针对字节缓冲区的操作,但是就目前发布的正式版本来说,相关的缓冲区操作还有待完善,不过很多都在“提案”里面了,其中就包括针对bulk memory operations。其中涉及如下一些有用的指令,它们已经在Web Assembly最新的spec草案里了,而且主流的浏览器也提供了部分支持。
- memory.init: 从指定的data section中指定一段内存片段来初始化Memory;
- memory.fill: 利用指定的字节内容来填充Memory的一段连续的缓冲区;
- memory.copy:连续内存片段的拷贝;
接下来我们来演示一下针对memory.fill指令的应用。在如下所示的WAT程序中(app.wat),我们定义并导出了一个Memory对象。导出的fill函数调用memory.fill指令往导出的这个Memory指定的位置填充指定数量($count)的值($value)。
(module
(memory (export "memory") 1)
(func (export "fill") (param $offset i32) (param $value i32) (param $count i32)
(memory.fill (local.get $offset) (local.get $value) (local.get $count))
)
)
在作为宿主的index.html页面中,我们两次调用导出的fill函数从Memory缓冲区的初始位置开始填充两个值255和266。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then((results) => {
var exports = results.instance.exports; exports.fill(0,255,2);
var array = new Int8Array(exports.memory.buffer, 0, 8);
array.forEach((value, index, _)=> console.log(`[${index}] = ${value}`)); exports.fill(0,256,2);
var array = new Int8Array(exports.memory.buffer, 0, 8);
array.forEach((value, index, _)=> console.log(`[${index}] = ${value}`));
});
</script>
</body>
</html>
我们将缓冲区映射为一个Int8Array对象,并将其前8个字节输出到控制台上。作为memory.fill指令的第二个参数,表示填充值得数据类型应该是Byte,但是wasm支持的整数类型只有i32和i64,所以这里的参数类型只能表示为i32,但是该指令只会使用指定值的低8位。这一点可以从输出结果得到印证:第一次调用指定的值是255(00 00 00 FF,转换成Int8就是-1),最终只会填充前面2个字节(FF FF)。第二次调用指定的值为256(00 00 01 00),所以填充的前两个字节为00 00。

WebAssembly核心编程[4]: Memory的更多相关文章
- QT核心编程之调试技术 (g)
Qt应用程序的调试可以通过DDD进行跟踪调试和打印各种调试或警告信息.DDD(Data Display Debugger)是使用gdb调试工具的图形工具,它安装在Linux操作系统中,使用方法可参考D ...
- 【windows核心编程】远程线程DLL注入
15.1 DLL注入 目前公开的DLL注入技巧共有以下几种: 1.注入表注入 2.ComRes注入 3.APC注入 4.消息钩子注入 5.远线程注入 6.依赖可信进程注入 7.劫持进程创建注入 8.输 ...
- C++Windows核心编程读书笔记
转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔 ...
- 【转】《windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- C++核心编程
C++核心编程 本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓. 1 内存分区模型 C++程序在执行时,将内存大方向划分为4个区域 代码区:存放函数体的二进制代码,由操作系统 ...
- Qt on Android 核心编程
Qt on Android 核心编程(最好看的Qt编程书!CSDN博主foruok倾力奉献!) 安晓辉 著 ISBN 978-7-121-24457-5 2015年1月出版 定价:65.00元 4 ...
- windows核心编程 - 线程同步机制
线程同步机制 常用的线程同步机制有很多种,主要分为用户模式和内核对象两类:其中 用户模式包括:原子操作.关键代码段 内核对象包括:时间内核对象(Event).等待定时器内核对象(WaitableTim ...
- windows核心编程---第九章 同步设备IO与异步设备IO之同步IO
同步设备IO 所谓同步IO是指线程在发起IO请求后会被挂起,IO完成后继续执行. 异步IO是指:线程发起IO请求后并不会挂起而是继续执行.IO完毕后会得到设备的通知.而IO完成端口就是实现这种通知的很 ...
- windows核心编程---第八章 使用内核对象进行线程同步
使用内核对象进行线程同步. 前面我们介绍了用户模式下线程同步的几种方式.在用户模式下进行线程同步的最大好处就是速度非常快.因此当需要使用线程同步时用户模式下的线程同步是首选. 但是用户模式下的线程同步 ...
- python核心编程(第二版)习题
重新再看一遍python核心编程,把后面的习题都做一下.
随机推荐
- 《深入理解计算机系统》(CSAPP)实验四 —— Attack Lab
这是CSAPP的第四个实验,这个实验比较有意思,也比较难.通过这个实验我们可以更加熟悉GDB的使用和机器代码的栈和参数传递机制. @ 目录 实验目的 准备工作 内容简介 代码注入攻击 Level 1 ...
- rem在手机移动端app中的兼容适配问题
这是我之前一直使用的第一种rem方案.贴代码 1 <script> 2 // 适用于750的设计稿 3 var iScale = 1; 4 // 通过页面加载的时候去获取用户设备的物理像素 ...
- C#爬虫知识介绍
爬虫 爬虫(Web Crawler)是指使用程序自动获取互联网上的信息和数据的一种技术手段.它通常从一个起始网址出发,按照一定的规则递归地遍历网页,并将有用的信息提取出来,然后存储到本地或者数据库中, ...
- java进阶(32)--Collections工具类
一.简介:Collection与Collections区别 1.Java.until.Collection是集合接口 2.Java.until.Collections是集合工具类,方便集合的操作 二. ...
- RSA 加密,解密,签名,验签
一.RSA加密简介 RSA加密是一种非对称加密.可以在不直接传递密钥的情况下,完成解密.这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险. 是由一对密钥来进行加解密的过程,分别称为公钥和 ...
- Oracle 专用模式与共享模式的学习与思考
Oracle 专用模式与共享模式的学习与思考 说明 Oracle数据库中的专用模式和共享模式是两种不同的数据库运行模式,它们在应用场景和权限管理上有所不同. 专用模式(Dedicated Mode): ...
- [转帖]arm linux下编译xtrabackup-2.4.5
环境:aarch64/centos7.6 glibc-2.17 编译器:gcc version 5.5.0 (GCC) 官方参考文档:https://www.percona.com/doc/perc ...
- [转帖]通过 TiUP 部署 TiDB 集群的拓扑文件配置
https://docs.pingcap.com/zh/tidb/stable/tiup-cluster-topology-reference 通过 TiUP 部署或扩容 TiDB 集群时,需要提供一 ...
- [转帖]GCC 编译及编译选项
俗话说:'工欲善其事,必先利其器',一直在工作中使用GNU C编译器(以下简称GCC),这里对GCC的一些警告选项细致的分析,并列举几个简单的例子[注1]供分析参考. 1. -Wall集合警告选项我们 ...
- [转帖]Perf IPC以及CPU性能
https://plantegg.github.io/2021/05/16/Perf%20IPC%E4%BB%A5%E5%8F%8ACPU%E5%88%A9%E7%94%A8%E7%8E%87/ Pe ...