摘要:本文是圖說系列文章的第四篇。它們表示一種可以在普遍流行機(jī)器上高效使用的指令集合。這是因?yàn)槭且环N稱為堆棧機(jī)器。盡管是根據(jù)堆棧機(jī)器來設(shè)計(jì)的,但是這并不是它在真實(shí)物理機(jī)器上工作的方式。這些內(nèi)容稱為段。
本文是圖說 WebAssembly 系列文章的第四篇。如果您還未閱讀之前的文章,建議您從第一篇入手。
WebAssembly 是一種使得除 JavaScript 以外的編程語言也能運(yùn)行在網(wǎng)頁上的技術(shù)。
在過去,當(dāng)我們需要通過編程來控制網(wǎng)頁內(nèi)容時(shí),我們的選擇只有 JavaScript 。
所以當(dāng)大家都說 WebAssembly 運(yùn)行速度很快時(shí),其實(shí)它的比較對(duì)象就是指 JavaScript 。
不過這并不意味著你只能使用 JavaScript 和 WebAssembly 中的一種。
反而,更推薦的做法是同時(shí)使用它們。即便是你不寫 WebAssembly ,你也是可以從它身上獲得好處的。
WebAssembly 模塊定義了可以被 JavaScript 調(diào)用的函數(shù)。
就像我們現(xiàn)在可以直接從 npm 下載 lodash 模塊并調(diào)用其接口一樣,未來我們也可以下載 WebAssembly 模塊并使用它。
所以,今天我們來看看如何創(chuàng)建 WebAssembly 模塊,以及如何使用 JavaScript 調(diào)用它。
角色在上一篇文章中,我們介紹了編譯器如何把高級(jí)語言編譯為機(jī)器碼。
在上圖中,WebAssembly 對(duì)應(yīng)哪個(gè)角色呢?
聰明的你可能已經(jīng)想到,它只不過是另一種目標(biāo)匯編語言而已。
從某種意義上來說,這種想法是對(duì)的,只不過圖中的 x86、ARM 等其實(shí)對(duì)應(yīng)的是一種特定的計(jì)算機(jī)架構(gòu)。
對(duì)于開發(fā)者來說,他所開發(fā)的代碼是希望能夠運(yùn)行在互聯(lián)網(wǎng)上所有用戶機(jī)器上的,但是他其實(shí)并不知道運(yùn)行這些代碼的機(jī)器屬于哪種架構(gòu)。
所以 WebAssembly 跟匯編相比還是有略微不同之處。
它面向的是一種概念上機(jī)器的機(jī)器語言,而不是一種真實(shí)存在的物理機(jī)器。
這也就導(dǎo)致了 WebAssembly 指令是一種虛擬指令。
與 JavaScript 源碼相比,虛擬指令跟機(jī)器碼的映射來得更為直接。
它們表示一種可以在普遍流行機(jī)器上高效使用的指令集合。但同時(shí)它們也不會(huì)直接映射到特定的機(jī)器碼。
瀏覽器會(huì)下載 WebAssembly,然后把它變成目標(biāo)機(jī)器的匯編。
編譯目前對(duì) WebAssembly 支持最多的編譯器工具鏈稱為 LLVM 。有很多不同的編譯器前端和后端都在使用 LLVM 。
注意: 大多數(shù)的 WebAssembly 模塊開發(fā)者都會(huì)使用 C 和 Rust 這樣的語言,然后編譯為 WebAssembly,但是也有其他方式創(chuàng)建 WebAssembly 模塊。比如,有一個(gè)實(shí)驗(yàn)工具可以把 TypeScript 編譯為 WebAssembly 模塊,更有甚者,
可以直接手寫 WebAssembly 。
這里,假如我們想把 C 編譯為 WebAssembly 。
我們可以使用 C 語言編譯器前端把 C 代碼編譯為 LLVM 中間代碼。一旦變成 LLVM 的中間代碼,LLVM 就可以理解并分析代碼,然后做一些優(yōu)化。
為了把 LLVM 中間代碼變成 WebAssembly,我們還需要一個(gè)編譯器后端。剛好,LLVM 項(xiàng)目中確實(shí)有一個(gè)正在開發(fā)編譯器后端,未來它應(yīng)該是大部分人的共同選擇,而且應(yīng)該很快就要完成了。不過,現(xiàn)在用它的話還是相當(dāng)棘手。
不過不用灰心,還有另一個(gè)工具稱為 Emscripten,目前用起來會(huì)更加簡單點(diǎn)。
它擁有自己編譯器后端,可以把中間代碼編譯為 asm.js ,進(jìn)而轉(zhuǎn)化為 WebAssembly 。
不過它也支持 LLVM,因此我們也可以在 Emscripten 和其他后端之間相互切換。
Emscripten 還包含了很多其他工具和庫,允許開發(fā)者移植整個(gè) C/C++ 代碼,因此與其說它是編譯器,其實(shí)它更像是軟件開發(fā)套件(SDK)。
不管用什么工具鏈,最終的結(jié)果都是得到一個(gè) .wasm 文件。后面我們會(huì)介紹 .wasm 文件的結(jié)構(gòu),不過首先讓我們來看看如何在 JavaScript 中使用它。
加載.wasm 文件就是 WebAssembly 模塊,它可以直接使用 JavaScript 加載。
截止到目前,這種加載方式略微復(fù)雜。
function fetchAndInstantiate(url, importObject) { return fetch(url).then(res => res.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, importObject)) .then(results => results.instance); }
想深入的話,可以參考這個(gè)MDN 文檔
我們正在努力把這個(gè)過程變得更加簡單。我們也希望能夠把工具鏈變得更加友好,希望能夠直接集成到諸如 webpack 或者 SystemJS 等打包器中。相信未來 WebAssembly 模塊可以跟加載 JavaScript 模塊一樣簡單好用。
不過,WebAssembly 模塊和 JavaScript 模塊之間有一個(gè)主要的不同之處。
當(dāng)前,WebAssembly 模塊中的函數(shù)只能使用數(shù)字作為參數(shù)或者返回值。
對(duì)于其他任何更復(fù)雜的數(shù)據(jù)類型,如字符串,我們必須直接操作 WebAssembly 模塊的內(nèi)存。
如果你大部分的時(shí)間都在使用 JavaScript,那么你可能對(duì)直接操作內(nèi)容不太熟悉。
像 C、C++ 和 Rust 這些高性能的語言,它們都必須手動(dòng)管理內(nèi)存。
WebAssembly 模塊的內(nèi)存就模擬了這些語言的堆內(nèi)存。
為了能夠操作內(nèi)存,我們需要使用 JavaScript 中的 ArrayBuffer。
它是字節(jié)數(shù)組,所以它的索引當(dāng)做內(nèi)存地址來使用。
如果想要在 JavaScript 和 WebAssembly 之間傳遞字符串,那么必須先把字符串轉(zhuǎn)為等效的字符碼,然后寫入 ArrayBuffer。由于數(shù)組索引是整數(shù),所以索引可以傳遞給 WebAssembly 函數(shù)。這樣,索引就變成了指向字符串首個(gè)字符的指針了。
不過大部分情況下,WebAssembly 模塊開發(fā)者都會(huì)把模塊做友好地封裝。此時(shí),模塊的使用者可能就沒必要知道其內(nèi)部是如何管理內(nèi)存的了。
如果你對(duì)內(nèi)存管理感興趣,可以查看 MDN 文檔結(jié)構(gòu)
如果你編程使用的是高級(jí)語言然后編譯為 WebAssembly,那其實(shí)你沒必要了解 WebAssembly 模塊的結(jié)構(gòu),不過它可以幫你理解基礎(chǔ)信息。
下面是一個(gè) C 函數(shù),我們將把它編譯為 WebAssembly 。
int add42(int num) { return num + 42; }
你可以使用 WasmExplorer來編譯這個(gè)函數(shù)。
打開編譯好的 .wasm 文件后,我們可能會(huì)看到類似以下的內(nèi)容:
00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60 01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80 80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06 81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65 6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69 00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20 00 41 2A 6A 0B
這是模塊的“二進(jìn)制”表示。之所以給“二進(jìn)制”加上引號(hào),是因?yàn)槎M(jìn)制表示通常是顯示為十六進(jìn)制的,但是可以很簡單的轉(zhuǎn)為二進(jìn)制,或者人類可讀的格式。
舉例來說,下面是 num + 42 的模樣:
運(yùn)行你可能會(huì)對(duì)上圖中的內(nèi)容感到疑惑,下面我們把這些指令的作用標(biāo)注出來。
你可能已經(jīng)注意到 add 操作并沒有說要相加的兩個(gè)數(shù)從哪里來。這是因?yàn)?WebAssembly 是一種稱為堆棧機(jī)器。這意味著,操作碼在操作之前,它所需的操作數(shù)已經(jīng)在堆棧的隊(duì)列當(dāng)中了。
像 add 這樣的操作碼本身就知道它需要多少個(gè)操作數(shù)。因?yàn)?add 需要兩個(gè)操作數(shù),所以它會(huì)從堆棧的頂部取出兩個(gè)值來作為操作數(shù)。
這樣種設(shè)計(jì)中,add 指令可以變得很短,只占用一字節(jié),因?yàn)樗⒉恍枰付ㄔ春湍繕?biāo)寄存器地址。這樣就減小了 .wasm 文件的大小,從而更利于網(wǎng)絡(luò)傳輸。
盡管 WebAssembly 是根據(jù)堆棧機(jī)器來設(shè)計(jì)的,但是這并不是它在真實(shí)物理機(jī)器上工作的方式。
當(dāng)瀏覽器把 WebAssembly 編譯為機(jī)器碼時(shí),它仍然會(huì)用到寄存器。不過,由于 WebAssembly 代碼并不指定寄存器,所以瀏覽器能夠更自由的為其指定最高效的寄存器。
除了 add42 函數(shù)本身,.wasm 也還包含了其他內(nèi)容。這些內(nèi)容稱為段(Section)。有些段是任何模塊都必須有的,有些則是可選的。
必選的有:
類型:包含模塊中函數(shù)和任何導(dǎo)入函數(shù)的函數(shù)簽名。
函數(shù):給模塊中的每個(gè)函數(shù)提供索引。
代碼:模塊中每個(gè)函數(shù)的函數(shù)體。
可選的有:
導(dǎo)出:使得函數(shù)、內(nèi)存、表格和全局變量對(duì)其他模塊和 JS 可訪問。這可以使得模塊可以多帶帶編譯,然后動(dòng)態(tài)鏈接起來。
導(dǎo)入:指定從其他模塊或者 JS 中導(dǎo)入的函數(shù)、內(nèi)存、表格和全局變量等。
入口:模塊加載時(shí)自動(dòng)運(yùn)行的函數(shù)。
全局:模塊中的全局變量聲明。
內(nèi)存:定義模塊使用的內(nèi)存。
表格:用于映射不透明值,這些值不能在 WebAssembly 中表示或直接訪問,例如 JS 的對(duì)象。
數(shù)據(jù):用于初始化導(dǎo)入的或本地的內(nèi)存
元素:用于初始化導(dǎo)入的或者本地的表格
更多的資料可參考 MDN 文檔結(jié)束
經(jīng)過本文,相信你已經(jīng)知道該如何使用 WebAssembly 模塊了。下一篇文章我們將探索它為何如此快。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/94766.html
摘要:性能簡史在年,被創(chuàng)造出來時(shí)并不是沖著性能去的。而且在之后的十年發(fā)展中,它的性能一直是很低的。的引入成就了性能提升的一個(gè)轉(zhuǎn)折點(diǎn),其執(zhí)行速度比以往快了之多。性能提升也使得在全新的問題上使用成為可能?,F(xiàn)在,極可能是下一個(gè)性能轉(zhuǎn)折點(diǎn)。 你可能已經(jīng)聽說 WebAssembly 代碼跑起來非??臁5悄阒肋@是為什么嗎?在本系列文章中,我們將探究其原因。 何為 WebAssembly WebAss...
摘要:現(xiàn)狀年月日,主流的四大瀏覽器達(dá)成了共識(shí)并宣布的最小可行產(chǎn)品已經(jīng)完成。更快的函數(shù)調(diào)用當(dāng)前,在中調(diào)用函數(shù)比想象的要慢。直接操作目前,沒有任何方式能夠操作。這就導(dǎo)致了部分應(yīng)用可能會(huì)因此而推遲發(fā)布時(shí)間。結(jié)束現(xiàn)如今已經(jīng)相當(dāng)快速。 本文是圖說 WebAssembly 系列文章的最后一篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 現(xiàn)狀 2017 年 2 月 28 日,主流的四大瀏覽器達(dá)成了共識(shí)...
摘要:本文是圖說系列文章的第五篇。這樣的話,使用的開發(fā)者也不需要做任何適配,但是它們卻能獲得更高性能。該圖并不是用來準(zhǔn)確的衡量其性能的。運(yùn)行編寫出高性能的代碼是可能的。這種清理工作由引擎自動(dòng)進(jìn)行,稱為垃圾回收。 本文是圖說 WebAssembly 系列文章的第五篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 在上一篇文章中,我們說到了使用 WebAssembly 和 JavaScript...
摘要:編譯器優(yōu)缺點(diǎn)與解釋器相比,編譯器有著相反的優(yōu)缺點(diǎn)。它們?yōu)橐嫘略隽艘粋€(gè)組件,稱為監(jiān)視器,或者。優(yōu)化編譯器會(huì)基于監(jiān)視器記錄的代碼運(yùn)行信息來作出一些判斷。通常來說,優(yōu)化編譯器會(huì)使得代碼跑的更快。而這正是優(yōu)化編譯器所做的優(yōu)化之一。 本文是圖說 WebAssembly 系列文章的第二篇,如果你還沒閱讀其它的,建議您從第一篇開始。 JavaScript 的運(yùn)行,一開始是很慢的,但是后面會(huì)變得越來...
摘要:為了更好的理解,我們有必要去先理解什么是匯編,以及編譯器是如何產(chǎn)生匯編的。什么是匯編現(xiàn)在,我們來看看外星人的大腦是如何工作的。這些注釋就是匯編,也稱為符號(hào)機(jī)器碼。結(jié)束以上的內(nèi)容就是什么是匯編以及它是如何從高級(jí)編程語言翻譯過來的。 本文是圖說 WebAssembly 系列文章的第三篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 為了更好的理解 WebAssembly ,我們有必要去先...
閱讀 7014·2021-09-22 15:08
閱讀 2042·2021-08-24 10:03
閱讀 2530·2021-08-20 09:36
閱讀 1468·2020-12-03 17:22
閱讀 2534·2019-08-30 15:55
閱讀 987·2019-08-29 16:13
閱讀 3138·2019-08-29 12:41
閱讀 3329·2019-08-26 12:12