assembly在电脑上是什么意思,windowsassembly是什么文件

首页 > 教育 > 作者:YD1662024-04-05 02:04:32

此文由公众号@windliang(美团前端攻城狮)授权发布

引言

先看下 官网 给的定义。

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

WebAssembly 是基于栈式虚拟机的二进制指令集,可以作为编程语言的编译目标,能够部署在 web 客户端和服务端的应用中。

第一次看到这个定义的时候是一头雾水,翻了一些资料渐渐有了点轮廓,下边分享下我目前的理解。

首先 WebAssembly 是由 Web 和 Assembly 两个词构成,其中 Web 表明它一定和前端有关。Assembly 的意思是汇编,汇编对应机器码,而机器码和 CPU 的指令集有关,接下来补一下相关的知识。

相关概念

其中指令集、操作系统相关的知识,之前总结过几篇文章,到底学哪一门编程语言、X86,x64,x86-64,AMD64,arm指令集架构之间的关系、linux和Android的关系,可以先过去看一下,这里的话抽主要的部分回顾一下。

assembly在电脑上是什么意思,windowsassembly是什么文件(1)

参考上图,计算机的主要架构如上。最底层是 CPU 的指令集,主要分为复杂指令集和简单指令集。

复杂指令集是 x86、x64(也叫 x86-64, amd64) 两种架构,专利在 Intel 和 AMD 两家公司手里, 该架构 CPU 主要是 Intel 和 AMD 两家公司,这种 CPU 常用在 PC 机上,包括 Windows,macOS 和 Linux。

简单指令集是 arm 一种架构,专利在 ARM 公司手里,该架构 CPU 主要有高通、三星、苹果、华为海思、联发科等公司。这种 CPU 常用在手机上,包括安卓和苹果。

指令集是什么呢?直接把阮一峰的老师的一个 例子 粘过来,大家可以看一下。

c 语言的源程序。

int add_a_and_b(int a, int b) { return a b; } int main() { return add_a_and_b(2, 3); }

所对应的汇编就是下边的样子。

_add_a_and_b: push ?x mov ?x, [%esp 8] mov ?x, [%esp 12] add ?x, ?x pop ?x ret _main: push 3 push 2 call _add_a_and_b add %esp, 8 ret

这里的 push 、mov 每一条指令就是指令集规定的内容,规定了操作码、操作数以及具体的功能。当然这里是用汇编表示的,主要是为了我们人类来读写,最终还会转成 0,1 序列。上边每个单词都会有一个数字相对应,比如 add 指令对应 00000011 。

通过规定的指令集(加法的指令,压栈指令等),编写相关程序,然后 CPU 就会一条一条的执行,最终实现相应的功能。

而 WebAssembly 就规定了一套指令集,更准确的来说是虚拟指令集,因为这套指令集是跑在虚拟机上的,而不是直接由硬件运行。

历史

上边我们知道了 WebAssembly 的 Assembly ,即汇编,也就是指令集。下边在回顾下 Web,即 WebAssembly诞生的原因。

这里就得谈到 javaScript 了,众所周知, javaScript 是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于我们开发者,确实很方便,但对于运行它的引擎就很有问题了。参考 这里 的一张图,看一下 V8 引擎从 js 源码到执行的一个过程。

assembly在电脑上是什么意思,windowsassembly是什么文件(2)

由于 js 的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以 V8 引擎采用了 JIT(即时编译技术) 技术,监控一些经常执行的代码,将其编译成 CPU 直接执行的机器码,提高执行速度。但由于 js 动态类型,在某些情况下还得反优化,回到字节码进行执行。

随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。

NaCl/PNaCl

2011 年 Google 在 Chrome 中使用了 NaCl 技术,可以使得 C 语言编写的程序运行到浏览器中,下边是维基百科 的定义。

Google Native Client(缩写为NaCl),是一个由谷歌所发起的开放源代码计划,采用BSD许可证。它采用沙盒技术,让Intel x86、ARM或MIPS子集的机器代码直接在沙盒上运行。它能够从浏览器直接运行程序机器代码,独立于用户的操作系统之外,使Web应用程序可以用接近于机器代码运作的速度来运行,同时兼顾安全性。其功能类似于微软的 ActiveX,但是ActiveX只支持视窗系统。

但一个完整的 NaCl 应用,在分发时需要提供支持多个架构平台(X86 / X64 / ARM 等)的模块文件,后来谷歌又推出了与底层架构无关的 PNaCl 技术。但由于其开发难度、兼容性等问题最终没有普及开来。在 2017 年 Google 宣布放弃 PNaCl 转向 WebAssembly。

asm.js

ASM.js 是 Mozilla 在 2013 年推出的,是 javaScript 的一个严格子集,可以作为 C/C 编译的目标语言,从而使得 js 引擎可以采用 AOT(Ahead Of Time) 的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。

ASM.js 通常不直接编写,而是作为一种通过编译器生成的中间语言,该编译器获取 C 或其他语言的源代码,然后输出 ASM.js。

例如下边的 C 语言代码。

int f(int i) { return i 1; }

经过编译器编译会生成下边的 js 代码。

Function f(i) { i = i|0; return (i 1)|0; }

注意这里的|0 在 js 中相当于和 0 进行了或操作,所以不影响原本的逻辑。在 asm.js 中起到了类型标记的作用,这样 js 引擎执行的时候就知道 i 是一个整型,返回值是一个整型。除了或操作这种,ASM.js 标准中还规定了很多类似的标记规则,用于告诉 js 引擎变量的类型,便于进行 AOT 优化。

这看起来和 TypeScript 很像,但其实不是一种东西。TypeScript 是 js 的一个超集,浏览器并不能直接执行 ts,还需要转换为 js 去执行。ts 主要是帮助我们开发人员去看的,增加了代码的可读性,也可以让编辑器提前发现一些错误。而 asm.js 是用于引擎的编译优化。

WebAssembly

接下来看一下 WebAssembly 的历史。

2015 年 4 月,WebAssembly Community Group 成立;

2015 年 6 月,WebAssembly 第一次以 WCG 的官方名义向外界公布;

2016 年 8 月,WebAssembly 开始进入了漫长的 “Browser Preview” 阶段;

2017 年 2 月,WebAssembly 官方 LOGO 在 Github 上的众多讨论中被最终确定;同年同月,一个历史性的阶段,四大浏览器(FireFox、Chrome、Edge、WebKit)在 WebAssembly 的 MVP(最小可用版本)标准实现上达成共识,这意味着 WebAssembly 在其 MVP 标准上的 “Brower Preview” 阶段已经结束;

2017 年 8 月,W3C WebAssembly Working Group 成立,意味着 WebAssembly 正式成为 W3C 众多技术标准中的一员。

WebAssembly 于 2019 年 12 月 5 日成为万维网联盟(W3C)的推荐标准,与 HTML,CSS 和 JavaScript 一起成为 Web 的第四种语言。

可以看一下目前浏览器的支持程度,已经算比较高了。

assembly在电脑上是什么意思,windowsassembly是什么文件(3)

初体验

内部结构

目前已经有了将 C/C 、Rust、ts、C#、Go、Kotlin、Swift 等语言转换为 WebAssembly(wasm) 的工具,下边我们体验一下 C 转换的过程。

首先编写一个 C 程序 fibonacci.cc,斐波纳契数字的递归写法。

#include <emscripten.h> extern "C" { EMSCRIPTEN_KEEPALIVE int fibonacci(int n) { if(n < 2) { return 1; } return fibonacci(n - 1) fibonacci(n - 2); } }

函数的定义置在 extern “C” {} 结构中,是为了防止函数名编译后被改变。EMSCRIPTEN_KEEPALIVE 是为了确保函数不会在编译器的编译过程中,被 DCE(Dead Code 」limination)过程处理掉。

然后需要安装 Emscripten 用来将 C 程序编译为 WebAssembly(wasm) 的程序,安装后执行下边的命令。

emcc fibonacci.cc -s WASM=1 -O3 --no-entry -o fibonacci.wasm

-s WASM=1 表明编译成 Webassembly 的程序,-O3 表明编译的优化程度,–no-entry 参数告诉编译器没有声明 main 函数,-o 指定生成的文件名。

让我们看一下生成的字节码文件 fibonacci.wasm。

Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00000000: 00 61 73 6D 01 00 00 00 01 11 04 60 00 01 7F 60 .asm.......`...` 00000010: 01 7F 01 7F 60 00 00 60 01 7F 00 03 07 06 02 01 ....`..`........ 00000020: 00 03 01 00 04 05 01 70 01 02 02 05 06 01 01 80 .......p........ 00000030: 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 ........A..@.... 00000040: 41 84 08 0B 07 88 01 09 06 6D 65 6D 6F 72 79 02 A........memory. 00000050: 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E ..__indirect_fun 00000060: 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 09 66 69 ction_table...fi 00000070: 62 6F 6E 61 63 63 69 00 01 0B 5F 69 6E 69 74 69 bonacci..._initi 00000080: 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F alize...__errno_ 00000090: 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B location...stack 000000a0: 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 Save...stackRest 000000b0: 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 ore...stackAlloc 000000c0: 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 ...__data_end... 000000d0: 07 01 00 41 01 0B 01 00 0A 66 06 03 00 01 0B 3D ...A.....f.....= 000000e0: 01 02 7F 41 01 21 01 20 00 41 02 4E 04 7F 41 00 ...A.!...A.N..A. 000000f0: 21 01 03 40 20 00 41 7F 6A 10 01 20 01 6A 21 01 !..@..A.j....j!. 00000100: 20 00 41 03 4A 21 02 20 00 41 7E 6A 21 00 20 02 ..A.J!...A~j!... 00000110: 0D 00 0B 20 01 41 01 6A 05 41 01 0B 0B 04 00 23 .....A.j.A.....# 00000120: 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B ......$....#...k 00000130: 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B Apq".$......A...

让我们来解读下,最开始的前八个字节 0x0 0x61 0x73 0x6d 0x1 0x0 0x0 0x0 表明当前是一个 wasm 的模块。然后会分很多 Section ,Function Section, Code Section 等等,都有特定的数字对应,还有就是文章开头讲的指令操作符所对应的一些数字。

看着上边的字节码仿佛回到了上古时期直接用机器码编程的时代,当年出现了汇编语言。这里也会有类似汇编的东西,那就是 WAT(WebAssembly Text Format) 。

需要安装 WABT , 然后执行 wasm2wat 命令。

../wabt/bin/wasm2wat fibonacci.wasm -o fibonacci.wat

然后就生成了 fibonacci.wat 文件。

(module (type (;0;) (func (result i32))) (type (;1;) (func (param i32) (result i32))) (type (;2;) (func)) (type (;3;) (func (param i32))) (func (;0;) (type 2) nop) (func (;1;) (type 1) (param i32) (result i32) (local i32 i32) i32.const 1 local.set 1 local.get 0 i32.const 2 i32.ge_s if (result i32) ;; label = @1 i32.const 0 local.set 1 loop ;; label = @2 local.get 0 i32.const -1 i32.add call 1 local.get 1 i32.add local.set 1 local.get 0 i32.const 3 i32.gt_s local.set 2 local.get 0 i32.const -2 i32.add local.set 0 local.get 2 br_if 0 (;@2;) end local.get 1 i32.const 1 i32.add else i32.const 1 end) (func (;2;) (type 0) (result i32) global.get 0) (func (;3;) (type 3) (param i32) local.get 0 global.set 0) (func (;4;) (type 1) (param i32) (result i32) global.get 0 local.get 0 i32.sub i32.const -16 i32.and local.tee 0 global.set 0 local.get 0) (func (;5;) (type 0) (result i32) i32.const 1024) (table (;0;) 2 2 funcref) (memory (;0;) 256 256) (global (;0;) (mut i32) (i32.const 5243920)) (global (;1;) i32 (i32.const 1028)) (export "memory" (memory 0)) (export "__indirect_function_table" (table 0)) (export "fibonacci" (func 1)) (export "_initialize" (func 0)) (export "__errno_location" (func 5)) (export "stackSave" (func 2)) (export "stackRestore" (func 3)) (export "stackAlloc" (func 4)) (export "__data_end" (global 1)) (elem (;0;) (i32.const 1) func 0))

上边的格式属于 「S- 表达式」, Lisp 语言就是采用的这种表达式,每条语句都是先执行最里边括号的表达式然后依次展开。

使用方法

上边主要介绍了 .wasm 具体长什么样子,下边看一下怎么用到浏览器中。

从 .wasm 源文件到实例化的对象主要有三个步骤,加载 -> 编译 -> 实例化 -> 调用。

加载:读取 .wasm 字节码到本地中,一般是通过 fetch 从网络中取得。

编译:在 Worker 线程进行,编译成平台相关的代码。

实例化:将宿主环境的一些对象、方法导入到 wasm 模块中,比如导入操作 dom 的方法。

调用:通过上一步已经实例化的对象,来调用 wasm 模块中的方法。

主要有两种类型的 API,一种是 js 提供的 api ,另一种是 Web 提供的 api ,Web 提供的 api 支持流式编译实例化。

js 的方法,

WebAssembly.instantiate(bufferSource, importObject),可以完成编译和实例化。

bufferSource 是含有效 Wasm 模块二进制字节码的 ArrayBuffer 或 TypedArray 对象。

importObject 是要导入到 Wasm 模块中的对象。

方法在调用后返回一个Promise 对象,resolve 后返回一个对象,该对象包含编译好的 module 和已经实例化的 instance,模块导出的方法可以通过 instance 对象进行调用。

web 的方法,

WebAssembly.instantiateStreaming(source, importObject)。

不同之处在于第一个参数,这里的 source 指的是尚未 Resolve 的 Response 对象(window.fetch 调用后会返回该对象),好处就是可以边读取 .wasm 字节流,边进行编译。

其他参数和返回值和 js 的 api 均一致。

js API 尝试

先简单的尝试一下,我们直接构造一个 wasm 模块的 TypedArray 对象,该模块包含了一个 add 方法,然后调用 WebAssembly.instantiate 进行编译和实例化。

对应的 C 代码。

#include <emscripten.h> extern "C" { EMSCRIPTEN_KEEPALIVE int add(int a, int b) { return a b; } }

对应的 .wasm 字节码。

00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60 00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01 7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02 02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D 65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07 00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B

然后直接在控制台输入下边的代码。

WebAssembly.instantiate(new Uint8Array(` 00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60 00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01 7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02 02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D 65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07 00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B`.trim().split(/[\s\r\n] /g).map(str => parseInt(str, 16)) )).then(({instance}) => { const { add } = instance.exports console.log('2 4 =', add(2, 4)) })

然后就会看到输出了 2 4 = 6。

assembly在电脑上是什么意思,windowsassembly是什么文件(4)

首页 123下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.