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