WebAssembly學習(三):AssemblyScript - TypeScript到WebAssembly的編譯


雖然說只要高級語言能轉換成 LLVM IR,就能被編譯成 WebAssembly 字節碼,官方也推薦c/c++的方式,但是讓一個前端工程師去熟練使用c/c++顯然是有點困難,那么TypeScript 的方式便是前端編寫 WebAssembly 最佳選擇。

要將TypeScript 編譯為WebAssembly,就要用到AssemblyScript編譯器了。

AssemblyScript使用Binaryen(Emscripten的WebAssembly后端)將嚴格類型化的TypeScript(基本的帶有類型的JavaScript)編譯為WebAssembly,雖然它提供了幾種新的特定於WebAssembly的類型和內置函數,但它本身並不是一種真正的語言,而是一種編譯器變體。 它生成精簡的WebAssembly模塊,只需要一個npm安裝。

1.安裝

推薦使用cnpm安裝,npm太慢了。

1 cnpm install --save-dev AssemblyScript/assemblyscript

執行過程如下,安裝完成之后只有一個node_modules文件夾

1 E:\Code\assembly>cnpm install --save-dev AssemblyScript/assemblyscript
2 - [@AssemblyScript/assemblyscript] install  from git github:AssemblyScript/assemblyscript, may be very slow, please keep patience
3 √ Installed 1 packages
4 √ Linked 15 latest versions
5 √ Run 0 scripts
6 Recently updated (since 2019-02-17): 1 packages (detail see file E:\Code\assembly\node_modules\.recently_updates.txt)
7 √ All packages installed (14 packages installed from npm registry, 1 packages installed from git, used 1m(network 1m), speed 10.27kB/s, json 14(31.43kB), tarball 707.23kB)

2.創建項目

正確安裝AssemblyScript后,它會提供一個名為asinit的小型實用工具來構建一個新項目,例如,在當前目錄中:

1 npx asinit .

 就像使用express初始化項目一樣,執行過程如下

 1 E:\Code\assembly>npx asinit .
 2 Version: 0.6.0
 3 
 4 This command will make sure that the following files exist in the project
 5 directory 'E:\Code\assembly':
 6 
 7   ./assembly
 8   Directory holding the AssemblyScript sources being compiled to WebAssembly.
 9 
10   ./assembly/tsconfig.json
11   TypeScript configuration inheriting recommended AssemblyScript settings.
12 
13   ./assembly/index.ts
14   Exemplary entry file being compiled to WebAssembly to get you started.
15 
16   ./build
17   Build artifact directory where compiled WebAssembly files are stored.
18 
19   ./build/.gitignore
20   Git configuration that excludes compiled binaries from source control.
21 
22   ./index.js
23   Main file loading the WebAssembly module and exporting its exports.
24 
25   ./package.json
26   Package info containing the necessary commands to compile to WebAssembly.
27 
28 The command will try to update existing files to match the correct settings
29 for this instance of the compiler in 'E:\Code\assembly\node_modules\_assemblyscript@0.6.0@assemblyscript'.
30 
31 Do you want to proceed? [Y/n] y
32 
33 - Making sure that the project directory exists...
34   Exists: E:\Code\assembly
35 
36 - Making sure that the 'assembly' directory exists...
37   Created: E:\Code\assembly\assembly
38 
39 - Making sure that 'assembly/tsconfig.json' is set up...
40   Created: E:\Code\assembly\assembly\tsconfig.json
41 
42 - Making sure that 'assembly/index.ts' exists...
43   Created: E:\Code\assembly\assembly\index.ts
44 
45 - Making sure that the 'build' directory exists...
46   Created: E:\Code\assembly\build
47 
48 - Making sure that 'build/.gitignore' is set up...
49   Created: E:\Code\assembly\build\.gitignore
50 
51 - Making sure that 'package.json' contains the build commands...
52   Updated: E:\Code\assembly\package.json
53 
54 - Making sure that 'index.js' exists...
55   Created: E:\Code\assembly\index.js
56 
57 Done!
58 
59 To edit the entry file, open 'assembly/index.ts' in your editor of choice.
60 Create as many additional files as necessary and use them as imports.
61 
62 To build the entry file to WebAssembly when you are ready, run:
63 
64   npm run asbuild
65 
66 Running the command above creates the following binaries incl. their respective
67 text format representations and source maps:
68 
69   ./build/untouched.wasm
70   ./build/untouched.wasm.map
71   ./build/untouched.wat
72 
73   ^ The untouched WebAssembly module as generated by the compiler.
74     This one matches your sources exactly, without any optimizations.
75 
76   ./build/optimized.wasm
77   ./build/optimized.wasm.map
78   ./build/optimized.wat
79 
80   ^ The optimized WebAssembly module using default optimization settings (-O2s).
81     You can change the optimization settings in 'package.json'.
82 
83 Additional documentation is available at the AssemblyScript wiki:
84 
85   https://github.com/AssemblyScript/assemblyscript/wiki
86 
87 Have a nice day!
88 
89 E:\Code\assembly>

完成的目錄結構是這樣的:

目錄結構含義:

assembly / index.ts上的示例性條目文件

index.js中的通用索引文件,用於加載已編譯的二進制文件和必要的配置文件,如package.json和tsconfig.json

初始化后,只需在編碼時使用現有的TypeScript工具,並使用編譯器手動構建到WebAssembly,或者使用生成的構建任務:按照提示,運行以下命令:

1 npm run asbuild

build文件夾初始化只有一個.gitignore,該命令會在build文件夾中創建.wat與.wasm文件

以上只是構建一個示例項目,如果需要適合開發的安裝,可以通過克隆GitHub存儲庫來實現:

1 $> git clone https://github.com/AssemblyScript/assemblyscript.git
2 $> cd assemblyscript
3 $> npm install
4 $> npm link

 請注意,編譯器的新克隆將使用dist /中的分發文件,但它也可以在npm運行清理后直接通過ts節點運行源,這在開發中很有用。 也可以通過運行asc -v來檢查這種情況(如果它聲明-dev則運行源)。

3.使用編譯器

類似於TypeScript的tsc編譯為JavaScript,AssemblyScript的asc編譯為WebAssembly:

例如,用 TypeScript 實現斐波那契序列計算的模塊 f.ts 如下:

1 export function f(x: i32): i32 {
2     if (x === 1 || x === 2) {
3         return 1;
4     }
5     return f(x - 1) + f(x - 2)
6 }

執行以下asc命令,就能把以上代碼編譯成可運行的 WebAssembly 模塊,比起c/c++的編譯方式,這種人性化多了。

1 asc f.ts -o f.wasm

以上代碼中出現了一個新的內置類型 i32,這是 AssemblyScript 在 TypeScript 的基礎上內置的類型。 AssemblyScript 和 TypeScript 有細微區別,AssemblyScript 是 TypeScript 的子集,為了方便編譯成 WebAssembly 在 TypeScript 的基礎上加了更嚴格的類型限制, 區別如下:

  • 比 TypeScript 多了很多更細致的內置類型,以優化性能和內存占用,詳情文檔;
  • 不能使用 any 和 undefined 類型,以及枚舉類型;
  • 可空類型的變量必須是引用類型,而不能是基本數據類型如 string、number、boolean;
  • 函數中的可選參數必須提供默認值,函數必須有返回類型,無返回值的函數返回類型需要是 void;
  • 不能使用 JS 環境中的內置函數,只能使用 AssemblyScript 提供的內置函數

總體來說 AssemblyScript 比 TypeScript 又多了很多限制,編寫起來會覺得局限性很大; 用 AssemblyScript 來寫 WebAssembly 經常會出現 tsc 編譯通過但運行 WebAssembly 時出錯的情況,這很可能就是你沒有遵守以上限制導致的;但 AssemblyScript 通過修改 TypeScript 編譯器默認配置能在編譯階段找出大多錯誤。

AssemblyScript 的實現原理其實也借助了 LLVM,它通過 TypeScript 編譯器把 TS 源碼解析成 AST,再把 AST 翻譯成 IR,再通過 LLVM 編譯成 WebAssembly 字節碼實現; 上面提到的各種限制都是為了方便把 AST 轉換成 LLVM IR。

asc編譯器具體API如下

 1 SYNTAX
 2   asc [entryFile ...] [options]
 3 
 4 EXAMPLES
 5   asc hello.ts
 6   asc hello.ts -b hello.wasm -t hello.wat
 7   asc hello1.ts hello2.ts -b -O > hello.wasm
 8 
 9 OPTIONS
10   --version, -v         Prints just the compiler's version and exits.
11   --help, -h            Prints this message and exits.
12   --optimize, -O        Optimizes the module. Also has the usual shorthands:
13 
14                          -O     Uses defaults. Equivalent to -O2s
15                          -O0    Equivalent to --optimizeLevel 0
16                          -O1    Equivalent to --optimizeLevel 1
17                          -O2    Equivalent to --optimizeLevel 2
18                          -O3    Equivalent to --optimizeLevel 3
19                          -Oz    Equivalent to -O but with --shrinkLevel 2
20                          -O3s   Equivalent to -O3 with --shrinkLevel 1 etc.
21 
22   --optimizeLevel       How much to focus on optimizing code. [0-3]
23   --shrinkLevel         How much to focus on shrinking code size. [0-2, s=1, z=2]
24   --validate, -c        Validates the module using Binaryen. Exits if invalid.
25   --baseDir             Specifies the base directory of input and output files.
26   --outFile, -o         Specifies the output file. File extension indicates format.
27   --binaryFile, -b      Specifies the binary output file (.wasm).
28   --textFile, -t        Specifies the text output file (.wat).
29   --asmjsFile, -a       Specifies the asm.js output file (.js).
30   --idlFile, -i         Specifies the WebIDL output file (.webidl).
31   --tsdFile, -d         Specifies the TypeScript definition output file (.d.ts).
32   --sourceMap           Enables source map generation. Optionally takes the URL
33                         used to reference the source map from the binary file.
34   --debug               Enables debug information in emitted binaries.
35   --noAssert            Replaces assertions with just their value without trapping.
36   --noEmit              Performs compilation as usual but does not emit code.
37   --importMemory        Imports the memory instance provided by the embedder.
38   --sharedMemory        Declare memory as shared by settings the max shared memory.
39   --memoryBase          Sets the start offset of compiler-generated static memory.
40   --importTable         Imports the function table instance provided by the embedder.
41   --noLib               Does not include the shipped standard library.
42   --lib                 Adds one or multiple paths to custom library components and
43                         uses exports of all top-level files at this path as globals.
44   --use, -u             Aliases a global object under another name, e.g., to switch
45                         the default 'Math' implementation used: --use Math=JSMath
46   --trapMode            Sets the trap mode to use.
47 
48                          allow  Allow trapping operations. This is the default.
49                          clamp  Replace trapping operations with clamping semantics.
50                          js     Replace trapping operations with JS semantics.
51 
52   --runPasses           Specifies additional Binaryen passes to run after other
53                         optimizations, if any. See: Binaryen/src/passes/pass.cpp
54   --enable              Enables additional (experimental) WebAssembly features.
55 
56                          sign-extension  Enables sign-extension operations
57                          mutable-global  Enables mutable global imports and exports
58                          bulk-memory     Enables bulk memory operations
59                          simd            Enables SIMD types and operations.
60                          threads         Enables threading and atomic operations.
61 
62   --transform           Specifies the path to a custom transform to 'require'.
63   --measure             Prints measuring information on I/O and compile times.
64   --noColors            Disables terminal colors.

編譯器API也可以以編程方式使用。 它接受與CLI相同的選項,但也允許您覆蓋stdout和stderr和/或提供回調:

 1 const asc = require("assemblyscript/cli/asc");
 2 asc.main([
 3   "myModule.ts",
 4   "--binaryFile", "myModule.wasm",
 5   "--optimize",
 6   "--sourceMap",
 7   "--measure"
 8 ], {
 9   stdout: process.stdout,
10   stderr: process.stderr
11 }, function(err) {
12   if (err)
13     throw err;
14   ...
15 });

可以通過編程方式獲取可用的命令行選項:

1 const options = require("assemblyscript/cli/asc.json");
2 ...

您也可以直接編譯源字符串,例如在瀏覽器環境中:

1 const { binary, text, stdout, stderr } = asc.compileString(`...`, { optimize: 2 });
2 ... 

4.使用編譯的.wasm模塊

index.js示例代碼如下:

1 const fs = require("fs");
2 const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
3 const imports = {};
4 Object.defineProperty(module, "exports", {
5   get: () => new WebAssembly.Instance(compiled, imports).exports
6 });

使用上面編譯的f.wasm

1 fetch('f.wasm') // 網絡加載 f.wasm 文件
2      .then(res => res.arrayBuffer()) // 轉成 ArrayBuffer
3      .then(WebAssembly.instantiate) // 編譯為當前 CPU 架構的機器碼 + 實例化
4      .then(mod => { // 調用模塊實例上的 f 函數計算
5      console.log(mod.instance.f(50));
6      });

5.參考文獻:

GitHub—AssemblyScript 開源項目

吳浩麟—WebAssembly 現狀與實戰


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM