1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
---
language: WebAssembly
filename: learn-wasm.wast
contributors:
- ["Dean Shaff", "http://dean-shaff.github.io"]
---
```
;; learn-wasm.wast
(module
;; In WebAssembly, everything is included in a module. Moreover, everything
;; can be expressed as an s-expression. Alternatively, there is the
;; "stack machine" syntax, but that is not compatible with Binaryen
;; intermediate representation (IR) syntax.
;; The Binaryen IR format is *mostly* compatible with WebAssembly text format.
;; There are some small differences:
;; local_set -> local.set
;; local_get -> local.get
;; We have to enclose code in functions
;; Data Types
(func $data_types
;; WebAssembly has only four types:
;; i32 - 32 bit integer
;; i64 - 64 bit integer (not supported in JavaScript)
;; f32 - 32 bit floating point
;; f64 - 64 bit floating point
;; We can declare local variables with the "local" keyword
;; We have to declare all variables before we start doing anything
;; inside the function
(local $int_32 i32)
(local $int_64 i64)
(local $float_32 f32)
(local $float_64 f64)
;; These values remain uninitialized.
;; To set them to a value, we can use <type>.const:
(local.set $int_32 (i32.const 16))
(local.set $int_32 (i64.const 128))
(local.set $float_32 (f32.const 3.14))
(local.set $float_64 (f64.const 1.28))
)
;; Basic operations
(func $basic_operations
;; In WebAssembly, everything is an s-expression, including
;; doing math, or getting the value of some variable
(local $add_result i32)
(local $mult_result f64)
(local.set $add_result (i32.add (i32.const 2) (i32.const 4)))
;; the value of add_result is now 6!
;; We have to use the right data type for each operation:
;; (local.set $mult_result (f32.mul (f32.const 2.0) (f32.const 4.0))) ;; WRONG! mult_result is f64!
(local.set $mult_result (f64.mul (f64.const 2.0) (f64.const 4.0))) ;; WRONG! mult_result is f64!
;; WebAssembly has some builtin operations, like basic math and bitshifting.
;; Notably, it does not have built in trigonometric functions.
;; In order to get access to these functions, we have to either
;; - implement them ourselves (not recommended)
;; - import them from elsewhere (later on)
)
;; Functions
;; We specify arguments with the `param` keyword, and specify return values
;; with the `result` keyword
;; The current value on the stack is the return value of a function
;; We can call other functions we've defined with the `call` keyword
(func $get_16 (result i32)
(i32.const 16)
)
(func $add (param $param0 i32) (param $param1 i32) (result i32)
(i32.add
(local.get $param0)
(local.get $param1)
)
)
(func $double_16 (result i32)
(i32.mul
(i32.const 2)
(call $get_16))
)
;; Up until now, we haven't be able to print anything out, nor do we have
;; access to higher level math functions (pow, exp, or trig functions).
;; Moreover, we haven't been able to use any of the WASM functions in Javascript!
;; The way we get those functions into WebAssembly
;; looks different whether we're in a Node.js or browser environment.
;; If we're in Node.js we have to do two steps. First we have to convert the
;; WASM text representation into actual webassembly. If we're using Binyaren,
;; we can do that with a command like the following:
;; wasm-as learn-wasm.wast -o learn-wasm.wasm
;; We can apply Binaryen optimizations to that file with a command like the
;; following:
;; wasm-opt learn-wasm.wasm -o learn-wasm.opt.wasm -O3 --rse
;; With our compiled WebAssembly, we can now load it into Node.js:
;; const fs = require('fs')
;; const instantiate = async function (inFilePath, _importObject) {
;; var importObject = {
;; console: {
;; log: (x) => console.log(x),
;; },
;; math: {
;; cos: (x) => Math.cos(x),
;; }
;; }
;; importObject = Object.assign(importObject, _importObject)
;;
;; var buffer = fs.readFileSync(inFilePath)
;; var module = await WebAssembly.compile(buffer)
;; var instance = await WebAssembly.instantiate(module, importObject)
;; return instance.exports
;; }
;;
;; const main = function () {
;; var wasmExports = await instantiate('learn-wasm.wasm')
;; wasmExports.print_args(1, 0)
;; }
;; The following snippet gets the functions from the importObject we defined
;; in the JavaScript instantiate async function, and then exports a function
;; "print_args" that we can call from Node.js
(import "console" "log" (func $print_i32 (param i32)))
(import "math" "cos" (func $cos (param f64) (result f64)))
(func $print_args (param $arg0 i32) (param $arg1 i32)
(call $print_i32 (local.get $arg0))
(call $print_i32 (local.get $arg1))
)
(export "print_args" (func $print_args))
;; Loading in data from WebAssembly memory.
;; Say that we want to apply the cosine function to a Javascript array.
;; We need to be able to access the allocated array, and iterate through it.
;; This example will modify the input array inplace.
;; f64.load and f64.store expect the location of a number in memory *in bytes*.
;; If we want to access the 3rd element of an array, we have to pass something
;; like (i32.mul (i32.const 8) (i32.const 2)) to the f64.store function.
;; In JavaScript, we would call `apply_cos64` as follows
;; (using the instantiate function from earlier):
;;
;; const main = function () {
;; var wasm = await instantiate('learn-wasm.wasm')
;; var n = 100
;; const memory = new Float64Array(wasm.memory.buffer, 0, n)
;; for (var i=0; i<n; i++) {
;; memory[i] = i;
;; }
;; wasm.apply_cos64(n)
;; }
;;
;; This function will not work if we allocate a Float32Array on the JavaScript
;; side.
(memory (export "memory") 100)
(func $apply_cos64 (param $array_length i32)
;; declare the loop counter
(local $idx i32)
;; declare the counter that will allow us to access memory
(local $idx_bytes i32)
;; constant expressing the number of bytes in a f64 number.
(local $bytes_per_double i32)
;; declare a variable for storing the value loaded from memory
(local $temp_f64 f64)
(local.set $idx (i32.const 0))
(local.set $idx_bytes (i32.const 0)) ;; not entirely necessary
(local.set $bytes_per_double (i32.const 8))
(block
(loop
;; this sets idx_bytes to bytes offset of the value we're interested in.
(local.set $idx_bytes (i32.mul (local.get $idx) (local.get $bytes_per_double)))
;; get the value of the array from memory:
(local.set $temp_f64 (f64.load (local.get $idx_bytes)))
;; now apply the cosine function:
(local.set $temp_64 (call $cos (local.get $temp_64)))
;; now store the result at the same location in memory:
(f64.store
(local.get $idx_bytes)
(local.get $temp_64))
;; do it all in one step instead
(f64.store
(local.get $idx_bytes)
(call $cos
(f64.load
(local.get $idx_bytes))))
;; increment the loop counter
(local.set $idx (i32.add (local.get $idx) (i32.const 1)))
;; stop the loop if the loop counter is equal the array length
(br_if 1 (i32.eq (local.get $idx) (local.get $array_length)))
(br 0)
)
)
)
(export "apply_cos64" (func $apply_cos64))
;; Wasm is a stack-based language, but for returning values more complicated
;; than an int/float, a separate memory stack has to be manually managed. One
;; approach is to use a mutable global to store the stack_ptr. We give
;; ourselves 1MiB of memstack and grow it downwards.
;;
;; Below is a demonstration of how this C code **might** be written by hand
;;
;; typedef struct {
;; int a;
;; int b;
;; } sum_struct_t;
;;
;; sum_struct_t sum_struct_create(int a, int b) {
;; return (sum_struct_t){a, b};
;; }
;;
;; int sum_local() {
;; sum_struct_t s = sum_struct_create(40, 2);
;; return s.a + s.b;
;; }
;; Unlike C, we must manage our own memory stack. We reserve 1MiB
(global $memstack_ptr (mut i32) (i32.const 65536))
;; Structs can only be returned by reference
(func $sum_struct_create
(param $sum_struct_ptr i32)
(param $var$a i32)
(param $var$b i32)
;; c// sum_struct_ptr->a = a;
(i32.store
(get_local $sum_struct_ptr)
(get_local $var$a)
)
;; c// sum_struct_ptr->b = b;
(i32.store offset=4
(get_local $sum_struct_ptr)
(get_local $var$b)
)
)
(func $sum_local (result i32)
(local $var$sum_struct$a i32)
(local $var$sum_struct$b i32)
(local $local_memstack_ptr i32)
;; reserve memstack space
(i32.sub
(get_global $memstack_ptr)
(i32.const 8)
)
tee_local $local_memstack_ptr ;; tee both stores and returns given value
set_global $memstack_ptr
;; call the function, storing the result in the memstack
(call $sum_struct_create
((;$sum_struct_ptr=;) get_local $local_memstack_ptr)
((;$var$a=;) i32.const 40)
((;$var$b=;) i32.const 2)
)
;; retrieve values from struct
(set_local $var$sum_struct$a
(i32.load offset=0 (get_local $local_memstack_ptr))
)
(set_local $var$sum_struct$b
(i32.load offset=4 (get_local $local_memstack_ptr))
)
;; unreserve memstack space
(set_global $memstack_ptr
(i32.add
(get_local $local_memstack_ptr)
(i32.const 8)
)
)
(i32.add
(get_local $var$sum_struct$a)
(get_local $var$sum_struct$b)
)
)
(export "sum_local" (func $sum_local))
)
```
|