Skip to content

Commit

Permalink
[ch03] Function Tables
Browse files Browse the repository at this point in the history
  • Loading branch information
zaki-yama committed Jun 21, 2021
1 parent d00353d commit 6c198dd
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 0 deletions.
19 changes: 19 additions & 0 deletions the-art-of-webassembly/ch03/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,22 @@

- "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."

### 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 func- tion 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 経由より直接呼び出しのほうが速い
70 changes: 70 additions & 0 deletions the-art-of-webassembly/ch03/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const fs = require("fs");
const export_bytes = fs.readFileSync(__dirname + "/table_export.wasm");
const test_bytes = fs.readFileSync(__dirname + "/table_test.wasm");

let i = 0;
let increment = () => {
i++;
return i;
};

let decrement = () => {
i--;
return i;
};

const importObject = {
js: {
tbl: null, // tbl is initially null and is set for the second WASM module
increment, // JavaScript increment function
decrement, // JavaScript decrement function
wasm_increment: null, // Initially null, set to function by second module
wasm_decrement: null, // Initially null, set to function by second module
},
};

(async () => {
// instantiate the module that uses a function table
let table_exp_obj = await WebAssembly.instantiate(
new Uint8Array(export_bytes),
importObject
);

// set the tbl variable to the exported table
importObject.js.tbl = table_exp_obj.instance.exports.tbl;

importObject.js.wasm_increment = table_exp_obj.instance.exports.increment;
importObject.js.wasm_decrement = table_exp_obj.instance.exports.decrement;
let obj = await WebAssembly.instantiate(
new Uint8Array(test_bytes),
importObject
);

// use destructuring syntax to create JS functions from exports
({ js_table_test, js_import_test, wasm_table_test, wasm_import_test } =
obj.instance.exports);

i = 0;
let start = Date.now();
js_table_test();
let time = Date.now() - start;
console.log("js_table_test time=" + time);

i = 0;
start = Date.now();
js_import_test();
time = Date.now() - start;
console.log("js_import_test time=" + time);

i = 0;
start = Date.now();
wasm_table_test();
time = Date.now() - start;
console.log("wasm_table_test time=" + time);

i = 0;
start = Date.now();
wasm_import_test();
time = Date.now() - start;
console.log("wasm_import_test time=" + time);
})();
Binary file added the-art-of-webassembly/ch03/table_export.wasm
Binary file not shown.
25 changes: 25 additions & 0 deletions the-art-of-webassembly/ch03/table_export.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
(module
;; javascript increment function
(import "js" "increment" (func $js_increment (result i32)))
;; javascript decrement function
(import "js" "decrement" (func $js_decrement (result i32)))

(table $tbl (export "tbl") 4 anyfunc) ;; exported table with 4 functions

(global $i (mut i32) (i32.const 0))

(func $increment (export "increment") (result i32)
(global.set $i (i32.add (global.get $i) (i32.const 1))) ;; $i++
global.get $i
)

(func $decrement (export "decrement") (result i32)
(global.set $i (i32.sub (global.get $i) (i32.const 1))) ;; $i--
global.get $i
)

;; populate the table
;; the first parameter is the starting index we want to update.
(elem (i32.const 0) $js_increment $js_decrement $increment $decrement)
)

Binary file added the-art-of-webassembly/ch03/table_test.wasm
Binary file not shown.
93 changes: 93 additions & 0 deletions the-art-of-webassembly/ch03/table_test.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
(module
(import "js" "tbl" (table $tbl 4 anyfunc))
;; import increment function
(import "js" "increment" (func $increment (result i32)))
;; import decrement function
(import "js" "decrement" (func $decrement (result i32)))

;; import wasm_increment function
(import "js" "wasm_increment" (func $wasm_increment (result i32)))
;; import wasm_decrement function
(import "js" "wasm_decrement" (func $wasm_decrement (result i32)))

;; table function type definitions all i32 and take no parameters
(type $returns_i32 (func (result i32)))

(global $inc_ptr i32 (i32.const 0)) ;; JS increment function table index
(global $dec_ptr i32 (i32.const 1)) ;; JS decrement function table index

(global $wasm_inc_ptr i32 (i32.const 2)) ;; WASM increment function index
(global $wasm_dec_ptr i32 (i32.const 3)) ;; WASM decrement function index

;; Test performance of an indirect table call of JavaScript functions
(func (export "js_table_test")
(loop $inc_cycle
;; indirect call to JavaScript increment function
(call_indirect (type $returns_i32) (global.get $inc_ptr))
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $inc_ptr <= 4,000,000?
br_if $inc_cycle ;; if so, loop
)

(loop $dec_cycle
;; indirect call to JavaScript decrement function
(call_indirect (type $returns_i32) (global.get $dec_ptr))
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $dec_ptr <= 4,000,000?
br_if $dec_cycle ;; if so, loop
)
)

;; Test performance of direct call to JavaScript functions
(func (export "js_import_test")
(loop $inc_cycle
call $increment ;; direct call to JavaScript increment function
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $increment <= 4,000,000?
br_if $inc_cycle ;; if so, prime_test_loop
)

(loop $dec_cycle
call $decrement ;; direct call to JavaScript decrement function
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $decrement <= 4,000,000?
br_if $dec_cycle ;; if so, loop
)
)

;; Test performance of an indirect table call to WASM functions
(func (export "wasm_table_test")
(loop $inc_cycle
;; indirect call to WASM increment function
(call_indirect (type $returns_i32) (global.get $wasm_inc_ptr))
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $inc_ptr <= 4,000,000?
br_if $inc_cycle ;; if so, loop
)

(loop $dec_cycle
;; indirect call to WASM decrement function
(call_indirect (type $returns_i32) (global.get $wasm_dec_ptr))
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $dec_ptr <= 4,000,000?
br_if $dec_cycle ;; if so, loop
)
)

;; Test performance of direct call to WASM functions
(func (export "wasm_import_test")
(loop $inc_cycle
call $wasm_increment ;; direct call to WASM increment function
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $wasm_increment <= 4,000,000?
br_if $inc_cycle ;; if so, prime_test_loop
)

(loop $dec_cycle
call $wasm_decrement ;; direct call to WASM decrement function
i32.const 4_000_000
i32.le_u ;; is the value returned by call to $wasm_decrement <= 4,000,000?
br_if $dec_cycle ;; if so, loop
)
)
)

0 comments on commit 6c198dd

Please sign in to comment.