WebAssembly入门笔记[3]:利用Table传递引用
在《WebAssembly入门笔记[2]》中,我们介绍了如何利用Memory在作为宿主的JavaScript应用和wasm模块之间传递数据,但是Momory面向单纯二进制字节的读写在使用起来还是不太方便,此时我们会更多地用到另一个重要的对象Table。Table利用用来存储一组指定类型的对象,说得准确一点是对象的引用,所以可以读取出来直接消费。
一、利用Table存储wasm函数引用
二、执行call_indirect执行函数
三、利用Table存储JavaScript函数引用
一、利用Table存储wasm函数引用
就目前的版本来说,Table只支持funcref和externref两种引用类型,前者表示wasm原生函数,后者则用来存储宿主程序提供的任何JavaScript对象,所以如果存储JavaScript函数,Table元素的类型必需指定为externref。下面的实例演示了这样的场景:wasm模块将自身定义的函数存储在导出的Table中供宿主程序使用。
如下所示的采用WebAssembly Text(WAT)格式定义的app.wat文件的定义。我们定义了用来执行加、减、乘、除运算的四个函数,并将它们存储在导出的Table中。由于存储的是wasm函数,所以Table定义语句(table (export "table") funcref (elem $add $sub $mul $div))将元素类型设置为funcref。我们利用elem语句将四个函数的引用填充到Table中。(源代码)
(module
(func $add (param $op1 i32) (param $op2 i32) (result i32)
(local.get $op1)
(local.get $op2)
(i32.add)
)
(func $sub (param $op1 i32) (param $op2 i32) (result i32)
(local.get $op1)
(local.get $op2)
(i32.sub)
)
(func $mul (param $op1 i32) (param $op2 i32) (result i32)
(local.get $op1)
(local.get $op2)
(i32.mul)
)
(func $div (param $op1 i32) (param $op2 i32) (result i32)
(local.get $op1)
(local.get $op2)
(i32.div_u)
)
(table (export "table") funcref (elem $add $sub $mul $div))
)
上面的定义主要是为了解释wasm基于“堆栈”的参数传递方式,代码相对繁琐。如果切换如下所示的“嵌套模式”,就会简洁很多。(源代码)
(module
(func $add (param $op1 i32) (param $op2 i32) (result i32)
(i32.add (local.get $op1) (local.get $op2))
)
(func $sub (param $op1 i32) (param $op2 i32) (result i32)
(i32.sub (local.get $op1) (local.get $op2))
)
(func $mul (param $op1 i32) (param $op2 i32) (result i32)
(i32.mul (local.get $op1) (local.get $op2))
)
(func $div (param $op1 i32) (param $op2 i32) (result i32)
(i32.div_u (local.get $op1) (local.get $op2))
)
(table (export "table") funcref (elem $add $sub $mul $div))
)
在承载宿主应用的index.html中,在得到导出的Table对象之后,我们将存储(0-3)的位置作为参数调用其get方法得到对应的wasm函数。我们传入相同的参数(2,1)调用这四个函数。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then(results => {
var table = results.instance.exports.table;
document.getElementById("container").innerHTML =
`<p>2 + 1 = ${table.get(0)(2,1)}</p>`+
`<p>2 - 1 = ${table.get(1)(2,1)}</p>`+
`<p>2 * 1 = ${table.get(2)(2,1)}</p>`+
`<p>2 / 1 = ${table.get(3)(2,1)}</p>`;
});
</script>
</body>
</html>
我们将包含结果的运算表达式格式化成HTML,所以页面加载后将会呈现出如下的输出。
二、执行call_indirect执行函数
对于存储在Table中的wasm函数,我们还可以按照如下的方式执行call_indirect指令间接执行它。执行call_indirect指定时需要以”类型“的形式确定待执行函数的签名,由于四个函数的签名都是一致的(两个参数和返回值类型均为i32类型),所以我们定义了一个名为$i32_i32_i32的函数类型。(源代码)
(module
(func $add (param $op1 i32) (param $op2 i32) (result i32)
(i32.add (local.get $op1) (local.get $op2))
)
(func $sub (param $op1 i32) (param $op2 i32) (result i32)
(i32.sub (local.get $op1) (local.get $op2))
)
(func $mul (param $op1 i32) (param $op2 i32) (result i32)
(i32.mul (local.get $op1) (local.get $op2))
)
(func $div (param $op1 i32) (param $op2 i32) (result i32)
(i32.div_u (local.get $op1) (local.get $op2))
)
(table funcref (elem $add $sub $mul $div)) (type $i32_i32_i32 (func (param i32) (param i32) (result i32)))
(func (export "calc") (param $index i32) (param $op1 i32) (param $op2 i32) (result i32)
(call_indirect (type $i32_i32_i32) (local.get $op1) (local.get $op2) (local.get $index))
)
)
我们定义了名为calc的导出函数执行存储在Table中的函数,该函数的第一个参数$index表示函数在Table中的位置,后续两个参数才是算数操作数。传入call_indirect指令的4个参数分别是函数类型、传入目标函数的参数和函数在Table中的位置。index.html中的JavaScript代码以如下的方式调用导出函数calc,所以页面会呈现出与上面相同的输出。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
WebAssembly
.instantiateStreaming(fetch("app.wasm"))
.then(results => {
var calc = results.instance.exports.calc;
document.getElementById("container").innerHTML =
`<p>2 + 1 = ${calc(0, 2 ,1)}</p>`+
`<p>2 - 1 = ${calc(1, 2 ,1)}</p>`+
`<p>2 * 1 = ${calc(2, 2 ,1)}</p>`+
`<p>2 / 1 = ${calc(3, 2 ,1)}</p>`;
});
</script>
</body>
</html>
三、利用Table存储JavaScript函数引用
第一个实例演示了将wasm函数存储在Table中供JavaScript应用调用,那么是否可以反其道而行之,将JavaScript函数存储在Table中传入wasm模块中执行呢?答案是不可能,至少目前不可以。我们在前面提到过,包含函数在内的JavaScript对象只能以externref的形式存在在Table中,对于externref,wasm模块无法对其”解引用“,自然也不能直接对它进行消费。
但是我们可以将它们回传到作为宿主的JavaScript应用中执行,下面的代码很好地演示了这一点。这次我们选择在JavaScript应用中创建Table,并将其导入到wasm模块。如下面的代码片段所示,一并导入的还有一个被命名为$apply函数。这个函数具有三个参数,第一个参数类型为externref,表示存储在Table中的JavaScript函数,后面两个参数运算操作上。(源代码)
(module
(import "imports" "table" (table 4 externref))
(func $apply (import "imports" "apply") (param externref) (param i32) (param i32))
(func $calc (param $index i32) (param $op1 i32) (param $op2 i32)
(call $apply (table.get (local.get $index)) (local.get $op1) (local.get $op2))
)
(func (export "calculate") (param $op1 i32) (param $op2 i32)
(call $calc (i32.const 0) (local.get $op1) (local.get $op2))
(call $calc (i32.const 1) (local.get $op1) (local.get $op2))
(call $calc (i32.const 2) (local.get $op1) (local.get $op2))
(call $calc (i32.const 3) (local.get $op1) (local.get $op2))
)
)
JavaScript函数的执行实现在$calc函数中,它的第一个参数表示函数在Table中的位置。我们通过执行table.get指令得到存储在Table以externref形式存在的JavaScript函数,并将它和两个操作数作为参数调用导入的$apply函数。导出函数calculate调用$calc函数完成针对4中运算的执行。
导入的apply函数在index.html中以如下的形式定义。我们调用构造函数WebAssembly.Table创建了一个Table对象,并将初始化大小和元素类型设置为4和externref。我们调用Table对象的set方法将四个JavaScript函数存储在这个Table中,四个函数会执行加、减、乘、除运算并将表达式拼接在html字符串上,后者将会作为<div>的innerHTML,所以页面程序的输出还是与上面一致。
<html>
<head></head>
<body>
<div id="container"></div>
<script>
var html = "";
var apply = (func, op1, op2)=> func(op1, op2);
const table = new WebAssembly.Table({ initial: 4, element: "externref" }); table.set(0, (op1, op2)=> html += `<p>${op1} + ${op2} = ${op1 + op2}</p>`);
table.set(1, (op1, op2)=> html += `<p>${op1} - ${op2} = ${op1 - op2}</p>`);
table.set(2, (op1, op2)=> html += `<p>${op1} * ${op2} = ${op1 * op2}</p>`);
table.set(3, (op1, op2)=> html += `<p>${op1} / ${op2} = ${op1 / op2}</p>`); WebAssembly
.instantiateStreaming(fetch("app.wasm"), {"imports":{"table":table, "apply":apply}})
.then(results => {
html = "";
results.instance.exports.calculate(4,2);
document.getElementById("container").innerHTML = html;
});
</script>
</body>
</html>
WebAssembly入门笔记[3]:利用Table传递引用的更多相关文章
- 《从零开始学Swift》学习笔记(Day 20)——函数中参数的传递引用
原创文章,欢迎转载.转载请注明:关东升的博客 参数的传递引用 类是引用类型,其他的数据类型如整型.浮点型.布尔型.字符.字符串.元组.集合.枚举和结构体全部是值类型. 有的时候就是要将一个值类型参数以 ...
- Blazor入门笔记(6)-组件间通信
1.环境 VS2019 16.5.1.NET Core SDK 3.1.200Blazor WebAssembly Templates 3.2.0-preview2.20160.5 2.简介 在使用B ...
- MySQL入门笔记
MySQL入门笔记 版本选择: 5.x.20 以上版本比较稳定 一.MySQL的三种安装方式: 安装MySQL的方式常见的有三种: · rpm包形式 · 通用二进制 ...
- FlatBuffer入门笔记
FlatBuffer入门笔记 1 flatbuffer资料 flatbuffer下载地址:https://github.com/google/flatbuffers flatbuffer官方使用文档: ...
- [R语言] ggplot2入门笔记4—前50个ggplot2可视化效果
文章目录 通用教程简介(Introduction To ggplot2) 4 ggplot2入门笔记4-前50个ggplot2可视化效果 1 相关性(Correlation) 1.1 散点图(Scat ...
- [Java入门笔记] 面向对象编程基础(二):方法详解
什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...
- React.js入门笔记
# React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...
- redis入门笔记(2)
redis入门笔记(2) 上篇文章介绍了redis的基本情况和支持的数据类型,本篇文章将介绍redis持久化.主从复制.简单的事务支持及发布订阅功能. 持久化 •redis是一个支持持久化的内存数据库 ...
- 「Android 开发」入门笔记
「Android 开发」入门笔记(界面编程篇) ------每日摘要------ DAY-1: 学习笔记: Android应用结构分析 界面编程与视图(View)组件 布局管理器 问题整理: Andr ...
- redis入门笔记
redis入门笔记 参考redis实战手册 1. Redis在windows下安装 下载地址:https://github.com/MSOpenTech/redis/tags 安装Redis 1.1. ...
随机推荐
- PPT 如何做出高大上的表格
字不如表.表不如图 如何做 https://www.bilibili.com/video/BV1ha411g7f5?p=17
- MySQL 错误记录:Data too long for column 'xxx' at row 1
Content 字段是 text 类型(Text是6万多)改成了 longtext 就OK了 ALTER TABLE `Article` CHANGE `Content` `Content` LONG ...
- zsh踩坑记录
1. zsh: no matches found: uvicorn[standard] 方法一 # 在~/.zshrc中添加下面这句话 setopt no_nomatch # 然后source ~/. ...
- CMake + Protobuf 自动生成 cpp 文件(pb.h, pb.cc)
[Protoc]VS2019 (VS平台) 使用 CMake 编译安装.使用 Protobuf 库 本文介绍在 macOS 系统下 cmake 和 protobuf 一起使用的一种方式--使用 cma ...
- 【网摘】SQL练习题
原文链接:Here
- Mynavi Programming Contest 2021(AtCoder Beginner Contest 201)A ~ E题题解
A - Tiny Arithmetic Sequence 水题,判断3个数是否能构成等差数列 void solve() { int a, b, c; cin >> a >> b ...
- django的简单学习
前言 以下项目实现基于一个投票系统 安装django 命令行安装 pip install django pycharm安装 pycharm的setting里找到这个,点击+号,搜索django 点击I ...
- normalize.css——移动端css初始化推荐
保护了有价值的默认值 修复了浏览器bug 是模块化的 拥有详细的文档 https://www.jianshu.com/p/9d7ff89757fd
- uni-app editor富文本编辑器
https://blog.csdn.net/xudejun/article/details/91508189
- python进阶(5)--函数
文档目录: 一.函数体二.实参与形参三.返回值四.举例:函数+while循环五.举例:列表/元组/字典传递六.模块与函数的导入 ------------------------------------ ...