在《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传递引用的更多相关文章

  1. 《从零开始学Swift》学习笔记(Day 20)——函数中参数的传递引用

    原创文章,欢迎转载.转载请注明:关东升的博客 参数的传递引用 类是引用类型,其他的数据类型如整型.浮点型.布尔型.字符.字符串.元组.集合.枚举和结构体全部是值类型. 有的时候就是要将一个值类型参数以 ...

  2. Blazor入门笔记(6)-组件间通信

    1.环境 VS2019 16.5.1.NET Core SDK 3.1.200Blazor WebAssembly Templates 3.2.0-preview2.20160.5 2.简介 在使用B ...

  3. MySQL入门笔记

    MySQL入门笔记 版本选择: 5.x.20 以上版本比较稳定 一.MySQL的三种安装方式: 安装MySQL的方式常见的有三种: ·          rpm包形式 ·          通用二进制 ...

  4. FlatBuffer入门笔记

    FlatBuffer入门笔记 1 flatbuffer资料 flatbuffer下载地址:https://github.com/google/flatbuffers flatbuffer官方使用文档: ...

  5. [R语言] ggplot2入门笔记4—前50个ggplot2可视化效果

    文章目录 通用教程简介(Introduction To ggplot2) 4 ggplot2入门笔记4-前50个ggplot2可视化效果 1 相关性(Correlation) 1.1 散点图(Scat ...

  6. [Java入门笔记] 面向对象编程基础(二):方法详解

    什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...

  7. React.js入门笔记

    # React.js入门笔记 核心提示 这是本人学习react.js的第一篇入门笔记,估计也会是该系列涵盖内容最多的笔记,主要内容来自英文官方文档的快速上手部分和阮一峰博客教程.当然,还有我自己尝试的 ...

  8. redis入门笔记(2)

    redis入门笔记(2) 上篇文章介绍了redis的基本情况和支持的数据类型,本篇文章将介绍redis持久化.主从复制.简单的事务支持及发布订阅功能. 持久化 •redis是一个支持持久化的内存数据库 ...

  9. 「Android 开发」入门笔记

    「Android 开发」入门笔记(界面编程篇) ------每日摘要------ DAY-1: 学习笔记: Android应用结构分析 界面编程与视图(View)组件 布局管理器 问题整理: Andr ...

  10. redis入门笔记

    redis入门笔记 参考redis实战手册 1. Redis在windows下安装 下载地址:https://github.com/MSOpenTech/redis/tags 安装Redis 1.1. ...

随机推荐

  1. Winform 嵌入html,数据交互

    Winform 嵌入html,数据交互,将JS中的数据等传给winform 点击浏览器中的按钮,触发 Winform 中的方法 https://github.com/cefsharp/CefSharp ...

  2. .NET Moq mock internal类型

    问题 Can not create proxy for type xxx because type xxx is not accessible. Make it public, or internal ...

  3. SCOI2005 互不侵犯 (状态压缩入门题)

    使用状态压缩,最好了解 位运算使用 SCOI2005 互不侵犯 ​ 在 \(N\times N\) 的棋盘里面放 \(K\) 个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左 ...

  4. XSS、CSRF 以及如何防范

  5. 一文看完String的前世今生,内容有点多,请耐心看完!

    写在开头 String字符串作为一种引用类型,在Java中的地位举足轻重,也是代码中出现频率最高的一种数据结构,因此,我们需要像分析Object一样,将String作为一个topic,单独拿出来总结, ...

  6. centos7 docker配置防火墙firewalld

    docker防火墙使用的是底层iptables,封装后的firewalld默认不生效 如果想要使用firewalld,需要做以下调整: 让firewalld移除DOCKER-USER并新建一个 # R ...

  7. 4.Prometheus之存储及WAL

    一.整体介绍 二.block 2.1 head block 三.WAL(Write-ahead logging, 预写日志) 3.1 数据流向 四.和存储相关的启动参数 五.总结 一.整体介绍 Pro ...

  8. Angular系列教程之观察者模式和RxJS

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  9. 介绍这个库:C# Blazor中显示Markdown文件

    1 讲目的 前几天上线了一个在线Icon转换工具,为了让大家使用放心,改了点代码,在转换下载Icon图标后立即删除临时文件,并在工具下面贴上了工具的开发步骤和代码,大家看这样改是否合适,见Issue ...

  10. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2024.01.08)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...