《WebAssembly 权威指南》(7)WebAssembly 表
本文是《WebAssembly 权威指南》系列文章第 7 篇,系列文章列表:
《WebAssembly 权威指南》连载公告
《WebAssembly 权威指南》(1)WebAssembly 简介
《WebAssembly 权威指南》(2)WebAssembly 入门
《WebAssembly 权威指南》(3)WebAssembly 模块
《WebAssembly 权威指南》(4)WebAssembly 内存
《WebAssembly 权威指南》(5)使用 C/C++ 和 WebAssembly
《WebAssembly 权威指南》(6)在浏览器中运行遗留代码
译者注:这篇文章是《WebAssembly 权威指南》一书的第七章,介绍了 WebAssembly 的表(table)的概念和用法。表是一种存储函数指针的数据结构,可以让模块间动态地调用彼此的函数。文章分析了表的类型、元素、导入和导出等特性,并给出了几个使用表的示例代码,包括 C/C++ 和 JavaScript 的互操作。文章最后讨论了表的局限性和未来发展方向。
人们经常在餐桌上分享自己的想法和故事。与他人一起吃饭比自己一个人吃饭更有趣。如果你把一群来自各行各业的人聚集在一起,可能有谈不完的话题。没有人可以面面俱到。有的人可能分享相同故事的某些方面。其他人可能有他们自己的版本。然而必须有一定的礼仪、克制,并愿意接受其他参与者提供的东西。那些行为不端、喋喋不休或相互踩线的客人会毁了大家的晚餐。
表是 WebAssembly 成为一个现代软件系统的一个特点,其功能依赖将由额外的模块来满足。与静态链接库相比,它提供了相当于动态共享库的能力。不是每个模块都需要具有所有功能才能工作。这将使效率低的可怕。相反,它是根据一些其他模块在运行时满足需求的承诺来编写的。这在 C 和 C++ 世界中被称为动态链接。很明显,餐桌论只是对表(Table)这个词的玩味,就像在吃饭时需要礼仪一样,库之间的分享也需要规范。让我们更仔细地探讨这个想法,然后看看 WebAssembly 是如何支持的。
静态链接与动态链接
任何在 Twitter 上关注我的人都知道我妻子是一个多么了不起的厨师。她来自一个伟大的厨师家庭,有机会向很多大师学习。人们经常看到我发布的关于她制烹饪的帖子,并向我索取食谱。这通常不像发送一个链接那么容易,因为她经常把来自多个来源的想法结合起来,然后把自己的想法放在上面。
在我们家,她可以依靠她所积累的食谱库。她可以说,"用那本书里的酱汁做这个。用另一本书中描述的技术准备牛肉。在牛肉达到你想要的熟度后,加入这些我认为会让它变得更好的额外成分"。
在我们家,她可以参考已知来源的步骤和配料表,并以她的额外步骤修正过程。但是当她想把菜谱交给别人时,她不能默认人们有这些书。在这种情况下,她将不得不把她的来源中的食谱复制到完整的食谱文件中。这时,所有的步骤和成分都会被定义在一个地方,食谱就可以发给别人了。
这基本上就是静态链接和动态链接的区别。一个典型的程序需要读写文件的内容,打开窗口,收集用户的输入,或在网络上发送消息。这些都是很常见的任务,它们通常可以作为操作系统提供的库中的功能。当你希望使用其中的一个函数时,你会告诉链接器允许运行时链接。否则,它将抱怨缺少符号参考。
在运行时,操作系统将搜索其配置路径,告诉它在哪里可以找到这些共享库。在启动程序之前,它将把库中的功能映射到一个可以动态链接到其余代码的内存位置。 这样做有很多原因。首先是效率问题。比方说,你有一个名为 a ()
的函数被十几个其他程序引用。通过静态链接,每个可执行程序都有自己的副本。程序占用了更多的磁盘空间。它们在运行时的内存足迹也会变大。这不将浪费磁盘和内存空间。
如果动态库被加载到一个共享的内存空间,那么我们的磁盘上只需要一个文件副本。根据你的操作系统的复杂性,内存中可能也只需要一个副本。
动态链接库通常有自己的发布周期。如果你正在使用一个可执行程序的系统库,你可能会更新操作系统并得到一个带有安全补丁的新版本的库。只要编号机制正常,并且是向后兼容的,就可以通过使用打了补丁的版本来加强你的应用程序的安全性,而不需要做任何其他事情。
请看例 7-1,这是一个独立的函数,没有 main ()
函数。它的目的是作为一个库来使用。我们可以把它编译成一个静态库,但现在我们只需创建目标代码,并将我们的 main ()
程序与之链接。注意, 这个函数也依赖于 printf ()
,所以它必须导入 stdio.h
头。
例 7-1. 一个有函数调用的库
#include <stdio.h>void sayHello (char *message) {printf ("% s\n", message);
}
在例 7-2 中,你会看到 main ()
函数首先调用 printf ()
,然后再调用我们的函数,该函数也调用 printf ()
。
例 7-2. 一个调用库函数的 main ()
方法示例
#include <stdio.h>extern void sayHello (char *message);int main () {printf ("Hello, world.\n"); sayHello ("How are you?"); return 0;
}
默认情况下,如果你用 clang 编译这两个文件,它将生成一个输出文件。我们使用默认的名字。当我们运行它时,我们会看到我们所期望的行为。默认情况下,编译器将对系统库使用动态链接,以满足我已经列出的所有需求。
brian@tweezer ~/g/w/s/ch07> clang main.c library.c brian@tweezer ~/g/w/s/ch07> ls
a.out* library.c main.c
brian@tweezer ~/g/w/s/ch07> ./a.out
Hello, world.
How are you?
你可以用 nm 命令验证这里使用了动态链接。首先,我们看到我们的二进制文件提供了 main ()
和 sayHello ()
的定义,但没有 printf ()
。这是从标准库中重复使用的函数:
brian@tweezer ~/g/w/s/ch07> nm a.out
0000000100008008 d __dyld_private
0000000100000000 T __mh_execute_header
0000000100003f10 T _mainU _printf
0000000100003f50 T _sayHelloU dyld_stub_binder
在 Linux 上,你可以看到同样的构建步骤产生了一个带有额外功能的二进制文件。这很自然,因为它是一个不同的操作系统,有不同的运行时和不同的二进制格式。突出的一点是,我们的方法是在二进制文件中提供的,但 printf ()
却没有。
brian@bbfcfm:~/src/hello$ nm a.out
0000000000404030 B __bss_start
0000000000404030 b completed.8060
0000000000404020 D __data_start
0000000000404020 W data_start
0000000000401080 t deregister_tm_clones
0000000000401070 T _dl_relocate_static_pie
00000000004010f0 t __do_global_dtors_aux
0000000000403e08 d __do_global_dtors_aux_fini_array_entry 0000000000404028 D __dso_handle
0000000000403e10 d _DYNAMIC
0000000000404030 D _edata
0000000000404038 B _end
0000000000401218 T _fini
0000000000401120 t frame_dummy
0000000000403e00 d __frame_dummy_init_array_entry
000000000040216c r __FRAME_END__
0000000000404000 d _GLOBAL_OFFSET_TABLE_w __gmon_start__
0000000000402024 r __GNU_EH_FRAME_HDR
0000000000401000 T _init
0000000000403e08 d __init_array_end
0000000000403e00 d __init_array_start
0000000000402000 R _IO_stdin_used
0000000000401210 T __libc_csu_fini
00000000004011a0 T __libc_csu_initU __libc_start_main@@GLIBC_2.2.5
0000000000401130 T mainU printf@@GLIBC_2.2.5
00000000004010b0 t register_tm_clones
0000000000401170 T sayHello
0000000000401040 T _start
0000000000404030 D __TMC_END__
otool 命令是另一个可以在 macOS 上使用的命令,它可以显示哪些动态库是成功执行你的二进制文件所需要的。显示的是系统库的 macOS 版本:
brian@tweezer ~/g/w/s/ch07> otool -L a.out
a.out:/usr/lib/libSystem.B.dylib (compatibility vers 1.0.0, current vers 1292.60.1)
otool 在 Linux 上并不存在,但我们可以通过使用 objdump 看到类似的结果。为了节省空间,我把部分输出删除了,但相关部分显示在下面的片段中。在 Windows 上也会有类似的工具来检查你的 DLL 依赖性。正如你所看到的,我们需要 libc.so.6
来满足我们二进制文件的需要。
brian@bbfcfm:~/src/hello$ objdump -x a.outa.out: file format elf64-x86-64a.outarchitecture: i386:x86-64, flags 0x00000112:EXEC_P, HAS_SYMS, D_PAGEDstart address 0x0000000000401040
...
Dynamic Section:NEEDED libc.so.6INIT 0x0000000000401000FINI 0x0000000000401218INIT_ARRAY 0x0000000000403e00INIT_ARRAYSZ 0x0000000000000008FINI_ARRAY 0x0000000000403e08FINI_ARRAYSZ 0x0000000000000008HASH 0x00000000004002e8GNU_HASH 0x0000000000400310STRTAB 0x0000000000400390SYMTAB 0x0000000000400330STRSZ 0x000000000000003fSYMENT 0x0000000000000018DEBUG 0x0000000000000000PLTGOT 0x0000000000404000PLTRELSZ 0x0000000000000018PLTREL 0x0000000000000007JMPREL 0x0000000000400428RELA 0x00000000004003f8RELASZ 0x0000000000000030RELAENT 0x0000000000000018VERNEED 0x00000000004003d8VERNEEDNUM 0x0000000000000001VERSYM 0x00000000004003d0
Version References:required from libc.so.6:0x09691a75 0x00 02 GLIBC_2.2.5
...
WebAssembly 与操作系统显然不是一回事,但它得益于类似的概念。我们的选择是一样的:把所有的函数定义放到一个模块里,这样它就可以独立存在,或者从另一个模块调用行为,以满足我们的需要。考虑到我们要经常通过网络下载 WebAssembly 模块,让它们偏小是可取的。这也会影响到磁盘存储、模块验证、在内存中加载实例等。为此,我们有 Table 实例。
在模块中创建表
Table 实例有一些类似于我们在第 4 章[1]中介绍的 Memory 实例的特征。目前每个模块只能有一个,但它可以在模块中定义,也可以通过导入的对象传入。每个模块只有一个实例的限制在未来可能会被取消,但目前我们必须遵守这一规定。
我们在 WebAssembly 中采用这种结构,而不是仅仅使用 Memory 实例,部分原因是后者可以被模块操纵。进行晚餐谈话,我们不希望任何个人参与者改写行为准则。在共享模块上也是如此。如果我们已经加载并验证了一个通过表实例导出函数的模块,我们不希望另一个模块给其他人带来麻烦。因此,你所能做的就是对存储在表中的函数引用进行间接函数调用。目前,函数引用是唯一可以存储在表实例中的东西,但这也有望在未来改变。
在这一点上,我不想把事情搞得太复杂,回到 Wat 中的简单函数定义,以演示创建表实例和导出它们的方法。
在例 7-3 中,我创建了两个函数。$add
函数接收两个参数,将它们相加,然后返回结果。$sub
函数接收两个参数,用第一个参数减去第二个参数,然后返回结果。那又怎样呢?这不过是复习前几章的内容。这里的区别在于接下来会发生什么。
例 7-3. 一个导出其表实例的模块
(module(func $add (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.add)(func $sub (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.sub)(table (export "tbl") funcref (elem $add $sub))
)
我们引入了一个新的 Wat 关键字 ——table。这定义了一个函数引用的集合。注意内联导出命令。这允许主机环境调用 $add
和 $sub
函数,但不能通过函数名称调用。宿主只能通过表的实例来调用这两个函数。Anyfunc 类型目前是这个结构唯一允许的类型,正如我们之前指出的那样。根据 elem 引用中的排序,$add
将在第 0 个位置,$sub
将在第 1 个位置 [^1]。
我们可以把我们的 Wat 文件变成一个 Wasm 模块,并检查其内容,如下所示。注意表部分、类型部分和导出部分。
brian@tweezer ~/g/w/s/ch07> wat2wasm math.wat
brian@tweezer ~/g/w/s/ch07> wasm-objdump -x math.wasmmath.wasm: file format wasm 0x1Section Details:Type [1]:- type [0] (i32, i32) -> i32Function [2]:- func [0] sig=0- func [1] sig=0Table [1]:- table [0] type=funcref initial=2 max=2Export [1]:- table [0] -> "tbl"Elem [1]:- segment [0] flags=0 table=0 count=2 - init i32=0- elem [0] = func [0]- elem [1] = func [1]Code [2]:- func [0] size=7- func [1] size=7
例 7-4 中的 JavaScript 实例化了我们的模块,就像我们在之前章节中做的那样。从那里,它从模块的导出部分提取 Table 实例。
例 7-4. 使用从 JavaScript 导出的表实例
<!doctype html><html><head><title>WASM Table test</title><meta charset="utf-8"><script src="utils.js"></script><link rel="icon" href="data:;base64,="></head><body><script>var t;fetchAndInstantiate ('math.wasm').then (function (instance) {var tbl = instance.exports.tbl;t = tbl;console.log ("3 + 1 =" + tbl.get (0)(3,1));console.log ("3 - 1 =" + tbl.get (1)(3,1));});</script></body>
</html>
在我们获取引用后,我们可以检索到与第 0 个位置相关的函数并调用它。请记住,从get ()
调用回来的是一个函数的引用。为了调用它,我们提交第二组括号中的参数,然后将结果打印到控制台。然后我们对第 1 个位置上的函数也这样做。
通过 HTTP 发送 HTML,并打开 JavaScript 控制台。当你的浏览器执行该代码时,它应该如图 7-1 所示。
表实例只能有两个引用。如果你试图访问一个超过 tbl.length
的位置,就会引起一个异常。
WebAssembly 中的动态链接
我们的最后一个例子是在 WebAssembly 中使用动态链接。我们将定义两个模块。一个将包含我们预先定义的 $add
和 $sub
方法。第一个模块在例 7-5 中。与我们之前看到的主要区别是,这个模块从主机中导入了一个表。我们用 elem 指令将算术函数放入这个表中。加法函数被存放在 0 号位置,减法函数被存放在 1 号位置。
例 7-5. 一个动态链接的模块
(module(import "js" "table" (table 2 funcref))(func $add (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.add)(func $sub (param $a i32) (param $b i32) (result i32)local.get $alocal.get $bi32.sub)(elem (i32.const 0) $add)(elem (i32.const 1) $sub)
)
我们的第二个模块将输出两个函数,myadd 和 mysub。它向其客户宣传加减两个数字的能力。在内部,它将调用导入表实例中的函数引用,我们也从主机的 JavaScript 环境中导入。
我们所宣传的功能的实现见于例 7-6。两个函数都调用了 call_indirect
指令。在前面的章节中,我们看到使用调用指令来调用当前模块中定义的函数。call_indirect 指令通过确定你想调用的表的哪个元素来调用一个函数。
例 7-6. 依赖于动态链接模块的一个模块
(module(import "js" "table" (table 2 funcref))(type $sig (func (param $a i32) (param $b i32) (result i32)))(func (export "myadd") (param $a i32) (param $b i32) (result i32)(call_indirect (type $sig) (local.get $a) (local.get $b) (i32.const 0)))(func (export "mysub") (param $a i32) (param $b i32) (result i32)(call_indirect (type $sig) (local.get $a) (local.get $b) (i32.const 1)))
)
其中一个会让你眼前一亮的东西是类型指令的使用。这将定义一个函数的签名,以便在 WebAssembly 中提供一定程度的类型安全。我们的想法是,导入的表函数应该有你想要调用的签名。
在这种情况下,我们定义了一个函数签名,它接收两个 i32 并返回一个 i32。当我们通过表调用这些方法时,表明这是我们所期望的类型。在签名之后,我们将函数的参数推到堆栈中,最后推到表的位置号。对于加法,它的常量值是 0,代表表的第一个位置。对于减法,它将是第二个位置。
我们在例 7-7 中把这一切放在一起。我们做的第一件事是创建一个共享的表实例。这将通过 importObject
传递给两个模块。不同的是,math2.wat
模块将其函数 $add
和 $sub
分别写在 0 和 1 的位置。mymath.wat
模块从主机 JavaScript 环境中调用 myadd
和 mysub
时间接地调用了这些位置。作为调用的一部分,它们也将把它们被赋予的参数传递给动态链接的函数。
因为我们处理的是两个模块,所以我们的实例化机制略有不同。我们调用 Promise.all ()
方法,而不是等待一个单一的 Promise,该方法会阻止所有的从属 Promise 得到满足。在这种情况下,这意味着两个模块都已加载并准备就绪。
例 7-7. 实例化两个模块并在它们之间建立动态链接
<!doctype html><html><head><title>WASM Dynamic Linking test</title><meta charset="utf-8"><script src="utils.js"></script><link rel="icon" href="data:;base64,="></head><body><script>var importObject = {js: {memory: new WebAssembly.Memory({ initial: 1 }),table: new WebAssembly.Table({ initial:2, element:"anyfunc" })}};Promise.all([fetchAndInstantiate('math2.wasm', importObject),fetchAndInstantiate('mymath.wasm', importObject)]).then(function(instances) {console.log("4 + 3 = " + instances[1].exports.myadd(4,3));console.log("4 - 3 = " + instances[1].exports.mysub(4,3));});</script></body>
</html>
一旦模块都可用,这段代码就用一些参数调用 myadd
和 mysub
方法。注意我们正在选择第二个模块实例,代表我们的行为版本。这是一个数组的实例,而不是一个单一的实例。
在通过 HTTP 提供服务后,浏览器中的结果如图 7-2 所示。一个模块通过共享的 Table 实例间接调用在另一个模块中实现的行为。
至此,我们对 WebAssembly 作为一个平台的主要功能元素的介绍结束了。本书的其余部分将以这些基础知识为基础,向你展示几个例子,介绍如何使用 WebAssembly,以及它的未来。包括一些我们尚未涉及的更高级的功能。
获取更多云原生社区资讯,加入微信群,请加入云原生社区,点击阅读原文了解更多。
《WebAssembly 权威指南》(7)WebAssembly 表相关推荐
- 《WebAssembly 权威指南》(6)在浏览器中运行遗留代码
译者注:这篇文章是<WebAssembly 权威指南>一书的第六章,介绍了如何使用 WebAssembly 在浏览器中运行遗留代码,即已经存在的 C/C++ 代码库.文章以一个实际的例子, ...
- CSS权威指南-候选样式表
候选样式表, 就是允许为一个页面提供多种风格的样式表,用户在浏览该页面时可以选择自己喜欢的页面风格. 将rel属性的值设置为alternate stylesheet,只有在用户选择这个样式表时才会用于 ...
- 前端大全(基础总结)(根据js权威指南扩展)
javascript 权威指南第六版 // 提出问题 + 实例 + 练习 第一部分 基础知识 用:来分隔开.如果一条语句单独占一行,可以不用: 数据类型 (基本数据类型)原始数据类型:数字Number ...
- HTML5与CSS3权威指南之CSS3学习记录
title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...
- 爬虫书籍-Python网络爬虫权威指南OCR库 NLTK 数据清洗 BeautifulSoup Lambda表达式 Scrapy 马尔可夫模型
Python网络爬虫权威指南 编辑推荐 适读人群 :需要抓取Web 数据的相关软件开发人员和研究人员 作为一种采集和理解网络上海量信息的方式,网页抓取技术变得越来越重要.而编写简单的自动化程序(网络爬 ...
- 《VMware vCAT权威指南:成功构建云环境的核心技术和方法》一3.6 vCloud计量
本节书摘来自华章出版社<VMware vCAT权威指南:成功构建云环境的核心技术和方法>一书中的第3章,第3.6节,作(美)VMware vCAT 团队,更多章节内容可以访问云栖社区&qu ...
- 《JS权威指南学习总结--1.1语言核心》
1.1语言核心 --本节主要介绍<js权威指南>基础部分各章讲解内容和一些简单的示例 本小节内容: 一.第二章讲解js注释.分号和Unicode,第三章主要讲解js变量和赋值 简单示例: ...
- 小弟的新书《Ext JS权威指南》终于出版了
链接:http://product.china-pub.com/3661375&weibo#ml <ext js权威指南> 前 言 第1章 ext js 4开发入门 / 1 1.1 ...
- python网络爬虫权威指南 百度云-分析《Python网络爬虫权威指南第2版》PDF及代码...
对那些没有学过编程的人来说,计算机编程看着就像变魔术.如果编程是魔术(magic),那么网页抓取(Web scraping)就是巫术(wizardry),也就是运用"魔术"来实现精 ...
最新文章
- Solr -- Solr Facet 2
- 16:9或4:3,哪种屏幕宽高比更适合用户?
- profiles 配置详解
- 怎么查看oracle数据库表的主键,Oracle中查看所有的表,用户表,列名,主键,外键...
- 开源一些Delphi系统:诗词成语字典
- 做po_requisitions_interface_all接口开发问题
- xgboost通俗_【通俗易懂】XGBoost从入门到实战,非常详细
- elfutils库交叉编译
- rn php,RN和React路由详解及对比
- U盘图标显示成文件夹图标
- 3种方式获取Wifi名称 兼容获取Wifi名字为空 WifiInfo.getSSID为空的情况
- 【百科】详解阿里云技术核心——飞天
- [重要笔记]路由器的包转发操作(全面认识路由器)
- 美国软件供应链安全行动中的科技巨头们
- html表头纵向,实现纵向表头的table
- 计算机动画的处理及应用教案,电脑动画制作教案(2篇)
- 【转载】看懂通信协议:自定义通信协议设计之TLV编码应用
- GAN之父Ian Goodfellow回归谷歌!将在DeepMind远程办公
- mysql如何手写代码_mysql手写_mysql
- C++连接CTP接口实现简单量化交易(行情、交易、k线、策略)
热门文章
- android手机nfc功能安装,Android手机NFC分享功能实测-头条网
- Keras深度学习实战(22)——生成对抗网络详解与实现
- 单击“收件人”、“抄送”或“密件抄送”按钮时缺少
- innerHTML中添加变量
- jquery ajax与js XMLHttprequest
- CSS 3D变形动画属性 之逆天立方体叠加动画
- 制作侧边栏显示和隐藏效果
- SpringSession系统对接CAS遇到的反序列化问题
- 计算机对未来商业市场的发展,2020年中国笔记本电脑行业市场规模、市场格局及未来发展趋势分析:笔记本电脑行业将在5G时代下快速发展[图]...
- 《Linux 黑客基础教程》翻译版发布