|
1 | 1 | package prelude
|
2 | 2 |
|
3 |
| -const goroutines = ` |
4 |
| -var $stackDepthOffset = 0; |
5 |
| -var $getStackDepth = function() { |
6 |
| - var err = new Error(); |
7 |
| - if (err.stack === undefined) { |
8 |
| - return undefined; |
9 |
| - } |
10 |
| - return $stackDepthOffset + err.stack.split("\n").length; |
11 |
| -}; |
| 3 | +import ( |
| 4 | + _ "embed" |
| 5 | +) |
12 | 6 |
|
13 |
| -var $panicStackDepth = null, $panicValue; |
14 |
| -var $callDeferred = function(deferred, jsErr, fromPanic) { |
15 |
| - if (!fromPanic && deferred !== null && $curGoroutine.deferStack.indexOf(deferred) == -1) { |
16 |
| - throw jsErr; |
17 |
| - } |
18 |
| - if (jsErr !== null) { |
19 |
| - var newErr = null; |
20 |
| - try { |
21 |
| - $panic(new $jsErrorPtr(jsErr)); |
22 |
| - } catch (err) { |
23 |
| - newErr = err; |
24 |
| - } |
25 |
| - $callDeferred(deferred, newErr); |
26 |
| - return; |
27 |
| - } |
28 |
| - if ($curGoroutine.asleep) { |
29 |
| - return; |
30 |
| - } |
31 |
| -
|
32 |
| - $stackDepthOffset--; |
33 |
| - var outerPanicStackDepth = $panicStackDepth; |
34 |
| - var outerPanicValue = $panicValue; |
35 |
| -
|
36 |
| - var localPanicValue = $curGoroutine.panicStack.pop(); |
37 |
| - if (localPanicValue !== undefined) { |
38 |
| - $panicStackDepth = $getStackDepth(); |
39 |
| - $panicValue = localPanicValue; |
40 |
| - } |
41 |
| -
|
42 |
| - try { |
43 |
| - while (true) { |
44 |
| - if (deferred === null) { |
45 |
| - deferred = $curGoroutine.deferStack[$curGoroutine.deferStack.length - 1]; |
46 |
| - if (deferred === undefined) { |
47 |
| - /* The panic reached the top of the stack. Clear it and throw it as a JavaScript error. */ |
48 |
| - $panicStackDepth = null; |
49 |
| - if (localPanicValue.Object instanceof Error) { |
50 |
| - throw localPanicValue.Object; |
51 |
| - } |
52 |
| - var msg; |
53 |
| - if (localPanicValue.constructor === $String) { |
54 |
| - msg = localPanicValue.$val; |
55 |
| - } else if (localPanicValue.Error !== undefined) { |
56 |
| - msg = localPanicValue.Error(); |
57 |
| - } else if (localPanicValue.String !== undefined) { |
58 |
| - msg = localPanicValue.String(); |
59 |
| - } else { |
60 |
| - msg = localPanicValue; |
61 |
| - } |
62 |
| - throw new Error(msg); |
63 |
| - } |
64 |
| - } |
65 |
| - var call = deferred.pop(); |
66 |
| - if (call === undefined) { |
67 |
| - $curGoroutine.deferStack.pop(); |
68 |
| - if (localPanicValue !== undefined) { |
69 |
| - deferred = null; |
70 |
| - continue; |
71 |
| - } |
72 |
| - return; |
73 |
| - } |
74 |
| - var r = call[0].apply(call[2], call[1]); |
75 |
| - if (r && r.$blk !== undefined) { |
76 |
| - deferred.push([r.$blk, [], r]); |
77 |
| - if (fromPanic) { |
78 |
| - throw null; |
79 |
| - } |
80 |
| - return; |
81 |
| - } |
82 |
| -
|
83 |
| - if (localPanicValue !== undefined && $panicStackDepth === null) { |
84 |
| - /* error was recovered */ |
85 |
| - if (fromPanic) { |
86 |
| - throw null; |
87 |
| - } |
88 |
| - return; |
89 |
| - } |
90 |
| - } |
91 |
| - } catch(e) { |
92 |
| - // Deferred function threw a JavaScript exception or tries to unwind stack |
93 |
| - // to the point where a panic was handled. |
94 |
| - if (fromPanic) { |
95 |
| - // Re-throw the exception to reach deferral execution call at the end |
96 |
| - // of the function. |
97 |
| - throw e; |
98 |
| - } |
99 |
| - // We are at the end of the function, handle the error or re-throw to |
100 |
| - // continue unwinding if necessary, or simply stop unwinding if we got far |
101 |
| - // enough. |
102 |
| - $callDeferred(deferred, e, fromPanic); |
103 |
| - } finally { |
104 |
| - if (localPanicValue !== undefined) { |
105 |
| - if ($panicStackDepth !== null) { |
106 |
| - $curGoroutine.panicStack.push(localPanicValue); |
107 |
| - } |
108 |
| - $panicStackDepth = outerPanicStackDepth; |
109 |
| - $panicValue = outerPanicValue; |
110 |
| - } |
111 |
| - $stackDepthOffset++; |
112 |
| - } |
113 |
| -}; |
114 |
| -
|
115 |
| -var $panic = function(value) { |
116 |
| - $curGoroutine.panicStack.push(value); |
117 |
| - $callDeferred(null, null, true); |
118 |
| -}; |
119 |
| -var $recover = function() { |
120 |
| - if ($panicStackDepth === null || ($panicStackDepth !== undefined && $panicStackDepth !== $getStackDepth() - 2)) { |
121 |
| - return $ifaceNil; |
122 |
| - } |
123 |
| - $panicStackDepth = null; |
124 |
| - return $panicValue; |
125 |
| -}; |
126 |
| -var $throw = function(err) { throw err; }; |
127 |
| -
|
128 |
| -var $noGoroutine = { asleep: false, exit: false, deferStack: [], panicStack: [] }; |
129 |
| -var $curGoroutine = $noGoroutine, $totalGoroutines = 0, $awakeGoroutines = 0, $checkForDeadlock = true, $exportedFunctions = 0; |
130 |
| -var $mainFinished = false; |
131 |
| -var $go = function(fun, args) { |
132 |
| - $totalGoroutines++; |
133 |
| - $awakeGoroutines++; |
134 |
| - var $goroutine = function() { |
135 |
| - try { |
136 |
| - $curGoroutine = $goroutine; |
137 |
| - var r = fun.apply(undefined, args); |
138 |
| - if (r && r.$blk !== undefined) { |
139 |
| - fun = function() { return r.$blk(); }; |
140 |
| - args = []; |
141 |
| - return; |
142 |
| - } |
143 |
| - $goroutine.exit = true; |
144 |
| - } catch (err) { |
145 |
| - if (!$goroutine.exit) { |
146 |
| - throw err; |
147 |
| - } |
148 |
| - } finally { |
149 |
| - $curGoroutine = $noGoroutine; |
150 |
| - if ($goroutine.exit) { /* also set by runtime.Goexit() */ |
151 |
| - $totalGoroutines--; |
152 |
| - $goroutine.asleep = true; |
153 |
| - } |
154 |
| - if ($goroutine.asleep) { |
155 |
| - $awakeGoroutines--; |
156 |
| - if (!$mainFinished && $awakeGoroutines === 0 && $checkForDeadlock && $exportedFunctions === 0) { |
157 |
| - console.error("fatal error: all goroutines are asleep - deadlock!"); |
158 |
| - if ($global.process !== undefined) { |
159 |
| - $global.process.exit(2); |
160 |
| - } |
161 |
| - } |
162 |
| - } |
163 |
| - } |
164 |
| - }; |
165 |
| - $goroutine.asleep = false; |
166 |
| - $goroutine.exit = false; |
167 |
| - $goroutine.deferStack = []; |
168 |
| - $goroutine.panicStack = []; |
169 |
| - $schedule($goroutine); |
170 |
| -}; |
171 |
| -
|
172 |
| -var $scheduled = []; |
173 |
| -var $runScheduled = function() { |
174 |
| - // For nested setTimeout calls browsers enforce 4ms minimum delay. We minimize |
175 |
| - // the effect of this penalty by queueing the timer preemptively before we run |
176 |
| - // the goroutines, and later cancelling it if it turns out unneeded. See: |
177 |
| - // https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#nested_timeouts |
178 |
| - var nextRun = setTimeout($runScheduled); |
179 |
| - try { |
180 |
| - var start = Date.now(); |
181 |
| - var r; |
182 |
| - while ((r = $scheduled.shift()) !== undefined) { |
183 |
| - r(); |
184 |
| - // We need to interrupt this loop in order to allow the event loop to |
185 |
| - // process timers, IO, etc. However, invoking scheduling through |
186 |
| - // setTimeout is ~1000 times more expensive, so we amortize this cost by |
187 |
| - // looping until the 4ms minimal delay has elapsed (assuming there are |
188 |
| - // scheduled goroutines to run), and then yield to the event loop. |
189 |
| - var elapsed = Date.now() - start; |
190 |
| - if (elapsed > 4 || elapsed < 0) { break; } |
191 |
| - } |
192 |
| - } finally { |
193 |
| - if ($scheduled.length == 0) { |
194 |
| - // Cancel scheduling pass if there's nothing to run. |
195 |
| - clearTimeout(nextRun); |
196 |
| - } |
197 |
| - } |
198 |
| -}; |
199 |
| -
|
200 |
| -var $schedule = function(goroutine) { |
201 |
| - if (goroutine.asleep) { |
202 |
| - goroutine.asleep = false; |
203 |
| - $awakeGoroutines++; |
204 |
| - } |
205 |
| - $scheduled.push(goroutine); |
206 |
| - if ($curGoroutine === $noGoroutine) { |
207 |
| - $runScheduled(); |
208 |
| - } |
209 |
| -}; |
210 |
| -
|
211 |
| -var $setTimeout = function(f, t) { |
212 |
| - $awakeGoroutines++; |
213 |
| - return setTimeout(function() { |
214 |
| - $awakeGoroutines--; |
215 |
| - f(); |
216 |
| - }, t); |
217 |
| -}; |
218 |
| -
|
219 |
| -var $block = function() { |
220 |
| - if ($curGoroutine === $noGoroutine) { |
221 |
| - $throwRuntimeError("cannot block in JavaScript callback, fix by wrapping code in goroutine"); |
222 |
| - } |
223 |
| - $curGoroutine.asleep = true; |
224 |
| -}; |
225 |
| -
|
226 |
| -var $restore = function(context, params) { |
227 |
| - if (context !== undefined && context.$blk !== undefined) { |
228 |
| - return context; |
229 |
| - } |
230 |
| - return params; |
231 |
| -} |
232 |
| -
|
233 |
| -var $send = function(chan, value) { |
234 |
| - if (chan.$closed) { |
235 |
| - $throwRuntimeError("send on closed channel"); |
236 |
| - } |
237 |
| - var queuedRecv = chan.$recvQueue.shift(); |
238 |
| - if (queuedRecv !== undefined) { |
239 |
| - queuedRecv([value, true]); |
240 |
| - return; |
241 |
| - } |
242 |
| - if (chan.$buffer.length < chan.$capacity) { |
243 |
| - chan.$buffer.push(value); |
244 |
| - return; |
245 |
| - } |
246 |
| -
|
247 |
| - var thisGoroutine = $curGoroutine; |
248 |
| - var closedDuringSend; |
249 |
| - chan.$sendQueue.push(function(closed) { |
250 |
| - closedDuringSend = closed; |
251 |
| - $schedule(thisGoroutine); |
252 |
| - return value; |
253 |
| - }); |
254 |
| - $block(); |
255 |
| - return { |
256 |
| - $blk: function() { |
257 |
| - if (closedDuringSend) { |
258 |
| - $throwRuntimeError("send on closed channel"); |
259 |
| - } |
260 |
| - } |
261 |
| - }; |
262 |
| -}; |
263 |
| -var $recv = function(chan) { |
264 |
| - var queuedSend = chan.$sendQueue.shift(); |
265 |
| - if (queuedSend !== undefined) { |
266 |
| - chan.$buffer.push(queuedSend(false)); |
267 |
| - } |
268 |
| - var bufferedValue = chan.$buffer.shift(); |
269 |
| - if (bufferedValue !== undefined) { |
270 |
| - return [bufferedValue, true]; |
271 |
| - } |
272 |
| - if (chan.$closed) { |
273 |
| - return [chan.$elem.zero(), false]; |
274 |
| - } |
275 |
| -
|
276 |
| - var thisGoroutine = $curGoroutine; |
277 |
| - var f = { $blk: function() { return this.value; } }; |
278 |
| - var queueEntry = function(v) { |
279 |
| - f.value = v; |
280 |
| - $schedule(thisGoroutine); |
281 |
| - }; |
282 |
| - chan.$recvQueue.push(queueEntry); |
283 |
| - $block(); |
284 |
| - return f; |
285 |
| -}; |
286 |
| -var $close = function(chan) { |
287 |
| - if (chan.$closed) { |
288 |
| - $throwRuntimeError("close of closed channel"); |
289 |
| - } |
290 |
| - chan.$closed = true; |
291 |
| - while (true) { |
292 |
| - var queuedSend = chan.$sendQueue.shift(); |
293 |
| - if (queuedSend === undefined) { |
294 |
| - break; |
295 |
| - } |
296 |
| - queuedSend(true); /* will panic */ |
297 |
| - } |
298 |
| - while (true) { |
299 |
| - var queuedRecv = chan.$recvQueue.shift(); |
300 |
| - if (queuedRecv === undefined) { |
301 |
| - break; |
302 |
| - } |
303 |
| - queuedRecv([chan.$elem.zero(), false]); |
304 |
| - } |
305 |
| -}; |
306 |
| -var $select = function(comms) { |
307 |
| - var ready = []; |
308 |
| - var selection = -1; |
309 |
| - for (var i = 0; i < comms.length; i++) { |
310 |
| - var comm = comms[i]; |
311 |
| - var chan = comm[0]; |
312 |
| - switch (comm.length) { |
313 |
| - case 0: /* default */ |
314 |
| - selection = i; |
315 |
| - break; |
316 |
| - case 1: /* recv */ |
317 |
| - if (chan.$sendQueue.length !== 0 || chan.$buffer.length !== 0 || chan.$closed) { |
318 |
| - ready.push(i); |
319 |
| - } |
320 |
| - break; |
321 |
| - case 2: /* send */ |
322 |
| - if (chan.$closed) { |
323 |
| - $throwRuntimeError("send on closed channel"); |
324 |
| - } |
325 |
| - if (chan.$recvQueue.length !== 0 || chan.$buffer.length < chan.$capacity) { |
326 |
| - ready.push(i); |
327 |
| - } |
328 |
| - break; |
329 |
| - } |
330 |
| - } |
331 |
| -
|
332 |
| - if (ready.length !== 0) { |
333 |
| - selection = ready[Math.floor(Math.random() * ready.length)]; |
334 |
| - } |
335 |
| - if (selection !== -1) { |
336 |
| - var comm = comms[selection]; |
337 |
| - switch (comm.length) { |
338 |
| - case 0: /* default */ |
339 |
| - return [selection]; |
340 |
| - case 1: /* recv */ |
341 |
| - return [selection, $recv(comm[0])]; |
342 |
| - case 2: /* send */ |
343 |
| - $send(comm[0], comm[1]); |
344 |
| - return [selection]; |
345 |
| - } |
346 |
| - } |
347 |
| -
|
348 |
| - var entries = []; |
349 |
| - var thisGoroutine = $curGoroutine; |
350 |
| - var f = { $blk: function() { return this.selection; } }; |
351 |
| - var removeFromQueues = function() { |
352 |
| - for (var i = 0; i < entries.length; i++) { |
353 |
| - var entry = entries[i]; |
354 |
| - var queue = entry[0]; |
355 |
| - var index = queue.indexOf(entry[1]); |
356 |
| - if (index !== -1) { |
357 |
| - queue.splice(index, 1); |
358 |
| - } |
359 |
| - } |
360 |
| - }; |
361 |
| - for (var i = 0; i < comms.length; i++) { |
362 |
| - (function(i) { |
363 |
| - var comm = comms[i]; |
364 |
| - switch (comm.length) { |
365 |
| - case 1: /* recv */ |
366 |
| - var queueEntry = function(value) { |
367 |
| - f.selection = [i, value]; |
368 |
| - removeFromQueues(); |
369 |
| - $schedule(thisGoroutine); |
370 |
| - }; |
371 |
| - entries.push([comm[0].$recvQueue, queueEntry]); |
372 |
| - comm[0].$recvQueue.push(queueEntry); |
373 |
| - break; |
374 |
| - case 2: /* send */ |
375 |
| - var queueEntry = function() { |
376 |
| - if (comm[0].$closed) { |
377 |
| - $throwRuntimeError("send on closed channel"); |
378 |
| - } |
379 |
| - f.selection = [i]; |
380 |
| - removeFromQueues(); |
381 |
| - $schedule(thisGoroutine); |
382 |
| - return comm[1]; |
383 |
| - }; |
384 |
| - entries.push([comm[0].$sendQueue, queueEntry]); |
385 |
| - comm[0].$sendQueue.push(queueEntry); |
386 |
| - break; |
387 |
| - } |
388 |
| - })(i); |
389 |
| - } |
390 |
| - $block(); |
391 |
| - return f; |
392 |
| -}; |
393 |
| -` |
| 7 | +//go:embed goroutines.js |
| 8 | +var goroutines string |
0 commit comments