龙空技术网

WebAssembly 常用开发语言和工具链

闪念基因 361

前言:

今天同学们对“基于c语言的系统设计参考文献”大约比较关切,我们都想要分析一些“基于c语言的系统设计参考文献”的相关内容。那么小编在网上网罗了一些对于“基于c语言的系统设计参考文献””的相关内容,希望各位老铁们能喜欢,同学们一起来学习一下吧!

1. 前言

WebAssembly 作为一项新兴的技术,已经发展至 2.0 版本;相较于 1.0 版本,2.0 版本增加了更全面的指令支持和对大容量内存的友好性;同时,向量指令的加入也提高了 WebAssembly 在复杂场景下的性能表现。目前,WebAssembly 已经广泛应用于各种 Web 和非 Web 场景,例如 Web 端的视频渲染、编解码、算法移植,以及非 Web 端的 Serverless、客户端跨平台等领域。

除了广泛的应用场景,WebAssembly 还具有跨平台、高效、安全的优点。它可以在各种计算机架构上运行,并且具有接近原生代码的性能。此外,WebAssembly 的代码可以在沙箱中运行,不会影响主机环境的稳定性和安全性,从而有助于保护用户数据和隐私。

随着 WebAssembly 技术的不断进步,未来还有许多值得期待的方向。例如 WebAssembly 的多线程支持、AI 模型的推理和训练,以及在区块链领域的智能合约编写和执行。这些发展方向有助于进一步提高 WebAssembly 的性能和功能,从而推动 Web 上更多应用场景的发展。

针对 WebAssembly 的巨大发展潜力和广泛应用前景,社区和公司也在建设 WebAssembly 生态上投入了巨大的精力。多个流行语言已经相继支持了 WebAssembly 产物,社区开发者们和企业或机构也相继提供了相当多的 WebAssembly 工具。

本文将介绍当前 WebAssembly 生态中比较流行的开发语言和工具链,并为读者提供相关应用场景的介绍。值得注意的是,截至 2023 年 2 月,许多语言和工具链仍在不断迭代和发展。

2. WebAssembly 语言生态介绍2.1 C/C++

由于 C/C++ 的历史地位,其拥有庞大的用户群体以及完备的工具链。在 WebAssembly 生态中,C/C++ 是先行者,也是绕不开的重量级语言。现如今,C/C++ 最常用编译工具链之一的 LLVM 已经把 WebAssembly 添加为受支持的后端。

此外,社区发展了 WebAssembly System Interface 标准(简称 WASI,非 WebAssembly Proposal)。基于这套标准,WebAssembly 的运行环境能够获取系统 IO 等能力,不再依赖宿主语言,变成了一个独立的能够运行在非 Web 端的产品。自此,WebAssembly 的能力从 Web 端拓展到了非 Web 端,服务端成为了 WebAssembly 下一个热门的发展方向。从 C/C++ 的角度来看,大量使用系统调用的基础库能够移植到 WebAssembly,将 WebAssembly 的能力下沉到了原生,甚至是系统层面。

2.2 Rust

Rust 和 Emscripten 如今都由 Mozilla 公司负责,因此 WebAssembly 社区的发展和 Rust 有着密不可分的联系。Mozilla、英特尔、RedHat 和 Fastly 公司联合成立字节码联盟 (Bytecode Alliance)[1],其中 Rust 实现的 wasmtime 虚拟机就是最重量级的 WebAssembly 高性能虚拟机之一。

Rust 编译器借助 LLVM 的能力生成 WebAssembly 产物,因此 WebAssembly 也是 Rust 的目标语言之一。同时,Rust 兼容 C/C++ 的内存模型,无 GC,因此生成的 WebAssembly 产物体积小巧性能表现良好,与 C/C++ 生成的 WebAssembly 模块能够无缝衔接。

Rust 生态同样支持 WASI 标准。因此非 Web 端应用也成了 Rust WebAssembly 生态发展的热门方向。在 wasmtime 虚拟机[2]的加持下,Rust 在服务端应用同样大有可为。

2.3 Go

Go 作为服务端领域的主流语言,与 WebAssembly 在服务端的应用方向不谋而合。从 Go 1.11 开始,WebAssembly 已经正式被 Go 官方支持,这说明 Go 团队也认同了 WebAssembly 的发展潜力。同时,社区也存在 Go 实现的大量开源项目,例如 wazero[3] 就是 Go 实现的高性能 WebAssembly 虚拟机。

然而,Go 语言在 WebAssembly 社区也有令人诟病的几个问题。首先就是 WASI 支持,虽然 Go 在服务端发力,但是由于 Go 的 WebAssembly 不支持 WASI,因而 Go 的 WebAssembly 产物不能脱离 Web 环境,这可能会阻碍 Go 的 WebAssembly 社区在服务端的发展。其次,Go 的 GC 依赖使得 WebAssembly 产物体积严重膨胀,难以做到 Web 端所需的轻量化。

总而言之,虽然 Go 能够作为 WebAssembly 开发语言使用,但是也有不小的局限性。

2.4 AssemblyScript

AssemblyScript 使用 TypesScript 中的部分语法并做了部分修改,是一门专为 WebAssembly 服务的语言。WebAssembly 最初就是为了前端场景设计的,AssemblyScript 的语法让前端开发者能够很自然地写出可编译为 WebAssembly 的程序。因此,在前端社区中,越来越多的开发者选择 AssemblyScript 开发 WebAssembly 项目。

AssemblyScript 最初就是为了 Web 端设计的,因此它不支持 WASI 标准。此外,AssemblyScript 生成的 WebAssembly 包含了 GC,产物大小会比 C/C++ 生成的产物大不少。AssemblyScript 底层也借用了 LLVM 的能力,它将类 TypeScript 的语言解析成 AST,生成 LLVM IR,最后生成 WebAssembly 产物。

AssemblyScript 虽然方便了前端程序员编写 WebAssembly 项目,但是其内置的部分库,以及生成的 WebAssembly 稍显臃肿,优化也没有做到最优。例如,导入导出部分生成的代码进行了内存拷贝、pow 库函数生成了大量冗余代码,这些都有待优化。

2.5 JavaScript

JavaScript 是前端领域必不可少的语言,大量的 JavaScript 项目运行在各类平台,浏览器、移动端 APP、PC 桌面端都能见到它的身影。WebAssembly 作为 Web 端的希望之星,设计之初就能够和 JavaScript 交互使用。为此,WebAssembly 自有一套 JS-API 标准[4],用于规范 JavaScript 和 WebAssembly 互操作。

然而,由于 JavaScript 语言特性,它难以生成完备的 WebAssembly 产物。大量 JavaScript 项目无法无缝迁移到 WebAssembly 上。当然,想让 JavaScript 跑在 WebAssembly 上也不是毫无办法。生产环境中已经存在将 JavaScript 引擎编译到 WebAssembly,运行在 WebAssembly 引擎中的例子。这种场景直觉上没什么应用前景,但实际上他与 Serverless 和容器化场景不谋而合。在服务端场景中,这种设计让用户能够继续编写 JavaScript 代码,服务端则能够获得较少的性能损失,获得更小的体积和更快的启动速度,可以说是双赢。

2.6 其他支持的语言

除了上面提到的语言之外,还有一些主流语言以各种方式支持了 WebAssembly。例如,Lua 社区存在将 Lua 引擎编译到 WebAssembly 的项目,它让 Lua 脚本运行在浏览器上;C# 社区存在将 C# 编译到 WebAssembly 的实验项目,同时微软官方也有实验中的 C# to WebAssembly 项目。然而,这些项目大部分都处于实验性质,或多或少存在一些限制,导致其无法用在生产环境。同时,社区也处于初步发展阶段。因此,相关内容不在此做详细介绍,读者可参考 awesome-wasm-langs[5] 做进一步的了解和学习。

3. WebAssembly 工具生态3.1 Emscripten3.1.1 概述

Emscripten[6] 是 Alon Zakai 创建的项目,源于将 C/C++ 编译生成的 LLVM IR 翻译成 JavaScript [7]的想法。为此,Alon Zakai 发明了一种 Relooper 算法,这种算法能将任意控制流图重新转化为结构化的控制流图。

后续,Emscripten 加入了对 WebAssembly 的支持,其功能变为了将 C/C++ 或是其他基于 LLVM IR 的语言的项目工程编译到 WebAssembly。任何可移植的 C/C++ 库都可以被 Emscripten 编译成 WebAssembly,例如图形库、声音库等。Emscripten 主要在 Emcc 中使用 Clang + LLVM 将目标代码编译成 WebAssembly。同时,Emcc 还会生成包含 API 的 JavaScript 代码,这些代码能够在 Node.js 里运行,或者能够被 HTML 包含,在浏览器里面运行。

3.1.2 库支持

为了支持 C/C++ 标准库,Emscripten 在 musl libc 和 LLVM libcxx 库的基础上做了定制化,实现了 WASI 标准接口,提供了大部分的 C/C++ 标准库能力。此外,Emscripten 提供了部分他们适配过的常用库,包括 socket 库、html 库、gl 库等。这些库能力的支持让 Emscripten 能够将大部分 C/C++ 工程无缝迁移到 WebAssembly 上,大大拓展了 WebAssembly 的生态。

然而,尽管 Emscripten 做了大量的工作,它仍然不能完美地将 C/C++ 工程迁移到 WebAssembly。下面将会简要介绍部分局限性:

多线程:WebAssembly 的多线程支持早就有了提案,但是截止到本文写作时间,提案依然还处于 phase3 实现状态,没有完成。因此,即使 Emscripten 移植了 pthread 库,这种移植依然处于类似用户态的模拟多线程,并非原生多线程。模拟的多线程有巨大的开销,在生产环境中的性能表现不好,应用价值也不大。计时:由于 WebAssembly 的隔离性,它不能直接访问硬件资源,因此 WebAssembly 无法直接获取硬件能力,例如 CPU 时钟。如果想在 WebAssembly 里面使用 C 库的能力比如 clock_t,只能使用模拟的方式,误差大精度低。库导入:由于 WebAssembly 是独立的二进制格式,Emscripten 无法将第三方静态或者动态库与 WebAssembly 产物进行连接。因此,如果想要移植现有的 C/C++ 项目,必须保证项目中不依赖现成的静动态库,必须全源码编译。3.1.3 编译

Emscripten 底层依然使用的是 clang + wasm-ld 的能力将工程编译成 WebAssembly 产物。此外,在 LLVM 的基础上,Emscripten 做了大量移植工作,提供了更多样的选项。为了实现这些能力,Emscripten 开发了 emcc 项目。

emcc 基于 clang,提供了大量额外的编译选项。例如,emcc 能让用户选择是否编译 WASI 产物,选择需要导出到外部的函数,选择是否生成 JavaScript 和 HTML 胶水代码;并且,emcc 会根据用户的编译选项,自动化地选择依赖的库,生成额外的编译和链接命令,省去了大量手动配置的时间。更多选项可以查阅 emcc settings 配置说明[8]。

除了 clang 和 wasm-ld 的优化能力,emcc 还引入了 Closure Compiler[9] 和 Binaryen[10]。前者是一个 JavaScript to JavaScript 优化器,优化 Emscripten 生成的 JavaScript 代码;后者则是一个独立的 WebAssembly to WebAssembly 优化器,提供了 LLVM 以外的多个优化 Pass[11]。

3.1.4 调试

Emscripten 能够生成 JavaScript 胶水代码和 HTML 代码。在浏览器中打开生成的 html 文件,浏览器会加载整个工程。Chrome DevTool 有实验性质的 DWARF 调试信息支持,Emscripten 生成的 WebAssembly 产物包含 DWARF 信息,Chrome 能够依据 DWARF 信息,定位并显示 C/C++ 源码调试信息。

关于如何调试 Emscripten 生成的 WebAssembly;可以进一步阅读 WebAssembly 调试原理和方法简介相关内容。

3.2 Binaryen

Binaryen 的目的是让编译到 WebAssembly 的工作变得 简单,快速,有效

简单:Binaryen 在一个头文件中有一个简单的 C API,也可以从 JavaScript 中使用。它接受类似 WebAssembly 的形式的输入,但也接受通用控制流图。快速:Binaryen 的内部 IR 使用紧凑的数据结构,设计为完全并行的代码生成和优化,使用所有可用的 CPU 内核。Binaryen 的 IR 也非常容易和快速地编译为 WebAssembly,因为它本质上是 WebAssembly 的一个子集。有效:相比于其他通用编译器,Binaryen 更关注与 WebAssembly 特性强相关的优化。Binaryen 的优化器有许多途径可以提高运行速度,减小产物体积。其强大的优化能力足以使 Binaryen 作为编译器后端使用。

Binaryen 可以理解为 WebAssembly 编译器后端 + WebAssembly 编译工具链

作为编译器后端:接收 WebAssembly 格式的输入,并能够进行一系列的优化,最终生成 WebAssembly。接收其他 CFG 格式的输入,将其编译成 WebAssembly 产物。作为编译工具链提供了一系列能力:解析并重新生成 WebAssembly:Binaryen 可以加载并解析 WebAssembly,优化,并重新生成 WebAssembly。简而言之,就是 Wasm2Wasm。解释执行 WebAssembly 程序。Emscripten 集成:与 Emscripten 对接,以提供一个完整的 C/C++ to WebAssembly 工具链。JavaScript Polyfill:提供基于 JavaScript 的 WebAssembly 解释器,在没有支持 WebAssembly 的浏览器环境中运行 WebAssembly 程序。3.2.1 Binaryen IR

Binaryen 自己实现了一套 AST 格式的 IR,用来支持编译器后端的优化等工作,称之为 Binaryen IR。Binaryen IR 与 WebAssembly 几乎等价,是 WebAssembly 的子集。

在 WebAssembly 早期版本中,WebAssembly 以 AST 形式组织,因此,Binaryen IR 自然地被设计成树状结构。在标准的后续演进中,WebAssembly 变为了 Stack Machine,自此 Binaryen IR 与 WebAssembly 分道扬镳。综上,Binaryen IR 发展成树状有历史原因,社区也经过了一系列的讨论,不在本文中展开说明,有兴趣的读者可以参考文档[12][13]相关内容。

3.2.2 CFG

如前所述,Binaryen 的原生 IR 是 AST,但它也可以接收任意控制流图中的输入。Binaryen 可以将代码 reloop 到结构化的控制流中。这适用于任何 CFG,甚至不可规约(irreducible)的 CFG。

3.2.3 优化 Pipeline

Binaryen 包含了许多优化过程,使 WebAssembly 更小、更快。你可以通过使用 wasm-opt 来运行 Binaryen 优化器,同时它们也可以在使用其他工具时运行,比如 wasm2jswasm-metadce

默认的优化流水线是由 addDefaultFunctionOptimizationPasses 类似的函数设置的。

用户可以通过设置多种类型的参数选项:调整优化和 shrink 级别;是否忽略 unlikely traps;使用快速数学优化等。详细配置选项可以通过 wasm-opt --help 进行获取;对优化 Pass 感兴趣读者,可以阅读 Binaryen Optimizations[11]相关内容。

Binaryen 始终启用 LTO(Link Time Optimization),因为它通常在最终链接的 WebAssembly 上运行。优化器中的高级优化技术包括 SSAification、Flat IR 和 Stack/Poppy IR。此外,Binaryen 还包含各种不做优化的传递,例如 JavaScript 的合法化、Asyncify 等。

3.2.4 Toolswasm-opt:加载 WebAssembly 并运行 Binaryen IR 传递。wasm-as:将 WebAssembly 文本格式(目前是 S-Expression)汇编成二进制格式(通过 Binaryen IR)。wasm-dis:将 WebAssembly 的二进制格式反汇编为文本格式(通过 Binaryen IR)。wasm2js:一个 WebAssembly 到 JavaScript 的编译器。被 Emscripten 用来生成 JavaScript 产物。wasm-reduce:WebAssembly 文件的 testcase reducer。给定一个有趣的(例如,它崩溃了一个特定的 VM) WebAssembly 测试文件,wasm-reduce 可以找到一个具有相同效果的更小的 WebAssembly 文件,这通常更容易调试。wasm-shell:一个可以加载和解释 WebAssembly 代码的 shell。它还可以运行规范测试套件。wasm-emscripten-finalize:接受一个由 llvm+lld 生成的 WebAssembly 二进制文件,并对其执行特定于 Emscripten 的 pass。wasm-ctor-eval:可以在编译时执行函数(或函数的一部分)的工具。binaryen.js:一个独立的 JavaScript 库,它公开了用于创建和优化 WebAssembly 模块的 Binaryen 函数。关于构建,请参阅 npm 上的 binaryen.js[14](或直接从 github,rawgit,或 unpkg 下载)。3.2.5 Debug

Binaryen 支持 DWARF 格式的调试信息,与 Emscripten 集成时可以接入 Emscripten 中的调试信息。

3.3 wasi-sdk

wasi-sdk[15] 可以看作是魔改过后的 LLVM,添加了 WASI 的支持。使用方式和 clang 编译 C/C++ 项目基本一致,可以直接使用 clang 命令。wasi-sdk 中,libc 的实现和 Emscripten 相似,都是使用了开源的 musl libc 库,进行了部分修改以适配 WASI。在 C/C++ 编写的 WebAssembly 工程中,可以使用 wasi-sdk 替代 LLVM 作为默认编译工具链。

同样的 wasi-sdk 也存在和 Emscripten 相似的局限性,包括多线程、time 能力等都是缺失的。

3.4 TinyGo

TinyGo[16] 是一个轻量级的 Go 编译器,主要用于嵌入式系统、WebAssembly 和命令行等较为轻量级的环境中。TinyGo 复用了 Go language tools 以及 LLVM,提供额外的编译 Go 的方法。

目前在服务端场景上,有部分 Go WebAssembly Serverless 函数服务,背后使用了 TinyGo 做支撑。

3.5 wabt

wabt[17] 是一个工具链集合,其中包括 WebAssembly 的反汇编工具、解释器、编译器、验证工具等。下面简单对几个常用工具进行介绍。

3.5.1 wasm2wat 和 wat2wasm

wasm2wat 和 wat2wasm 是两个最常用到的二进制和文本格式互相转换的工具。wat 是 WebAssembly 文本格式的文件后缀名,wasm 则是二进制格式的后缀名。顾名思义,这两个工具能将 WebAssembly 的文本格式和二进制格式在完美保留语义的前提下相互翻译。由于翻译的准确性,这两个工具能给程序编写者提供直接编写或者修改二进制的能力,这对于进阶 WebAssembly 开发者来说非常有用。

3.5.2 wasm2c

wasm2c 将 WebAssembly 二进制文件转换为 C 源文件和头文件。这个功能在部分场合非常有用,例如 Web3 场景或者是安全场景。一部分 Web3 平台的智能合约采用的是 WebAssembly,例如 eos,它将 C/C++ 编译到 WebAssembly,运行在 eos 区块链虚拟机平台上。用户可以手动下载合约,使用 wasm2c 查看合约代码。同时,wasm2c 允许用户将 WebAssembly 二进制产物 relooper 到 C,再编译成 LLVM IR。安全领域的区块链智能合约静态动态程序代码分析大多使用这种方式构造数据集。

3.6 wasm-pack

wasm-pack[18] 工具由 Rust / WebAssembly 工作组开发维护,并且是现在最为活跃的 WebAssembly 应用开发工具。它支持将代码打包成 npm 模块,并且随附了 Webpack 插件,可以轻松地与已有的 JavaScript 应用结合。

3.7 wasm-bindgen

wasm-bindgen[19] 让 WebAssembly 模块和 JavaScript 之间能够进行交互。

wasm-bindgen 允许 JavaScript / WebAssembly 通过字符串、JavaScript 对象、类等进行通信。运用 wasm-bindgen 可以在 Rust 中定义 JavaScript 类或从 JavaScript 获取一个字符串或返回一个字符串给 JavaScript。目前这个工具主要关注 Rust 生态,但底层原理与语言无关。开发团队希望随着时间的推移,这个工具可以稳定并扩展到 C/C ++ 等语言生态中。功能包括:

将 JavaScript 能力导入到 Rust 中, 比如 DOM 操作[20],控制台日志记录[21]或者性能监控[22]。导出 Rust 功能[23]到 JavaScript,如类,函数等。使用丰富的类型,如字符串,数字,类,闭包和对象,而不是简单的 u32 和浮点数。自动为 JavaScript 使用的 Rust 代码生成 TypeScript 绑定。

配合 wasm-pack,用户可以在 web 上运行 Rust,将其作为一个更大的应用程序的一部分发布,在 NPM 上发布 Rust 的 WebAssembly 产物。

4. 总结

本文介绍的多种编程语言和工具,包括 C/C++、Go、Rust 和 AssemblyScript 等,以及它们所提供的 WebAssembly 编译和打包功能。其中,C/C++ 和 Rust 语言提供了多种工具,如 Emscripten、wasi-sdk、wasm-pack 和 wasm-bindgen 等,可根据不同的应用场景实现 WebAssembly 的编译和打包。而 TinyGo 和 AssemblyScript 则分别提供了专为 Go 的 WebAssembly、嵌入式场景和 TypeScript 的变体语言开发的编译器。此外,还有两个通用工具:Binaryen 和 wabt。前者能够优化 WebAssembly 代码,后者提供常用的 WebAssembly 工具和格式转换功能;WebAssembly 相关编程语言和工具总结详见表 1.

随着 WebAssembly 越来越被广泛地关注,各主流编程语言和社区都已将 WebAssembly 视为更为重要的组成部分,不仅将其作为运行环境的编译目标,甚至将其作为默认产物提供支持。此外,为了更好地支持 WebAssembly,社区还专门设计了一些新语言,如 IDEA 研究院张宏波主导设计的新语言[24]。

WebAssembly 编程语言和工具集是为实际的应用开发场景服务而存在的;基于本文的语言和工具链介绍,WebAssembly 实战部分将详细介绍编程语言和工具如何在实际的 WebAssembly 应用场景中使用,感兴趣的读者可以阅读课程的相关章节。

表 1. WebAssembly 常用开发语言和工具集

5. 参考文献

[1]. Bytecode Alliance:

[2]. wasmtime:

[3]. wazero:

[4]. JS-API Specs:

[5]. awesome-wasm-langs:

[6]. Emscripten:

[7]. Alon Zakai. 2011. Emscripten: an LLVM-to-JavaScript compiler. In Proceedings of the ACM international conference companion on Object oriented programming systems languages and applications companion (OOPSLA '11). Association for Computing Machinery, New York, NY, USA, 301–312.

[8]. settings:

[9]. Closure Compiler:

[10]. Binaryen:

[11]. binaryen-optimizations:

[12]. Structured code for the stack machine:

[13]. Future of Binaryen in a stack machine world:

[14]. binaryen.js:

[15]. wasi-sdk:

[16]. TinyGo:

[17]. wabt:

[18]. wasm-pack:

[19]. wasm-bindgen:

[20]. DOM:

[21]. console:

[22]. performace:

[23]. paint:

[24]. 基于 WebAssembly 的程序语言设计:

作者:张地

来源:微信公众号:字节前端 ByteFE

出处:

标签: #基于c语言的系统设计参考文献