构建您自己的WebAssembly编译器

2020-05-03 03:12:26

您有没有想过编写自己的编译器?…。什么事?…。你当然有!我一直想尝试编写编译器,随着WebAssembly最近的发布,我有了一个完美的理由来尝试一下。

我最初的计划是发明我自己的编程语言,创建一个面向WebAssembly的编译器,并在FullStackNYC分享我的经验。第一部分进行了计划,我花了很多时间来构建、修补和改进我的编译器。不幸的是,我计划的最后一部分进行得不太顺利。长时间的延误和最终的取消意味着我终究不能去纽约了。😔😢😭。

因此,与其浪费所有的工作-我想我应该把我的演讲写成博客帖子-所以这篇文章的阅读时间就是‘19分钟’。所以请坐,请自便,我们要开始…了。

如果你以前没有听说过WebAssembly,并且想要一个真正详细的介绍,我会向你彻底推荐林克拉克的卡通指南。

在这篇博客文章中,您将了解WebAssembly的“是什么”,但我确实想简要介绍一下“为什么”。

上图显示了在浏览器中执行某些JavaScript代码的简化时间线。从左到右,代码(通常是以最小的乱七八糟的形式交付的!)。被解析成AST,最初在解释器中执行,然后逐步优化/重新优化,直到它最终运行得非常快。现在JavaScript速度很快--只需要一段时间就能跟上速度。

底部的关系图相当于WebAssembly。用多种语言(RUST、C、C#等…)编写的代码。编译为以二进制格式交付的WebAssembly。这非常容易解码、编译和执行-提供快速和可预测的性能。

WebAssembly在过去的一年里引起了不小的轰动。如此之多,以至于在Stack Overflow的开发者洞察力调查中,它被评为第五种“最受欢迎”的语言。

考虑到对于大多数人来说,WebAssembly是一个编译目标,而不是他们将直接使用的语言,这是一个有趣的结果。

这是我最初提出FullStackNYC演讲的部分动机。WebAssembly的技术方面真的很吸引人(让我想起几十年前的8位计算机),然而大多数人永远没有机会涉足WebAssembly本身-它只是一个他们编译到的黑匣子。

编写编译器真的是一个很好的机会,可以深入研究WebAssembly的细节,找出它是什么以及它是如何工作的。而且这也很有趣!

最后一点,我的目标从来不是创建一种功能齐全的编程语言,也不是一种真正好的编程语言。我的目标是创建一种“足够的”语言,使我能够编写一个程序来呈现曼德布罗特集(Mandelbrot Set)。该语言是使用我的编译器编译成WebAssembly的,该编译器是用打字脚本编写的,并在浏览器中运行。

我最后给语言鸿沟打了个电话,如果你愿意,你可以在网上玩。

在处理编译器之前,我们将从更简单的东西开始,创建一个最小的WebAssembly模块。

下面是一个发射器(该术语用于描述为目标系统输出指令的编译器部分),它创建最小的有效WebAssembly模块:

常量magicModuleHeader=[0x00,0x61,0x73,0x6d];常量模块版本=[0x01,0x00,0x00,0x00];导出常量发射器:发射器=()=>;Uint8Array。从([.。MagicModuleHeader,.。ModeVersion]);

它由两部分组成,“Magic”头(即ASCII字符串\0asm)和版本号。这八个字节形成有效的WebAssembly(或wasm)模块。更典型的情况是,这些文件将作为.wasm文件传送到浏览器。

如果运行上面的命令,您会发现该实例实际上并不执行任何操作,因为我们的wasm模块不包含任何指令!

如果您有兴趣亲自尝试这段代码,可以在GitHub上找到--每一步都有一个提交。

让我们通过实现一个将两个浮点数相加的函数来使wasm模块做一些更有用的事情。

WebAssembly是一种二进制格式,可读性不是很好(至少对人类而言),这就是为什么您通常会看到它是以WebAssembly文本格式(WAT)编写的。下面是一个以WAT格式呈现的模块,它定义了一个名为$add的导出函数,该函数接受两个浮点参数,然后将它们相加并返回:

(MODULE(func$add(Param F32)(Param F32)(Result F32)GET_LOCAL 0 GET_LOCAL 1 f32.add)(export";add";(Func 0)。

如果您只想体验一下Wat,可以使用WebAssembly二进制工具包中的wat2wasm工具将WAT文件编译成wasm模块。

WebAssembly是一种低级语言,只有一小部分(大约60条)指令集,其中许多指令与CPU指令的映射非常紧密。这使得将wasm模块编译成特定于CPU的机器码变得很容易。

它没有内置的