首页 > 其他分享 >WebAssembly入门笔记[3]:利用Table传递引用

WebAssembly入门笔记[3]:利用Table传递引用

时间:2024-01-29 09:15:02浏览次数:20  
标签:WebAssembly op1 op2 get i32 param Table local 入门

在《WebAssembly入门笔记[2]》中,我们介绍了如何利用Memory在作为宿主的JavaScript应用和wasm模块之间传递数据,但是Memory面向单纯二进制字节的读写在使用起来还是不太方便,此时我们会更多地用到另一个重要的对象Table。Table利用用来存储一组指定类型的对象,说得准确一点是对象的引用,所以可以读取出来直接消费。

一、利用Table存储wasm函数引用
二、执行call_indirect执行函数
三、利用Table存储JavaScript函数引用

一、利用Table存储wasm函数引用

就目前的版本来说,Table只支持funcrefexternref两种引用类型,前者表示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,所以页面加载后将会呈现出如下的输出。

image

二、执行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入门笔记[1]:与JavaScript的交互
WebAssembly入门笔记[2]:利用Memory传递字节数据
WebAssembly入门笔记[3]:利用Table传递引用

标签:WebAssembly,op1,op2,get,i32,param,Table,local,入门
From: https://www.cnblogs.com/artech/p/17989165/hello_wasm_3

相关文章

  • 数学建模入门笔记(3) 插值与拟合
    插值与拟合插值和拟合的区别:拟合不要求过每一个已知点,而插值要求过每一个已知点,因而插值可以看作过每一个点的拟合。插值适用于补全缺失值,因为使用一般拟合就有可能使已知值偏移,不符合需求。据说PS用某种样条插值,放大的时候最大程度的保留连续性,因此显得不是那么模糊.数学建模......
  • 【ElasticSearch】入门-基础概念
    什么是ES?是一个高可用分布式的搜索引擎。可以用于实时存储、检索数据。底层是使用Lucene全文检索框架。基本概念存储结构:由_index_type和_id标识唯一的一个文档_index:指向一个或多个物理分片的逻辑命名空间_type:用于区分同一个集合中的不同的细分(ES6.X中只允许一个i......
  • Windows Server 2012 R2 安装 Visual C++ Redistributable (VC_redist.x64) 失败 0x80
    PHP8需要 VisualC++RedistributableforVisualStudio2019,但怎么都装不上,有个0x80240017-未指定的错误。 看日志 Windows8.1-KB2999226-x64.msu好像有补丁安装失败了,网上找到一篇解决办法:https://blog.51cto.com/u_12701820/3032471能成功安装VC,但是PHP8无法......
  • C# 继承、多态性、抽象和接口详解:从入门到精通
    C#继承在C#中,可以将字段和方法从一个类继承到另一个类。我们将“继承概念”分为两类:派生类(子类)-从另一个类继承的类基类(父类)-被继承的类要从一个类继承,使用:符号。在以下示例中,Car类(子类)继承了Vehicle类(父类)的字段和方法:示例classVehicle//基类(父类){......
  • 如何通过观测云的RUM找到前端加载的瓶颈--可观测性入门篇
    声明与保证本文写作于2023年6月,性能优化的评价标准和优化方式仅适用于当前观测云控制台,当然随着产品迭代及技术更新,本文也会应要求适当更新。创建、修订时间创建修改人版本2023/6/24观测云***v1.0.01.网站性能评价的发展史(近20年)讲到网站性能优化,离不开网站技术发展史,更离不开网站......
  • 洛谷 P1749 [入门赛 #19] 分饼干 II 题解
    题目传送门先给结论:记\(S=1+2+\dots+k\),则当\(N\geS\)时为YES,当\(N<S\)时为NO。严谨一点,证明如下:若能成功分配饼干,记\(k\)名小朋友拿到的饼干数量分别为\(a_1,a_2,\dots,a_k\)。由于饼干数量各不相同且均为整数,不妨设\(a_1<a_2<\dots<a_k\),则\(a_2\gea_1+1,a_3\g......
  • 【C++入门到精通】C++入门 —— list (STL)
    @TOC前言文章绑定了VS平台下std::list的源码,大家可以下载了解一下......
  • StreamPark从零快速入门(本地调试、功能演示及源码分析)
    本文目录结构:1.引言2.StreamPark项目导入与调试|____Step1:物料准备|____Step2:导入项目|____Step3:配置与打包|____Step4:启动与调试3.演示(新建作业并上线)|____Step1:下载Flink安装包并启动集群|____Step2:配置Flink插件及集群|____Step3:配置作业并上线4.源码分......
  • 2024不可不会的StableDiffusion(一)
    1.引言这是我在学习StableDiffusion(稳定扩散模型简称SD)的第一篇入门文章,主要用于介绍稳定扩散模型和该领域的其他研究。在本文中,我想简要介绍一下如何使用Diffuser扩散库,来创建自己生成图像。下一篇文章,我们将深入研究这个库的各级组件。闲话少说,我们直接开始吧!2.SD功能介......
  • MySQL数据库精选(从入门使用到底层结构)
    基本使用MySQL通用语法及分类DDL:数据定义语言,用来定义数据库对象(数据库、表、字段)DML:数据操作语言,用来对数据库表中的数据进行增删改DQL:数据查询语言,用来查询数据库中表的记录DCL:数据控制语言,用来创建数据库用户、控制数据库的控制权限DDL(数据定义语言)数据定义......