找回密码
 会员注册
查看: 12|回复: 0

WebAssembly模块解析_UTF_8

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
75052
发表于 2024-9-30 07:56:20 | 显示全部楼层 |阅读模式
1. 前言在前面章节中,我们已经对 WebAssembly 的关键特性、历史演变和核心的应用场景做了详细的介绍;基于对 WebAssembly 的入门和初步了解,在第二部分的各个章节中,我们会从 WebAssembly 模块入手,和大家一起学习 WebAssembly 基础知识,包括核心规范,核心开发语言和工具链以及常用的执行引擎等相关内容。本文将从 WebAssembly 模块入手介绍相关基础概念和 W3C 二进制格式核心规范,与此同时,进一步介绍 WebAssembly 的文本格式及语法,并给出一个 WebAssembly 文本 demo;以便读者可以与本文格式的介绍相互印证,进一步加深理解。2. 基础概念WebAssembly 本质上也是一种语言格式,而且其在二进制格式之外还支持文本格式,它也是可人工编程的。与其他语言一样,它也包含变量、函数、指令等基础概念。本小节将首先介绍 WebAssembly 的基础概念。2.1 模块(Modules)模块(Module)是 WebAssembly 的基本单元;一个模块内部包含了完成其功能所需的函数、线性内存、全局变量以及表的完整定义;此外,模块还是通过导入功能从外部执行环境引入对象,同时,可以通过导出功能提供外部执行环境可用的对象,从而实现与外部的双向交互。模块作为一个程序的静态表示,它需要经过加载、解码与验证之后进行实例化,从而得到该程序的动态表示,我们称之为一个 WebAssembly 实例(instance)。WebAssembly 模块的实例化在宿主语言或是独立的虚拟机中完成:如在 JavaScript 环境中,该过程可以通过 WebAssembly.instantiate 或者 new WebAssembly.Instance 等函数接口实现。一般而言,一个 WebAssembly 实例包括一个操作数栈和一块可变的线性内存,与原生(native)程序中的栈和堆相对应,如下图 1 所示。图 1. WebAssembly 模块实例2.2 类型(Types)在 WebAssembly 生态中,一个基本设定就是 WebAssembly 使用通用的硬件能力,并在此之上构建一个虚拟的指令集架构(ISA)。遵循这个设定,WebAssembly 提供了四种基本变量类型,分别是 i32、i64、f32、f64,用来表示两种长度的整数和浮点数。其中,整型数值不区分有符号或无符号,但是针对整型的操作符通常分为 signed 和 unsigned 两种版本,以各自的方式对数值进行理解和进一步的操作。在 4 种基本类型之外,MVP 之后的 WebAssembly 核心规范吸收了 Fixed-width SIMD[4] 和 Reference Type[5] 两个提案,支持向量类型 v128 和引用类型。前者可以表示不同类型的打包数据,如 2 个 i64 或 f64、4 个 i32 或 f32 以及 8 个 16 位的整型。后者则可以表示各类函数或者来自宿主的实体(如一个对象的指针)。2.3 变量(Variables)WebAssembly 的变量按照作用域,可以分为局部变量(local)和全局变量(global)两种。局部变量只存在于一个函数内部,并在函数开始时进行声明。局部变量总是可变的,并与函数的参数共用索引空间,通过 i32 类型的 index 来访问。比如,指令 local.get 0 即表示加载序号为 0 的局部变量,如果该函数有参数的话,该指令表示加载第一个参数,否则加载第一个局部变量。全局变量可声明为可变或不可变的。与局部变量类似,通过序号和指令 global.get 或者 global.set 进行访问。此外,global 可以由外部引入,或者被导出到外部。2.4 函数(Functions)函数是 WebAssembly 指令的组织单位——每一条指令都必须属于某个函数。每一个函数都会接受一系列特定类型的参数,并返回一系列特定类型的值。值得注意的是,WebAssembly 函数可以返回多个值,而非局限于单返回值。在 WebAssembly 的规范中,函数并不允许嵌套定义。2.5 指令(Instructions)基于一个栈式的机器构建其计算模型,是 WebAssembly 的一个重要特征。因此,WebAssembly 的指令均围绕着一个隐式的操作数栈进行设计——通过压入和弹出的操作进行数据的操作。如前述的局部变量获取指令 local.get idx,就是加载第 idx 个局部变量到操作数栈栈顶。考虑到 WebAssembly 产物将频繁地通过网络进行传输,如是的设计与基于寄存器的指令集相比可以得到体积更小的产物,因而可以减少网络延迟[2]。2.6 陷阱(Traps)在某些情况下,如遇到一条 unreachable 指令,会出现一个陷阱。它会马上中断 WebAssembly 指令的执行,并且返回到宿主,因为 WebAssembly 内部无法处理陷阱。通常情况下,宿主会捕获 WebAssembly 陷阱,并进入处理例程。如 JavaScript 环境中,遭遇因 unreachable 指令产生的 WebAssembly 陷阱,JS 引擎会抛出一个 RuntimeError,如下图 2 所示。图 2. WebAssembly Trap2.7 表(Tables)WebAssembly 的表是一系列元素的向量,其中元素类型目前只支持前述的引用类型funcref | externref。表在定义时,要求提供存储的元素类型和初始大小,并可以选择性地指定最大大小。Table 主要作用就是可以动态地根据所提供的 index 来获取目标元素。比如,可以借助 Table 在运行时决定调用某个函数,使用 call_indirect 实现间接调用,从而模拟函数指针。2.8 线性内存(Linear memory)顾名思义,线性内存表示一个连续字节数组,作为 WebAssembly 程序的主要内存空间。一个 WebAssembly 模块暂时(WebAssembly 1.0/2.0 SPEC[3])只允许定义至多一个 memory,并可以通过 import/export 跟其他实例共享(但并非所有引擎都支持)。与表类似,在定义线性内存时,要求提供初始大小,并可以选择性地指定最大大小,其单位是页(page,大小为 64KB)。在 WebAssembly 程序的运行过程中,可以通过指令 grow_memory 动态地扩展线性内存的大小。通过 load/store 指令,并提供 i32 类型的偏移值参数,WebAssembly 程序可以完成对线性内存的读写。当然,会导致越界访问的偏移值会被动态的检测所拦截,并产生一个陷阱。考虑到线性内存是独立且纯粹的,与指令空间、执行栈、WebAssembly 引擎的元数据结构等均处于分离状态,故错误甚至恶意的 WebAssembly 代码最多只能破坏自身的 memory。这样的沙盒属性,对于安全运行通过网络传输、不受信任的 WebAssembly 代码至关重要。图 3. WebAssembly 线性内存3 模块结构WebAssembly 的二进制格式是 WebAssembly 的抽象语法的密集线性编码。和文本格式一样,产物是由抽象语法生成的,最终产生的终端符号是字节。包含二进制格式 WebAssembly 模块的文件的推荐扩展名是 ".wasm",推荐的媒体类型是 "application/wasm"。本小节将重点介绍 WebAssembly 二进制格式的文件结构;指令的二进制编码,可以阅读 W3C 核心规范该文档[6]。3.1 部分(Sections)一个完整的 WebAssembly 模块包含 12 个部分,部分和文本格式的域概念相近;部分是可选的,模块中省略的部分相当于该部分存在空内容;WebAssembly 模块结构如下图 4 所示。图 4. WebAssembly 模块段结构示意图自定义部分(custom section):自定义部分的 id 为 0。它们旨在用于调试信息或第三方扩展,并且被 WebAssembly 语义忽略。它们的内容包括进一步标识自定义部分的名称,后面是用于自定义使用的未解释的字节序列。如果解释器实现了自定义部分的数据,则该数据中的错误或部分的位置不能使模块失效。类型部分(type section):类型部分由 id 以及一个函数类型向量组成。它表示了模块中的所有自定义函数类型。函数部分(function section):函数段的 id 为 3。它由类型索引的向量组成,这些类型索引表示模块的 funcs 组件中函数的类型。函数的局部变量和函数体在代码部分中。表部分(table section),内存部分(memory section),全局部分(global section),导入部分(import section),导出部分(export section):这几部分顾名思义,由每部分名称对应的元素的向量组成。起始部分(start section):起始部分的 id 为 8,它能表示一个可选的开始函数。元素部分(element section):元素部分的 id 为 9,它由多个元素段(element segments)组成。元素段的二进制格式有 8 种,开头用一个 u32 整数来区分。u32 的第 0 位表示被动(passive)或声明性(declarative)段,第 1 位表示主动(active)段存在显式表索引(explicit table index),并以其他方式区分被动和声明性段,第 2 位表示使用元素类型和元素表达式,而不是元素类型和元素索引。在 WebAssembly 的未来版本中可能会添加更多的元素类型。代码部分(code section):代码部分的 id 为 10,它包括了一个代码向量。向量里的每个元素是一段代码,每段代码包括代码长度,函数的局部变量和函数体表达式。与其他部分一样,解码不需要代码大小,但可用于在浏览二进制文件时跳过函数。如果大小与相应函数代码的长度不匹配,则模块格式错误。数据部分(data section):数据部分的 id 为 11,它由数据向量组成。表示数据的二进制格式有 3 种,用开头的 u32 整数区分。位 0 表示被动段,位 1 表示主动段的显式内存索引的存在。在当前版本的 WebAssembly 中,在单个模块中最多可以定义或导入一个内存,因此所有有效的活动数据段都有一个 0 的内存值。数据计数部分(data count section):数据计数部分的 id 为 12。它由一个可选的 u32 数字组成,表示数据部分中数据段(data segment)的数量。如果此计数与数据段向量的长度不匹配,则模块格式错误。3.2 模块(Modules)本小节会介绍组成一个完整的 WebAssembly 二进制格式模块所需要的内容,除了部分(sections)之外,WebAssembly 二进制文本还需要魔数和版本数来表示这个模块是有效的。下面给出完整的模块定义:从定义 1 中可以看出,WebAssembly 模块的每一个段都是可选的,只有四字节的魔数和四字节的版本号不可省略。在每一个部分中间,工具链可以选择是否插入自定义部分。除了自定义部分,其他部分必须严格按照定义 1 给出的顺序来组织。自此,二进制格式基本介绍完成。读者可以根据本小节的内容了解 WebAssembly 二进制格式的组织形式,如果想要更进一步,可以参照标准指令格式文档来实现一个简单的 WebAssembly 加载器 (loader)。4. 文本格式作为一种低级的类汇编二进制指令格式,WebAssembly 有二进制格式.wasm 和对应的文本格式.wat。WebAssembly 的文本格式可以直接编写代码或者用于阅读,二进制格式则用于分发和执行。对于初次接触 WebAssembly 的读者,可以简单地将 WebAssembly 文本和二进制的关系理解为汇编语言和机器码的关系。但要注意的是,WebAssembly 编译工具链只会为字节码生成一种二进制产物,并且产物平台无关,因此需要一个虚拟机来解析执行。常见的 WebAssembly 虚拟机有 V8、JSC、WAMR、Wasm3、Wasmtime 等。此外,作为 W3C 标准支持的第四种语言,现代浏览器几乎都实现了 WebAssembly 的运行时(V8,JSC),可以直接执行 WebAssembly 代码。在本小节中,我们将会详细展开基础概念中较为笼统的部分,让读者能够更深入地理解 WebAssembly 标准的各种细节。4.1 S-expression虽然 WebAssembly 是一种类汇编的指令格式,但是它的文本格式和汇编语言的指令-寄存器有些许不同—— WebAssembly 采用 S-表达式(S-expression 或是 sexp)作为文本组织形式。Sexp 是一种通过人类可读的文本形式表达半结构化数据的约定,它既可以用于表示代码,也能够用于表示数据。因此,使用了 sexp 的语言天然的存在数据和逻辑的一致性。例如 Lisp 强大的 macro system 就得益于此。下面给出 sexp 的形式化定义:从定义上不难看出,实际上 sexp 可以表现为一棵树的先序遍历形式。为了表现出这一点,我们给定一个简单的表达式 1,并给出它的二叉树图像:图 5. S 表达式树形结构可以看出,这个二叉树的中序遍历就是表达式 1 的原始形式。如果将这个二叉树进行先序遍历,则会得到表达式 2:我们将其稍微做一点转换,就成为了 sexp 形式:再做一点形式简化,最终,我们得到了表达式 1 的 sexp 表示:4.2 词法定义简而言之,sexp 是一棵 AST 的先序遍历表示。接下来我们将会进一步展开讨论 WebAssembly 的词法定义。为了不机械地重复 WebAssembly 标准,本节只会有选择地介绍每个词法单元的定义,然后会从 WebAssembly 示例代码开始,逐步深入探索文本格式的奥秘。WebAssembly 的文本形式是由 WebAssembly 的属性语法(又称属性文法)定义的。属性语法是语法制导定义(Syntax-Directed Definitions,简称 SDD)的无副作用形式。详细内容不在本文展开,读者可以自行阅读 属性语法[7] 相关内容。由于词法和语法强关联,因此在介绍词法的时候,会连带解释部分语法含义,以便读者更好地理解词法含义。4.2.1 词法格式(Lexical Format)WebAssembly 文本由字符串构成,每个字符都是一个合法的 Unicode [8]。首先我们看一个最简单的 WebAssembly 模块——空 module:(module);;thisisalinecomment(;thisisablockcomment;)这段代码展示了一个最小的合法 WebAssembly 文本格式,他的二进制产物可以被 WebAssembly 虚拟机解释执行,不会有任何输出。3 到 5 行展示了 WebAssembly 的两种注释格式,一种是行注释(linecomment),一种是块注释(blockcomment)。行注释最终会忽略分号后的内容,块注释则会忽略括号对里的所有内容。此外,WebAssembly 支持水平制表(U+09)、换行(U+0A)和回车(U+0D)三种格式化字符。空格,格式化字符以及注释并称为 WebAssembly 的空白(write space),可以看做是无意义的段落。4.2.2 值(Values)本小节中我们将介绍 WebAssembly 的词法(lexical syntax),词法是属性语法定义的产物,每个词法单元都必须有意义,因此不允许空白存在。词法中存在五种值:整数(Integers)、浮点数(Floating-Point)、字符串(Strings)、名称(Names)、标识符(Identifiers)。其中,前三种值很好理解,是我们编程中常用的词法规范。其中,整数包含正负符号,十进制数字,十六进制数字;浮点数包含数字,小数点,十六进制数字和自然底数 e/E;字符串包含了字符,转义符,引号,换行符,制表符,以及\u{hexnum}表示字符 Unicode 编码。词法的后两种值则略微有点难以理解。首先是名称,名称的常见组织形式是字符串,在 WebAssembly 导出函数时,函数会有特定的名称。其他时候,名称则很少被提到;其次是标识符,标识符可以看做是变量的名字,包含两个词法单元—— id 和 idchar。id 通常是$和多个 idchar 拼接,下面给出具体示例:(func$add...)...(export"add"(func$add));;"add"是一个名称,$add是一个id,'a','d',' 都是idchar4.2.3 类型(Types)WebAssembly 是强类型的,因此文本格式中,值都具有其特定的类型。本小节将会介绍 WebAssembly 文本格式中的类型词法,其中包括:数字(Number)、向量(Vector)、引用(Reference)、值(Value)、函数(Function)、限定(Limits)、内存(Memory)、表(Table)和全局(Global)类型。数字类型包括 32 和 64 位的整型 i32 i64 和浮点型 f32 f64。向量类型则是 128 位长度的向量 v128,可以解释为整型或者浮点型数据。引用类型包括函数引用 funcref 或者外部引用 externref。所有的引用类型都是不透明的,用户无法观测到大小或者是位模式(bit pattern),引用类型的值可以存在表里。值类型则是数字,向量,引用类型的统称。它没有自己的特定的类型标识符,仅仅用于词法定义上函数类型能够表示完整的函数文本。函数词法定义中,用到了值类型作为占位标识符,下面给出形式化定义:定义 3 中 t 是值类型,它可以在实际代码中被替换为 i32 i64 f32 f64 v128 funcref externref,下面的示例中,t 最终被替换为 i32:(func$add(param$lhsi32)(param$rhsi32)(resulti32);;t最终被替换为i32get_local$lhsget_local$rhsi32.add)限定和内存类型的词法都表示为数字,WebAssembly 中声明一块内存,则可以完全的用限定类型表示,示例中的 1 和 0 1 都是限定类型的文本表示:(memory1);;1pageinitialized(memory01);;maxinum1page表类型也需要用到限定类型,因为需要初始化表的大小,具体可以写成这样:(table2anyfunc);;2isalimittype最后就是全局类型。我们可以把全局类型近似于值类型,唯一的区别就是存在一个可选的 mut 字段表示是否是可变的,比如我们声明一个存在不可变 32 位整数参数的全局类型,初始化为 100:(global$g1(muti32)(i32.const100));;mutable4.2.4 指令(Instructions)指令类型内容繁多,主要内容都是 WebAssembly 指令的文本格式内容。本文不在此详细介绍,感兴趣的读者可以查阅 WebAssembly 文本格式指令标准文档[9]。在本文第一大章的最后一节,我们会实现一个简单的 WebAssembly 文本模块,其中也会介绍部分常用指令,读者可以参阅 "4.3 写一个 WebAssembly 模块"。4.2.5 模块(Modules)在了解了上面的内容之后,我们终于可以从整体上浏览 WebAssembly 文本了。这一小节,我们会介绍一个完整的 WebAssembly 模块应该具备的所有内容。希望在本小节结束之后,读者们能够上手实现一个完整的 WebAssembly 文本模块。一个完整的 WebAssembly 文本至少存在一个模块,每个模块由 0 或多个域(fields)组成。在文本中,程序员可以重复声明某个域,在二进制格式中,所有相同的域会合并到一个段中。WebAssembly 模块最多包含 10 个域,分别是:类型(type),导入(import),导出(export),函数(func),表(table),内存(mem),全局(global),起点(start),元素(elem),数据(data)。上面的小节中我们其实已经见过域的文本片段了,下面给出一个拥有所有域的 WebAssembly 模块:(module(type...)(import...)(func...)(table...)(mem...)(global...)(export...)(start...)(elem...)(data...接下来,我们会逐渐往这个 WebAssembly 模块中填写内容。函数域(Functions)在 4.2.3 小节中,我们已经见过了函数的词法定义(def 3),并且也给出了一个实际的函数定义,这里我们重新解读一下 add 函数的文本写法:(func$add(param$lhsi32)(param$rhsi32)(resulti32)...)开头的 (func ...) 其实就是声明了一个函数域。函数的唯一标识符是 $add,接收两个标识符为 $lhs 和 $rhs 的 32 位整数。函数最终返回一个 32 位整数。我们故意忽略了函数体的指令部分用来缩短篇幅,后面会加上它。类型域(Types)类型域的定义很像 C 语言中的 typedef,它的用法是给某个特定的函数类型取一个别名,使用的时候可以通过别名来指代函数类型。(module(type$ft1(func(parami32i32)(resulti32)))(func$add(type$ft1)...))这个案例定义了一个函数类型 $ft1,这个函数接收两个 32 位整数参数,返回一个 32 位整数。第 3 行的 func 域中定义了一个 $add 函数,这个函数的类型就是$ft1。导入导出域(Imports and Exports)导入域的作用是声明导入的内容。WebAssembly 中存在 4 种导入的对象的类型:函数(func),表(table),内存(memory),全局(global)。这四种类型在 4.2.3 小节已经介绍过了,这里不再赘述。我们来看他的具体形式:(module(import"console""log"(func$log(parami32)))(func(export"logIt")...))代码中 import 后跟了两个名称,意思是从 console 模块导入 log 方法。名称后的函数域则定义了 log 方法的类型。第 3 行存在一个导出域,export 后也跟了一个 logIt 名称。这行代码的意思是在函数域中定义一个会被导出的函数,导出的名称为 logIt。内存域(Memories)一块内存(memory)在 WebAssembly 中被定义为一段线性的(linear)未解释的(uninterpreted)原始字节序列(vector of raw bytes)。内存的限定(limits) 中的最小值是内存的初始大小,而最大值(如果存在)则限制了内存最大能增长到的大小。内存能主动初始化,也可以被数据段初始化,还可以导入导出。也就是说,内存域中可以内联(inline)数据段,也可以内联导入导出域。我们看一个示例:(module(memory$m011);;limits:{min:1,max:1}(memory$m1(data"Hello,""World!\n"));;limits:{min:1},inlinedata(memory$m2(import"env""m2")11);;inlineimport(memory$m3(export"env""m3")11);;inlineexport)现在的 WebAssembly 模块至多只能存在一块内存,在之后的版本中可能会放开限制。数据段(Data Segments)数据段在内存域小节中介绍过,它的用处是用一串字符串(strings)来初始化内存。由于当前版本的 WebAssembly 模块最多存在一块内存,因此在使用数据段初始化内存的时候,需要给定偏移量。看下面的示例:(module(memory416)(data(offset(i32.const100))"Hello,")(data(offset(i32.const108))"World!\n"))上面的示例使用 data 段初始化一块内存的另一种方式。由于只有一块内存,所以我们不必指定初始化的内存标识符。在语法定义中,数据段存在两种模式:1. 被动(passive)2. 主动(active)。上面的示例表示的是主动模式下的数据段定义。主动模式会在实例化内存的时候将数据段的内容拷贝到内存中。而被动模式下,data 段的数据只能通过调用 memory.init 指令才能拷贝到内存中:(module(memory416)(data$d0"Hello,""World!\n");;passivemode(func$f0memory.init$d0);;calling`memory.init`instructioninfunc$f0)全局域(Globals)全局域的作用是定义全局变量,每个全局域能够定义一个全局变量。全局域有 mut 关键词,能够定义变量是否可变。同时,全局域能够内联导入和导出域。(module(global$g1(import"env""gbl")i32);;import(global$g2(muti32)(i32.const100));;mutable(global$g3f32(f32.const3.14));;immutable(global$g4(export"gbl")...);;export)表域(Tables)当我们提到函数调用,我们可能首先想起的是静态函数调用,类似于:(module(func$log...)(func(export"writeHi")call$log)))上面的代码展示的是静态函数调用,但是在很多语言中, 并不只有静态函数调用,比如 C 语言的函数指针,C++ 的虚函数。WebAssembly 作为这些语言支持的目标格式,需要有能够表示动态调用的方式,这就是表域。表域可以理解为一个对用户透明的数组,数组中存储的元素是 WebAssembly 函数,用户仅能通过下标来访问元素。下面给出一个完整的示例:(module(func$f1(resulti32)i32.const42)(func$f2(resulti32)i32.const13)(tablefuncref(elem(offseti32.const0)$f1$f2))(type$return_i32(func(resulti32)))(func(export"callByIndex")(param$ii32)(resulti32)local.get$icall_indirect(type$return_i32)))示例代码声明了一个大小为 2 的表域,表域中的元素类型为任意函数。第 4 行定义了表域中的两个元素,分别是 $f1 和 $f2。第 5 行定义了一个函数类型。第 6 行则最终使用了表域中的元素。callByIndex 函数接收一个参数,这个参数实际上对应的就是表域的下标。如果传入的值是 1,那么 call_indrect 最终会调用表域中下标为 1 的函数(从 0 开始),也就是 $f2。元素段(Element Segments)元素段能够将一个模块中的任意函数子集以任意顺序列入其中,并允许出现重复。列入其中的函数将会被表格引用并,且引用顺序是元素的排列顺序。表域的第二个示例代码段中,元素段中的 (i32.const 0) 值是一个偏移量,作用是表明函数引用是在表中的什么索引位置开始存储的。这里我们指定的偏移量是 0,表格大小是 2,因此,我们可以在索引 0 和 1 的位置填入两个引用。如果想在偏移量 1 的位置开始写入引用,那么,我们必须使用 (i32.const 1) 并且表格大小必须是 3。起始函数(Start Function)起始函数会在 WebAssembly 模块实例化的时候被调用,调用时间点是在表和内存初始化完毕之后。注意,起始函数的作用是初始化模块的状态,在初始化阶段,外部是访问不到模块以及模块的导出元素的。不要将起始函数和 C 语言的 main 函数混为一谈。(module(func$start...)(start$start))4.3 手写一个简单的 WebAssembly 模块前一小节完整地介绍了 WebAssembly 的文本格式。在这一节,我们会尝试写一个有意义的 WebAssembly 文本。完成以下步骤:声明一个模块(module)插入两个函数类型,其中一个接受一个 i32 类型的参数,另外一个没有参数,且两个都没有返回值(module(type(;0;)(func(parami32)))(type(;1;)(func)))声明本模块需要导入一个函数,类型是索引为 0 的函数类型,函数自身的索引为 0(module(type(;0;)(func(parami32)))(type(;1;)(func))(import"imports""imported_func"(func(;0;)(type0))))增加一个内部定义的函数,使用索引为 1 的函数类型,这个函数自身的索引也为 1。这个函数将一个 i32 类型、值为 88 的常量压入栈,并调用索引为 0 的函数(module(type(;0;)(func(parami32)))(type(;1;)(func))(import"imports""imported_func"(func(;0;)(type0)))(func(;1;)(type1)i32.const88call0))最后,将第 4 步定义、索引为 1 的函数,用命名 exported_func 导出到外部(module(type(;0;)(func(parami32)))(type(;1;)(func))(import"imports""imported_func"(func(;0;)(type0)))(func(;1;)(type1)i32.const88call0)(export"exported_func"(func1)))将上述文件保存为 simple.wat,并使用 wat2wasm 转换为 WebAssembly 二进制文件#installwat2wasmby`npminstall-gwat2wasm`wat2wasmsimple.wat-osimple.wasm在 JavaScript 中调用它读取模块的二进制表示,并存储到一个 ArrayBuffer 中;根据模块所需的导入内容构建导入对象 import_obj;调用 WebAssembly.instantiate 实例化并调用导出的函数;得到了来自 WebAssembly 的问候!//test.js// execute:node test.jsconstfs=require('fs');constwasm_buffer=fs.readFileSync("simple.wasm");constjs_func=(i)=>console.log("FromWebAssembly:"+i);constimport_obj={imports:{imported_func:js_func}};WebAssembly.instantiate(wasm_buffer,import_obj).then((result)=>result.instance.exports.exported_func());5. 总结从整体上来看,WebAssembly 文本格式和二进制格式是相近的,都是从基本概念的语法生成而来。我们可以把二进制格式文本的语义和文本格式的语义一一对应。文本格式让用户能够阅读、Debug 工程生成的 WebAssembly 代码,或者用户可以手写文本。希望通过阅读这篇文章,读者能够学习理解 WebAssembly 的基础知识。更进一步地,期望读者能够获得部分手写 WebAssembly 文本的能力,同时,能够对二进制格式的结构有大概的了解。6. 参考文献[1]. WebAssembly Core specification: https://webassembly.github.io/spec/core/intro/introduction.html#wasm[2] Y. Shi, K. Casey, M. A. Ertl, and D. Gregg. Virtual Machine showdown: Stack versus registers. ACM Transactions on Architecture and Code Optimizations, 4(4):2:1–2:36, Jan. 2008.[3] WebAssembly Moudles: https://webassembly.github.io/spec/core/syntax/modules.html#memories[4]. Fixed-width SIMD: https://github.com/webassembly/simd[5]. Reference Type: https://github.com/WebAssembly/reference-types[6]. WebAssembly Core Specs: https://webassembly.github.io/spec/core/binary/instructions.html[7]. Attribute Grammar: https://en.wikipedia.org/wiki/Attribute_grammar[8]. Unicode: https://www.unicode.org/versions/Unicode15.0.0/[9]. WebAssembly 文本格式指令标准文档: https://webassembly.github.io/spec/core/text/instructions.html#instructions点击上方关注 · 我们下期再见点击左下方“阅读原文”,或扫描上方二维码,进入专栏阅读《走进 WebAssembly 的世界》完整版。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-15 18:37 , Processed in 0.552117 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表