WebAssembly 模塊解析

1. 前言

在前面章節中,我們已經對 WebAssembly 的關鍵特性、歷史演變和核心的應用場景做了詳細的介紹;基於

對 WebAssembly 的入門和初步瞭解,在第二部分的各個章節中,我們會從 WebAssembly 模塊入手,和大家一起學習 WebAssembly 基礎知識,包括核心規範,核心開發語言和工具鏈以及常用的執行引擎等相關內容。

本文將從 WebAssembly 模塊入手介紹相關基礎概念和 W3C 二進制格式核心規範,與此同時,進一步介紹 WebAssembly 的文本格式及語法,並給出一個 WebAssembly 文本 demo;以便讀者可以與本文格式的介紹相互印證,進一步加深理解。

2. 基礎概念

WebAssembly 本質上也是一種語言格式,而且其在二進制格式之外還支持文本格式,它也是可人工編程的。與其他語言一樣,它也包含變量、函數、指令等基礎概念。本小節將首先介紹 WebAssembly 的基礎概念。

2.1 模塊(Modules)

模塊(Module)是 WebAssembly 的基本單元;一個模塊內部包含了完成其功能所需的函數、線性內存、全局變量以及表的完整定義;此外,模塊還是通過導入功能從外部執行環境引入對象,同時,可以通過導出功能提供外部執行環境可用的對象,從而實現與外部的雙向交互。

模塊作爲一個程序的靜態表示,它需要經過加載、解碼與驗證之後進行實例化,從而得到該程序的動態表示,我們稱之爲一個 WebAssembly 實例(instance)。WebAssembly 模塊的實例化在宿主語言或是獨立的虛擬機中完成:如在 JavaScript 環境中,該過程可以通過 WebAssembly.instantiate 或者 new WebAssembly.Instance 等函數接口實現。一般而言,一個 WebAssembly 實例包括一個操作數棧和一塊可變的線性內存,與原生(native)程序中的棧和堆相對應,如下圖 1 所示。

圖 1. WebAssembly 模塊實例

2.2 類型(Types)

在 WebAssembly 生態中,一個基本設定就是 WebAssembly 使用通用的硬件能力,並在此之上構建一個虛擬的指令集架構(ISA)。遵循這個設定,WebAssembly 提供了四種基本變量類型,分別是 i32i64f32f64,用來表示兩種長度的整數和浮點數。其中,整型數值不區分有符號或無符號,但是針對整型的操作符通常分爲 signedunsigned 兩種版本,以各自的方式對數值進行理解和進一步的操作。

在 4 種基本類型之外,MVP 之後的 WebAssembly 核心規範吸收了 Fixed-width SIMD[4] 和 Reference Type[5] 兩個提案,支持向量類型 v128 和引用類型。前者可以表示不同類型的打包數據,如 2 個 i64f64、4 個 i32f32 以及 8 個 16 位的整型。後者則可以表示各類函數或者來自宿主的實體(如一個對象的指針)。

2.3 變量(Variables)

WebAssembly 的變量按照作用域,可以分爲局部變量(local)和全局變量(global)兩種。

局部變量只存在於一個函數內部,並在函數開始時進行聲明。局部變量總是可變的,並與函數的參數共用索引空間,通過 i32 類型的 index 來訪問。比如,指令 local.get 0 即表示加載序號爲 0 的局部變量,如果該函數有參數的話,該指令表示加載第一個參數,否則加載第一個局部變量。

全局變量可聲明爲可變或不可變的。與局部變量類似,通過序號和指令 global.get 或者 global.set 進行訪問。此外,global 可以由外部引入,或者被導出到外部。

2.4 函數(Functions)

函數是 WebAssembly 指令的組織單位——每一條指令都必須屬於某個函數。每一個函數都會接受一系列特定類型的參數,並返回一系列特定類型的值。值得注意的是,WebAssembly 函數可以返回多個值,而非侷限於單返回值。在 WebAssembly 的規範中,函數並不允許嵌套定義。

2.5 指令(Instructions)

基於一個棧式的機器構建其計算模型,是 WebAssembly 的一個重要特徵。因此,WebAssembly 的指令均圍繞着一個隱式的操作數棧進行設計——通過壓入和彈出的操作進行數據的操作。如前述的局部變量獲取指令 local.get idx,就是加載第 idx 個局部變量到操作數棧棧頂。

考慮到 WebAssembly 產物將頻繁地通過網絡進行傳輸,如是的設計與基於寄存器的指令集相比可以得到體積更小的產物,因而可以減少網絡延遲 [2]。

2.6 陷阱(Traps)

在某些情況下,如遇到一條 unreachable 指令,會出現一個陷阱。它會馬上中斷 WebAssembly 指令的執行,並且返回到宿主,因爲 WebAssembly 內部無法處理陷阱。通常情況下,宿主會捕獲 WebAssembly 陷阱,並進入處理例程。如 JavaScript 環境中,遭遇因 unreachable 指令產生的 WebAssembly 陷阱,JS 引擎會拋出一個 RuntimeError,如下圖 2 所示。

圖 2. WebAssembly Trap

2.7 表(Tables)

WebAssembly 的表是一系列元素的向量,其中元素類型目前只支持前述的引用類型funcref | externref。表在定義時,要求提供存儲的元素類型和初始大小,並可以選擇性地指定最大大小。Table 主要作用就是可以動態地根據所提供的 index 來獲取目標元素。比如,可以藉助 Table 在運行時決定調用某個函數,使用 call_indirect 實現間接調用,從而模擬函數指針。

2.8 線性內存(Linear memory)

顧名思義,線性內存表示一個連續字節數組,作爲 WebAssembly 程序的主要內存空間。

一個 WebAssembly 模塊暫時(WebAssembly 1.0/2.0 SPEC[3])只允許定義至多一個 memory,並可以通過 import/export 跟其他實例共享(但並非所有引擎都支持)。與表類似,在定義線性內存時,要求提供初始大小,並可以選擇性地指定最大大小,其單位是頁(page,大小爲 64KB)。在 WebAssembly 程序的運行過程中,可以通過指令 grow_memory 動態地擴展線性內存的大小。

通過 load/store 指令,並提供 i32 類型的偏移值參數,WebAssembly 程序可以完成對線性內存的讀寫。當然,會導致越界訪問的偏移值會被動態的檢測所攔截,併產生一個陷阱。考慮到線性內存是獨立且純粹的,與指令空間、執行棧、WebAssembly 引擎的元數據結構等均處於分離狀態,故錯誤甚至惡意的 WebAssembly 代碼最多隻能破壞自身的 memory。這樣的沙盒屬性,對於安全運行通過網絡傳輸、不受信任的 WebAssembly 代碼至關重要。

圖 3. WebAssembly 線性內存

3 模塊結構

WebAssembly 的二進制格式是 WebAssembly 的抽象語法的密集線性編碼。和文本格式一樣,產物是由抽象語法生成的,最終產生的終端符號是字節。包含二進制格式 WebAssembly 模塊的文件的推薦擴展名是 ".wasm",推薦的媒體類型是 "application/wasm"。

本小節將重點介紹 WebAssembly 二進制格式的文件結構;指令的二進制編碼,可以閱讀 W3C 核心規範該文檔 [6]。

3.1 部分(Sections)

一個完整的 WebAssembly 模塊包含 12 個部分,部分和文本格式的域概念相近;部分是可選的,模塊中省略的部分相當於該部分存在空內容;WebAssembly 模塊結構如下圖 4 所示。

圖 4. WebAssembly 模塊段結構示意圖

3.2 模塊(Modules)

本小節會介紹組成一個完整的 WebAssembly 二進制格式模塊所需要的內容,除了部分(sections)之外,WebAssembly 二進制文本還需要魔數和版本數來表示這個模塊是有效的。下面給出完整的模塊定義:

從定義 1 中可以看出,WebAssembly 模塊的每一個段都是可選的,只有四字節的魔數和四字節的版本號不可省略。在每一個部分中間,工具鏈可以選擇是否插入自定義部分。除了自定義部分,其他部分必須嚴格按照定義 1 給出的順序來組織。

自此,二進制格式基本介紹完成。讀者可以根據本小節的內容瞭解 WebAssembly 二進制格式的組織形式,如果想要更進一步,可以參照標準指令格式文檔來實現一個簡單的 WebAssembly 加載器 (loader)。

4. 文本格式

作爲一種低級的類彙編二進制指令格式,WebAssembly 有二進制格式. wasm 和對應的文本格式. wat。WebAssembly 的文本格式可以直接編寫代碼或者用於閱讀,二進制格式則用於分發和執行。對於初次接觸 WebAssembly 的讀者,可以簡單地將 WebAssembly 文本和二進制的關係理解爲彙編語言和機器碼的關係。但要注意的是,WebAssembly 編譯工具鏈只會爲字節碼生成一種二進制產物,並且產物平臺無關,因此需要一個虛擬機來解析執行。常見的 WebAssembly 虛擬機有 V8、JSC、WAMR、Wasm3、Wasmtime 等。此外,作爲 W3C 標準支持的第四種語言,現代瀏覽器幾乎都實現了 WebAssembly 的運行時(V8,JSC),可以直接執行 WebAssembly 代碼。

在本小節中,我們將會詳細展開基礎概念中較爲籠統的部分,讓讀者能夠更深入地理解 WebAssembly 標準的各種細節。

4.1 S-expression

雖然 WebAssembly 是一種類彙編的指令格式,但是它的文本格式和彙編語言的指令 - 寄存器有些許不同—— WebAssembly 採用 S - 表達式(S-expression 或是 sexp)作爲文本組織形式。

Sexp 是一種通過人類可讀的文本形式表達半結構化數據的約定,它既可以用於表示代碼,也能夠用於表示數據。因此,使用了 sexp 的語言天然的存在數據和邏輯的一致性。例如 Lisp 強大的 macro system 就得益於此。

下面給出 sexp 的形式化定義:

從定義上不難看出,實際上 sexp 可以表現爲一棵樹的先序遍歷形式。爲了表現出這一點,我們給定一個簡單的表達式 1,並給出它的二叉樹圖像:

5 x (7 + 3)

圖 5. S 表達式樹形結構

可以看出,這個二叉樹的中序遍歷就是表達式 1 的原始形式。如果將這個二叉樹進行先序遍歷,則會得到表達式 2:

x 5 + 7 3

我們將其稍微做一點轉換,就成爲了 sexp 形式:

(x (5 (+ (7 3))))

再做一點形式簡化,最終,我們得到了表達式 1 的 sexp 表示:

(x 5 (+ 7 3))

4.2 詞法定義

簡而言之,sexp 是一棵 AST 的先序遍歷表示。

接下來我們將會進一步展開討論 WebAssembly 的詞法定義。爲了不機械地重複 WebAssembly 標準,本節只會有選擇地介紹每個詞法單元的定義,然後會從 WebAssembly 示例代碼開始,逐步深入探索文本格式的奧祕。

WebAssembly 的文本形式是由 WebAssembly 的屬性語法(又稱屬性文法)定義的。屬性語法是語法制導定義(Syntax-Directed Definitions,簡稱 SDD)的無副作用形式。詳細內容不在本文展開,讀者可以自行閱讀 屬性語法 [7] 相關內容。

由於詞法和語法強關聯,因此在介紹詞法的時候,會連帶解釋部分語法含義,以便讀者更好地理解詞法含義。

4.2.1 詞法格式(Lexical Format)

WebAssembly 文本由字符串構成,每個字符都是一個合法的 Unicode [8]。首先我們看一個最簡單的 WebAssembly 模塊——空 module:

(module)
;; this is a linecomment
(;
this is a blockcomment
;)

這段代碼展示了一個最小的合法 WebAssembly 文本格式,他的二進制產物可以被 WebAssembly 虛擬機解釋執行,不會有任何輸出。3 到 5 行展示了 WebAssembly 的兩種註釋格式,一種是行註釋(linecomment),一種是塊註釋(blockcomment)。行註釋最終會忽略分號後的內容,塊註釋則會忽略括號對裏的所有內容。

此外,WebAssembly 支持水平製表(U+09)、換行(U+0A)和回車(U+0D)三種格式化字符。空格,格式化字符以及註釋並稱爲 WebAssembly 的空白(write space),可以看做是無意義的段落。

4.2.2 值(Values)

本小節中我們將介紹 WebAssembly 的詞法(lexical syntax),詞法是屬性語法定義的產物,每個詞法單元都必須有意義,因此不允許空白存在。詞法中存在五種值:整數(Integers)、浮點數(Floating-Point)、字符串(Strings)、名稱(Names)、標識符(Identifiers)。

其中,前三種值很好理解,是我們編程中常用的詞法規範。其中,整數包含正負符號,十進制數字,十六進制數字;浮點數包含數字,小數點,十六進制數字和自然底數 e/E;字符串包含了字符,轉義符,引號,換行符,製表符,以及\u{hexnum}表示字符 Unicode 編碼。

詞法的後兩種值則略微有點難以理解。首先是名稱,名稱的常見組織形式是字符串,在 WebAssembly 導出函數時,函數會有特定的名稱。其他時候,名稱則很少被提到;其次是標識符,標識符可以看做是變量的名字,包含兩個詞法單元—— id 和 idchar。id 通常是 $ 和多個 idchar 拼接,下面給出具體示例:

(func $add ...)
...
(export "add" (func $add)) ;; "add"是一個名稱,$add 是一個 id,'a''d''$'都是 idchar

4.2.3 類型(Types)

WebAssembly 是強類型的,因此文本格式中,值都具有其特定的類型。本小節將會介紹 WebAssembly 文本格式中的類型詞法,其中包括:數字(Number)、向量(Vector)、引用(Reference)、值(Value)、函數(Function)、限定(Limits)、內存(Memory)、表(Table)和全局(Global)類型。

定義 3 中 t 是值類型,它可以在實際代碼中被替換爲 i32 i64 f32 f64 v128 funcref externref,下面的示例中,t 最終被替換爲 i32

(func $add (param $lhs i32) (param $rhs i32) (result i32) ;; t 最終被替換爲 i32
  get_local $lhs
  get_local $rhs
  i32.add
)
(memory 1) ;; 1 page initialized
(memory 0 1) ;; maxinum 1 page
(table 2 anyfunc) ;; 2 is a limit type
(global $g1 (mut i32) (i32.const 100)) ;; mutable

4.2.4 指令(Instructions)

指令類型內容繁多,主要內容都是 WebAssembly 指令的文本格式內容。本文不在此詳細介紹,感興趣的讀者可以查閱 WebAssembly 文本格式指令標準文檔 [9]。在本文第一大章的最後一節,我們會實現一個簡單的 WebAssembly 文本模塊,其中也會介紹部分常用指令,讀者可以參閱 "4.3 寫一個 WebAssembly 模塊"。

4.2.5 模塊(Modules)

在瞭解了上面的內容之後,我們終於可以從整體上瀏覽 WebAssembly 文本了。這一小節,我們會介紹一個完整的 WebAssembly 模塊應該具備的所有內容。希望在本小節結束之後,讀者們能夠上手實現一個完整的 WebAssembly 文本模塊。

一個完整的 WebAssembly 文本至少存在一個模塊,每個模塊由 0 或多個域(fields)組成。在文本中,程序員可以重複聲明某個域,在二進制格式中,所有相同的域會合併到一個段中。WebAssembly 模塊最多包含 10 個域,分別是:類型(type),導入(import),導出(export),函數(func),表(table),內存(mem),全局(global),起點(start),元素(elem),數據(data)。上面的小節中我們其實已經見過域的文本片段了,下面給出一個擁有所有域的 WebAssembly 模塊:

(module
(type ... )
(import ... )
(func ... )
(table ... )
(mem ... )
(global ... )
(export ... )
(start ... )
(elem ... )
(data ...

接下來,我們會逐漸往這個 WebAssembly 模塊中填寫內容。

函數域(Functions)

在 4.2.3 小節中,我們已經見過了函數的詞法定義(def 3),並且也給出了一個實際的函數定義,這裏我們重新解讀一下 add 函數的文本寫法:

(func $add (param $lhs i32) (param $rhs i32) (result i32) ...)

開頭的 (func ...) 其實就是聲明瞭一個函數域。函數的唯一標識符是 $add,接收兩個標識符爲 $lhs$rhs 的 32 位整數。函數最終返回一個 32 位整數。我們故意忽略了函數體的指令部分用來縮短篇幅,後面會加上它。

類型域(Types)

類型域的定義很像 C 語言中的 typedef,它的用法是給某個特定的函數類型取一個別名,使用的時候可以通過別名來指代函數類型。

(module
  (type $ft1 (func (param i32 i32) (result i32)))
  (func $add (type $ft1) ... )
)

這個案例定義了一個函數類型 $ft1,這個函數接收兩個 32 位整數參數,返回一個 32 位整數。第 3 行的 func 域中定義了一個 $add 函數,這個函數的類型就是$ft1

導入導出域(Imports and Exports)

導入域的作用是聲明導入的內容。WebAssembly 中存在 4 種導入的對象的類型:函數(func),表(table),內存(memory),全局(global)。這四種類型在 4.2.3 小節已經介紹過了,這裏不再贅述。我們來看他的具體形式:

(module
  (import "console" "log" (func $log (param i32)))
  (func (export "logIt") ... )
)

代碼中 import 後跟了兩個名稱,意思是從 console 模塊導入 log 方法。名稱後的函數域則定義了 log 方法的類型。第 3 行存在一個導出域,export 後也跟了一個 logIt 名稱。這行代碼的意思是在函數域中定義一個會被導出的函數,導出的名稱爲 logIt

內存域(Memories)

一塊內存(memory)在 WebAssembly 中被定義爲一段線性的(linear)未解釋的(uninterpreted)原始字節序列(vector of raw bytes)。內存的限定(limits) 中的最小值是內存的初始大小,而最大值(如果存在)則限制了內存最大能增長到的大小。

內存能主動初始化,也可以被數據段初始化,還可以導入導出。也就是說,內存域中可以內聯(inline)數據段,也可以內聯導入導出域。我們看一個示例:

(module
  (memory $m0 1 1) ;; limits: {min: 1, max: 1}
  (memory $m1 (data "Hello, " "World!\n")) ;; limits: {min: 1}, inline data
  (memory $m2 (import "env" "m2") 1 1) ;; inline import
  (memory $m3 (export "env" "m3") 1 1) ;; inline export
)

現在的 WebAssembly 模塊至多隻能存在一塊內存,在之後的版本中可能會放開限制。

數據段(Data Segments)

數據段在內存域小節中介紹過,它的用處是用一串字符串(strings)來初始化內存。由於當前版本的 WebAssembly 模塊最多存在一塊內存,因此在使用數據段初始化內存的時候,需要給定偏移量。看下面的示例:

(module
  (memory 4 16)
  (data (offset (i32.const 100)) "Hello, ")
  (data (offset (i32.const 108)) "World!\n")
)

上面的示例使用 data 段初始化一塊內存的另一種方式。由於只有一塊內存,所以我們不必指定初始化的內存標識符。在語法定義中,數據段存在兩種模式:1. 被動(passive)2. 主動(active)。上面的示例表示的是主動模式下的數據段定義。主動模式會在實例化內存的時候將數據段的內容拷貝到內存中。而被動模式下,data 段的數據只能通過調用 memory.init 指令才能拷貝到內存中:

(module
  (memory 4 16)
  (data $d0 "Hello, " "World!\n") ;; passive mode
  (func $f0 memory.init $d0) ;; calling `memory.init` instruction in func $f0
)
全局域(Globals)

全局域的作用是定義全局變量,每個全局域能夠定義一個全局變量。全局域有 mut 關鍵詞,能夠定義變量是否可變。同時,全局域能夠內聯導入和導出域。

(module
  (global $g1 (import "env" "gbl") i32) ;; import
  (global $g2 (mut i32) (i32.const 100)) ;; mutable
  (global $g3 f32 (f32.const 3.14)) ;; immutable
  (global $g4 (export "gbl") ...) ;; export
)
表域(Tables)

當我們提到函數調用,我們可能首先想起的是靜態函數調用,類似於:

(module
  (func $log ...)
  (func (export "writeHi") call $log))
)

上面的代碼展示的是靜態函數調用,但是在很多語言中, 並不只有靜態函數調用,比如 C 語言的函數指針,C++ 的虛函數。WebAssembly 作爲這些語言支持的目標格式,需要有能夠表示動態調用的方式,這就是表域。表域可以理解爲一個對用戶透明的數組,數組中存儲的元素是 WebAssembly 函數,用戶僅能通過下標來訪問元素。下面給出一個完整的示例:

(module
(func $f1 (result i32) i32.const 42)
  (func $f2 (result i32) i32.const 13)
  (table funcref (elem (offset i32.const 0) $f1 $f2))
  (type $return_i32 (func (result i32)))
  (func (export "callByIndex") (param $i i32) (result i32)
    local.get $i
    call_indirect (type $return_i32))
)

示例代碼聲明瞭一個大小爲 2 的表域,表域中的元素類型爲任意函數。第 4 行定義了表域中的兩個元素,分別是 $f1$f2。第 5 行定義了一個函數類型。第 6 行則最終使用了表域中的元素。callByIndex 函數接收一個參數,這個參數實際上對應的就是表域的下標。如果傳入的值是 1,那麼 call_indrect 最終會調用表域中下標爲 1 的函數(從 0 開始),也就是 $f2

元素段(Element Segments)

元素段能夠將一個模塊中的任意函數子集以任意順序列入其中,並允許出現重複。列入其中的函數將會被表格引用並,且引用順序是元素的排列順序。

表域的第二個示例代碼段中,元素段中的 (i32.const 0) 值是一個偏移量,作用是表明函數引用是在表中的什麼索引位置開始存儲的。這裏我們指定的偏移量是 0,表格大小是 2,因此,我們可以在索引 0 和 1 的位置填入兩個引用。如果想在偏移量 1 的位置開始寫入引用,那麼,我們必須使用 (i32.const 1) 並且表格大小必須是 3。

起始函數(Start Function)

起始函數會在 WebAssembly 模塊實例化的時候被調用,調用時間點是在表和內存初始化完畢之後。注意,起始函數的作用是初始化模塊的狀態,在初始化階段,外部是訪問不到模塊以及模塊的導出元素的。不要將起始函數和 C 語言的 main 函數混爲一談。

(module
  (func $start ... )
  (start $start)
)

4.3 手寫一個簡單的 WebAssembly 模塊

前一小節完整地介紹了 WebAssembly 的文本格式。在這一節,我們會嘗試寫一個有意義的 WebAssembly 文本。完成以下步驟:

  1. 聲明一個模塊
(module)
  1. 插入兩個函數類型,其中一個接受一個 i32 類型的參數,另外一個沒有參數,且兩個都沒有返回值
(module
  (type (;0;) (func (param i32)))
  (type (;1;) (func))
)
  1. 聲明本模塊需要導入一個函數,類型是索引爲 0 的函數類型,函數自身的索引爲 0
(module
  (type (;0;) (func (param i32)))
  (type (;1;) (func))
  (import "imports" "imported_func" (func (;0;) (type 0)))
)
  1. 增加一個內部定義的函數,使用索引爲 1 的函數類型,這個函數自身的索引也爲 1。這個函數將一個 i32 類型、值爲 88 的常量壓入棧,並調用索引爲 0 的函數
(module
  (type (;0;) (func (param i32)))
  (type (;1;) (func))
  (import "imports" "imported_func" (func (;0;) (type 0)))
  (func (;1;) (type 1)
    i32.const 88
    call 0)
)
  1. 最後,將第 4 步定義、索引爲 1 的函數,用命名 exported_func 導出到外部
(module
(type (;0;) (func (param i32)))
(type (;1;) (func))
(import "imports" "imported_func" (func (;0;) (type 0)))
(func (;1;) (type 1)
  i32.const 88
  call 0)
(export "exported_func" (func 1))
)
  1. 將上述文件保存爲 simple.wat,並使用 wat2wasm 轉換爲 WebAssembly 二進制文件
# install wat2wasm by `npm install -g wat2wasm`
wat2wasm simple.wat -o simple.wasm
  1. 在 JavaScript 中調用它

  2. 讀取模塊的二進制表示,並存儲到一個 ArrayBuffer 中;

  3. 根據模塊所需的導入內容構建導入對象 import_obj;

  4. 調用 WebAssembly.instantiate 實例化並調用導出的函數;

  5. 得到了來自 WebAssembly 的問候!

// test.js
// execute:node test.js
const fs = require('fs');
const wasm_buffer = fs.readFileSync("simple.wasm");
const js_func = (i) => console.log("From WebAssembly: " + i);
const import_obj = {imports: {imported_func: js_func}};

WebAssembly.instantiate(wasm_buffer, import_obj)
.then((result) => result.instance.exports.exported_func());

5. 總結

從整體上來看,WebAssembly 文本格式和二進制格式是相近的,都是從基本概念的語法生成而來。我們可以把二進制格式文本的語義和文本格式的語義一一對應。文本格式讓用戶能夠閱讀、Debug 工程生成的 WebAssembly 代碼,或者用戶可以手寫文本。

希望通過閱讀這篇文章,讀者能夠學習理解 WebAssembly 的基礎知識。更進一步地,期望讀者能夠獲得部分手寫 WebAssembly 文本的能力,同時,能夠對二進制格式的結構有大概的瞭解。

6. 參考文獻

[1]. WebAssembly Core specification: https://webassembly.github.io/spec/core/intro/introduction.html#wasm
[2] Y. Shi, K. Casey, M. A. Ertl, and D. Gregg. Virtual Machine showdown: Stack versus registers. ACM Transactions on Architecture and Code Optimizations, 4(4):2:1–2:36, Jan. 2008.
[3] WebAssembly Moudles: https://webassembly.github.io/spec/core/syntax/modules.html#memories
[4]. Fixed-width SIMD: https://github.com/webassembly/simd
[5]. Reference Type: https://github.com/WebAssembly/reference-types
[6]. WebAssembly Core Specs: https://webassembly.github.io/spec/core/binary/instructions.html
[7]. Attribute Grammar: https://en.wikipedia.org/wiki/Attribute_grammar
[8]. Unicode: https://www.unicode.org/versions/Unicode15.0.0/
[9]. WebAssembly 文本格式指令標準文檔: https://webassembly.github.io/spec/core/text/instructions.html#instructions

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/AI2n1iFFfiEXIqqtT6g_Zg