This repository has been archived by the owner on Nov 29, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtinygo-wasmfx.patch
358 lines (350 loc) · 11.7 KB
/
tinygo-wasmfx.patch
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
diff --git a/builder/build.go b/builder/build.go
index 2008c897..7a5bf9b1 100644
--- a/builder/build.go
+++ b/builder/build.go
@@ -824,7 +824,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
default:
return fmt.Errorf("unknown opt level: %q", config.Options.Opt)
}
- cmd := exec.Command(goenv.Get("WASMOPT"), "--asyncify", "-g",
+ cmd := exec.Command(goenv.Get("WASMOPT"), "-g",
"--optimize-level", strconv.Itoa(optLevel),
"--shrink-level", strconv.Itoa(shrinkLevel),
executable, "--output", executable)
diff --git a/compiler/compiler.go b/compiler/compiler.go
index bbaf6432..129805d9 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1059,7 +1059,7 @@ func (b *builder) createFunctionStart(intrinsic bool) {
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
- if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
+ if b.info.exported {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
functionAttr := b.ctx.CreateStringAttribute("wasm-export-name", b.info.linkName)
diff --git a/e2e.sh b/e2e.sh
new file mode 100755
index 00000000..49d6870d
--- /dev/null
+++ b/e2e.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+./tinygo build -target wasi -o /tmp/tinygo.wasm "$1"
+wasm2wat /tmp/tinygo.wasm > /tmp/preprocess.wat
+raku effects.pl </tmp/preprocess.wat
diff --git a/effects.pl b/effects.pl
new file mode 100644
index 00000000..b0676448
--- /dev/null
+++ b/effects.pl
@@ -0,0 +1,213 @@
+use v6;
+
+my $file = slurp;
+
+# Gather some types that we use
+$file ~~ rx:sigspace/\(type \(\;(\d+)\;\) \(func\)\)/;
+my $unit_unit = $0;
+$file ~~ rx:sigspace/\(type \(\;(\d+)\;\) \(func \(param i32\)\)\)/;
+my $i32_unit = $0;
+$file ~~ rx:sigspace/\(type \(\;(\d+)\;\) \(func \(param i32 i32\)\)\)/;
+my $i32_i32_unit = $0;
+
+# Your program must use a dummy function to print:
+#
+# ```go
+# //go:noinline
+# func wrappedPrint(thething int32) {
+# println(thething)
+# }
+# ```
+
+# It has to actually print to prevent optimization. In the generated wat replace
+# $main.wrappedPrint
+
+# $file ~~ s:sigspace/
+# \(func \$main.wrappedPrint .*unreachable\)/
+# (func \$main.wrappedPrint (param i32)
+# (call \$spectest_print (local.get 0)))/;
+
+# # The following is useful only if avoiding wasi
+# # Remove the fd_write import and add
+#
+# $file ~~ s:sigspace/
+# \(import \"wasi_snapshot_preview1\" \"fd_write\" \(func \$runtime.fd_write.*?\)\)\)/
+# (import "spectest" "print_i32" (func \$spectest_print (param i32)))/;
+#
+# # And replace the fd_write call (in panic stuff) with unreachable
+# $file ~~ s:sigspace/
+# call .runtime.fd_write/
+# unreachable/;
+
+# Remove the asyncify imports that we're obseleting
+$file ~~ s:sigspace/\n
+ \(import \"asyncify\" \"stop_rewind\" .* \"start_rewind\" \(func \$start_rewind .*?\)\)\)//;
+
+# Add our types and tag
+$file ~~ s:sigspace/\n
+ \(func/
+ (type \$asyncify_resume_cont (cont REPLACE_EMPTY))
+ (type \$asyncify_call_indirect_cont (cont REPLACE_I32_I32))
+ (tag \$asyncify_effect)
+ (func/;
+
+# Replace `REPLACE_EMPTY` with the index for (func) and `REPLACE_I32_I32` with
+# the index for (i32, i32) -> ().
+$file ~~ s/REPLACE_EMPTY/$unit_unit/;
+$file ~~ s/REPLACE_I32_I32/$i32_i32_unit/;
+
+# for some reason putting this as a variable makes it work, the literal doesn't
+# To accomodate a later new table entry, expand the table
+# size, (table ...) just above it, in this case from 6 to 7 (adjust both numbers).
+my $replacement = Q[
+ ;; Table as simple queue (keeping it simple, no ring buffer)
+ (table $queue 0 (ref null $asyncify_resume_cont))
+ (global $qdelta i32 (i32.const 10))
+ (global $qback (mut i32) (i32.const 0))
+ (global $qfront (mut i32) (i32.const 0))
+
+ (func $queue-empty (result i32)
+ (i32.eq (global.get $qfront) (global.get $qback))
+ )
+
+ (func $dequeue (result (ref null $asyncify_resume_cont))
+ (local $i i32)
+ (if (call $queue-empty)
+ (then (return (ref.null $asyncify_resume_cont)))
+ )
+ (local.set $i (global.get $qfront))
+ (global.set $qfront (i32.add (local.get $i) (i32.const 1)))
+ (table.get $queue (local.get $i))
+ )
+
+ (func $enqueue (param $k (ref $asyncify_resume_cont))
+ ;; Check if queue is full
+ (if (i32.eq (global.get $qback) (table.size $queue))
+ (then
+ ;; Check if there is enough space in the front to compact
+ (if (i32.lt_u (global.get $qfront) (global.get $qdelta))
+ (then
+ ;; Space is below threshold, grow table instead
+ (drop (table.grow $queue (ref.null $asyncify_resume_cont) (global.get $qdelta)))
+ )
+ (else
+ ;; Enough space, move entries up to head of table
+ (global.set $qback (i32.sub (global.get $qback) (global.get $qfront)))
+ (table.copy $queue $queue
+ (i32.const 0) ;; dest = new front = 0
+ (global.get $qfront) ;; src = old front
+ (global.get $qback) ;; len = new back = old back - old front
+ )
+ (table.fill $queue ;; null out old entries to avoid leaks
+ (global.get $qback) ;; start = new back
+ (ref.null $asyncify_resume_cont) ;; init value
+ (global.get $qfront) ;; len = old front = old front - new front
+ )
+ (global.set $qfront (i32.const 0))
+ )
+ )
+ )
+ )
+ (table.set $queue (global.get $qback) (local.get $k))
+ (global.set $qback (i32.add (global.get $qback) (i32.const 1)))
+ )];
+
+$file ~~ s:sigspace/
+ \(table .*? (\d+) \d+ funcref\)/(table {$0+1} {$0+1} funcref)\n$replacement/;
+
+$replacement = Q[
+ (func $runtime.scheduler
+ (local i32)
+ block ;; label = @1
+ loop ;; label = @2
+ i32.const 0
+ i32.load8_u offset=65973
+ ;; check schedulerDone (which gets set at end of main)
+ br_if 1 (;@1;)
+ ;; pop the queue and resume the continuation
+ call $dequeue
+ br_on_null 1
+ (block $coroutine_suspend (param (ref $asyncify_resume_cont)) (result (ref $asyncify_resume_cont))
+ (resume $asyncify_resume_cont
+ (tag $asyncify_effect $coroutine_suspend))
+ ;; it suspended, so continue without re-enqueuing
+ br 1)
+ ;; re-enqeue the coroutine. tinygo makes this the coroutine's job, but
+ ;; we make it the scheduler's job
+ call $enqueue
+ br 0 (;@2;)
+ end
+ unreachable
+ end)];
+
+$file ~~ s:sigspace/
+ \(func \$runtime.scheduler.*?
+ (end|unreachable)\)/$replacement/;
+
+$replacement = Q[
+ (func $runtime.minSched
+ call $runtime.scheduler)];
+
+$file ~~ s:sigspace/
+ \(func \$runtime.minSched.*?
+ end\)/$replacement/;
+
+# Replace the existing queue.
+# Delete `$_*internal/task.Task_.Resume`.
+
+$replacement = Q[
+ (func $_*internal/task.Queue_.Pop (result i32) unreachable)
+ (func $_*internal/task.Queue_.Push (param i32))];
+
+$file ~~ s:sigspace/
+ \(func \$_\*internal\/task.Queue_.Pop.*
+ \(func \$_\*internal\/task.Task_.Resume.*?
+ unreachable\)/$replacement/;
+
+
+# Delete `tinygo_unwind`, `tinygo_launch`, `tinygo_rewind` (all next to each
+# other).
+
+$file ~~ s:sigspace/
+ \(func \$tinygo_unwind.*
+ \(func \$tinygo_rewind.*?
+ return\)//;
+
+# Replace Pause with a simple suspend:
+
+$replacement = Q[
+ (func $internal/task.Pause
+ suspend $asyncify_effect)];
+
+$file ~~ s:sigspace/
+ \(func \$internal\/task.Pause.*?
+ i32.store\)/$replacement/;
+
+# Find $internal/task.start. Starting with call `$enqueue_fn_args`, we *replace*
+# the call and the rest of the function with this code:
+# This depends on a function that reifies call_indirect.
+
+$replacement = Q[
+ (cont.new $asyncify_call_indirect_cont (ref.func $asyncify_call_indirect))
+ (cont.bind $asyncify_call_indirect_cont $asyncify_resume_cont)
+ call $enqueue)
+ (func $asyncify_call_indirect (param i32) (param i32)
+ (call_indirect (type REPLACE_TYPE) (local.get 1) (local.get 0)))];
+
+$file ~~ s:sigspace/(
+ \(func .internal.task.start.*?)
+ call .enqueue_fn_args.*?
+ global.set .__stack_pointer\)/$0$replacement/;
+
+# Above we reference (type REPLACE_TYPE). Replace with the index of the (i32) ->
+# () from the top of the file.
+$file ~~ s/REPLACE_TYPE/$i32_unit/;
+
+# Now you need to add the new `$asyncify_call_indirect` function to the elements
+# (elem ...) at the very bottom.
+
+$file ~~ s:sigspace/
+ (\(elem (\(\;0\;\))? \(i32.const 1\) .*?)\)/
+ $0 \$asyncify_call_indirect\)/;
+
+say $file;
diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go
index 939008bc..2099c1b4 100644
--- a/src/internal/task/task_asyncify.go
+++ b/src/internal/task/task_asyncify.go
@@ -45,12 +45,17 @@ type stackState struct {
// start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is immediately started.
+//go:noinline
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
- t := &Task{}
- t.state.initialize(fn, args, stackSize)
- runqueuePushBack(t)
+ enqueue_fn_args(fn, args)
+ t := &Task{}
+ t.state.initialize(fn, args, stackSize)
+ runqueuePushBack(t)
}
+//export enqueue_fn_args
+func enqueue_fn_args(fn uintptr, args unsafe.Pointer)
+
//export tinygo_launch
func (*state) launch()
diff --git a/src/internal/task/task_asyncify_wasm.S b/src/internal/task/task_asyncify_wasm.S
index 9aafc110..a9eb82f6 100644
--- a/src/internal/task/task_asyncify_wasm.S
+++ b/src/internal/task/task_asyncify_wasm.S
@@ -64,6 +64,14 @@ tinygo_launch: // func (state *state) launch()
return
end_function
+.global enqueue_fn_args
+.hidden enqueue_fn_args
+.type enqueue_fn_args,@function
+enqueue_fn_args:
+ .functype enqueue_fn_args (i32, i32) -> ()
+ return
+ end_function
+
.global tinygo_rewind
.hidden tinygo_rewind
.type tinygo_rewind,@function
diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go
index ab80cf1d..a2062aae 100644
--- a/src/runtime/runtime_wasm_js_scheduler.go
+++ b/src/runtime/runtime_wasm_js_scheduler.go
@@ -1,8 +1,15 @@
-//go:build wasm && !wasi && !scheduler.none
-// +build wasm,!wasi,!scheduler.none
+//go:build wasi && !scheduler.none
+// +build wasi,!scheduler.none
package runtime
+var handleEvent func()
+
+//go:linkname setEventHandler syscall/js.setEventHandler
+func setEventHandler(fn func()) {
+ handleEvent = fn
+}
+
//export resume
func resume() {
go func() {
diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go
index 52fd79d6..9ff7044c 100644
--- a/src/runtime/runtime_wasm_wasi.go
+++ b/src/runtime/runtime_wasm_wasi.go
@@ -14,12 +14,19 @@ type timeUnit int64
//export __wasm_call_ctors
func __wasm_call_ctors()
+// wasmNested is used to detect scheduler nesting (WASM calls into JS calls back into WASM).
+// When this happens, we need to use a reduced version of the scheduler.
+var wasmNested bool
+
//export _start
func _start() {
// These need to be initialized early so that the heap can be initialized.
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
+
+ wasmNested = true // ??? from js
run()
+ wasmNested = false // ??? from js
}
// Read the command line arguments from WASI.
diff --git a/targets/wasi.json b/targets/wasi.json
index e710b4bb..8f2a0ce2 100644
--- a/targets/wasi.json
+++ b/targets/wasi.json
@@ -2,7 +2,7 @@
"llvm-target": "wasm32-unknown-wasi",
"cpu": "generic",
"features": "+bulk-memory,+nontrapping-fptoint,+sign-ext",
- "build-tags": ["tinygo.wasm", "wasi", "runtime_memhash_leveldb"],
+ "build-tags": ["tinygo.wasm", "wasi"],
"goos": "linux",
"goarch": "arm",
"linker": "wasm-ld",