Skip to content

Commit

Permalink
The Art of WebAssembly (#33)
Browse files Browse the repository at this point in the history
* Chapter 1

* [Ch02] Hello World in WebAssembly

* [ch02] WAT Variables > Global Variables and Type Conversion

* [ch02] WAT Variables > Local Variables

* [ch02] WAT Variables > Unpacking S-Expressions

* [ch03] Writing an is_prime Function

* [ch03] ACCESSING THE STACK FROM A FUNCTION

* [ch03] Performance Implications of External Function Calls

* [ch03] Function Tables

* [ch05] Passing the String Length to JavaScript

* [ch05] Null-Terminated Strings

* [ch05] Length-Prefixed Strings

* [ch05] Copying Strings > Byte-by-Byte Copy

* [ch05] Copying Strings > 64-Bit Copy

* [ch05] Copying Strings > Combination Copy Function

* [ch05] Creating Number Strings [skip]

* [ch06] Linear Memory in WebAssembly

* [ch06] JavaScript Memory Object > Creating the WebAssembly Memory Object

* [ch06] JavaScript Memory Object > Logging to the Console with Colors

* [ch06] JavaScript Memory Object > Creating the JavaScript in store_data.js

* [ch06] JavaScript Memory Object (memo)

* [ch06] Collision Detection

* [ch07] Setting Up a Simple Node Server

* [ch07] Our First WebAssembly Web Application

* [ch07] Hex and Binary Strings

* [ch07] memo

* [ch08] Rendering to the Canvas

* [ch08] The WAT Module > Imported Values

* [ch08] The WAT Module > Clearing the Canvas

* [ch08] The WAT Module > Absolute Value Function

* [ch08] The WAT Module > Setting a Pixel Color

* [ch08] The WAT Module > Drawing the Object

* [ch08] The WAT Module > Setting and Getting Object Attributes

* [ch08] The WAT Module > The $main function

* [ch08] Summary

* [ch09] Using a Profiler > Chrome Profiler

* [ch09] wasm-opt

* [ch09] Strategies for Improving Performance

* [ch09] Comparing the Collision Detection App with JavaScript

* [ch09] Hand Optimizing WAT

* [ch09] Logging Performance

* [ch09] More Sophisticated Testing with benchmark.js

* [ch09] Comparing WebAssembly and JavaScript with `--print-bytecode`

* [ch10] Debugging from the Console

* [ch10] Stack Trace

* [ch11] AssemblyScript CLI

* [ch11] Hello World AssemblyScript

* [ch11] Object Oriented Programming in AssemblyScript

* [ch03] memo

* memo
  • Loading branch information
zaki-yama authored Aug 1, 2021
1 parent 34f8b6d commit fcd085e
Show file tree
Hide file tree
Showing 126 changed files with 8,851 additions and 0 deletions.
1 change: 1 addition & 0 deletions the-art-of-webassembly/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.vscode
20 changes: 20 additions & 0 deletions the-art-of-webassembly/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 疑問

- stack と local variable や global variable が格納される領域って違うの?
- ch06
- `(global $obj_base_addr (import "env" "obj_base_addr") i32)`
- これで import した値がセットされるんだっけ? global 変数の基本的な構文の話
- ch07
- `instantiate``instantiateStreaming` の違い (Node.js は前者しか使えない)
- ch08
- `i32.load``i32.store` ってどういう動きになるんだっけ?

## 学び

### Chapter 2 WebAssembly Text Basics

- importObject の第一階層、`env` である必要ないんだ
- Rust で書く場合はどうかな
- WAT は S 式 と linear instruction set の 2 つの書き方がある。混在もできる

### Chapter 3 Functions And Tables
11 changes: 11 additions & 0 deletions the-art-of-webassembly/ch01/AddInt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const fs = require("fs");

const bytes = fs.readFileSync(__dirname + "/AddInt.wasm");
const value_1 = parseInt(process.argv[2]);
const value_2 = parseInt(process.argv[3]);

(async () => {
const obj = await WebAssembly.instantiate(new Uint8Array(bytes));
let add_value = obj.instance.exports.AddInt(value_1, value_2);
console.log(`${value_1} + ${value_2} = ${add_value}`);
})();
Binary file added the-art-of-webassembly/ch01/AddInt.wasm
Binary file not shown.
9 changes: 9 additions & 0 deletions the-art-of-webassembly/ch01/AddInt.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(module
(func (export "AddInt")
(param $value_1 i32) (param $value_2 i32)
(result i32)
local.get $value_1
local.get $value_2
i32.add
)
)
Binary file added the-art-of-webassembly/ch01/file.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions the-art-of-webassembly/ch01/file.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(module)
100 changes: 100 additions & 0 deletions the-art-of-webassembly/ch02/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Chapter 2 WebAssembly Text Basics

この Chapter で紹介すること

- 2 comment styles in WebAssembly
- 伝統的な hello world アプリケーション
- 文字列扱うのが難しいので、いきなり hello world はやらない
- import object を使って JS から Wasm にデータをインポートする方法
- named and unnamed global and local variables
- S 式と、wat2wasm コンパイラーが S 式をどうアンパックするか
- if/else , branch tables, loops, blocks

## Writing the Simplest Module

すべての WAT アプリケーションはモジュールでなければならない。よって最初は module シンタックスから始まる

```wat
(module
;; This is where the module code goes.
)
```

- 複数行コメントは `(; ... ;)`

## Hello World in WebAssembly

### Creating Our Wat Module

- linear memory

```wat
(module
(import "env" "print_string" (func $print_string( param i32 )))
(import "env" "buffer" (memory 1))
)
```

- `(memory 1)` は buffer が linear memory 1 ページになることを示している
- page とは
- linear memory に一度に割り当てることのできる最小のメモリの塊
- Wasm では 1 ページ 64KB

以下はスキップ

- if/else Conditional Logic
- Loops and Blocks
- The loop Expression

## WAT Variables

### Unpacking S-Expressions

```wat
(i32.mul
(i32.add
(i32.const 3)
(i32.const 2)
)
(i32.sub
(i32.const 9)
(i32.const 7)
)
)
```


```wat
i32.const 3
i32.const 2
i32.add
i32.const 9
i32.const 7
i32.sub
i32.mul
```

と等価

## Loops and Blocks

### The `block` Statement

```wat
;; This code is for demonstration and not part of a larger app
(block $jump_to_end
br $jump_to_end
;; code below the branch does not execute. br jumps to the end of the block
nop
)
;; This is where the br statement jumps to
nop
```

- `br``block` を抜ける (the code can only jump to the end of a `block` if it’s inside that `block`)

## The loop Expression
10 changes: 10 additions & 0 deletions the-art-of-webassembly/ch02/SumSquared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const fs = require("fs");
const bytes = fs.readFileSync(__dirname + "/SumSquared.wasm");
const val1 = parseInt(process.argv[2]);
const val2 = parseInt(process.argv[3]);

(async () => {
const obj = await WebAssembly.instantiate(new Uint8Array(bytes));
let sum_sq = obj.instance.exports.SumSquared(val1, val2);
console.log(`(${val1} + ${val2}) * (${val1} + ${val2}) = ${sum_sq}`);
})();
Binary file added the-art-of-webassembly/ch02/SumSquared.wasm
Binary file not shown.
12 changes: 12 additions & 0 deletions the-art-of-webassembly/ch02/SumSquared.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(module
(func (export "SumSquared")
(param $value_1 i32) (param $value_2 i32)
(result i32)
(local $sum i32)

(i32.add (local.get $value_1) (local.get $value_2))
local.set $sum

(i32.mul (local.get $sum) (local.get $sum))
)
)
30 changes: 30 additions & 0 deletions the-art-of-webassembly/ch02/Unpacking-S-Expressions.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(i32.mul ;; executes 7th (last)
(i32.add ;; executes 3rd
(i32.const 3) ;; executes 1st
(i32.const 2) ;; executes 2nd
)
(i32.sub ;; executes 6th
(i32.const 9) ;; executes 4th
(i32.const 7) ;; executes 5th
)
)

(;
(i32.add ;; executes 3rd
(i32.const 3) ;; executes 1st
(i32.const 2) ;; executes 2nd
)
i32.const 3
i32.const 2
i32.add
と等価
;)
i32.const 3 ;; Stack = [3]
i32.const 2 ;; Stack = [2, 3]
i32.add ;; 2 & 3 popped from stack, added sum of 5 pushed onto stack [5]

i32.const 9 ;; Stack = [9,5]
i32.const 7 ;; Stack = [7,9,5]
i32.sub ;; 7 & 9 popped off stack . 9-7=2 pushed on stack [2,5]
i32.mul ;; 2,5 popped off stack, 2x5=10 is pushed on the stack [10]
28 changes: 28 additions & 0 deletions the-art-of-webassembly/ch02/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const fs = require("fs");
const bytes = fs.readFileSync(__dirname + "/globals.wasm");
let global_test = null;

let importObject = {
js: {
log_i32: (value) => {
console.log("i32: ", value);
},
log_f32: (value) => {
console.log("f32: ", value);
},
log_f64: (value) => {
console.log("f64: ", value);
},
},
env: {
import_i32: 5_000_000_000,
import_f32: 123.0123456789,
import_f64: 123.0123456789,
},
};

(async () => {
let obj = await WebAssembly.instantiate(new Uint8Array(bytes), importObject);
({ globaltest: global_test } = obj.instance.exports);
global_test();
})();
Binary file added the-art-of-webassembly/ch02/globals.wasm
Binary file not shown.
15 changes: 15 additions & 0 deletions the-art-of-webassembly/ch02/globals.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(module
(global $import_integer_32 (import "env" "import_i32") i32)
(global $import_float_32 (import "env" "import_f32") f32)
(global $import_float_64 (import "env" "import_f64") f64)

(import "js" "log_i32" (func $log_i32 (param i32)))
(import "js" "log_f32" (func $log_f32 (param f32)))
(import "js" "log_f64" (func $log_f64 (param f64)))

(func (export "globaltest")
(call $log_i32 (global.get $import_integer_32))
(call $log_f32 (global.get $import_float_32))
(call $log_f64 (global.get $import_float_64))
)
)
24 changes: 24 additions & 0 deletions the-art-of-webassembly/ch02/helloworld.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const fs = require("fs");
const bytes = fs.readFileSync(__dirname + "/helloworld.wasm");

let hello_world = null; // function will be set later
let start_string_index = 100; // linear memory location of string
let memory = new WebAssembly.Memory({ initial: 1 }); // linear memory

let importObject = {
env: {
buffer: memory,
start_string: start_string_index,
print_string: function (str_len) {
const bytes = new Uint8Array(memory.buffer, start_string_index, str_len);
const log_string = new TextDecoder("utf8").decode(bytes);
console.log(log_string);
},
},
};

(async () => {
let obj = await WebAssembly.instantiate(new Uint8Array(bytes), importObject);
({ helloworld: hello_world } = obj.instance.exports);
hello_world();
})();
Binary file added the-art-of-webassembly/ch02/helloworld.wasm
Binary file not shown.
12 changes: 12 additions & 0 deletions the-art-of-webassembly/ch02/helloworld.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(module
;; param i32: length of string
(import "env" "print_string" (func $print_string(param i32) ))
;; memory 1: allocate 1 page(smallest chunk of memory) of linear memory
(import "env" "buffer" (memory 1))
(global $start_string (import "env" "start_string") i32)
(global $string_len i32 (i32.const 12))
(data (global.get $start_string) "hello world!")
(func (export "helloworld")
(call $print_string (global.get $string_len))
)
)
63 changes: 63 additions & 0 deletions the-art-of-webassembly/ch03/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Chapter 3 Functions and Tables

この Chapter で学ぶこと

- WebAssembly の関数について学ぶ
- いつ、どのように JS や他の WebAssembly モジュールから関数を import するのか
- どのように WebAssembly の関数を export するのか
- table について

<!-- TOC -->

- [When to Call Functions from WAT](#when-to-call-functions-from-wat)
- [Writing an `is_prime` Function](#writing-an-is_prime-function)
- [Passing Parameters](#passing-parameters)
- [Declaring an Imported Function](#declaring-an-imported-function)
- [Performance Implications of External Function Calls](#performance-implications-of-external-function-calls)
- [Function Tables](#function-tables)

<!-- /TOC -->

## When to Call Functions from WAT

- `(export)`
- JS から Wasm の関数の呼び出しはオーバーヘッドがあるので、小さなタスクを実行するだけの関数であれば export すべきでないよ。JS でやった方がいい
- Wasm の関数は大量のデータを何回もループで処理するようなものに適している
- デバッグについては Chapter 10
- パフォーマンスチューニングの過程で、元々関数として切り出してたものをインライン化することもあるかもしれない
- パフォーマンス・チューニングについて詳しくは Chapter 9 で

## Writing an `is_prime` Function

### Passing Parameters

- `local.tee`: The local.tee command is like the local.set command in that it sets the value of the variable you pass to it to the value on top of the stack

## Declaring an Imported Function

### Performance Implications of External Function Calls

- "When you call a JavaScript function
in WAT, you lose some cycles to overhead. This number isn’t extremely large, but if you execute an external JavaScript function in a loop that iterates 4,000,000 times, it can add up."
- 2 つのベンチマーク比較
1. 内部で Wasm の関数を 4_000_000 回呼び出し
2. 内部で JS から import した関数を 4_000_000 回呼び出し

### Function Tables

- "Currently, tables only support the anyfunc type (anyfunc is a generic WebAssembly function type), but in the future they might support JavaScript objects and DOM elements as well."
- "Unlike import objects, JavaScript and WebAssembly can dynamically change tables at runtime."
- function table 経由での関数呼び出しは indirect なコールになるためパフォーマンスコストがある
- "However, you cannot add a JavaScript function to a function table from within JavaScript. There is a WebAssembly.Table function set that allows you to set functions in a table, only with a function defined in a WebAssembly module. We can work around this restriction by importing the JavaScript function into a WebAssembly module and adding it to the table there."
- table_export.wat で一度 import して table にセットしてるのはこれが理由

```
js_table_test time=67
js_import_test time=52
wasm_table_test time=26
wasm_import_test time=19
```

- オチとしては
- JS より Wasm のほうが速い
- table 経由より直接呼び出しのほうが速い
17 changes: 17 additions & 0 deletions the-art-of-webassembly/ch03/fail.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
;; wat2wasm will fail
(module
(func $inner
(result i32)
(local $l i32)
;; 99 is on the stack in the calling function
local.set $l

i32.const 2
)
(func (export "main")
(result i32)

i32.const 99 ;; push 99 onto stack - [99]
call $inner ;; 99 is on the stack here
)
)
Loading

0 comments on commit fcd085e

Please sign in to comment.