binaryen
binaryen是用C ++编写的WebAssembly的编译器和工具链基础架构库。它的目的是使websembly变得容易,快速和有效:
-
Easy : binaryen二进制标头中具有简单的C API,也可以从JavaScript中使用。它接受类似WebAssembly形式的输入,但也接受了更喜欢的编译器的一般控制流程图。
-
快速: binaryen的内部IR使用紧凑的数据结构,并设计用于使用所有可用CPU内核的完全并行代码和优化。 binaryen的IR还非常容易,非常快速地编译为WebAssembly,因为它本质上是WebAssembly的子集。
-
有效: binaryen的优化器具有许多通行证(稍后请参见概述),可以提高代码大小和速度。这些优化旨在使binaryen功能强大,以便将其用作编译器后端。一个特定的重点是特定于WebAssembly的优化(通用编译器可能不会做的),您可以将其视为WASM缩小,类似于JavaScript,CSS等的Minification,所有这些都是语言特定于语言的。
使用binaryen作为组件(通常运行的WASM-OPT)的工具链包括:
- Emscripten(C/C ++)
- wasm-pack(生锈)
- J2CL(Java; J2 WAMS)
- Kotlin(Kotlin/Wasm)
- 飞镖(颤音)
- WASM_OF_OCAML(OCAML)
有关其中一些工作方式的更多信息,请参阅V8 WASMGC移植Blogpost的工具链体系结构部分。
使用binaryen作为库的编译器包括:
- 汇编将打字稿变体到WebAssembly的汇编
- WASM2JS将WebAssembly编译到JS
- Asterius将Haskell编译为WebAssembly
- 将谷物汇编成WebAssembly的谷物
binaryen还提供了一组工具链实用程序
- 解析和发射WebAssembly。特别是,这使您可以加载WebAssembly,使用binaryen进行优化并重新发射,从而在单个命令中实现WASM-to-WASM优化器。
- 解释WebAssembly以及运行WebAssembly Spec测试。
- 与Emscripten集成,以提供从C和C ++到WebAssembly的完整编译器工具链。
- 如果浏览器尚未具有本机支持(对测试有用),则通过在解释器中运行将其运行到JavaScript中,请通过将其运行到JavaScript中。
如果您有兴趣参加,请咨询贡献说明。
binaryen IR
binaryen的内部IR被设计为
- 灵活且快速以进行优化。
- 尽可能靠近WebAssembly,因此可以简单而快速地从WebAssembly转换它。
binaryen IR和WebAssembly语言之间存在一些差异:
-
树结构
-
binaryen IR是一棵树,即,它具有层次结构,以方便优化。这与堆栈机的WebAssembly二进制格式不同。
-
binaryen使用堆栈IR来优化“堆栈”代码(无法以结构化形式表示)。
-
当必须在binaryen IR中表示二动代码(例如,使用多价值指令和块),它的元组类型表示,这些类型在WebAssembly语言中不存在。除了多价说明外,当地人和全球群体还可以在binaryen IR中具有元组类型,但在WebAssembly中不能具有元组类型。实验表明,对多估的更好的支持可以实现有用但小的代码尺寸节省1-3%,因此不值得更改核心IR结构以更好地支持它。
-
块输入值(当前仅在异常处理功能中支持的捕获块中支持)表示为POP子表达。
-
-
类型和无法达到的代码
-
WebAssembly限制块/if/loop类型为无,以及具体值类型(i32,i64,f32,f64)。 binaryen IR具有无法到达的类型,它允许块/if/loop拿走它,从而允许不需要知道全局上下文的本地变换。结果, binaryen的默认文本输出不一定是有效的WASM文本。 (要获得有效的WASM文本,您可以做 – 生成堆栈-IR -Print-stack-ir(打印堆栈ir),保证这对WASM Parsers有效。
-
binaryen支持StringRef类型。这类似于当前不活动的字符串Ref提案,其区别是字符串类型是ExternRef而不是AnyRef的子类型。这样做可以使工具链以使用JS字符串内置的形式发射代码,然后将binaryen在其内部IR中“抬起”到StringRef中,优化(例如,可以在编译为“ AB”时优化“ A”和“ B”的串联),然后再一次将其“较低”到JS字符串中。
-
-
块
-
binaryen IR只有一个控制流结构,其中包含一个可变的儿童列表:块。另一方面,WebAssembly允许所有控制流结构(例如武器和功能机构)有多个孩子。在binaryen IR中,这些其他控制流结构有一个孩子。这个孩子当然可能是一个障碍。该属性的动机是,许多通行证需要特殊的代码来迭代说明列表,因此使用单个IR节点列表可以简化它们。
-
与WASM文本格式一样,块和循环可能具有名称。 IR中的分支目标通过名称(而不是嵌套深度)解决。这有2个后果:
-
没有名称的块可能不是分支目标。
-
名称必须是唯一的。 (读取具有重复名称的WAT文件;在构造IR时将修改名称)。
-
-
作为优化,没有名称的块(永远不可能是分支目标)在生成WASM时不会发出。取而代之的是,其儿童列表将直接用于包含的控制流结构中。这样的块有时称为“隐式块”。
-
-
参考类型
-
WASM文本和二进制格式要求Ref.Func的地址的函数必须在表中或通过(Elem声明func $ ..)声明。 binaryen会在必要时发出该数据,但在IR中并不表示该数据。也就是说,可以在无需考虑声明功能参考的情况下进行IR。
-
binaryen IR允许以WASM规格所做的形式进行不可用的当地人,其中局部get必须在结构上由本地统治。尽管与WASM规范保持一致,但您可能会注意到一些较小的细节:
-
binaryen IR中的无名块不会干扰验证。无名块永远不会发射到二进制格式中(我们只是散发出其内容),因此我们忽略了它们是为了验证不可用的当地人。结果,如果您阅读了binaryen文件发出的WASM文本,那么您可能会看到似乎不应根据规范验证的代码(并且可能无法在WASM文本解析器中验证),但是除了bugs of Cerce的错误外,Biaryen发出的二进制文件始终可以始终使用二进制格式( binaryen发出的二进制文件)。
-
每次通过后, binaryen Pass Runner将自动修复验证(查找无法验证并修复它们的内容,通常是通过降级本地来无效)。结果,您在编写binaryen时不必担心这一点。有关更多详细信息,请参见Pass.H和Localtrustortartoralance类中的要求nonnonnullabllecalFixups()挂钩。
-
-
binaryen IR使用最精致的类型进行参考:特别是:
-
Ref.Func的IR类型始终是对定义功能类型的确切,不可删除的引用,而不是普通的FunCref,即使没有启用基本参考类型以外的功能。
-
即使未启用自定义描述符,诸如struct..new或array.new之类的分配指令的IR类型始终是确切的参考。
-
不可删除的类型也用于Try_table在分支上发送的类型(如果我们分支,永远不会发送null),也就是说,它发送(ref exn)而不是(ref null exn)。
-
结果,即使不启用GC或自定义描述符,IR通常也允许在IR中进行不可删除和确切的参考。阅读二进制文件时,将在我们构建IR时应用更精致的类型。
在所有情况下,二进制编写者将根据启用功能集的必要类型概括类型。例如,如果仅启用参考类型,则所有函数参考类型都将作为FunCref发射。
-
-
BR_IF输出类型在binaryen IR中更精致:当存在时,它们具有已发送值操作数的类型。在WASM规格中,该类型是分支目标的类型,可能不太精制。在此处使用更精致的类型确保我们使用所有类型信息以最佳方式优化,但这确实意味着某些圆形操作可能看起来有些不同。特别是,当我们散发出binaryen IR的类型更精致的br_if时,我们会立即发出铸件以恢复更精致的类型。在极少数情况下,这可能会导致一些额外大小的字节(在BR_IF值未使用的常见情况下,我们避免了此开销)。
-
结果,您可能会注意到,在某些角落情况下,往返转换(WASM => binaryen ir => wasm)会稍微更改代码。
- 优化binaryen时,请使用额外的IR堆栈IR(请参阅src/wasm-stack.h)。堆栈IR允许针对WebAssembly的二进制二进制格式量身定制的一堆优化(但是,堆栈IR比主要binaryen IR效率较低)。如果您有一个特别优化的WASM文件,则简单的往返转换(只需读写,而无需优化)可能会引起更明显的差异,因为binaryen将其拟合到binaryen ir更结构化的格式中。如果您在往返转换期间还进行了优化,则将运行堆栈IR选择,并且最终的WASM将得到更好的优化。
与binaryen IR合作时的注意:
- 如上所述, binaryen IR具有树结构。结果,每个表达式应完全有一个父 – 您不应通过将其出现在树中多次出现来“重复使用”一个节点。这种限制的动机是,当我们优化我们修改节点时,因此,如果它们不止一次出现在树上,则在另一个地方的变化可能会出现在另一个地方。
- 出于类似的原因,节点不应出现在一个以上的功能中。
内在
binaryen固有函数看起来像是对导入的调用,例如,
( import \" binaryen -intrinsics \" \" foo \" ( func $foo ))
以这种方式实施它们,可以通过其他工具读取和编写它们,并且避免了如果我们具有自定义的二进制格式扩展名,可能会在这些工具中发生的二进制格式错误混淆。
优化器可以优化一种固有的方法。如果不是这样,则必须在运送WASM之前降低它,否则,它看起来像是对不存在的导入的调用(VMS将在没有适当值的导入值时显示出错误)。最终降低不是自动完成的。 Intersics的用户必须明确地为此进行通行,因为这些工具不知道用户何时打算完成优化,因为用户可能具有多个优化步骤的管道,或者可能正在进行本地实验,或者进行本地实验,或者进行模糊/减少等等。只有用户知道最终优化何时在Wastm是“最终”之前进行“最终”和“最终”进行。请注意,通常,最终降低后可能会进行一些其他优化,因此有用的模式是正常使用内在物质优化一次,然后将它们降低,然后优化之后,例如:
wasm-opt input.wasm -o output.wasm -O --intrinsic-lowering -O
每个内在的语义都定义了其语义,其中包括允许优化器对其进行的操作以及最终降低将变为什么。有关详细的定义,请参见Intrinsics.h。快速摘要在这里显示:
- call.without.effects:类似于call_ref,它接收参数,以及对呼叫的函数的引用,并使用这些参数函数,除了优化器可以假定呼叫没有副作用,并且可能能够将其优化为(如果没有使用结果),通常通常使用)。
工具
该存储库包含在bin/中构建以下工具的代码(请参阅“构建说明”):
- WASM-OPT :加载WebAssembly并在其上运行binaryen IR。
- WASM-AS :以文本格式(当前S-表达格式)组装WebAssembly为二进制格式(通过binaryen IR)。
- WASM-DIS :以二进制格式为文本格式的未组装WebAssembly(通过binaryen IR)。
- WASM2JS :WebAssembly-to-JS编译器。 Emscripten用来生成JavaScript作为WebAssembly的替代方法。
- WASM-REDUCE :用于WebAssembly文件的测试柜还原器。给定一个出于某种原因很有趣的WASM文件(例如,它崩溃了特定的VM),Wasm-reduce可以找到具有相同属性的较小的WASM文件,通常更容易调试。有关更多详细信息,请参见文档。
- WASM-SHELL :可以加载和解释WebAssembly代码的外壳。它还可以运行规格测试套件。
- WASM-EMScripten-Finalize :采用LLVM+LLD产生的WASM二进制,并对它进行特定于Emscripten的通行证。
- WASM-CTOR-EVAL :可以在编译时执行功能(或功能部分)的工具。
- WASM-MERGE :将多个WASM文件合并到一个文件中,将相应的导入连接到出口。就像bundler的JS一样,但为WASM。
- WASM-METADCE :一种以灵活的方式删除WASM文件部分的工具,取决于模块的使用方式。
- binaryen .js :独立的JavaScript库,揭示了用于创建和优化WASM模块的binaryen方法。有关构建,请参见NPM上的binaryen .js(或直接从GitHub或Unpkg下载)。最小要求:Node.js V15.8或Chrome V75或Firefox V78。
所有binaryen工具都是确定性的,也就是说,给定相同的输入,您应该始终获得相同的输出。 (如果您看到其他行为的案例,请提交问题。)
每个使用说明在下面。
binaryen优化
binaryen包含大量优化通行证,以使WebAssembly更小,更快。您可以使用WASM-OPT运行binaryen优化器,但也可以在使用其他工具(例如WASM2J和WASM-METADCE)时运行它们。
- 默认优化管道由AddDefaultFunctionOptimizationPass等功能设置。
- 您可以设置各种通行证选项,以调整优化和收缩水平,是否忽略了不太可能的陷阱,插入启发式方法,快速记忆等。有关如何设置它们和其他细节,请参见WASM-OPT-支持。
有关其所做的详细信息,请参阅每个优化通过,但这是一些相关的概述:
- Cocelocals-密钥“注册分配”通过。进行实时分析,然后重复当地人以最大程度地减少其数量,并删除它们之间的副本。
- 代码折叠– 通过合并来避免复制代码(例如,如果武器在其末尾有一些共享指令,则避免代码。
- codepushing- “按下”代码将代码前进到分支机构操作,如果采用分支,则可能允许不运行代码。
- DeadAragumentelimination -LTO通过以相同常数调用函数将参数删除到函数。
- 死码头降临
- 指导– 当表索引恒定时,将间接呼叫变成普通呼叫。
- 重复的功能启示– LTO通行证。
- 内部– LTO通行证。
- Localcse-简单的局部常见子表达消除。
- loopinvariantcodemotion
- 内存包装– 键“优化数据片段”通过,将段,删除不需要的零件等。
- 合并– 在可能的情况下合并到外部块,减少其数量。
- 杂货店– 当两个当地人在其部分重叠中具有相同的价值时,以一种帮助塞赛车的方式选择以后会做得更好(从塞螺旋中分开以使后者简单简单)。
- MinifyImportSandExports-将它们减为“ A”,“ B”,等。
- OptimizeadDedConstants – 优化具有添加常数的负载/存储在常数偏移中。
- 优化Instructions-钥匙窥视孔优化通过,并不断增加模式列表。
- PickLoadSigns-调整负载是签名还是未签名,以避免以后签名/未符号操作。
- 预计– 使用内置解释器(可以保证能够处理任何常数表达式),在编译时间计算常数表达式。
- RERELOOP-将WASM结构化控制流变为CFG,然后使用Relooper算法返回结构化形式,该算法可能会发现更多最佳形状。
- REDUNDENSETELIMATION-删除本地存在的值。 (与塞胶合的重叠;这实现了仅提及的特定操作,而没有所有其他工作cocelocals的作用,因此在优化管道的其他地方很有用。)
- 删除nunsedbrs -键“次要控制流优化”通过,包括跳线和可以摆脱br或br_table的各种变换(例如,如果可能的话,将一个带有BR的块转动到中间的块)。
- 删除nunusedModuleements – “ Global DCE”,LTO通行证,该通行证在不使用时会删除导入,功能,全球等。
- 重新订购– 将更高的功能放在首位,有可能允许发出的LEB称其为较小(在一个非常大的程序中)。
- 重新定位– 将更固定的当地人放在首位,有可能允许发出的LEB使用它们更小(在很大的功能中)。分类后,它还删除了根本没有使用的当地人。
- 简化了全局– 以各种方式优化全球群体,例如将它们融合在一起,从永不修改的全局删除可变性,从不变的全局等恒定价值。
- SimpLifyLocals -KEY“ local.get/set/tee”优化通过,执行诸如更换集合的事情以及将设置的值移至get(并创建T恤)的事情。还创建块/if/loop返回值,而不是使用本地来传递该值。
- 真空– 钥匙“删除愚蠢的不需要代码”通过,执行诸如删除没有内容的if手臂,一滴恒定值,没有副作用,一个带单个孩子的块,等等。
上面的“ LTO”意味着优化是链接时间优化,因为它跨多个函数起作用,但是从某种意义上说, binaryen始终是“ LTO”,因为它通常在最终的链接WASM上运行。
binaryen优化器中的高级优化技术包括SSAIFIENT,FLAT IR和堆栈/罂粟IR。
有关如何有效使用优化器的更多信息,请参见“优化器食谱Wiki”页面。
binaryen还包含除优化以外的其他事情的各种通行证,例如JavaScript的合法化,异步等。
建筑
binaryen使用git subpoules(仅针对GTEST编写时),因此在构建之前,您必须初始化subsodules:
git submodule init git submodule update
之后,您可以使用Cmake构建:
cmake . && make
需要C ++ 17编译器。在MacOS上,您需要通过Brew Install CMake安装CMAKE。请注意,您还可以将忍者用作生成器:CMAKE -G忍者。 && Ninja。
为了避免GTEST依赖性,您可以将-dbuild_tests = OFF传递给cmake。
可以使用Emscripten构建binaryen .js,可以通过SDK安装。
- node.js的建筑物:
emcmake cmake . && emmake make binaryen _js - 为浏览器建造:
emcmake cmake -DBUILD_FOR_BROWSER=ON . && emmake make
视觉C ++
-
使用Microsoft Visual Studio安装程序,安装“用于CMAKE的Visual C ++工具”组件。
-
生成项目:
mkdir build cd build \" %VISUAL_STUDIO_ROOT%\\Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe \" ..
用Visual_studio_root替换了通往Visual Studio安装的路径。如果您使用的是Visual Studio构建工具,则该路径将为“ C:\\ Program Files(X86)\\ Microsoft Visual Studio \\ 2017 \\ buildTools”。
-
从开发人员命令提示符中,构建所需的项目:
msbuild binaryen .vcxprojCmake生成了一个名为“ All_build.vcxproj”的项目,以方便地构建所有项目。
发行
构建由使用binaryen各种工具链(如emscripten,wasm-pack等)分发。
https://*github*.co*m/webassembly/binaryen/releases
目前包括以下平台的构建:
- Linux-X86_64
- Linux-ARM64
- MACOS-X86_64
- MacOS-ARM64
- Windows-X86_64
- node.js(实验):wasm-opt端口到JavaScript+WebAssembly。运行节点wasm-opt.js,作为在Node.js运行的任何平台上的本机构建的置换替换。需要Node.js 18+(对于WASM EH和WASM线程)。 (请注意,此构建也可以在DENO,BUN或其他JavaScript+WebAssembly环境中运行,但仅在Node.js上进行测试)
跑步
wasm-opt
跑步
bin/wasm-opt [.wasm or .wat file] [options] [passes, see --help] [--help]
WASM Optimizer接收WebAssembly作为输入,并且可以在其上运行转换并打印(在转换之前和/或转换之后)。例如,尝试
bin/wasm-opt test/lit/passes/name-types.wast -all -S -o -
这将在测试套件中输出一个测试用例。要运行转换通行证,请尝试
bin/wasm-opt test/lit/passes/name-types.wast --name-types -all -S -o -
名称类型的通行证可确保每种类型都有一个名称,并且重命名了异常长的类型名称。您可以通过比较两个命令的输出来查看变换原因。
将自己的转换通行证添加到外壳上很容易,只需将.CPP文件添加到SRC/Passe中,然后重建外壳即可。示例代码,查看名称类型通过。
还有更多注释:
- 有关选项和通行证的完整列表,请参见bin/wasm-opt–螺旋。
- 通过 – 选将发出一些调试信息。可以通过将它们作为逗号分隔字符串列表传递给单个调试通道(通过#Depug_type xxx在源代码中定义的单个debug_type xxx)。例如:bin/wasm-opt -debug =二进制。这些调试通道也可以通过binaryen _debug环境变量启用。
wasm2js
跑步
bin/wasm2js [input.wasm file]
这将在控制台上打印出JavaScript。
例如,尝试
bin/wasm2js test/hello_world.wat
该输出包含
function add ( x , y ) { x = x | 0 ; y = y | 0 ; return x + y | 0 | 0 ; }
作为翻译
( func $add (; 0 ;) ( type $0 ) ( param $x i32 ) ( param $y i32 ) ( result i32 ) ( i32.add ( local.get $x ) ( local.get $y ) ) )
WASM2JS的输出为ES6模块格式 – 基本上,它将WASM模块转换为ES6模块(在较旧的浏览器和Node.js版本上运行您可以使用Babel等将其转换为ES5)。让我们看一个称呼“ Hello World Wat”的完整示例;首先,创建主JS文件:
// main.mjs import { add } from \"./hello_world.mjs\" ; console . log ( \'the sum of 1 and 2 is:\' , add ( 1 , 2 ) ) ;
运行此操作(请注意,您需要一个带有ES6模块支持的足够新的Node.js):
$ bin/wasm2js test/hello_world.wat -o hello_world.mjs $ node --experimental-modules main.mjs the sum of 1 and 2 is: 3
WASM2JS的输出始终牢记:
- 您应该使用-O或其他优化级别运行具有优化版本的WASM2J。这将沿整个管道(WASM和JS)进行优化。不过,它不会像Minify Whitepsace那样做JS Minifer所能完成的一切,因此您之后仍应运行普通的JS minifer。
- 不可能通过快速的JavaScript代码将WebAssembly语义100%匹配。例如,每个负载和存储都可能陷入困境,并且要使JavaScript执行相同的操作,我们需要在各处添加支票,这会很大而慢。取而代之的是,WASM2J假设负载和商店不会捕获,int/float转换不会捕获,等等。转换的角案例也可能存在一些差异,例如非捕获浮子到int。
wasm-ctor-eval
WASM-CTOR-EVAL在编译时执行功能或部分功能。这样做之后,它将运行时状态序列到WASM中,就像采用“快照”一样。当WASM后来加载并以VM运行时,它将从那时开始执行,而无需重新完成已经执行的工作。
例如,考虑这个小程序:
( module ;; A global variable that begins at 0. ( global $global ( mut i32 ) ( i32.const 0 )) ( import \" import \" \" import \" ( func $import )) ( func \" main \" ;; Set the global to 1. ( global.set $global ( i32.const 1 )) ;; Call the imported function. This *cannot* be executed at ;; compile time. ( call $import ) ;; We will never get to this point, since we stop at the ;; import. ( global.set $global ( i32.const 2 )) ) )
我们可以在这样的编译时评估其中的一部分:
wasm-ctor-eval input.wat --ctors=main -S -o -
这告诉它,我们要执行一个单个函数(“ ctor”是“全局构造函数”的缩写,“ global构造函数”是来自在程序的输入点之前执行的代码的名称),然后将其作为文本打印到STDOUT。结果是:
trying to eval main ...partial evalling successful, but stopping since could not eval: call import: import.import ...stopping (module (type $none_ = > _none (func)) (import \" import \" \" import \" (func $import )) (global $global (mut i32) (i32.const 1)) (export \" main \" (func $0 _0)) (func $0 _0 (call $import ) (global.set $global (i32.const 2) ) ) )
登录表明我们设法评估了Main()的一部分,但并非全部,但正如预期的那样:我们可以评估第一个全局。请注意,在输出WASM中,全局的值已从0更新到1,并且第一个全局。GET已删除:WASM现在处于一种状态,即当我们以VM运行时,将从WASM-CTOR-eval停止的地点继续无缝地运行。
在这个小例子中,我们刚刚保存了少量工作。可以节省多少工作取决于您的程序。 (它可以帮助您进行纯计算,并将导入的呼叫尽可能迟到。)
请注意,Wasm-ctor-eval的名称与前面提到的全局构造函数有关,但是您可以在此处执行什么都没有限制。如果其内容合适,则可以执行WASM的任何导出。例如,在Emscripten中,WASM-CTOR-EVAL甚至在可能的情况下都在Main()上运行。
WASM-MERGE
WASM-MERGE将WASM文件结合在一起。例如,想象一下您有一个项目,该项目使用来自多个工具链的WASM文件。然后,将它们全部合并到运输前的单个WASM文件中可能会有所帮助,因为在单个WASM中,模块之间的呼叫成为模块中的正常调用,这使它们可以夹住,消除了死亡代码,等等,依此类推,可能会提高速度和尺寸。
WASM-MERGE在普通的WASM文件上运行。与WASM-LD在WASM对象文件上运行一样,它与WASM-LD不同。 WASM-MERGE可以在多工链情况下有助于,其中至少一个工具链不使用WASM对象文件。
例如,想象一下我们有这两个WASM文件:
;; a.wasm ( module ( import \" second \" \" bar \" ( func $second.bar )) ( export \" main \" ( func $func )) ( func $func ( call $second.bar ) ) )
;; b.wasm ( module ( import \" outside \" \" log \" ( func $log ( param i32 ))) ( export \" bar \" ( func $func )) ( func $func ( call $log ( i32.const 42 ) ) ) )
您本地驱动器上的文件名是A. wam和b.wasm,但为了合并 /捆绑目的,可以说第一个被称为“第一个”,第二个称为“第二”。也就是说,我们希望第一个模块的“ second.bar”导入在第二个模块中调用函数$ func。这是wasm-merge命令:
wasm-merge a.wasm first b.wasm second -o output.wasm
我们将其提供给第一个WASM文件,然后是其名称,然后将其命名为第二个WASM文件,然后是其名称。合并的输出是:
( module ( import \" outside \" \" log \" ( func $log ( param i32 ))) ( export \" main \" ( func $func )) ( export \" bar \" ( func $func_2 )) ( func $func ( call $func_2 ) ) ( func $func_2 ( call $log ( i32.const 42 ) ) ) )
WASM-MERGE将两个文件组合到一个文件中,合并了它们的功能,导入等,同时修复了名称冲突并将相应的导入连接到导出。特别是,请注意$ func如何调用$ func_2,这正是我们想要的:$ func_2是第二个模块的功能(重命名以避免名称碰撞)。
请注意,此示例中的WASM输出可能受益于其他优化。首先,对$ func_2的呼叫现在可以轻松地夹住,因此我们可以运行WASM -OPT -O3为我们做到这一点。另外,我们可能不需要所有可以运行WASM-METADCE的进出口。一个良好的工作流程可能是运行WASM-MERGE,然后是WASM-METADCE,然后使用WASM-OPT结束。
从“ JS Bundler”的意义上讲,Wasm-Merge有点像WASM文件的捆绑器。也就是说,在上面的WASM文件中,想象我们有此JS代码可以在运行时实例化并将其连接到:
// Compile the first module. var first = await fetch ( \"a.wasm\" ) ; first = new WebAssembly . Module ( first ) ; // Compile the first module. var second = await fetch ( \"b.wasm\" ) ; second = new WebAssembly . Module ( second ) ; // Instantiate the second, with a JS import. second = new WebAssembly . Instance ( second , { outside : { log : ( value ) => { console . log ( \'value:\' , value ) ; } } } ) ; // Instantiate the first, importing from the second. first = new WebAssembly . Instance ( first , { second : second . exports } ) ; // Call the main function. first . exports . main ( ) ;
WASM-MERGE的作用基本上是JS所做的:它可以将导入到导出,使用您提供的模块名称解决名称。也就是说,通过运行WASM-MERGE,我们正在将连接模块连接的工作从运行时到编译时间。结果,在运行WASM-MERGE后,我们需要少得多的Js才能获得相同的结果:
// Compile the single module. var merged = await fetch ( \"merged.wasm\" ) ; merged = new WebAssembly . Module ( merged ) ; // Instantiate it with a JS import. merged = new WebAssembly . Instance ( merged , { outside : { log : ( value ) => { console . log ( \'value:\' , value ) ; } } } ) ; // Call the main function. merged . exports . main ( ) ;
我们仍然需要获取和编译合并的WASM,并提供JS导入,但是不再需要连接两个WASM模块的工作。
处理出口
默认情况下,如果存在重叠的导出名称,则WASM-MERGE错误。也就是说,WASM-MERGE将自动处理重叠函数名称,依此类推,因为这些功能名称不可见(代码仍然行为相同),但是如果我们重命名的导出,则需要修改外部以期望新的导出名称,因此我们在此名称冲突上误以为是错误的。
如果您确实希望出口重命名,请与rename-export-froccrocts一起运行WASM-MERGE。后来的出口将附加给他们的后缀,以确保它们不会与以前的出口重叠。后缀是确定性的,因此,一旦看到它们是什么,您可以从外面称呼它们。
另一个选择是使用-Skip-Export-Conflicts,它将简单地跳过以后的名称相互矛盾的出口。例如,在第一个模块是唯一与外部交互的模块和后来的模块仅与第一个模块交互的情况下,这可能很有用。
特征
WASM-MERGE使用多内存和多桌功能。也就是说,如果多个输入模块每个都有一个内存,则输出WASM将具有多个内存,并且取决于多记忆功能,这意味着较旧的WASM VMS可能无法运行WASM。 (作为如此旧的VM的解决方法,您可以运行WASM-OPT-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi-Multi降低了一个记忆。)
测试
./check.py
(或python check.py)将在测试/测试容器上运行WASM-SHELL,WASM-OPT等,并验证其输出。
check.py脚本支持一些选项:
./check.py [--interpreter = /path/to/interpreter] [TEST1] [TEST2]..
- 如果提供了解释器,我们将通过它运行输出,检查解析错误。
- 如果提供了测试,我们会准确地运行这些测试。如果没有提供,我们将全部运行。要查看可用的测试,请运行./check.py-list-suites。
- 一些测试需要路径中的EMCC或NODEJ。如果找不到该工具,它们将不会运行,您会看到警告。
- 我们在GIT子模块中进行了测试/规格上游的测试。运行./check.py应该更新这些。
请注意,我们正在尝试在修改它们时逐渐将旧版WASM-OPT测试移植以使用点亮和FileCheck。对于输出浪费的通行测试,可以使用脚本/port_pass_tests_to_lit.py自动完成此操作,并且对于输出浪费的非通行测试,请参见#4779,以获取如何进行简单的手动端口。
对于LIT测试,随着对binaryen更改,通常可以自动更新测试期望(检查线)。请参阅脚本/update_lit_checks.py。
在大多数情况下,也可以自动更新非光线测试。请参阅脚本/auto_update_tests.py。
设置依赖项
./third_party/setup.py [mozjs | v8 | wabt | all]
(或python third_party/setup.py)安装所需的依赖项,例如spidermonkey js shell,v8 js shell和wabt in thirt_party/。安装时其他脚本会自动拾取它们。
运行pip3 install -r unignts -dev.txt以获取LIT测试的要求。请注意,您需要在$路径(在Linux,〜/.local/bin上)安装位置PIP安装。
模糊
./scripts/fuzz_opt.py [-- binaryen -bin = build/bin]
(或python脚本/fuzz_opt.py)将在随机传递的随机输入上运行各种模糊模式,直到找到可能的错误为止。有关所有详细信息,请参见Wiki页面。
设计原理
- 名称的实习字符串:在节点上有名称,而不仅仅是数字索引等。为了避免字符串和数字索引之间的大多数性能差异,所有字符串均已实施,这意味着每个字符串的单个字符串中都有一个单字符串,字符串比较只是一个指针比较等。
- 在竞技场中分配:基于其他优化/转换工具链的经验,不值得仔细跟踪单个节点的记忆。取而代之的是,我们在竞技场中分配了模块的所有元素,并且在不再需要模块时可以释放整个竞技场。
调试信息支持
源地图
binaryen可以读取和写入源地图(请参阅WASM -OPT的-ism和-osm标志)。它还可以以文本格式读取和读取源地图注释,也就是说
;; @ src.cpp:100:33 ( i32.const 42 )
That 42 constant is annotated as appearing in a file called src.cpp at line 100 and column 33. Source maps and text format annotations are interchangeable, that is, they both lead to the same IR representation, so you can start with an annotated wat and have binaryen write that to a binary + a source map file, or read a binary + source map file and print text which will contain those annotations.
The IR representation of source map info is simple: in each function we have a map of expressions to their locations. Optimization passes should update the map as relevant. Often this \”just works\” because the optimizer tries to reuse nodes when possible, so they keep the same debug info.
Shorthand notation
The text format annotations support a shorthand in which repeated annotations are not necessary. For example, children are tagged with the debug info of the parent, if they have no annotation of their own:
;; @ src.cpp:100:33 ( i32.add ( i32.const 41 ) ;; This receives an annotation of src.cpp:100:33 ;; @ src.cpp:111:44 ( i32.const 1 ) )
The first const will have debug info identical to the parent, because it has none specified, and generally such nesting indicates a \”bundle\” of instructions that all implement the same source code.
Note that text printing will not emit such repeated annotations, which can be confusing. To print out all the annotations, set binaryen _PRINT_FULL=1 in the environment. That will print this for the above add:
[i32] ;; @ src.cpp:100:33 ( i32.add [i32] ;; @ src.cpp:100:33 ( i32.const 41 ) [i32] ;; @ src.cpp:111:44 ( i32.const 1 ) )
(full print mode also adds a [type] for each expression, right before the debug location).
The debug information is also propagated from an expression to its next sibling:
;; @ src.cpp:100:33 ( local.set $x ( i32.const 0 ) ) ( local.set $y ;; This receives an annotation of src.cpp:100:33 ( i32.const 0 ) )
You can prevent the propagation of debug info by explicitly mentioning that an expression has not debug info using the annotation ;;@ with nothing else:
;; @ src.cpp:100:33 ( local.set $x ;; @ ( i32.const 0 ) ;; This does not receive any annotation ) ;; @ ( local.set $y ;; This does not receive any annotation ( i32.const 7 ) )
This stops the propagatation to children and siblings as well. So, expression (i32.const 7) does not have any debug info either.
There is no shorthand in the binary format. That is, roundtripping (writing and reading) through a binary + source map should not change which expressions have debug info on them or the contents of that info.
实施详细信息
The source maps format defines a mapping using segments , that is, if a segment starts at binary offset 10 then it applies to all instructions at that offset and until another segment begins (or the end of the input is reached). binaryen \’s IR represents a mapping from expressions to locations, as mentioned, so we need to map to and from the segment-based format when writing and reading source maps.
That is mostly straightforward, but one thing we need to do is to handle the lack of debug info in between things that have it. If we have ABC where B lacks debug info, then just emitting a segment for A and C would lead A\’s segment to also cover B, since in source maps segments do not have a size – rather they end when a new segment begins. To avoid B getting smeared in this manner, we emit a source maps entry to B of size 1, which just marks the binary offset it has, and without the later 3 fields of the source file, line number, and column. (This appears to be the intent of the source maps spec, and works in browsers and tools.)
矮人
binaryen also has optional support for DWARF. This primarily just tracks the locations of expressions and rewrites the DWARF\’s locations accordingly; it does not handle things like re-indexing of locals, and so passes that might break DWARF are disabled by default. As a result, this mode is not suitable for a fully optimized release build, but it can be useful for local debugging.
常问问题
- Why the weird name for the project?
binaryen \’s name was inspired by Emscripten \’s: Emscripten\’s name suggests it converts something into a script – specifically Jav
