about summary refs log tree commit diff stats
path: root/apps/mu.subx
blob: 241163fd761918ff473a3dfe6d5edc1333a4371e (plain) (blame)
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
6
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - 074wait.cc</title>
<meta name="Generator" content="Vim/7.4">
<meta name="plugin-version" content="vim7.4_v2">
<meta name="syntax" content="cpp">
<meta name="settings" content="number_lines,use_css,pre_wrap,no_foldcolumn,expand_tabs,line_ids,prevent_copy=">
<meta name="colorscheme" content="minimal">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #aaaaaa; background-color: #080808; }
body { font-size: 12pt; font-family: monospace; color: #aaaaaa; background-color: #080808; }
a { color:#eeeeee; text-decoration: none; }
a:hover { text-decoration: underline; }
* { font-size: 12pt; font-size: 1em; }
.SalientComment { color: #00ffff; }
.muRecipe { color: #ff8700; }
.muData { color: #ffff00; }
.LineNr { color: #444444; }
.traceContains { color: #008000; }
.Delimiter { color: #800080; }
.Normal { color: #aaaaaa; background-color: #080808; padding-bottom: 1px; }
.cSpecial { color: #008000; }
.Conceal { color: #4e4e4e; }
.Comment { color: #9090ff; }
.Comment a { color:#0000ee; text-decoration:underline; }
.Constant { color: #00a0a0; }
.Special { color: #c00000; }
.Identifier { color: #c0a020; }
-->
</style>

<script type='text/javascript'>
<!--

/* function to open any folds containing a jumped-to line before jumping to it */
function JumpToLine()
{
  var lineNum;
  lineNum = window.location.hash;
  lineNum = lineNum.substr(1); /* strip off '#' */

  if (lineNum.indexOf('L') == -1) {
    lineNum = 'L'+lineNum;
  }
  lineElem = document.getElementById(lineNum);
  /* Always jump to new location even if the line was hidden inside a fold, or
   * we corrected the raw number to a line ID.
   */
  if (lineElem) {
    lineElem.scrollIntoView(true);
  }
  return true;
}
if ('onhashchange' in window) {
  window.onhashchange = JumpToLine;
}

-->
</script>
</head>
<body onload='JumpToLine();'>
<pre id='vimCodeElement'>
<span id="L1" class="LineNr">  1 </span><span class="Comment">//: Routines can be put in a 'waiting' state, from which it will be ready to</span>
<span id="L2" class="LineNr">  2 </span><span class="Comment">//: run again when a specific memory location changes its value. This is Mu's</span>
<span id="L3" class="LineNr">  3 </span><span class="Comment">//: basic technique for orchestrating the order in which different routines</span>
<span id="L4" class="LineNr">  4 </span><span class="Comment">//: operate.</span>
<span id="L5" class="LineNr">  5 </span>
<span id="L6" class="LineNr">  6 </span><span class="Delimiter">:(scenario wait_for_location)</span>
<span id="L7" class="LineNr">  7 </span><span class="muRecipe">def</span> f1 [
<span id="L8" class="LineNr">  8 </span>  <span class="Constant">10</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">34</span>
<span id="L9" class="LineNr">  9 </span>  start-running f2
<span id="L10" class="LineNr"> 10 </span>  <span class="Constant">20</span>:location<span class="Special"> &lt;- </span>copy <span class="Constant">10</span>/unsafe
<span id="L11" class="LineNr"> 11 </span>  wait-<span class="Normal">for</span>-reset-then-set <span class="Constant">20</span>:location
<span id="L12" class="LineNr"> 12 </span>  <span class="Comment"># wait for f2 to run and reset location 1</span>
<span id="L13" class="LineNr"> 13 </span>  <span class="Constant">30</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">10</span>:num
<span id="L14" class="LineNr"> 14 </span>]
<span id="L15" class="LineNr"> 15 </span><span class="muRecipe">def</span> f2 [
<span id="L16" class="LineNr"> 16 </span>  <span class="Constant">10</span>:location<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>/unsafe
<span id="L17" class="LineNr"> 17 </span>]
<span id="L18" class="LineNr"> 18 </span><span class="traceContains">+schedule: f1</span>
<span id="L19" class="LineNr"> 19 </span><span class="traceContains">+run: waiting for location 10 to <a href='000organization.cc.html#L134'>reset</a></span>
<span id="L20" class="LineNr"> 20 </span><span class="traceContains">+schedule: f2</span>
<span id="L21" class="LineNr"> 21 </span><span class="traceContains">+schedule: waking up routine 1</span>
<span id="L22" class="LineNr"> 22 </span><span class="traceContains">+schedule: f1</span>
<span id="L23" class="LineNr"> 23 </span><span class="traceContains">+mem: storing 1 in location 30</span>
<span id="L24" class="LineNr"> 24 </span>
<span id="L25" class="LineNr"> 25 </span><span class="Comment">//: define the new state that all routines can be in</span>
<span id="L26" class="LineNr"> 26 </span>
<span id="L27" class="LineNr"> 27 </span><span class="Delimiter">:(before &quot;End routine States&quot;)</span>
<span id="L28" class="LineNr"> 28 </span>WAITING<span class="Delimiter">,</span>
<span id="L29" class="LineNr"> 29 </span><span class="Delimiter">:(before &quot;End routine Fields&quot;)</span>
<span id="L30" class="LineNr"> 30 </span><span class="Comment">// only if state == WAITING</span>
<span id="L31" class="LineNr"> 31 </span><span class="Normal">int</span> waiting_on_location<span class="Delimiter">;</span>
<span id="L32" class="LineNr"> 32 </span><span class="Delimiter">:(before &quot;End routine Constructor&quot;)</span>
<span id="L33" class="LineNr"> 33 </span>waiting_on_location = <span class="Constant">0</span><span class="Delimiter">;</span>
<span id="L34" class="LineNr"> 34 </span>
<span id="L35" class="LineNr"> 35 </span><span class="Delimiter">:(before &quot;End Mu Test Teardown&quot;)</span>
<span id="L36" class="LineNr"> 36 </span><span class="Normal">if</span> <span class="Delimiter">(</span>Passed &amp;&amp; <a href='074wait.cc.html#L47'>any_routines_waiting</a><span class="Delimiter">())</span>
<span id="L37" class="LineNr"> 37 </span>  <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; Current_scenario<span class="Delimiter">-&gt;</span>name &lt;&lt; <span class="Constant">&quot;: deadlock!</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L38" class="LineNr"> 38 </span><span class="Delimiter">:(before &quot;End Run Routine&quot;)</span>
<span id="L39" class="LineNr"> 39 </span><span class="Normal">if</span> <span class="Delimiter">(</span><a href='074wait.cc.html#L47'>any_routines_waiting</a><span class="Delimiter">())</span> <span class="Delimiter">{</span>
<span id="L40" class="LineNr"> 40 </span>  <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <span class="Constant">&quot;deadlock!</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L41" class="LineNr"> 41 </span>  <a href='074wait.cc.html#L54'>dump_waiting_routines</a><span class="Delimiter">();</span>
<span id="L42" class="LineNr"> 42 </span><span class="Delimiter">}</span>
<span id="L43" class="LineNr"> 43 </span><span class="Delimiter">:(before &quot;End Test Teardown&quot;)</span>
<span id="L44" class="LineNr"> 44 </span><span class="Normal">if</span> <span class="Delimiter">(</span>Passed &amp;&amp; any_routines_with_error<span class="Delimiter">())</span>
<span id="L45" class="LineNr"> 45 </span>  <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <span class="Constant">&quot;some routines died with errors</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L46" class="LineNr"> 46 </span><span class="Delimiter">:(code)</span>
<span id="L47" class="LineNr"> 47 </span><span class="Normal">bool</span> <a href='074wait.cc.html#L47'>any_routines_waiting</a><span class="Delimiter">()</span> <span class="Delimiter">{</span>
<span id="L48" class="LineNr"> 48 </span>  <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span>  i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>Routines<span class="Delimiter">);</span>  ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L49" class="LineNr"> 49 </span>  <span class="Conceal">¦</span> <span class="Normal">if</span> <span class="Delimiter">(</span>Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>state == WAITING<span class="Delimiter">)</span>
<span id="L50" class="LineNr"> 50 </span>  <span class="Conceal">¦</span> <span class="Conceal">¦</span> <span class="Identifier">return</span> <span class="Constant">true</span><span class="Delimiter">;</span>
<span id="L51" class="LineNr"> 51 </span>  <span class="Delimiter">}</span>
<span id="L52" class="LineNr"> 52 </span>  <span class="Identifier">return</span> <span class="Constant">false</span><span class="Delimiter">;</span>
<span id="L53" class="LineNr"> 53 </span><span class="Delimiter">}</span>
<span id="L54" class="LineNr"> 54 </span><span class="Normal">void</span> <a href='074wait.cc.html#L54'>dump_waiting_routines</a><span class="Delimiter">()</span> <span class="Delimiter">{</span>
<span id="L55" class="LineNr"> 55 </span>  <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span>  i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>Routines<span class="Delimiter">);</span>  ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L56" class="LineNr"> 56 </span>  <span class="Conceal">¦</span> <span class="Normal">if</span> <span class="Delimiter">(</span>Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>state == WAITING<span class="Delimiter">)</span>
<span id="L57" class="LineNr"> 57 </span>  <span class="Conceal">¦</span> <span class="Conceal">¦</span> cerr &lt;&lt; i &lt;&lt; <span class="Constant">&quot;: &quot;</span> &lt;&lt; <a href='073scheduler.cc.html#L110'>routine_label</a><span class="Delimiter">(</span>Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">))</span> &lt;&lt; <span class="cSpecial">'\n'</span><span class="Delimiter">;</span>
<span id="L58" class="LineNr"> 58 </span>  <span class="Delimiter">}</span>
<span id="L59" class="LineNr"> 59 </span><span class="Delimiter">}</span>
<span id="L60" class="LineNr"> 60 </span>
<span id="L61" class="LineNr"> 61 </span><span class="Delimiter">:(scenario wait_for_location_can_deadlock)</span>
<span id="L62" class="LineNr"> 62 </span><span class="Special">% Hide_errors = true;</span>
<span id="L63" class="LineNr"> 63 </span><span class="muRecipe">def</span> <a href='000organization.cc.html#L113'>main</a> [
<span id="L64" class="LineNr"> 64 </span>  <span class="Constant">10</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">1</span>
<span id="L65" class="LineNr"> 65 </span>  <span class="Constant">20</span>:location<span class="Special"> &lt;- </span>copy <span class="Constant">10</span>/unsafe
<span id="L66" class="LineNr"> 66 </span>  wait-<span class="Normal">for</span>-reset-then-set <span class="Constant">20</span>:location
<span id="L67" class="LineNr"> 67 </span>]
<span id="L68" class="LineNr"> 68 </span><span class="traceContains">+error: deadlock!</span>
<span id="L69" class="LineNr"> 69 </span>
<span id="L70" class="LineNr"> 70 </span><span class="Comment">//: Primitive recipe to put routines in that state.</span>
<span id="L71" class="LineNr"> 71 </span><span class="Comment">//: This primitive is also known elsewhere as compare-and-set (CAS). Used to</span>
<span id="L72" class="LineNr"> 72 </span><span class="Comment">//: build locks.</span>
<span id="L73" class="LineNr"> 73 </span>
<span id="L74" class="LineNr"> 74 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Declarations&quot;)</span>
<span id="L75" class="LineNr"> 75 </span>WAIT_FOR_RESET_THEN_SET<span class="Delimiter">,</span>
<span id="L76" class="LineNr"> 76 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Numbers&quot;)</span>
<span id="L77" class="LineNr"> 77 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span> <span class="Constant">&quot;wait-for-reset-then-set&quot;</span><span class="Delimiter">,</span> WAIT_FOR_RESET_THEN_SET<span class="Delimiter">);</span>
<span id="L78" class="LineNr"> 78 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Checks&quot;)</span>
<span id="L79" class="LineNr"> 79 </span><span class="Normal">case</span> WAIT_FOR_RESET_THEN_SET: <span class="Delimiter">{</span>
<span id="L80" class="LineNr"> 80 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span><a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">)</span> != <span class="Constant">1</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L81" class="LineNr"> 81 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'wait-for-reset-then-set' requires exactly one ingredient, but got '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>inst<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L82" class="LineNr"> 82 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L83" class="LineNr"> 83 </span>  <span class="Delimiter">}</span>
<span id="L84" class="LineNr"> 84 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_mu_location<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
<span id="L85" class="LineNr"> 85 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'wait-for-reset-then-set' requires a location ingredient, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L86" class="LineNr"> 86 </span>  <span class="Delimiter">}</span>
<span id="L87" class="LineNr"> 87 </span>  <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L88" class="LineNr"> 88 </span><span class="Delimiter">}</span>
<span id="L89" class="LineNr"> 89 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Implementations&quot;)</span>
<span id="L90" class="LineNr"> 90 </span><span class="Normal">case</span> WAIT_FOR_RESET_THEN_SET: <span class="Delimiter">{</span>
<span id="L91" class="LineNr"> 91 </span>  <span class="Normal">int</span> loc = <span class="Normal">static_cast</span>&lt;<span class="Normal">int</span>&gt;<span class="Delimiter">(</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">));</span>
<span id="L92" class="LineNr"> 92 </span>  <a href='003trace.cc.html#L161'>trace</a><span class="Delimiter">(</span><span class="Constant">9998</span><span class="Delimiter">,</span> <span class="Constant">&quot;run&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;wait: *&quot;</span> &lt;&lt; loc &lt;&lt; <span class="Constant">&quot; = &quot;</span> &lt;&lt; <a href='001help.cc.html#L228'>get_or_insert</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">)</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L93" class="LineNr"> 93 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span><a href='001help.cc.html#L228'>get_or_insert</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">)</span> == <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L94" class="LineNr"> 94 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L161'>trace</a><span class="Delimiter">(</span><span class="Constant">9998</span><span class="Delimiter">,</span> <span class="Constant">&quot;run&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;location &quot;</span> &lt;&lt; loc &lt;&lt; <span class="Constant">&quot; is already 0; setting&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L95" class="LineNr"> 95 </span>  <span class="Conceal">¦</span> <a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">,</span> <span class="Constant">1</span><span class="Delimiter">);</span>
<span id="L96" class="LineNr"> 96 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L97" class="LineNr"> 97 </span>  <span class="Delimiter">}</span>
<span id="L98" class="LineNr"> 98 </span>  <a href='003trace.cc.html#L161'>trace</a><span class="Delimiter">(</span><span class="Constant">9998</span><span class="Delimiter">,</span> <span class="Constant">&quot;run&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;waiting for location &quot;</span> &lt;&lt; loc &lt;&lt; <span class="Constant">&quot; to reset&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L99" class="LineNr"> 99 </span>  Current_routine<span class="Delimiter">-&gt;</span>state = WAITING<span class="Delimiter">;</span>
<span id="L100" class="LineNr">100 </span>  Current_routine<span class="Delimiter">-&gt;</span>waiting_on_location = loc<span class="Delimiter">;</span>
<span id="L101" class="LineNr">101 </span>  <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L102" class="LineNr">102 </span><span class="Delimiter">}</span>
<span id="L103" class="LineNr">103 </span>
<span id="L104" class="LineNr">104 </span><span class="Comment">//: Counterpart to unlock a lock.</span>
<span id="L105" class="LineNr">105 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Declarations&quot;)</span>
<span id="L106" class="LineNr">106 </span>RESET<span class="Delimiter">,</span>
<span id="L107" class="LineNr">107 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Numbers&quot;)</span>
<span id="L108" class="LineNr">108 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span> <span class="Constant">&quot;reset&quot;</span><span class="Delimiter">,</span> RESET<span class="Delimiter">);</span>
<span id="L109" class="LineNr">109 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Checks&quot;)</span>
<span id="L110" class="LineNr">110 </span><span class="Normal">case</span> RESET: <span class="Delimiter">{</span>
<span id="L111" class="LineNr">111 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span><a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">)</span> != <span class="Constant">1</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L112" class="LineNr">112 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'reset' requires exactly one ingredient, but got '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>inst<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L113" class="LineNr">113 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L114" class="LineNr">114 </span>  <span class="Delimiter">}</span>
<span id="L115" class="LineNr">115 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_mu_location<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
<span id="L116" class="LineNr">116 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'reset' requires a location ingredient, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L117" class="LineNr">117 </span>  <span class="Delimiter">}</span>
<span id="L118" class="LineNr">118 </span>  <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L119" class="LineNr">119 </span><span class="Delimiter">}</span>
<span id="L120" class="LineNr">120 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Implementations&quot;)</span>
<span id="L121" class="LineNr">121 </span><span class="Normal">case</span> RESET: <span class="Delimiter">{</span>
<span id="L122" class="LineNr">122 </span>  <span class="Normal">int</span> loc = <span class="Normal">static_cast</span>&lt;<span class="Normal">int</span>&gt;<span class="Delimiter">(</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">));</span>
<span id="L123" class="LineNr">123 </span>  <a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">,</span> <span class="Constant">0</span><span class="Delimiter">);</span>
<span id="L124" class="LineNr">124 </span>  <a href='003trace.cc.html#L161'>trace</a><span class="Delimiter">(</span><span class="Constant">9998</span><span class="Delimiter">,</span> <span class="Constant">&quot;run&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;reset: *&quot;</span> &lt;&lt; loc &lt;&lt; <span class="Constant">&quot; = &quot;</span> &lt;&lt; <a href='001help.cc.html#L228'>get_or_insert</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">)</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L125" class="LineNr">125 </span>  <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L126" class="LineNr">126 </span><span class="Delimiter">}</span>
<span id="L127" class="LineNr">127 </span>
<span id="L128" class="LineNr">128 </span><span class="Comment">//: scheduler tweak to get routines out of that state</span>
<span id="L129" class="LineNr">129 </span>
<span id="L130" class="LineNr">130 </span><span class="Delimiter">:(before &quot;End Scheduler State Transitions&quot;)</span>
<span id="L131" class="LineNr">131 </span><span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span>  i &lt; <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>Routines<span class="Delimiter">);</span>  ++i<span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L132" class="LineNr">132 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>state != WAITING<span class="Delimiter">)</span> <span class="Identifier">continue</span><span class="Delimiter">;</span>
<span id="L133" class="LineNr">133 </span>  <span class="Normal">int</span> loc = Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>waiting_on_location<span class="Delimiter">;</span>
<span id="L134" class="LineNr">134 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>loc &amp;&amp; <a href='001help.cc.html#L228'>get_or_insert</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">)</span> == <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L135" class="LineNr">135 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L161'>trace</a><span class="Delimiter">(</span><span class="Constant">9999</span><span class="Delimiter">,</span> <span class="Constant">&quot;schedule&quot;</span><span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;waking up routine &quot;</span> &lt;&lt; Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>id &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L136" class="LineNr">136 </span>  <span class="Conceal">¦</span> <a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Memory<span class="Delimiter">,</span> loc<span class="Delimiter">,</span> <span class="Constant">1</span><span class="Delimiter">);</span>
<span id="L137" class="LineNr">137 </span>  <span class="Conceal">¦</span> Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>state = <a href='073scheduler.cc.html#L41'>RUNNING</a><span class="Delimiter">;</span>
<span id="L138" class="LineNr">138 </span>  <span class="Conceal">¦</span> Routines<span class="Delimiter">.</span>at<span class="Delimiter">(</span>i<span class="Delimiter">)-&gt;</span>waiting_on_location = <span class="Constant">0</span><span class="Delimiter">;</span>
<span id="L139" class="LineNr">139 </span>  <span class="Delimiter">}</span>
<span id="L140" class="LineNr">140 </span><span class="Delimiter">}</span>
<span id="L141" class="LineNr">141 </span>
<span id="L142" class="LineNr">142 </span><span class="Comment">//: Primitive to help compute locations to wait on.</span>
<span id="L143" class="LineNr">143 </span><span class="Comment">//: Only supports elements immediately inside containers; no arrays or</span>
<span id="L144" class="LineNr">144 </span><span class="Comment">//: containers within containers yet.</span>
<span id="L145" class="LineNr">145 </span>
<span id="L146" class="LineNr">146 </span><span class="Delimiter">:(scenario get_location)</span>
<span id="L147" class="LineNr">147 </span><span class="muRecipe">def</span> <a href='000organization.cc.html#L113'>main</a> [
<span id="L148" class="LineNr">148 </span>  <span class="Constant">12</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">34</span>
<span id="L149" class="LineNr">149 </span>  <span class="Constant">13</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">35</span>
<span id="L150" class="LineNr">150 </span>  <span class="Constant">15</span>:location<span class="Special"> &lt;- </span>get-location <span class="Constant">12</span>:point<span class="Delimiter">,</span> <span class="Constant">1:offset</span>
<span id="L151" class="LineNr">151 </span>]
<span id="L152" class="LineNr">152 </span><span class="traceContains">+mem: storing 13 in location 15</span>
<span id="L153" class="LineNr">153 </span>
<span id="L154" class="LineNr">154 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Declarations&quot;)</span>
<span id="L155" class="LineNr">155 </span>GET_LOCATION<span class="Delimiter">,</span>
<span id="L156" class="LineNr">156 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Numbers&quot;)</span>
<span id="L157" class="LineNr">157 </span><a href='001help.cc.html#L221'>put</a><span class="Delimiter">(</span>Recipe_ordinal<span class="Delimiter">,</span> <span class="Constant">&quot;get-location&quot;</span><span class="Delimiter">,</span> GET_LOCATION<span class="Delimiter">);</span>
<span id="L158" class="LineNr">158 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Checks&quot;)</span>
<span id="L159" class="LineNr">159 </span><span class="Normal">case</span> GET_LOCATION: <span class="Delimiter">{</span>
<span id="L160" class="LineNr">160 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span><a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">)</span> != <span class="Constant">2</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L161" class="LineNr">161 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'get-location' expects exactly 2 ingredients in '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>inst<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L162" class="LineNr">162 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L163" class="LineNr">163 </span>  <span class="Delimiter">}</span>
<span id="L164" class="LineNr">164 </span>  reagent<span class="Comment">/*</span><span class="Comment">copy</span><span class="Comment">*/</span> base = inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">);</span>
<span id="L165" class="LineNr">165 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!canonize_type<span class="Delimiter">(</span>base<span class="Delimiter">))</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L166" class="LineNr">166 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!base<span class="Delimiter">.</span>type<span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L167" class="LineNr">167 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;first ingredient of 'get-location' should be a container, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L168" class="LineNr">168 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L169" class="LineNr">169 </span>  <span class="Delimiter">}</span>
<span id="L170" class="LineNr">170 </span>  <span class="Normal">const</span> type_tree* base_root_type = base<span class="Delimiter">.</span>type<span class="Delimiter">-&gt;</span>atom ? base<span class="Delimiter">.</span>type : base<span class="Delimiter">.</span>type<span class="Delimiter">-&gt;</span>left<span class="Delimiter">;</span>
<span id="L171" class="LineNr">171 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!base_root_type<span class="Delimiter">-&gt;</span>atom || base_root_type<span class="Delimiter">-&gt;</span>value == <span class="Constant">0</span> || !contains_key<span class="Delimiter">(</span>Type<span class="Delimiter">,</span> base_root_type<span class="Delimiter">-&gt;</span>value<span class="Delimiter">)</span> || get<span class="Delimiter">(</span>Type<span class="Delimiter">,</span> base_root_type<span class="Delimiter">-&gt;</span>value<span class="Delimiter">).</span>kind != <a href='010vm.cc.html#L173'>CONTAINER</a><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L172" class="LineNr">172 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;first ingredient of 'get-location' should be a container, but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L173" class="LineNr">173 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L174" class="LineNr">174 </span>  <span class="Delimiter">}</span>
<span id="L175" class="LineNr">175 </span>  <a href='010vm.cc.html#L123'>type_ordinal</a> base_type = base<span class="Delimiter">.</span>type<span class="Delimiter">-&gt;</span>value<span class="Delimiter">;</span>
<span id="L176" class="LineNr">176 </span>  <span class="Normal">const</span> reagent&amp; offset = inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">);</span>
<span id="L177" class="LineNr">177 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_literal<span class="Delimiter">(</span>offset<span class="Delimiter">)</span> || !is_mu_scalar<span class="Delimiter">(</span>offset<span class="Delimiter">))</span> <span class="Delimiter">{</span>
<span id="L178" class="LineNr">178 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;second ingredient of 'get-location' should have type 'offset', but got '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>original_string &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L179" class="LineNr">179 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L180" class="LineNr">180 </span>  <span class="Delimiter">}</span>
<span id="L181" class="LineNr">181 </span>  <span class="Normal">int</span> offset_value = <span class="Constant">0</span><span class="Delimiter">;</span>
<span id="L182" class="LineNr">182 </span>  <span class="Comment">//: later layers will permit non-integer offsets</span>
<span id="L183" class="LineNr">183 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span><a href='002test.cc.html#L85'>is_integer</a><span class="Delimiter">(</span>offset<span class="Delimiter">.</span>name<span class="Delimiter">))</span> <span class="Delimiter">{</span>
<span id="L184" class="LineNr">184 </span>  <span class="Conceal">¦</span> offset_value = <a href='002test.cc.html#L91'>to_integer</a><span class="Delimiter">(</span>offset<span class="Delimiter">.</span>name<span class="Delimiter">);</span>
<span id="L185" class="LineNr">185 </span>  <span class="Conceal">¦</span> <span class="Normal">if</span> <span class="Delimiter">(</span>offset_value &lt; <span class="Constant">0</span> || offset_value &gt;= <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Type<span class="Delimiter">,</span> base_type<span class="Delimiter">).</span>elements<span class="Delimiter">))</span> <span class="Delimiter">{</span>
<span id="L186" class="LineNr">186 </span>  <span class="Conceal">¦</span> <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;invalid offset &quot;</span> &lt;&lt; offset_value &lt;&lt; <span class="Constant">&quot; for '&quot;</span> &lt;&lt; get<span class="Delimiter">(</span>Type<span class="Delimiter">,</span> base_type<span class="Delimiter">).</span>name &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L187" class="LineNr">187 </span>  <span class="Conceal">¦</span> <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L188" class="LineNr">188 </span>  <span class="Conceal">¦</span> <span class="Delimiter">}</span>
<span id="L189" class="LineNr">189 </span>  <span class="Delimiter">}</span>
<span id="L190" class="LineNr">190 </span>  <span class="Normal">else</span> <span class="Delimiter">{</span>
<span id="L191" class="LineNr">191 </span>  <span class="Conceal">¦</span> offset_value = offset<span class="Delimiter">.</span>value<span class="Delimiter">;</span>
<span id="L192" class="LineNr">192 </span>  <span class="Delimiter">}</span>
<span id="L193" class="LineNr">193 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>empty<span class="Delimiter">())</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L194" class="LineNr">194 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>!is_mu_location<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">)))</span> <span class="Delimiter">{</span>
<span id="L195" class="LineNr">195 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Recipe<span class="Delimiter">,</span> r<span class="Delimiter">).</span>name<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'get-location &quot;</span> &lt;&lt; base<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;, &quot;</span> &lt;&lt; offset<span class="Delimiter">.</span>original_string &lt;&lt; <span class="Constant">&quot;' should write to type location but '&quot;</span> &lt;&lt; inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>name &lt;&lt; <span class="Constant">&quot;' has type '&quot;</span> &lt;&lt; names_to_string_without_quotes<span class="Delimiter">(</span>inst<span class="Delimiter">.</span>products<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">).</span>type<span class="Delimiter">)</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L196" class="LineNr">196 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L197" class="LineNr">197 </span>  <span class="Delimiter">}</span>
<span id="L198" class="LineNr">198 </span>  <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L199" class="LineNr">199 </span><span class="Delimiter">}</span>
<span id="L200" class="LineNr">200 </span><span class="Delimiter">:(before &quot;End Primitive Recipe Implementations&quot;)</span>
<span id="L201" class="LineNr">201 </span><span class="Normal">case</span> GET_LOCATION: <span class="Delimiter">{</span>
<span id="L202" class="LineNr">202 </span>  reagent<span class="Comment">/*</span><span class="Comment">copy</span><span class="Comment">*/</span> base = <a href='026call.cc.html#L85'>current_instruction</a><span class="Delimiter">().</span>ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">);</span>
<span id="L203" class="LineNr">203 </span>  canonize<span class="Delimiter">(</span>base<span class="Delimiter">);</span>
<span id="L204" class="LineNr">204 </span>  <span class="Normal">int</span> base_address = base<span class="Delimiter">.</span>value<span class="Delimiter">;</span>
<span id="L205" class="LineNr">205 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>base_address == <span class="Constant">0</span><span class="Delimiter">)</span> <span class="Delimiter">{</span>
<span id="L206" class="LineNr">206 </span>  <span class="Conceal">¦</span> <a href='003trace.cc.html#L168'>raise</a> &lt;&lt; <a href='013update_operation.cc.html#L25'>maybe</a><span class="Delimiter">(</span><a href='026call.cc.html#L83'>current_recipe_name</a><span class="Delimiter">())</span> &lt;&lt; <span class="Constant">&quot;tried to access location 0 in '&quot;</span> &lt;&lt; to_original_string<span class="Delimiter">(</span>current_instruction<span class="Delimiter">())</span> &lt;&lt; <span class="Constant">&quot;'</span><span class="cSpecial">\n</span><span class="Constant">&quot;</span> &lt;&lt; <a href='003trace.cc.html#L197'>end</a><span class="Delimiter">();</span>
<span id="L207" class="LineNr">207 </span>  <span class="Conceal">¦</span> <span class="Identifier">break</span><span class="Delimiter">;</span>
<span id="L208" class="LineNr">208 </span>  <span class="Delimiter">}</span>
<span id="L209" class="LineNr">209 </span>  <span class="Normal">const</span> type_tree* base_type = get_base_type<span class="Delimiter">(</span>base<span class="Delimiter">.</span>type<span class="Delimiter">);</span>
<span id="L210" class="LineNr">210 </span>  <span class="Normal">int</span> offset = ingredients<span class="Delimiter">.</span>at<span class="Delimiter">(</span><span class="Constant">1</span><span class="Delimiter">).</span>at<span class="Delimiter">(</span><span class="Constant">0</span><span class="Delimiter">);</span>
<span id="L211" class="LineNr">211 </span>  <span class="Normal">if</span> <span class="Delimiter">(</span>offset &lt; <span class="Constant">0</span> || offset &gt;= <a href='001help.cc.html#L141'>SIZE</a><span class="Delimiter">(</span>get<span class="Delimiter">(</span>Type<span class="Delimiter">,</span> base_type<span class="Delimiter">-&gt;</span>value<span class="Delimiter">).</span>elements<span class="Delimiter">))</span> <span class="Identifier">break</span><span class="Delimiter">;</span>  <span class="Comment">// copied from Check above</span>
<span id="L212" class="LineNr">212 </span>  <span class="Normal">int</span> result = base_address<span class="Delimiter">;</span>
<span id="L213" class="LineNr">213 </span>  <span class="Normal">for</span> <span class="Delimiter">(</span><span class="Normal">int</span> i = <span class="Constant">0</span><span class="Delimiter">;</span>  i &lt; offset<span class="Delimiter">;</span>  ++i<span class="Delimiter">)</span>
<span id="L214" class="LineNr">214 </span>  <span class="Conceal">¦</span> result += size_of<span class="Delimiter">(</span><a href='030container.cc.html#L429'>element_type</a><span class="Delimiter"pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
# The Mu computer's level-2 language, also called Mu.
# http://akkartik.name/post/mu-2019-2
#
# To run:
#   $ ./translate_subx init.linux [0-9]*.subx apps/mu.subx

# == Goals
# 1. Be memory safe. It should be impossible to corrupt the heap, or to create
# a bad pointer. (Requires strong type safety.)
# 2. Do as little as possible to achieve goal 1. The translator should be
# implementable in machine code.
#   - minimize impedance mismatch between source language and SubX target
#     (e.g. programmer manages registers manually)
#   - checks over syntax
#     (e.g. programmer's register allocation is checked)
#   - runtime checks to avoid complex static analysis
#     (e.g. array indexing always checks bounds)

# == Language description
# A program is a sequence of function definitions.
#
# Function example:
#   fn foo n: int -> result/eax: int {
#     ...
#   }
#
# Functions consist of a name, optional inputs, optional outputs and a block.
#
# Function inputs and outputs are variables. All variables have a type and
# storage specifier. They can be placed either in memory (on the stack) or in
# one of 6 named registers.
#   eax ecx edx ebx esi edi
# Variables in registers must be primitive 32-bit types.
# Variables not explicitly placed in a register are on the stack.
#
# Function inputs are always passed in memory (on the stack), while outputs
# are always returned in registers.
#
# Blocks mostly consist of statements.
#
# Statements mostly consist of a name, optional inputs and optional outputs.
#
# Statement inputs are variables or literals. Variables need to specify type
# (and storage) the first time they're mentioned but not later.
#
# Statement outputs, like function outputs, must be variables in registers.
#
# Statement names must be either primitives or user-defined functions.
#
# Primitives can write to any register.
# User-defined functions only write to hard-coded registers. Outputs of each
# call must have the same registers as in the function definition.
#
# There are some other statement types:
#   - blocks. Multiple statements surrounded by '{...}' and optionally
#     prefixed with a label name and ':'
#       - {
#           ...
#         }
#       - foo: {
#           ...
#         }
#
#   - variable definitions on the stack. E.g.:
#       - var foo: int
#       - var bar: (array int 3)
#     There's no initializer; variables are automatically initialized.
#     The type of a local variable is either word-length (4 bytes) or starts with 'ref'.
#
#   - variables definitions in a register. E.g.:
#       - var foo/eax: int <- add bar 1
#     The initializer is mandatory and must be a valid instruction that writes
#     a single output to the right register. In practice registers will
#     usually be either initialized by primitives or copied from eax.
#       - var eax: int <- foo bar quux
#         var floo/ecx: int <- copy eax
#
# Still todo:
#   global variables
#   heap allocations (planned name: 'handle')
#   user-defined types: 'type' for structs, 'choice' for unions
#   short-lived 'address' type for efficiently writing inside nested structs
#
# We don't have 'handle' types yet, but we try to distinguish 'ref', 'handle'
# and 'address' in comments. Their definitions are in layer 50, but really you
# can ignore the distinctions on a first reading of this program.
#
# Formal types:
#   A program is a linked list of functions
#   A function contains:
#     name: (handle array byte)
#     inouts: linked list of vars  <-- 'inouts' is more precise than 'inputs'
#       data: (handle var)
#       next: (handle list)
#     outputs: linked list of vars
#       data: (handle var)
#       next: (handle list)
#     body: (handle block)
#   A var-type contains:
#     name: (handle array byte)
#     type: (handle tree type-id)
#
#   A statement can be:
#     tag 0: a block
#     tag 1: a simple statement (stmt1)
#     tag 2: a variable defined on the stack
#     tag 3: a variable defined in a register
#
#   A block contains:
#     tag: 0
#     statements: (handle list stmt)
#     name: (handle array byte) -- starting with '$'
#
#   A regular statement contains:
#     tag: 1
#     operation: (handle array byte)
#     inouts: (handle list operand)
#     outputs: (handle list var)
#
#   A variable defined on the stack contains:
#     tag: 2
#     name: (handle array byte)
#     type: (handle tree type-id)
#
#   A variable defined in a register contains:
#     tag: 3
#     name: (handle array byte)
#     type: (handle tree type-id)
#     reg: (handle array byte)

# == Translation: managing the stack
# Now that we know what the language looks like in the large, let's think
# about how translation happens from the bottom up. One crucial piece of the
# puzzle is how Mu will clean up variables defined on the stack for you.
#
# Assume that we maintain a 'functions' list while parsing source code. And a
# 'primitives' list is a global constant. Both these contain enough information
# to perform type-checking on function calls or primitive statements, respectively.
#
# Defining variables pushes them on a stack with the current block depth and
# enough information about their location (stack offset or register).
# Starting a block increments the current block id.
# Each statement now has enough information to emit code for it.
# Ending a block is where the magic happens:
#   pop all variables at the current block depth
#   emit code to restore all register variables introduced at the current depth
#   emit code to clean up all stack variables at the current depth (just increment esp)
#   decrement the current block depth
#
# Formal types:
#   live-vars: stack of vars
#   var:
#     name: (handle array byte)
#     type: (handle tree type-id)
#     block: int
#     stack-offset: int  (added to ebp)
#     register: (handle array byte)
#       either usual register names
#       or '*' to indicate any register
#   At most one of stack-offset or register-index must be non-zero.
#   A register of '*' designates a variable _template_. Only legal in formal
#   parameters for primitives.

# == Translating a single function call
# This one's easy. Assuming we've already checked things, we just drop the
# outputs (which use hard-coded registers) and emit inputs in a standard format.
#
# out1, out2, out3, ... <- name inout1, inout2, inout3, ...
# =>
# (subx-name inout1 inout2 inout3)
#
# Formal types:
#   functions: linked list of info
#     name: (handle array byte)
#     inouts: linked list of vars
#     outputs: linked list of vars
#     body: block (singleton linked list)
#     subx-name: (handle array byte)

# == Translating a single primitive instruction
# A second crucial piece of the puzzle is how Mu converts fairly regular
# primitives with their uniform syntax to SubX instructions with their gnarly
# x86 details.
#
# Mu instructions have inputs and outputs. Primitives can have up to 2 of
# them.
# SubX instructions have rm32 and r32 operands.
# The translation between them covers almost all the possibilities.
#   Instructions with 1 inout may turn into ones with 1 rm32
#     (e.g. incrementing a var on the stack)
#   Instructions with 1 output may turn into ones with 1 rm32
#     (e.g. incrementing a var in a register)
#   1 inout and 1 output may turn into 1 rm32 and 1 r32
#     (e.g. adding a var to a reg)
#   2 inouts may turn into 1 rm32 and 1 r32
#     (e.g. adding a reg to a var)
#   1 inout and 1 literal may turn into 1 rm32 and 1 imm32
#     (e.g. adding a constant to a var)
#   1 output and 1 literal may turn into 1 rm32 and 1 imm32
#     (e.g. adding a constant to a reg)
#   2 outputs to hardcoded registers and 1 inout may turn into 1 rm32
#     (special-case: divide edx:eax by a var or reg)
# Observations:
#   We always emit rm32. It may be the first inout or the first output.
#   We may emit r32 or imm32 or neither.
#   When we emit r32 it may come from first inout or second inout or first output.
#
# Accordingly, the formal data structure for a primitive looks like this:
#   primitives: linked list of info
#     name: (handle array byte)
#     mu-inouts: linked list of vars to check
#     mu-outputs: linked list of vars to check; at most a singleton
#     subx-name: (handle array byte)
#     subx-rm32: enum arg-location
#     subx-r32: enum arg-location
#     subx-imm32: enum arg-location
#     subx-disp32: enum arg-location
#     output-is-write-only: boolean
#   arg-location: enum
#     0 means none
#     1 means first inout
#     2 means second inout
#     3 means first output

# == Translating a block
# Emit block name if necessary
# Emit '{'
# When you encounter a statement, emit it as above
# When you encounter a variable declaration
#   emit any code needed for it (bzeros)
#   push it on the var stack
#   update register dict if necessary
# When you encounter '}'
#   While popping variables off the var stack until block id changes
#     Emit code needed to clean up the stack
#       either increment esp
#       or pop into appropriate register

# The rest is straightforward.

== data

Program:
_Program-functions:  # (handle function)
  0/imm32
_Program-types:  # (handle typeinfo)
  0/imm32

# Some constants for simulating the data structures described above.
# Many constants here come with a type in a comment.
#
# Sometimes the type is of the value at that offset for the given type. For
# example, if you start at a function record and move forward Function-inouts
# bytes, you'll find a (handle list var).
#
# At other times, the type is of the constant itself. For example, the type of
# the constant Function-size is (addr int). To get the size of a function,
# look in *Function-size.

Function-name:  # (handle array byte)
  0/imm32
Function-subx-name:  # (handle array byte)
  4/imm32
Function-inouts:  # (handle list var)
  8/imm32
Function-outputs:  # (handle list var)
  0xc/imm32
Function-body:  # (handle block)
  0x10/imm32
Function-next:  # (handle function)
  0x14/imm32
Function-size:  # (addr int)
  0x18/imm32/24

Primitive-name:  # (handle array byte)
  0/imm32
Primitive-inouts:  # (handle list var)
  4/imm32
Primitive-outputs:  # (handle list var)
  8/imm32
Primitive-subx-name:  # (handle array byte)
  0xc/imm32
Primitive-subx-rm32:  # enum arg-location
  0x10/imm32
Primitive-subx-r32:  # enum arg-location
  0x14/imm32
Primitive-subx-imm32:  # enum arg-location
  0x18/imm32
Primitive-subx-disp32:  # enum arg-location  -- only for branches
  0x1c/imm32
Primitive-output-is-write-only:  # boolean
  0x20/imm32
Primitive-next:  # (handle function)
  0x24/imm32
Primitive-size:  # (addr int)
  0x28/imm32/36

Stmt-tag:  # int
  0/imm32

Block-stmts:  # (handle list stmt)
  4/imm32
Block-var:  # (handle var)
  8/imm32

Stmt1-operation:  # (handle array byte)
  4/imm32
Stmt1-inouts:  # (handle stmt-var)
  8/imm32
Stmt1-outputs:  # (handle stmt-var)
  0xc/imm32

Vardef-var:  # (handle var)
  4/imm32

Regvardef-operation:  # (handle array byte)
  4/imm32
Regvardef-inouts:  # (handle stmt-var)
  8/imm32
Regvardef-outputs:  # (handle stmt-var)  # will have exactly one element
  0xc/imm32

Stmt-size:  # (addr int)
  0x10/imm32

Var-name:  # (handle array byte)
  0/imm32
Var-type:  # (handle tree type-id)
  4/imm32
Var-block-depth:  # int
  8/imm32
Var-offset:  # int
  0xc/imm32
Var-register:  # (handle array byte) -- name of a register
  0x10/imm32
Var-size:  # (addr int)
  0x14/imm32

Any-register:  # wildcard
  # size
  1/imm32
  # data
  2a/asterisk

List-value:
  0/imm32
List-next:  # (handle list _)
  4/imm32
List-size:  # (addr int)
  8/imm32

# A stmt-var is like a list of vars with call-site specific metadata
Stmt-var-value:  # (handle var)
  0/imm32
Stmt-var-next:  # (handle stmt-var)
  4/imm32
Stmt-var-is-deref:  # boolean
  8/imm32
Stmt-var-size:  # (addr int)
  0xc/imm32

# Types are expressed as trees (s-expressions) of type-ids (ints).
# However, there's no need for singletons, so we can assume (int) == int
#   - if x->right == nil, x is an atom
#   - x->left contains either a pointer to a pair, or an atomic type-id directly.
#     type ids will be less than 0x10000 (MAX_TYPE_ID).

Tree-left:  # either type-id or (addr tree type-id)
  0/imm32
Tree-right:  # (addr tree type-id)
  4/imm32
Tree-size:  # (addr int)
  8/imm32

# Types

Max-type-id:
  0x10000/imm32

Type-id:  # (stream (address array byte))
  0x1c/imm32/write
  0/imm32/read
  0x100/imm32/length
  # data
  "literal"/imm32  # 0
  "int"/imm32  # 1
  "addr"/imm32  # 2
  "array"/imm32  # 3
  "handle"/imm32  # 4
  "boolean"/imm32  # 5
  "constant"/imm32  # 6: like a literal, but replaced with its value in Var-offset
  0/imm32
  # 0x20
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32

# Program->types contains some typeinfo for each type definition.
# Types contain vars with types, but can't specify registers.
Typeinfo-id:  # type-id
  0/imm32
# each field of a type is represented using two var's:
#   1. the input var: expected type of the field; convenient for creating using parse-var-with-type
#   2. the output var: a constant containing the byte offset; convenient for code-generation
Typeinfo-fields:  # (handle table string {(handle var), (handle var)})
  4/imm32
Typeinfo-next:  # (handle typeinfo)
  8/imm32
Typeinfo-size:  # (addr int)
  0xc/imm32

== code

Entry:
    # . prologue
    89/<- %ebp 4/r32/esp
    (new-segment *Heap-size Heap)
    # if (argv[1] == "test') run-tests()
    {
      # if (argc <= 1) break
      81 7/subop/compare *ebp 1/imm32
      7e/jump-if-<= break/disp8
      # if (argv[1] != "test") break
      (kernel-string-equal? *(ebp+8) "test")  # => eax
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= break/disp8
      #
      (run-tests)
      # syscall(exit, *Num-test-failures)
      8b/-> *Num-test-failures 3/r32/ebx
      eb/jump $mu-main:end/disp8
    }
    # otherwise convert Stdin
    (convert-mu Stdin Stdout)
    (flush Stdout)
    # syscall(exit, 0)
    bb/copy-to-ebx 0/imm32
$mu-main:end:
    b8/copy-to-eax 1/imm32/exit
    cd/syscall 0x80/imm8

convert-mu:  # in: (addr buffered-file), out: (addr buffered-file)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (parse-mu *(ebp+8))
    (check-mu-types)
    (emit-subx *(ebp+0xc))
$convert-mu:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-empty-input:
    # empty input => empty output
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    #
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
    (check-stream-equal _test-output-stream "" "F - test-convert-empty-input")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-skeleton:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-skeleton/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-skeleton/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-skeleton/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-skeleton/3")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-skeleton/4")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-skeleton/5")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-skeleton/6")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-skeleton/7")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-multiple-function-skeletons:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "}\n")
    (write _test-input-stream "fn bar {\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check first function
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-multiple-function-skeletons/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-multiple-function-skeletons/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-multiple-function-skeletons/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/3")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-multiple-function-skeletons/4")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/5")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/6")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-multiple-function-skeletons/7")
    # check second function
    (check-next-stream-line-equal _test-output-stream "bar:"                    "F - test-convert-multiple-function-skeletons/10")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-multiple-function-skeletons/11")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-multiple-function-skeletons/12")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-multiple-function-skeletons/13")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-multiple-function-skeletons/14")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-multiple-function-skeletons/15")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-multiple-function-skeletons/16")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-multiple-function-skeletons/17")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-arg:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    #
    (write _test-input-stream "fn foo n: int {\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-arg/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-arg/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-arg/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg/3")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-arg/4")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg/5")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-arg/6")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-arg/7")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-arg-and-body:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo n: int {\n")
    (write _test-input-stream "  increment n\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-arg-and-body/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-arg-and-body/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-arg-and-body/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-arg-and-body/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-arg-and-body/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-arg-and-body/5")
    (check-next-stream-line-equal _test-output-stream "    ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-arg-and-body/6")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-arg-and-body/7")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-arg-and-body/8")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-arg-and-body/9")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-arg-and-body/10")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-arg-and-body/11")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-arg-and-body/12")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-distinguishes-args:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: int, b: int {\n")
    (write _test-input-stream "  increment b\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-distinguishes-args/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-distinguishes-args/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-distinguishes-args/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-distinguishes-args/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-distinguishes-args/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-distinguishes-args/5")
    (check-next-stream-line-equal _test-output-stream "    ff 0/subop/increment *(ebp+0x0000000c)"  "F - test-convert-function-distinguishes-args/6")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-distinguishes-args/7")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-distinguishes-args/8")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-distinguishes-args/9")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-distinguishes-args/10")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-distinguishes-args/11")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-distinguishes-args/12")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-returns-result:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: int, b: int -> result/eax: int {\n")
    (write _test-input-stream "  result <- copy a\n")
    (write _test-input-stream "  result <- increment\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-returns-result/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-returns-result/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-returns-result/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-returns-result/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-returns-result/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-returns-result/5")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-function-returns-result/6")
    (check-next-stream-line-equal _test-output-stream "    40/increment-eax"    "F - test-convert-function-returns-result/7")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-returns-result/8")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-returns-result/9")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-returns-result/10")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-returns-result/11")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-returns-result/12")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-returns-result/13")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-literal-arg:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: int, b: int -> result/eax: int {\n")
    (write _test-input-stream "  result <- copy a\n")
    (write _test-input-stream "  result <- add 1\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-literal-arg/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-literal-arg/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-literal-arg/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-literal-arg/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-literal-arg/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-literal-arg/5")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-function-literal-arg/6")
    (check-next-stream-line-equal _test-output-stream "    05/add-to-eax 1/imm32"  "F - test-convert-function-literal-arg/7")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-literal-arg/8")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-literal-arg/9")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-literal-arg/10")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-literal-arg/11")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-literal-arg/12")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-literal-arg/13")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-literal-arg-2:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: int, b: int -> result/ebx: int {\n")
    (write _test-input-stream "  result <- copy a\n")
    (write _test-input-stream "  result <- add 1\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-literal-arg-2/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-literal-arg-2/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-literal-arg-2/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-literal-arg-2/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-literal-arg-2/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-literal-arg-2/5")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000003/r32"  "F - test-convert-function-literal-arg-2/6")
    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %ebx 1/imm32"  "F - test-convert-function-literal-arg-2/7")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-literal-arg-2/8")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-literal-arg-2/9")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-literal-arg-2/10")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-literal-arg-2/11")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-literal-arg-2/12")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-literal-arg-2/13")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-call-with-literal-arg:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn main -> result/ebx: int {\n")
    (write _test-input-stream "  result <- do-add 3 4\n")
    (write _test-input-stream "}\n")
    (write _test-input-stream "fn do-add a: int, b: int -> result/ebx: int {\n")
    (write _test-input-stream "  result <- copy a\n")
    (write _test-input-stream "  result <- add b\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "main:"                   "F - test-convert-function-call-with-literal-arg/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-call-with-literal-arg/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-call-with-literal-arg/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-call-with-literal-arg/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-call-with-literal-arg/4")
    (check-next-stream-line-equal _test-output-stream "$main:0x00000001:loop:"  "F - test-convert-function-call-with-literal-arg/5")
    (check-next-stream-line-equal _test-output-stream "    (do-add 3 4)"        "F - test-convert-function-call-with-literal-arg/6")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-call-with-literal-arg/7")
    (check-next-stream-line-equal _test-output-stream "$main:0x00000001:break:" "F - test-convert-function-call-with-literal-arg/8")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-call-with-literal-arg/9")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-call-with-literal-arg/10")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-call-with-literal-arg/11")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-call-with-literal-arg/12")
    (check-next-stream-line-equal _test-output-stream "do-add:"                 "F - test-convert-function-call-with-literal-arg/13")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-call-with-literal-arg/14")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-call-with-literal-arg/15")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-call-with-literal-arg/16")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-call-with-literal-arg/17")
    (check-next-stream-line-equal _test-output-stream "$do-add:0x00000002:loop:"  "F - test-convert-function-call-with-literal-arg/18")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000003/r32"  "F - test-convert-function-call-with-literal-arg/19")
    (check-next-stream-line-equal _test-output-stream "    03/add *(ebp+0x0000000c) 0x00000003/r32"  "F - test-convert-function-call-with-literal-arg/20")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-call-with-literal-arg/21")
    (check-next-stream-line-equal _test-output-stream "$do-add:0x00000002:break:"  "F - test-convert-function-call-with-literal-arg/22")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-call-with-literal-arg/23")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-call-with-literal-arg/24")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-call-with-literal-arg/25")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-call-with-literal-arg/26")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-local-var-in-mem:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  var x: int\n")
    (write _test-input-stream "  increment x\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-local-var-in-mem/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-local-var-in-mem/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-local-var-in-mem/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-in-mem/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-local-var-in-mem/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-local-var-in-mem/5")
    (check-next-stream-line-equal _test-output-stream "    68/push 0/imm32"     "F - test-convert-function-with-local-var-in-mem/6")
    (check-next-stream-line-equal _test-output-stream "    ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-local-var-in-mem/7")
    (check-next-stream-line-equal _test-output-stream "    81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-local-var-in-mem/8")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-local-var-in-mem/9")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-local-var-in-mem/10")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-local-var-in-mem/11")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-in-mem/12")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-local-var-in-mem/13")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-local-var-in-mem/14")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-local-var-in-reg:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  var x/ecx: int <- copy 3\n")
    (write _test-input-stream "  x <- increment\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-local-var-in-reg/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-local-var-in-reg/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-local-var-in-reg/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-in-reg/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-local-var-in-reg/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-local-var-in-reg/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-local-var-in-reg/6")
    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"  "F - test-convert-function-with-local-var-in-reg/7")
    (check-next-stream-line-equal _test-output-stream "    41/increment-ecx"    "F - test-convert-function-with-local-var-in-reg/8")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-with-local-var-in-reg/9")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-local-var-in-reg/10")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-local-var-in-reg/11")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-local-var-in-reg/12")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-in-reg/13")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-local-var-in-reg/14")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-local-var-in-reg/15")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-local-var-dereferenced:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  var x/ecx: (addr int) <- copy 0\n")
    (write _test-input-stream "  increment *x\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-local-var-dereferenced/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-local-var-dereferenced/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-local-var-dereferenced/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-dereferenced/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-local-var-dereferenced/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-local-var-dereferenced/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-with-local-var-dereferenced/6")
    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 0/imm32"  "F - test-convert-function-with-local-var-dereferenced/7")
    (check-next-stream-line-equal _test-output-stream "    ff 0/subop/increment *ecx"  "F - test-convert-function-with-local-var-dereferenced/8")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-with-local-var-dereferenced/9")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-local-var-dereferenced/10")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-local-var-dereferenced/11")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-local-var-dereferenced/12")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-dereferenced/13")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-local-var-dereferenced/14")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-local-var-dereferenced/15")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-compare-register-with-literal:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  var x/ecx: int <- copy 0\n")
    (write _test-input-stream "  compare x, 0\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-compare-register-with-literal/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-compare-register-with-literal/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-compare-register-with-literal/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-compare-register-with-literal/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-compare-register-with-literal/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-compare-register-with-literal/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-compare-register-with-literal/6")
    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 0/imm32"  "F - test-convert-compare-register-with-literal/7")
    (check-next-stream-line-equal _test-output-stream "    81 7/subop/compare %ecx 0/imm32"  "F - test-convert-compare-register-with-literal/8")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-compare-register-with-literal/9")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-compare-register-with-literal/10")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-compare-register-with-literal/11")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-compare-register-with-literal/12")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-compare-register-with-literal/13")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-compare-register-with-literal/14")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-compare-register-with-literal/15")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-local-var-in-block:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-local-var-in-block/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-local-var-in-block/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-local-var-in-block/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-in-block/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-local-var-in-block/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-local-var-in-block/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-local-var-in-block/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-local-var-in-block/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-local-var-in-block/8")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-local-var-in-block/9")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-local-var-in-block/10")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-local-var-in-block/11")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-local-var-in-block/12")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-local-var-in-block/13")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-local-var-in-block/14")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-local-var-in-block/15")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-in-block/16")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-local-var-in-block/17")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-local-var-in-block/18")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-local-var-in-named-block:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  $bar: {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-local-var-in-named-block/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-local-var-in-named-block/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-local-var-in-named-block/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-local-var-in-named-block/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-local-var-in-named-block/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-local-var-in-named-block/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-local-var-in-named-block/6")
    (check-next-stream-line-equal _test-output-stream "$bar:loop:"              "F - test-convert-function-with-local-var-in-named-block/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-local-var-in-named-block/8")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-local-var-in-named-block/9")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-local-var-in-named-block/10")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-local-var-in-named-block/11")
    (check-next-stream-line-equal _test-output-stream "$bar:break:"             "F - test-convert-function-with-local-var-in-named-block/12")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-local-var-in-named-block/13")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-local-var-in-named-block/14")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-local-var-in-named-block/15")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-local-var-in-named-block/16")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-local-var-in-named-block/17")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-local-var-in-named-block/18")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-branches-in-block:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo x: int {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    break-if->=\n")
    (write _test-input-stream "    loop-if-addr<\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "    loop\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-branches-in-block/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-branches-in-block/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-branches-in-block/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-branches-in-block/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-branches-in-block/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-branches-in-block/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-branches-in-block/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-branches-in-block/7")
    (check-next-stream-line-equal _test-output-stream "      0f 8d/jump-if->= break/disp32"  "F - test-convert-function-with-branches-in-block/8")
    (check-next-stream-line-equal _test-output-stream "      0f 82/jump-if-addr< loop/disp32"  "F - test-convert-function-with-branches-in-block/9")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-branches-in-block/10")
    (check-next-stream-line-equal _test-output-stream "      e9/jump loop/disp32"  "F - test-convert-function-with-branches-in-block/11")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-branches-in-block/12")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-branches-in-block/13")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-branches-in-block/14")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-branches-in-block/15")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-branches-in-block/16")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-branches-in-block/17")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-branches-in-block/18")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-branches-in-block/19")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-branches-in-named-block:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo x: int {\n")
    (write _test-input-stream "  $bar: {\n")
    (write _test-input-stream "    break-if->= $bar\n")
    (write _test-input-stream "    loop-if-addr< $bar\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "    loop\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-branches-in-named-block/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-branches-in-named-block/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-branches-in-named-block/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-branches-in-named-block/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-branches-in-named-block/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-branches-in-named-block/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-branches-in-named-block/6")
    (check-next-stream-line-equal _test-output-stream "$bar:loop:"              "F - test-convert-function-with-branches-in-named-block/7")
    (check-next-stream-line-equal _test-output-stream "      0f 8d/jump-if->= $bar:break/disp32"  "F - test-convert-function-with-branches-in-named-block/8")
    (check-next-stream-line-equal _test-output-stream "      0f 82/jump-if-addr< $bar:loop/disp32"  "F - test-convert-function-with-branches-in-named-block/9")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0x00000008)"  "F - test-convert-function-with-branches-in-named-block/10")
    (check-next-stream-line-equal _test-output-stream "      e9/jump loop/disp32"   "F - test-convert-function-with-branches-in-named-block/11")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-branches-in-named-block/12")
    (check-next-stream-line-equal _test-output-stream "$bar:break:"             "F - test-convert-function-with-branches-in-named-block/13")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-branches-in-named-block/14")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-branches-in-named-block/15")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-branches-in-named-block/16")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-branches-in-named-block/17")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-branches-in-named-block/18")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-branches-in-named-block/19")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-var-in-nested-block:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo x: int {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var x: int\n")
    (write _test-input-stream "      increment x\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-var-in-nested-block/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-var-in-nested-block/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-var-in-nested-block/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-var-in-nested-block/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-var-in-nested-block/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-var-in-nested-block/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-var-in-nested-block/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-var-in-nested-block/7")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-var-in-nested-block/8")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-var-in-nested-block/9")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-var-in-nested-block/10")
    (check-next-stream-line-equal _test-output-stream "        ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-var-in-nested-block/11")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-var-in-nested-block/12")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-var-in-nested-block/13")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-var-in-nested-block/14")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-var-in-nested-block/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-var-in-nested-block/16")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-var-in-nested-block/17")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-var-in-nested-block/18")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-var-in-nested-block/19")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-var-in-nested-block/20")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-var-in-nested-block/21")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-var-in-nested-block/22")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-multiple-vars-in-nested-blocks:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo x: int {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x/eax: int <- copy 0\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var y: int\n")
    (write _test-input-stream "      x <- add y\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-multiple-vars-in-nested-blocks/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-multiple-vars-in-nested-blocks/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-multiple-vars-in-nested-blocks/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-multiple-vars-in-nested-blocks/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-multiple-vars-in-nested-blocks/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-multiple-vars-in-nested-blocks/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-multiple-vars-in-nested-blocks/7")
    (check-next-stream-line-equal _test-output-stream "      ff 6/subop/push %eax"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/8")
    (check-next-stream-line-equal _test-output-stream "      b8/copy-to-eax 0/imm32"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/9")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-multiple-vars-in-nested-blocks/10")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-multiple-vars-in-nested-blocks/11")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-multiple-vars-in-nested-blocks/12")
    (check-next-stream-line-equal _test-output-stream "        03/add *(ebp+0xfffffff8) 0x00000000/r32"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/13")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/14")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-multiple-vars-in-nested-blocks/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/16")
    (check-next-stream-line-equal _test-output-stream "      8f 0/subop/pop %eax"   "F - test-convert-function-with-multiple-vars-in-nested-blocks/17")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-multiple-vars-in-nested-blocks/18")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/19")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-multiple-vars-in-nested-blocks/20")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/21")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-multiple-vars-in-nested-blocks/22")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-multiple-vars-in-nested-blocks/23")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-multiple-vars-in-nested-blocks/24")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-multiple-vars-in-nested-blocks/25")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-branches-and-local-vars:
    # A conditional 'break' after a 'var' in a block is converted into a
    # nested block that performs all necessary cleanup before jumping. This
    # results in some ugly code duplication.
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    break-if->=\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-branches-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-branches-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-branches-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-branches-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-branches-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-branches-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-branches-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-branches-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-branches-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-branches-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "        0f 8c/jump-if-< break/disp32"  "F - test-convert-function-with-branches-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-branches-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        e9/jump $foo:0x00000002:break/disp32"  "F - test-convert-function-with-branches-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-branches-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-branches-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-branches-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-branches-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-branches-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-branches-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-branches-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-branches-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-branches-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-branches-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-branches-and-local-vars/23")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-conditional-loops-and-local-vars:
    # A conditional 'loop' after a 'var' in a block is converted into a nested
    # block that performs all necessary cleanup before jumping. This results
    # in some ugly code duplication.
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    loop-if->=\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-conditional-loops-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-conditional-loops-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-conditional-loops-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-conditional-loops-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-conditional-loops-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-conditional-loops-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-conditional-loops-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-conditional-loops-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-conditional-loops-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-conditional-loops-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "        0f 8c/jump-if-< break/disp32"  "F - test-convert-function-with-conditional-loops-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-conditional-loops-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        e9/jump $foo:0x00000002:loop/disp32"  "F - test-convert-function-with-conditional-loops-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-conditional-loops-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-conditional-loops-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-conditional-loops-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-conditional-loops-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-conditional-loops-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-conditional-loops-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-conditional-loops-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-conditional-loops-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-conditional-loops-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-conditional-loops-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-conditional-loops-and-local-vars/23")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-unconditional-loops-and-local-vars:
    # An unconditional 'loop' after a 'var' in a block is emitted _after_ the
    # regular block cleanup. Any instructions after 'loop' are dead and
    # therefore skipped.
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    loop\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-unconditional-loops-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-unconditional-loops-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-unconditional-loops-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-unconditional-loops-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-unconditional-loops-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-unconditional-loops-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-unconditional-loops-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-unconditional-loops-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-unconditional-loops-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-unconditional-loops-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "      e9/jump loop/disp32"  "F - test-convert-function-with-unconditional-loops-and-local-vars/10")
    # not emitted:                                           ff 0/subop/increment *(ebp+0xfffffffc)
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-unconditional-loops-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-unconditional-loops-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-unconditional-loops-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-unconditional-loops-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-unconditional-loops-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-unconditional-loops-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-unconditional-loops-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-unconditional-loops-and-local-vars/18")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-branches-and-loops-and-local-vars:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    break-if->=\n")
    (write _test-input-stream "    increment x\n")
    (write _test-input-stream "    loop\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-branches-and-loops-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-branches-and-loops-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-branches-and-loops-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-branches-and-loops-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-branches-and-loops-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-branches-and-loops-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-branches-and-loops-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-branches-and-loops-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-branches-and-loops-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-branches-and-loops-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "        0f 8c/jump-if-< break/disp32"  "F - test-convert-function-with-branches-and-loops-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-branches-and-loops-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        e9/jump $foo:0x00000002:break/disp32"  "F - test-convert-function-with-branches-and-loops-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-branches-and-loops-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "      ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-branches-and-loops-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-branches-and-loops-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "      e9/jump loop/disp32"  "F - test-convert-function-with-branches-and-loops-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-branches-and-loops-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-branches-and-loops-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-branches-and-loops-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-branches-and-loops-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-branches-and-loops-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-branches-and-loops-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-branches-and-loops-and-local-vars/23")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-branches-and-loops-and-local-vars/24")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-nonlocal-branches-and-loops-and-local-vars:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  a: {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var y: int\n")
    (write _test-input-stream "      break-if->= a\n")
    (write _test-input-stream "      increment x\n")
    (write _test-input-stream "      loop\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "a:loop:"                 "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        {"               "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "          0f 8c/jump-if-< break/disp32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "          81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "          81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "          e9/jump a:break/disp32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "        }"               "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "        ff 0/subop/increment *(ebp+0xfffffffc)"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "        e9/jump loop/disp32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/23")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/24")
    (check-next-stream-line-equal _test-output-stream "a:break:"                "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/25")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/26")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/27")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/28")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/29")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/30")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-nonlocal-branches-and-loops-and-local-vars/31")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-nonlocal-unconditional-break-and-local-vars:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  a: {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var y: int\n")
    (write _test-input-stream "      break a\n")
    (write _test-input-stream "      increment x\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "a:loop:"                 "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "        e9/jump a:break/disp32"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "a:break:"                "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/23")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/24")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-nonlocal-unconditional-break-and-local-vars/25")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-unconditional-break-and-local-vars:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var y: int\n")
    (write _test-input-stream "      break\n")
    (write _test-input-stream "      increment x\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-unconditional-break-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-unconditional-break-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-unconditional-break-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-unconditional-break-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-unconditional-break-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-unconditional-break-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-unconditional-break-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:loop:"   "F - test-convert-function-with-unconditional-break-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-unconditional-break-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-unconditional-break-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-unconditional-break-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-unconditional-break-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-unconditional-break-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-unconditional-break-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-unconditional-break-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-unconditional-break-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-unconditional-break-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000002:break:"  "F - test-convert-function-with-unconditional-break-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-unconditional-break-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-unconditional-break-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-unconditional-break-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-unconditional-break-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-unconditional-break-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-unconditional-break-and-local-vars/23")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-with-nonlocal-unconditional-loop-and-local-vars:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  a: {\n")
    (write _test-input-stream "    var x: int\n")
    (write _test-input-stream "    {\n")
    (write _test-input-stream "      var y: int\n")
    (write _test-input-stream "      loop a\n")
    (write _test-input-stream "      increment x\n")
    (write _test-input-stream "    }\n")
    (write _test-input-stream "  }\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/5")
    (check-next-stream-line-equal _test-output-stream "    {"                   "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/6")
    (check-next-stream-line-equal _test-output-stream "a:loop:"                 "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/7")
    (check-next-stream-line-equal _test-output-stream "      68/push 0/imm32"   "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/8")
    (check-next-stream-line-equal _test-output-stream "      {"                 "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/9")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:loop:"   "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/10")
    (check-next-stream-line-equal _test-output-stream "        68/push 0/imm32" "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/11")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/12")
    (check-next-stream-line-equal _test-output-stream "        81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/13")
    (check-next-stream-line-equal _test-output-stream "        e9/jump a:loop/disp32"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/14")
    (check-next-stream-line-equal _test-output-stream "      }"                 "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000003:break:"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/16")
    (check-next-stream-line-equal _test-output-stream "      81 0/subop/add %esp 0x00000004/imm32"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/17")
    (check-next-stream-line-equal _test-output-stream "    }"                   "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/18")
    (check-next-stream-line-equal _test-output-stream "a:break:"                "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/19")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/20")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/21")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/22")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/23")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/24")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-with-nonlocal-unconditional-loop-and-local-vars/25")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-length-of-array:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: (addr array int) {\n")
    (write _test-input-stream "  var b/eax: (addr array int) <- copy a\n")
    (write _test-input-stream "  var c/eax: int <- length b\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-length-of-array/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-length-of-array/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-length-of-array/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-length-of-array/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-length-of-array/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-length-of-array/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-length-of-array/6")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-length-of-array/7")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-length-of-array/8")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *eax 0x00000000/r32"  "F - test-convert-length-of-array/9")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax"  "F - test-convert-length-of-array/10")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax"  "F - test-convert-length-of-array/11")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-length-of-array/12")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-length-of-array/13")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-length-of-array/14")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-length-of-array/15")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-length-of-array/16")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-length-of-array/17")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-index-into-array:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo {\n")
    (write _test-input-stream "  var arr/eax: (addr array int) <- copy 0\n")
    (write _test-input-stream "  var idx/ecx: int <- copy 3\n")
    (write _test-input-stream "  var x/eax: (addr int) <- index arr, idx\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                                        "F - test-convert-length-of-array/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"                              "F - test-convert-length-of-array/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"                               "F - test-convert-length-of-array/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"                      "F - test-convert-length-of-array/3")
    (check-next-stream-line-equal _test-output-stream "  {"                                         "F - test-convert-length-of-array/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"                       "F - test-convert-length-of-array/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"                    "F - test-convert-length-of-array/6")
    (check-next-stream-line-equal _test-output-stream "    b8/copy-to-eax 0/imm32"                  "F - test-convert-length-of-array/7")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"                    "F - test-convert-length-of-array/8")
    (check-next-stream-line-equal _test-output-stream "    b9/copy-to-ecx 3/imm32"                  "F - test-convert-length-of-array/9")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"                    "F - test-convert-length-of-array/10")
    (check-next-stream-line-equal _test-output-stream "    8d/copy-address *(eax + ecx<<2 + 4) 0x00000000/r32"  "F - test-convert-length-of-array/11")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax"                     "F - test-convert-length-of-array/12")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx"                     "F - test-convert-length-of-array/13")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax"                     "F - test-convert-length-of-array/14")
    (check-next-stream-line-equal _test-output-stream "  }"                                         "F - test-convert-length-of-array/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"                      "F - test-convert-length-of-array/16")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"                              "F - test-convert-length-of-array/17")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"                      "F - test-convert-length-of-array/18")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"                             "F - test-convert-length-of-array/19")
    (check-next-stream-line-equal _test-output-stream "  c3/return"                                 "F - test-convert-length-of-array/20")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-convert-function-and-type-definition:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    c7 0/subop/copy *Next-block-index 1/imm32
    #
    (write _test-input-stream "fn foo a: (addr t) {\n")
    (write _test-input-stream "  var _a/eax: (addr t) <- copy a\n")
    (write _test-input-stream "  var b/ecx: (addr int) <- get _a, x\n")
    (write _test-input-stream "  var c/ecx: (addr int) <- get _a, y\n")
    (write _test-input-stream "}\n")
    (write _test-input-stream "type t {\n")
    (write _test-input-stream "  x: int\n")
    (write _test-input-stream "  y: int\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                    "F - test-convert-function-and-type-definition/0")
    (check-next-stream-line-equal _test-output-stream "  # . prologue"          "F - test-convert-function-and-type-definition/1")
    (check-next-stream-line-equal _test-output-stream "  55/push-ebp"           "F - test-convert-function-and-type-definition/2")
    (check-next-stream-line-equal _test-output-stream "  89/<- %ebp 4/r32/esp"  "F - test-convert-function-and-type-definition/3")
    (check-next-stream-line-equal _test-output-stream "  {"                     "F - test-convert-function-and-type-definition/4")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:loop:"   "F - test-convert-function-and-type-definition/5")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %eax"  "F - test-convert-function-and-type-definition/6")
    (check-next-stream-line-equal _test-output-stream "    8b/copy-from *(ebp+0x00000008) 0x00000000/r32"  "F - test-convert-function-and-type-definition/7")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-and-type-definition/8")
    (check-next-stream-line-equal _test-output-stream "    8d/copy-address *(eax + 0x00000000) 0x00000001/r32"  "F - test-convert-function-and-type-definition/9")
    (check-next-stream-line-equal _test-output-stream "    ff 6/subop/push %ecx"  "F - test-convert-function-and-type-definition/10")
    (check-next-stream-line-equal _test-output-stream "    8d/copy-address *(eax + 0x00000004) 0x00000001/r32"  "F - test-convert-function-and-type-definition/11")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-and-type-definition/12")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %ecx" "F - test-convert-function-and-type-definition/13")
    (check-next-stream-line-equal _test-output-stream "    8f 0/subop/pop %eax" "F - test-convert-function-and-type-definition/14")
    (check-next-stream-line-equal _test-output-stream "  }"                     "F - test-convert-function-and-type-definition/15")
    (check-next-stream-line-equal _test-output-stream "$foo:0x00000001:break:"  "F - test-convert-function-and-type-definition/16")
    (check-next-stream-line-equal _test-output-stream "  # . epilogue"          "F - test-convert-function-and-type-definition/17")
    (check-next-stream-line-equal _test-output-stream "  89/<- %esp 5/r32/ebp"  "F - test-convert-function-and-type-definition/18")
    (check-next-stream-line-equal _test-output-stream "  5d/pop-to-ebp"         "F - test-convert-function-and-type-definition/19")
    (check-next-stream-line-equal _test-output-stream "  c3/return"             "F - test-convert-function-and-type-definition/20")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

#######################################################
# Parsing
#######################################################

parse-mu:  # in: (addr buffered-file)
    # pseudocode
    #   var curr-function: (addr (handle function)) = Program->functions
    #   var curr-type: (addr (handle typeinfo)) = Program->types
    #   var line: (stream byte 512)
    #   var word-slice: slice
    #   while true                                  # line loop
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if (line->write == 0) break               # end of file
    #     word-slice = next-mu-token(line)
    #     if slice-empty?(word-slice)               # end of line
    #       continue
    #     else if slice-starts-with?(word-slice, "#")  # comment
    #       continue                                # end of line
    #     else if slice-equal?(word-slice, "fn")
    #       var new-function: (handle function) = allocate(function)
    #       var vars: (stack (addr var) 256)
    #       populate-mu-function-header(in, new-function, vars)
    #       populate-mu-function-body(in, new-function, vars)
    #       assert(vars->top == 0)
    #       *curr-function = new-function
    #       curr-function = &new-function->next
    #     else if slice-equal?(word-slice, "type")
    #       word-slice = next-mu-token(line)
    #       type-id = pos-or-insert-slice(Type-id, word-slice)
    #       var new-type: (handle typeinfo) = find-or-create-typeinfo(type-id)
    #       assert(next-word(line) == "{")
    #       populate-mu-type(in, new-type)
    #     else
    #       abort()
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # var line/ecx: (stream byte 512)
    81 5/subop/subtract %esp 0x200/imm32
    68/push 0x200/imm32/length
    68/push 0/imm32/read
    68/push 0/imm32/write
    89/<- %ecx 4/r32/esp
    # var word-slice/edx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %edx 4/r32/esp
    # var curr-function/edi: (addr (handle function))
    bf/copy-to-edi _Program-functions/imm32
    # var curr-type/esi: (addr (handle typeinfo))
    be/copy-to-esi _Program-types/imm32
    # var vars/ebx: (stack (addr var) 256)
    81 5/subop/subtract %esp 0x400/imm32
    68/push 0x400/imm32/length
    68/push 0/imm32/top
    89/<- %ebx 4/r32/esp
    {
$parse-mu:line-loop:
      (clear-stream %ecx)
      (read-line-buffered *(ebp+8) %ecx)
      # if (line->write == 0) break
      81 7/subop/compare *ecx 0/imm32
      0f 84/jump-if-= break/disp32
#?       # dump line {{{
#?       (write 2 "parse-mu: ^")
#?       (write-stream 2 %ecx)
#?       (write 2 "$\n")
#?       (rewind-stream %ecx)
#?       # }}}
      (next-mu-token %ecx %edx)
      # if slice-empty?(word-slice) continue
      (slice-empty? %edx)
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= loop/disp32
      # if (*word-slice->start == "#") continue
      # . eax = *word-slice->start
      8b/-> *edx 0/r32/eax
      8a/copy-byte *eax 0/r32/AL
      81 4/subop/and %eax 0xff/imm32
      # . if (eax == '#') continue
      3d/compare-eax-and 0x23/imm32/hash
      0f 84/jump-if-= loop/disp32
      # if (slice-equal?(word-slice, "fn")) parse a function
      {
$parse-mu:fn:
        (slice-equal? %edx "fn")
        3d/compare-eax-and 0/imm32/false
        0f 84/jump-if-= break/disp32
        # var new-function/eax: (handle function) = populate-mu-function(line, in, vars)
        (allocate Heap *Function-size)  # => eax
        (zero-out %eax *Function-size)
        (clear-stack %ebx)
        (populate-mu-function-header %ecx %eax %ebx)
        (populate-mu-function-body *(ebp+8) %eax %ebx)
        # *curr-function = new-function
        89/<- *edi 0/r32/eax
        # curr-function = &new-function->next
        8d/address-> *(eax+0x14) 7/r32/edi  # Function-next
        e9/jump $parse-mu:line-loop/disp32
      }
      # if (slice-equal?(word-slice, "type")) parse a type (struct/record) definition
      {
$parse-mu:type:
        (slice-equal? %edx "type")
        3d/compare-eax-and 0/imm32
        0f 84/jump-if-= break/disp32
        (next-mu-token %ecx %edx)
        # var type-id/eax: int
        (pos-or-insert-slice Type-id %edx)  # => eax
        # var new-type/eax: (handle typeinfo)
        (find-or-create-typeinfo %eax)  # => eax
        # TODO: ensure that 'line' has nothing else but '{'
        (populate-mu-type *(ebp+8) %eax)  # => eax
        e9/jump $parse-mu:line-loop/disp32
      }
      # otherwise abort
      e9/jump $parse-mu:error1/disp32
    } # end line loop
$parse-mu:end:
    # . reclaim locals
    81 0/subop/add %esp 0x630/imm32
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-mu:error1:
    # error("unexpected top-level command: " word-slice "\n")
    (write-buffered Stderr "unexpected top-level command: ")
    (write-slice-buffered Stderr %edx)
    (write-buffered Stderr "\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$parse-mu:error2:
    # error(vars->top " vars not reclaimed after fn '" new-function->name "'\n")
    (print-int32-buffered Stderr *ebx)
    (write-buffered Stderr " vars not reclaimed after fn '")
    (write-slice-buffered Stderr *eax)  # Function-name
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# scenarios considered:
# ✗ fn foo  # no block
# ✓ fn foo {
# ✗ fn foo { {
# ✗ fn foo { }
# ✗ fn foo { } {
# ✗ fn foo x {
# ✗ fn foo x: {
# ✓ fn foo x: int {
# ✓ fn foo x: int {
# ✓ fn foo x: int -> y/eax: int {
populate-mu-function-header:  # first-line: (addr stream byte), out: (handle function), vars: (addr stack (handle var))
    # pseudocode:
    #   var name: slice
    #   next-mu-token(first-line, name)
    #   assert(name not in '{' '}' '->')
    #   out->name = slice-to-string(name)
    #   var next-offset: int = 8
    #   ## inouts
    #   while true
    #     ## name
    #     name = next-mu-token(first-line)
    #     if (name == '{') goto done
    #     if (name == '->') break
    #     assert(name != '}')
    #     var v: (handle var) = parse-var-with-type(name, first-line)
    #     assert(v->register == null)
    #     v->stack-offset = next-offset
    #     next-offset += size-of(v)
    #     # v->block-depth is implicitly 0
    #     out->inouts = append(out->inouts, v)
    #     push(vars, v)
    #   ## outputs
    #   while true
    #     ## name
    #     name = next-mu-token(first-line)
    #     assert(name not in '{' '}' '->')
    #     var v: (handle var) = parse-var-with-type(name, first-line)
    #     assert(v->register != null)
    #     out->outputs = append(out->outputs, v)
    #   done:
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    57/push-edi
    # edi = out
    8b/-> *(ebp+0xc) 7/r32/edi
    # var word-slice/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # var next-offset/edx = 8
    ba/copy-to-edx 8/imm32
    # read function name
    (next-mu-token *(ebp+8) %ecx)
    # error checking
    # TODO: error if name starts with 'break' or 'loop'
    # if (word-slice == '{') abort
    (slice-equal? %ecx "{")   # => eax
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
    # if (word-slice == '->') abort
    (slice-equal? %ecx "->")   # => eax
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
    # if (word-slice == '}') abort
    (slice-equal? %ecx "}")   # => eax
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
    # save function name
    (slice-to-string Heap %ecx)  # => eax
    89/<- *edi 0/r32/eax  # Function-name
    # initialize default subx-name as well
    89/<- *(edi+4) 0/r32/eax  # Function-subx-name
    # save function inouts
    {
$populate-mu-function-header:check-for-inout:
      (next-mu-token *(ebp+8) %ecx)
      # if (word-slice == '{') goto done
      (slice-equal? %ecx "{")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= $populate-mu-function-header:done/disp32
      # if (word-slice == '->') break
      (slice-equal? %ecx "->")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= break/disp32
      # if (word-slice == '}') abort
      (slice-equal? %ecx "}")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
      # var v/ebx: (handle var) = parse-var-with-type(word-slice, first-line)
      (parse-var-with-type %ecx *(ebp+8))  # => eax
      89/<- %ebx 0/r32/eax
      # assert(v->register == null)
      81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
      0f 85/jump-if-!= $populate-mu-function-header:error2/disp32
      # v->stack-offset = next-offset
      89/<- *(ebx+0xc) 2/r32/edx  # Var-offset
      # next-offset += size-of(v)
      (size-of %ebx)  # => eax
      01/add %edx 0/r32/eax
      # v->block-depth is implicitly 0
      #
      # out->inouts = append(out->inouts, v)
      (append-list Heap %ebx *(edi+8))  # Function-inouts => eax
      89/<- *(edi+8) 0/r32/eax  # Function-inouts
      # push(vars, v)
      (push *(ebp+0x10) %ebx)
      #
      e9/jump loop/disp32
    }
    # save function outputs
    {
$populate-mu-function-header:check-for-out:
      (next-mu-token *(ebp+8) %ecx)
      # if (word-slice == '{') break
      (slice-equal? %ecx "{")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= break/disp32
      # if (word-slice == '->') abort
      (slice-equal? %ecx "->")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
      # if (word-slice == '}') abort
      (slice-equal? %ecx "}")   # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= $populate-mu-function-header:error1/disp32
      #
      (parse-var-with-type %ecx *(ebp+8))  # => eax
      89/<- %ebx 0/r32/eax
      # assert(var->register != null)
      81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
      0f 84/jump-if-= $populate-mu-function-header:error3/disp32
      (append-list Heap %ebx *(edi+0xc))  # Function-outputs => eax
      89/<- *(edi+0xc) 0/r32/eax  # Function-outputs
      e9/jump loop/disp32
    }
$populate-mu-function-header:done:
    (check-no-tokens-left *(ebp+8))
$populate-mu-function-header:end:
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$populate-mu-function-header:error1:
    # error("function header not in form 'fn <name> {'")
    (write-buffered Stderr "function header not in form 'fn <name> [inouts] [-> outputs] {' -- '")
    (flush Stderr)
    (rewind-stream *(ebp+8))
    (write-stream 2 *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$populate-mu-function-header:error2:
    # error("function input '" var "' cannot be in a register")
    (write-buffered Stderr "function input '")
    (write-buffered Stderr *ebx)  # Var-name
    (write-buffered Stderr "' cannot be in a register")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$populate-mu-function-header:error3:
    # error("function input '" var "' must be in a register")
    (write-buffered Stderr "function input '")
    (write-buffered Stderr *eax)  # Var-name
    (write-buffered Stderr " must be in a register'")
    (flush Stderr)
    (rewind-stream *(ebp+8))
    (write-stream 2 *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

test-function-header-with-arg:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "foo n: int {\n")
    # var result/ecx: function
    2b/subtract-> *Function-size 4/r32/esp
    89/<- %ecx 4/r32/esp
    (zero-out %ecx *Function-size)
    # var vars/ebx: (stack (addr var) 16)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ebx 4/r32/esp
    # convert
    (populate-mu-function-header _test-input-stream %ecx %ebx)
    # check result
    (check-strings-equal *ecx "foo" "F - test-function-header-with-arg/name")  # Function-name
    # edx: (handle list var) = result->inouts
    8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
    # ebx: (handle var) = result->inouts->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "n" "F - test-function-header-with-arg/inout:0")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-arg/inout:0/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-arg/inout:0/type:1")  # Tree-right
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-function-header-with-multiple-args:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "foo a: int, b: int c: int {\n")
    # result/ecx: (handle function)
    2b/subtract-> *Function-size 4/r32/esp
    89/<- %ecx 4/r32/esp
    (zero-out %ecx *Function-size)
    # var vars/ebx: (stack (addr var) 16)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ebx 4/r32/esp
    # convert
    (populate-mu-function-header _test-input-stream %ecx %ebx)
    # check result
    (check-strings-equal *ecx "foo")  # Function-name
    # edx: (handle list var) = result->inouts
    8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
$test-function-header-with-multiple-args:inout0:
    # ebx: (handle var) = result->inouts->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args/inout:0")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args/inout:0/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args/inout:0/type:1")  # Tree-right
    # edx = result->inouts->next
    8b/-> *(edx+4) 2/r32/edx  # List-next
$test-function-header-with-multiple-args:inout1:
    # ebx = result->inouts->next->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args/inout:1")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args/inout:1/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args/inout:1/type:1")  # Tree-right
    # edx = result->inouts->next->next
    8b/-> *(edx+4) 2/r32/edx  # List-next
$test-function-header-with-multiple-args:inout2:
    # ebx = result->inouts->next->next->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args/inout:2")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args/inout:2/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args/inout:2/type:1")  # Tree-right
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-function-with-multiple-args-and-outputs:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "foo a: int, b: int, c: int -> x/ecx: int y/edx: int {\n")
    # result/ecx: (handle function)
    2b/subtract-> *Function-size 4/r32/esp
    89/<- %ecx 4/r32/esp
    (zero-out %ecx *Function-size)
    # var vars/ebx: (stack (addr var) 16)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ebx 4/r32/esp
    # convert
    (populate-mu-function-header _test-input-stream %ecx %ebx)
    # check result
    (check-strings-equal *ecx "foo")  # Function-name
    # edx: (handle list var) = result->inouts
    8b/-> *(ecx+8) 2/r32/edx  # Function-inouts
    # ebx: (handle var) = result->inouts->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "a" "F - test-function-header-with-multiple-args-and-outputs/inout:0")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args-and-outputs/inout:0/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args-and-outputs/inout:0/type:1")  # Tree-right
    # edx = result->inouts->next
    8b/-> *(edx+4) 2/r32/edx  # List-next
    # ebx = result->inouts->next->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "b" "F - test-function-header-with-multiple-args-and-outputs/inout:1")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args-and-outputs/inout:1/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args-and-outputs/inout:1/type:1")  # Tree-right
    # edx = result->inouts->next->next
    8b/-> *(edx+4) 2/r32/edx  # List-next
    # ebx = result->inouts->next->next->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "c" "F - test-function-header-with-multiple-args-and-outputs/inout:2")  # Var-name
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args-and-outputs/inout:2/type:0")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args-and-outputs/inout:2/type:1")  # Tree-right
    # edx: (handle list var) = result->outputs
    8b/-> *(ecx+0xc) 2/r32/edx  # Function-outputs
    # ebx: (handle var) = result->outputs->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "x" "F - test-function-header-with-multiple-args-and-outputs/output:0")  # Var-name
    (check-strings-equal *(ebx+0x10) "ecx" "F - test-function-header-with-multiple-args-and-outputs/output:0/register")  # Var-register
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args-and-outputs/output:0/type:1")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args-and-outputs/output:0/type:1")  # Tree-right
    # edx = result->outputs->next
    8b/-> *(edx+4) 2/r32/edx  # List-next
    # ebx = result->outputs->next->value
    8b/-> *edx 3/r32/ebx  # List-value
    (check-strings-equal *ebx "y" "F - test-function-header-with-multiple-args-and-outputs/output:1")  # Var-name
    (check-strings-equal *(ebx+0x10) "edx" "F - test-function-header-with-multiple-args-and-outputs/output:0/register")  # Var-register
    8b/-> *(ebx+4) 3/r32/ebx  # Var-type
    (check-ints-equal *ebx 1 "F - test-function-header-with-multiple-args-and-outputs/output:1/type:1")  # Tree-left
    (check-ints-equal *(ebx+4) 0 "F - test-function-header-with-multiple-args-and-outputs/output:1/type:1")  # Tree-right
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# format for variables with types
#   x: int
#   x: int,
#   x/eax: int
#   x/eax: int,
# ignores at most one trailing comma
# WARNING: modifies name
parse-var-with-type:  # name: (addr slice), first-line: (addr stream byte) -> result/eax: (handle var)
    # pseudocode:
    #   var v: (handle var) = allocate(Heap, Var-size)
    #   var s: slice
    #   if (!slice-ends-with(name, ":"))
    #     abort
    #   --name->end to skip ':'
    #   next-token-from-slice(name->start, name->end, '/', s)
    #   v->name = slice-to-string(s)
    #   ## register
    #   next-token-from-slice(s->end, name->end, '/', s)
    #   if (!slice-empty?(s))
    #     v->register = slice-to-string(s)
    #   ## type
    #   var type: (handle tree type-id) = parse-type(first-line)
    #   v->type = type
    #   return v
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # esi = name
    8b/-> *(ebp+8) 6/r32/esi
    # if (!slice-ends-with?(name, ":")) abort
    8b/-> *(esi+4) 1/r32/ecx  # Slice-end
    49/decrement-ecx
    8a/copy-byte *ecx 1/r32/CL
    81 4/subop/and %ecx 0xff/imm32
    81 7/subop/compare %ecx 0x3a/imm32/colon
    0f 85/jump-if-!= $parse-var-with-type:abort/disp32
    # --name->end to skip ':'
    ff 1/subop/decrement *(esi+4)
    # var result/edi: (handle var) = allocate(Heap, Var-size)
    (allocate Heap *Var-size)  # => eax
    (zero-out %eax *Var-size)
    89/<- %edi 0/r32/eax
    # var s/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
$parse-var-with-type:save-name:
    # save v->name
    (next-token-from-slice *esi *(esi+4) 0x2f %ecx)  # Slice-start, Slice-end, '/'
    # . end/edx = s->end
    8b/-> *(ecx+4) 2/r32/edx
$parse-var-with-type:write-name:
    (slice-to-string Heap %ecx)  # => eax
    89/<- *edi 0/r32/eax  # Var-name
    # save v->register
$parse-var-with-type:save-register:
    (next-token-from-slice %edx *(esi+4) 0x2f %ecx)  # end, name->end, '/'
    # if (!slice-empty?(s)) v->register = slice-to-string(s)
    {
$parse-var-with-type:write-register:
      (slice-empty? %ecx)  # => eax
      3d/compare-eax-and 0/imm32/false
      75/jump-if-!= break/disp8
      (slice-to-string Heap %ecx)
      89/<- *(edi+0x10) 0/r32/eax  # Var-register
    }
$parse-var-with-type:save-type:
    (parse-type Heap *(ebp+0xc))  # => eax
    89/<- *(edi+4) 0/r32/eax  # Var-type
$parse-var-with-type:end:
    # return result
    89/<- %eax 7/r32/edi
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-var-with-type:abort:
    # error("var should have form 'name: type' in '" line "'\n")
    (write-buffered Stderr "var should have form 'name: type' in '")
    (flush Stderr)
    (rewind-stream *(ebp+0xc))
    (write-stream 2 *(ebp+0xc))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

parse-type:  # ad: (address allocation-descriptor), in: (addr stream byte) -> result/eax: (handle tree type-id)
    # pseudocode:
    #   var s: slice = next-mu-token(in)
    #   assert s != ""
    #   assert s != "->"
    #   assert s != "{"
    #   assert s != "}"
    #   if s == ")"
    #     return 0
    #   result = allocate(Tree)
    #   zero-out(result, *Tree-size)
    #   if s != "("
    #     result->left = pos-or-insert-slice(Type-id, s)
    #     return
    #   result->left = parse-type(ad, in)
    #   result->right = parse-type-tree(ad, in)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # var s/ecx: slice
    68/push 0/imm32
    68/push 0/imm32
    89/<- %ecx 4/r32/esp
    # s = next-mu-token(in)
    (next-mu-token *(ebp+0xc) %ecx)
#?     (write-buffered Stderr "tok: ")
#?     (write-slice-buffered Stderr %ecx)
#?     (write-buffered Stderr "$\n")
#?     (flush Stderr)
    # assert s != ""
    (slice-equal? %ecx "")
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $parse-type:abort/disp32
    # assert s != "{"
    (slice-equal? %ecx "{")
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $parse-type:abort/disp32
    # assert s != "}"
    (slice-equal? %ecx "}")
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $parse-type:abort/disp32
    # assert s != "->"
    (slice-equal? %ecx "->")
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $parse-type:abort/disp32
    # if (s == ")") return 0
    (slice-equal? %ecx ")")
    3d/compare-eax-and 0/imm32/false
    b8/copy-to-eax 0/imm32
    0f 85/jump-if-!= $parse-type:end/disp32
    # var result/edx: (handle tree type-id)
    (allocate *(ebp+8) *Tree-size)  # => eax
    (zero-out %eax *Tree-size)
    89/<- %edx 0/r32/eax
    {
      # if (s != "(") break
      (slice-equal? %ecx "(")
      3d/compare-eax-and 0/imm32/false
      75/jump-if-!= break/disp8
      # result->left = pos-or-insert-slice(Type-id, s)
      (pos-or-insert-slice Type-id %ecx)  # => eax
#?       (write-buffered Stderr "=> {")
#?       (print-int32-buffered Stderr %eax)
#?       (write-buffered Stderr ", 0}\n")
#?       (flush Stderr)
      89/<- *edx 0/r32/eax  # Tree-left
      e9/jump $parse-type:return-edx/disp32
    }
    # otherwise s == "("
    # result->left = parse-type(ad, in)
    (parse-type *(ebp+8) *(ebp+0xc))
#?     (write-buffered Stderr "=> {")
#?     (print-int32-buffered Stderr %eax)
    89/<- *edx 0/r32/eax  # Tree-left
    # result->right = parse-type-tree(ad, in)
    (parse-type-tree *(ebp+8) *(ebp+0xc))
#?     (write-buffered Stderr Space)
#?     (print-int32-buffered Stderr %eax)
#?     (write-buffered Stderr "}\n")
#?     (flush Stderr)
    89/<- *(edx+4) 0/r32/eax  # Tree-right
$parse-type:return-edx:
    89/<- %eax 2/r32/edx
$parse-type:end:
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-type:abort:
    # error("unexpected token when parsing type: '" s "'\n")
    (write-buffered Stderr "unexpected token when parsing type: '")
    (write-slice-buffered Stderr %ecx)
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

parse-type-tree:  # ad: (address allocation-descriptor), in: (addr stream byte) -> result/eax: (handle tree type-id)
    # pseudocode:
    #   var tmp: (handle tree type-id) = parse-type(ad, in)
    #   if tmp == 0
    #     return 0
    #   result = allocate(Tree)
    #   zero-out(result, *Tree-size)
    #   result->left = tmp
    #   result->right = parse-type-tree(ad, in)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # var tmp/eax: (handle tree type-id) = parse-type(ad, in)
    (parse-type *(ebp+8) *(ebp+0xc))
    # if (tmp == 0) return tmp
    3d/compare-eax-and 0/imm32
    74/jump-if-= $parse-type-tree:end/disp8
    # var tmp2/ecx = tmp
    89/<- %ecx 0/r32/eax
    # var result/edx: (handle tree type-id)
    (allocate *(ebp+8) *Tree-size)  # => eax
    (zero-out %eax *Tree-size)
    89/<- %edx 0/r32/eax
    # result->left = tmp2
    89/<- *edx 1/r32/ecx  # Tree-left
    # result->right = parse-type-tree(ad, in)
    (parse-type-tree *(ebp+8) *(ebp+0xc))
    89/<- *(edx+4) 0/r32/eax  # Tree-right
$parse-type-tree:return-edx:
    89/<- %eax 2/r32/edx
$parse-type-tree:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

next-mu-token:  # in: (addr stream byte), out: (addr slice)
    # pseudocode:
    # start:
    #   skip-chars-matching-whitespace(in)
    #   if in->read >= in->write              # end of in
    #     out = {0, 0}
    #     return
    #   out->start = &in->data[in->read]
    #   var curr-byte/eax: byte = in->data[in->read]
    #   if curr->byte == ','                  # comment token
    #     ++in->read
    #     goto start
    #   if curr-byte == '#'                   # comment
    #     goto done                             # treat as eof
    #   if curr-byte == '"'                   # string literal
    #     skip-string(in)
    #     goto done                           # no metadata
    #   if curr-byte == '('
    #     ++in->read
    #     goto done
    #   if curr-byte == ')'
    #     ++in->read
    #     goto done
    #   # read a word
    #   while true
    #     if in->read >= in->write
    #       break
    #     curr-byte = in->data[in->read]
    #     if curr-byte == ' '
    #       break
    #     if curr-byte == '\r'
    #       break
    #     if curr-byte == '\n'
    #       break
    #     if curr-byte == '('
    #       break
    #     if curr-byte == ')'
    #       break
    #     if curr-byte == ','
    #       break
    #     ++in->read
    # done:
    #   out->end = &in->data[in->read]
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    57/push-edi
    # esi = in
    8b/-> *(ebp+8) 6/r32/esi
    # edi = out
    8b/-> *(ebp+0xc) 7/r32/edi
$next-mu-token:start:
    (skip-chars-matching-whitespace %esi)
$next-mu-token:check0:
    # if (in->read >= in->write) return out = {0, 0}
    # . ecx = in->read
    8b/-> *(esi+4) 1/r32/ecx
    # . if (ecx >= in->write) return out = {0, 0}
    3b/compare 1/r32/ecx *esi
    c7 0/subop/copy *edi 0/imm32
    c7 0/subop/copy *(edi+4) 0/imm32
    0f 8d/jump-if->= $next-mu-token:end/disp32
    # out->start = &in->data[in->read]
    8d/copy-address *(esi+ecx+0xc) 0/r32/eax
    89/<- *edi 0/r32/eax
    # var curr-byte/eax: byte = in->data[in->read]
    31/xor %eax 0/r32/eax
    8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
    {
$next-mu-token:check-for-comma:
      # if (curr-byte != ',') break
      3d/compare-eax-and 0x2c/imm32/comma
      75/jump-if-!= break/disp8
      # ++in->read
      ff 0/subop/increment *(esi+4)
      # restart
      e9/jump $next-mu-token:start/disp32
    }
    {
$next-mu-token:check-for-comment:
      # if (curr-byte != '#') break
      3d/compare-eax-and 0x23/imm32/pound
      75/jump-if-!= break/disp8
      # return eof
      e9/jump $next-mu-token:done/disp32
    }
    {
$next-mu-token:check-for-string-literal:
      # if (curr-byte != '"') break
      3d/compare-eax-and 0x22/imm32/dquote
      75/jump-if-!= break/disp8
      (skip-string %esi)
      # return
      e9/jump $next-mu-token:done/disp32
    }
    {
$next-mu-token:check-for-open-paren:
      # if (curr-byte != '(') break
      3d/compare-eax-and 0x28/imm32/open-paren
      75/jump-if-!= break/disp8
      # ++in->read
      ff 0/subop/increment *(esi+4)
      # return
      e9/jump $next-mu-token:done/disp32
    }
    {
$next-mu-token:check-for-close-paren:
      # if (curr-byte != ')') break
      3d/compare-eax-and 0x29/imm32/close-paren
      75/jump-if-!= break/disp8
      # ++in->read
      ff 0/subop/increment *(esi+4)
      # return
      e9/jump $next-mu-token:done/disp32
    }
    {
$next-mu-token:regular-word-without-metadata:
      # if (in->read >= in->write) break
      # . ecx = in->read
      8b/-> *(esi+4) 1/r32/ecx
      # . if (ecx >= in->write) break
      3b/compare *esi 1/r32/ecx
      7d/jump-if->= break/disp8
      # var c/eax: byte = in->data[in->read]
      31/xor %eax 0/r32/eax
      8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
      # if (c == ' ') break
      3d/compare-eax-and 0x20/imm32/space
      74/jump-if-= break/disp8
      # if (c == '\r') break
      3d/compare-eax-and 0xd/imm32/carriage-return
      74/jump-if-= break/disp8
      # if (c == '\n') break
      3d/compare-eax-and 0xa/imm32/newline
      74/jump-if-= break/disp8
      # if (c == '(') break
      3d/compare-eax-and 0x28/imm32/open-paren
      0f 84/jump-if-= break/disp32
      # if (c == ')') break
      3d/compare-eax-and 0x29/imm32/close-paren
      0f 84/jump-if-= break/disp32
      # if (c == ',') break
      3d/compare-eax-and 0x2c/imm32/comma
      0f 84/jump-if-= break/disp32
      # ++in->read
      ff 0/subop/increment *(esi+4)
      #
      e9/jump loop/disp32
    }
$next-mu-token:done:
    # out->end = &in->data[in->read]
    8b/-> *(esi+4) 1/r32/ecx
    8d/copy-address *(esi+ecx+0xc) 0/r32/eax
    89/<- *(edi+4) 0/r32/eax
$next-mu-token:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

pos-or-insert-slice:  # arr: (addr stream (handle array byte)), s: (addr slice) -> index/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # if (pos-slice(arr, s) != -1) return it
    (pos-slice *(ebp+8) *(ebp+0xc))  # => eax
    3d/compare-eax-and -1/imm32
    75/jump-if-not-equal $pos-or-insert-slice:end/disp8
    #
    (slice-to-string Heap *(ebp+0xc))  # => eax
    (write-int *(ebp+8) %eax)
    (pos-slice *(ebp+8) *(ebp+0xc))  # => eax
$pos-or-insert-slice:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# return the index in an array of strings matching 's', -1 if not found
# index is denominated in elements, not bytes
pos-slice:  # arr: (addr stream (handle array byte)), s: (addr slice) -> index/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
#?     (write-buffered Stderr "pos-slice: ")
#?     (write-slice-buffered Stderr *(ebp+0xc))
#?     (write-buffered Stderr "\n")
#?     (flush Stderr)
    # esi = arr
    8b/-> *(ebp+8) 6/r32/esi
    # var index/ecx: int = 0
    b9/copy-to-ecx 0/imm32
    # var curr/edx: (addr (addr array byte)) = arr->data
    8d/copy-address *(esi+0xc) 2/r32/edx
    # var max/ebx: (addr (addr array byte)) = &arr->data[arr->write]
    8b/-> *esi 3/r32/ebx
    8d/copy-address *(esi+ebx+0xc) 3/r32/ebx
    {
#?       (write-buffered Stderr "  ")
#?       (print-int32-buffered Stderr %ecx)
#?       (write-buffered Stderr "\n")
#?       (flush Stderr)
      # if (curr >= max) return -1
      39/compare %edx 3/r32/ebx
      b8/copy-to-eax -1/imm32
      73/jump-if-addr>= $pos-slice:end/disp8
      # if (slice-equal?(s, *curr)) break
      (slice-equal? *(ebp+0xc) *edx)  # => eax
      3d/compare-eax-and 0/imm32/false
      75/jump-if-!= break/disp8
      # ++index
      41/increment-ecx
      # curr += 4
      81 0/subop/add %edx 4/imm32
      #
      eb/jump loop/disp8
    }
    # return index
    89/<- %eax 1/r32/ecx
$pos-slice:end:
#?     (write-buffered Stderr "=> ")
#?     (print-int32-buffered Stderr %eax)
#?     (write-buffered Stderr "\n")
    # . restore registers
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-var-with-type:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "x:"
    b8/copy-to-eax "x:"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    # _test-input-stream contains "int"
    (clear-stream _test-input-stream)
    (write _test-input-stream "int")
    #
    (parse-var-with-type %ecx _test-input-stream)
    8b/-> *eax 2/r32/edx  # Var-name
    (check-strings-equal %edx "x" "F - test-var-with-type/name")
    8b/-> *(eax+4) 2/r32/edx  # Var-type
    (check-ints-equal *edx 1 "F - test-var-with-type/type")
    (check-ints-equal *(edx+4) 0 "F - test-var-with-type/type")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-var-with-type-and-register:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "x/eax:"
    b8/copy-to-eax "x/eax:"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    # _test-input-stream contains "int"
    (clear-stream _test-input-stream)
    (write _test-input-stream "int")
    #
    (parse-var-with-type %ecx _test-input-stream)
    8b/-> *eax 2/r32/edx  # Var-name
    (check-strings-equal %edx "x" "F - test-var-with-type-and-register/name")
    8b/-> *(eax+0x10) 2/r32/edx  # Var-register
    (check-strings-equal %edx "eax" "F - test-var-with-type-and-register/register")
    8b/-> *(eax+4) 2/r32/edx  # Var-type
    (check-ints-equal *edx 1 "F - test-var-with-type-and-register/type")
    (check-ints-equal *(edx+4) 0 "F - test-var-with-type-and-register/type")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-var-with-trailing-characters:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "x:"
    b8/copy-to-eax "x:"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    # _test-input-stream contains "int,"
    (clear-stream _test-input-stream)
    (write _test-input-stream "int,")
    #
    (parse-var-with-type %ecx _test-input-stream)
    8b/-> *eax 2/r32/edx  # Var-name
    (check-strings-equal %edx "x" "F - test-var-with-trailing-characters/name")
    8b/-> *(eax+0x10) 2/r32/edx  # Var-register
    (check-ints-equal %edx 0 "F - test-var-with-trailing-characters/register")
    8b/-> *(eax+4) 2/r32/edx  # Var-type
    (check-ints-equal *edx 1 "F - test-var-with-trailing-characters/type")
    (check-ints-equal *(edx+4) 0 "F - test-var-with-trailing-characters/type")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-var-with-register-and-trailing-characters:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "x/eax:"
    b8/copy-to-eax "x/eax:"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    # _test-input-stream contains "int,"
    (clear-stream _test-input-stream)
    (write _test-input-stream "int,")
    #
    (parse-var-with-type %ecx _test-input-stream)
    8b/-> *eax 2/r32/edx  # Var-name
    (check-strings-equal %edx "x" "F - test-var-with-register-and-trailing-characters/name")
    8b/-> *(eax+0x10) 2/r32/edx  # Var-register
    (check-strings-equal %edx "eax" "F - test-var-with-register-and-trailing-characters/register")
    8b/-> *(eax+4) 2/r32/edx  # Var-type
    (check-ints-equal *edx 1 "F - test-var-with-register-and-trailing-characters/type")
    (check-ints-equal *(edx+4) 0 "F - test-var-with-register-and-trailing-characters/type")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-var-with-compound-type:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "x:"
    b8/copy-to-eax "x:"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    # _test-input-stream contains "(addr int)"
    (clear-stream _test-input-stream)
    (write _test-input-stream "(addr int)")
    #
    (parse-var-with-type %ecx _test-input-stream)
    8b/-> *eax 2/r32/edx  # Var-name
    (check-strings-equal %edx "x" "F - test-var-with-compound-type/name")
    8b/-> *(eax+0x10) 2/r32/edx  # Var-register
    (check-ints-equal %edx 0 "F - test-var-with-compound-type/register")
    # var type/edx: (handle tree type-id) = var->type
    8b/-> *(eax+4) 2/r32/edx  # Var-type
    # type->left == atom(addr)
    8b/-> *edx 0/r32/eax  # Atom-value
    (check-ints-equal *eax 2 "F - test-var-with-compound-type/type:0")  # Tree-left
    # type->right->left == atom(int)
    8b/-> *(edx+4) 2/r32/edx  # Tree-right
    8b/-> *edx 0/r32/eax  # Tree-left
    (check-ints-equal *eax 1 "F - test-var-with-compound-type/type:1")  # Atom-value
    # type->right->right == null
    (check-ints-equal *(edx+4) 0 "F - test-var-with-compound-type/type:2")  # Tree-right
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# identifier starts with a letter or '$' or '_'
# no constraints at the moment on later letters
# all we really want to do so far is exclude '{', '}' and '->'
is-identifier?:  # in: (addr slice) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # if (slice-empty?(in)) return false
    (slice-empty? *(ebp+8))  # => eax
    3d/compare-eax-and 0/imm32/false
    75/jump-if-!= $is-identifier?:false/disp8
    # var c/eax: byte = *in->start
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *eax 0/r32/eax
    8a/copy-byte *eax 0/r32/AL
    81 4/subop/and %eax 0xff/imm32
    # if (c == '$') return true
    3d/compare-eax-and 0x24/imm32/$
    74/jump-if-= $is-identifier?:true/disp8
    # if (c == '_') return true
    3d/compare-eax-and 0x5f/imm32/_
    74/jump-if-= $is-identifier?:true/disp8
    # drop case
    25/and-eax-with 0x5f/imm32
    # if (c < 'A') return false
    3d/compare-eax-and 0x41/imm32/A
    7c/jump-if-< $is-identifier?:false/disp8
    # if (c > 'Z') return false
    3d/compare-eax-and 0x5a/imm32/Z
    7f/jump-if-> $is-identifier?:false/disp8
    # otherwise return true
$is-identifier?:true:
    b8/copy-to-eax 1/imm32/true
    eb/jump $is-identifier?:end/disp8
$is-identifier?:false:
    b8/copy-to-eax 0/imm32/false
$is-identifier?:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-dollar:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "$a"
    b8/copy-to-eax "$a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-dollar")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-underscore:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "_a"
    b8/copy-to-eax "_a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-underscore")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-a:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "a$"
    b8/copy-to-eax "a$"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-a")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-z:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "z$"
    b8/copy-to-eax "z$"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-z")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-A:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "A$"
    b8/copy-to-eax "A$"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-A")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-Z:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "Z$"
    b8/copy-to-eax "Z$"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 1 "F - test-is-identifier-Z")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-@:
    # character before 'A' is invalid
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "@a"
    b8/copy-to-eax "@a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-@")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-square-bracket:
    # character after 'Z' is invalid
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "[a"
    b8/copy-to-eax "[a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-@")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-backtick:
    # character before 'a' is invalid
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "`a"
    b8/copy-to-eax "`a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-backtick")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-curly-brace-open:
    # character after 'z' is invalid; also used for blocks
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "{a"
    b8/copy-to-eax "{a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-open")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-curly-brace-close:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "}a"
    b8/copy-to-eax "}a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-curly-brace-close")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-is-identifier-hyphen:
    # disallow leading '-' since '->' has special meaning
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # (eax..ecx) = "-a"
    b8/copy-to-eax "-a"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (is-identifier? %ecx)
    (check-ints-equal %eax 0 "F - test-is-identifier-hyphen")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

populate-mu-function-body:  # in: (addr buffered-file), out: (handle function), vars: (addr stack (handle var))
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    56/push-esi
    57/push-edi
    # esi = in
    8b/-> *(ebp+8) 6/r32/esi
    # edi = out
    8b/-> *(ebp+0xc) 7/r32/edi
    # initialize some global state
    c7 0/subop/copy *Curr-block-depth 1/imm32
    c7 0/subop/copy *Next-local-stack-offset -4/imm32
    # var eax: (handle block) = parse-mu-block(in, vars, fn)
    (parse-mu-block %esi *(ebp+0x10) %edi)  # => eax
    # out->body = eax
    89/<- *(edi+0x10) 0/r32/eax  # Function-body
$populate-mu-function-body:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

== data

# Global state added to each var record when parsing a function

Curr-block-depth:  # (addr int)
    0/imm32
Next-local-stack-offset:  # (addr int)
    -4/imm32

Next-block-index:  # (addr int)
    1/imm32

== code

# parses a block, assuming that the leading '{' has already been read by the caller
parse-mu-block:  # in: (addr buffered-file), vars: (addr stack (handle var)), fn: (handle function) -> result/eax: (handle block)
    # pseudocode:
    #   var line: (stream byte 512)
    #   var word-slice: slice
    #   increment *Curr-block-depth
    #   result/eax = allocate(Heap, Stmt-size)
    #   result->tag = 0/block
    #   result->name = some unique name
    #   while true                                  # line loop
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if (line->write == 0) break               # end of file
    #     word-slice = next-mu-token(line)
    #     if slice-empty?(word-slice)               # end of line
    #       continue
    #     else if slice-starts-with?(word-slice, "#")
    #       continue
    #     else if slice-equal?(word-slice, "{")
    #       assert(no-tokens-in(line))
    #       block = parse-mu-block(in, vars, fn)
    #       append-to-block(result, block)
    #     else if slice-equal?(word-slice, "}")
    #       break
    #     else if slice-ends-with?(word-slice, ":")
    #       # TODO: error-check the rest of 'line'
    #       --word-slice->end to skip ':'
    #       named-block = parse-mu-named-block(word-slice, in, vars, fn)
    #       append-to-block(result, named-block)
    #     else if slice-equal?(word-slice, "var")
    #       var-def = parse-mu-var-def(line, vars)
    #       append-to-block(result, var-def)
    #     else
    #       stmt = parse-mu-stmt(line, vars, fn)
    #       append-to-block(result, stmt)
    #   decrement *Curr-block-depth
    #   return result
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    57/push-edi
    # var line/ecx: (stream byte 512)
    81 5/subop/subtract %esp 0x200/imm32
    68/push 0x200/imm32/length
    68/push 0/imm32/read
    68/push 0/imm32/write
    89/<- %ecx 4/r32/esp
    # var word-slice/edx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %edx 4/r32/esp
    # edi = result
    (allocate Heap *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    89/<- %edi 0/r32/eax
    # set result->tag
    c7 0/subop/copy *edi 0/imm32/block  # Stmt-tag
    # set result->var
    (new-block-name *(ebp+0x10))  # => eax
    89/<- *(edi+8) 0/r32/eax  # Block-var
    # push result->var to vars
    (push *(ebp+0xc) %eax)
    # increment *Curr-block-depth
    ff 0/subop/increment *Curr-block-depth
    {
$parse-mu-block:line-loop:
      # line = read-line-buffered(in)
      (clear-stream %ecx)
      (read-line-buffered *(ebp+8) %ecx)
#?       (write-buffered Stderr "line: ")
#?       (write-stream-data Stderr %ecx)
#?       (write-buffered Stderr Newline)
#?       (flush Stderr)
      # if (line->write == 0) break
      81 7/subop/compare *ecx 0/imm32
      0f 84/jump-if-= break/disp32
      # word-slice = next-mu-token(line)
      (next-mu-token %ecx %edx)
#?       (write-buffered Stderr "word: ")
#?       (write-slice-buffered Stderr %edx)
#?       (write-buffered Stderr Newline)
#?       (flush Stderr)
      # if slice-empty?(word-slice) continue
      (slice-empty? %edx)
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= loop/disp32
      # if (slice-starts-with?(word-slice, '#') continue
      # . eax = *word-slice->start
      8b/-> *edx 0/r32/eax
      8a/copy-byte *eax 0/r32/AL
      81 4/subop/and %eax 0xff/imm32
      # . if (eax == '#') continue
      3d/compare-eax-and 0x23/imm32/hash
      0f 84/jump-if-= loop/disp32
      # if slice-equal?(word-slice, "{")
      {
$parse-mu-block:check-for-block:
        (slice-equal? %edx "{")
        3d/compare-eax-and 0/imm32/false
        74/jump-if-= break/disp8
        (check-no-tokens-left %ecx)
        # parse new block and append
        (parse-mu-block *(ebp+8) *(ebp+0xc) *(ebp+0x10))  # => eax
        (append-to-block Heap %edi %eax)
        e9/jump $parse-mu-block:line-loop/disp32
      }
      # if slice-equal?(word-slice, "}") break
$parse-mu-block:check-for-end:
      (slice-equal? %edx "}")
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= break/disp32
      # if slice-ends-with?(word-slice, ":") parse named block and append
      {
$parse-mu-block:check-for-named-block:
        # . eax = *(word-slice->end-1)
        8b/-> *(edx+4) 0/r32/eax
        48/decrement-eax
        8a/copy-byte *eax 0/r32/AL
        81 4/subop/and %eax 0xff/imm32
        # . if (eax != ':') break
        3d/compare-eax-and 0x3a/imm32/colon
        0f 85/jump-if-!= break/disp32
        # TODO: error-check the rest of 'line'
        #
        # skip ':'
        ff 1/subop/decrement *(edx+4)  # Slice-end
        #
        (parse-mu-named-block %edx *(ebp+8) *(ebp+0xc) *(ebp+0x10))  # => eax
        (append-to-block Heap %edi %eax)
        e9/jump $parse-mu-block:line-loop/disp32
      }
      # if slice-equal?(word-slice, "var")
      {
$parse-mu-block:check-for-var:
        (slice-equal? %edx "var")
        3d/compare-eax-and 0/imm32/false
        74/jump-if-= break/disp8
        #
        (parse-mu-var-def %ecx *(ebp+0xc))  # => eax
        (append-to-block Heap %edi %eax)
        e9/jump $parse-mu-block:line-loop/disp32
      }
$parse-mu-block:regular-stmt:
      # otherwise
      (parse-mu-stmt %ecx *(ebp+0xc) *(ebp+0x10))  # => eax
      (append-to-block Heap %edi %eax)
      e9/jump loop/disp32
    } # end line loop
    # decrement *Curr-block-depth
    ff 1/subop/decrement *Curr-block-depth
    #
    (pop *(ebp+0xc))  # => eax
    # return result
    89/<- %eax 7/r32/edi
$parse-mu-block:end:
    # . reclaim locals
    81 0/subop/add %esp 0x214/imm32
    # . restore registers
    5f/pop-to-edi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-mu-block:abort:
    # error("'{' or '}' should be on its own line, but got '")
    (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
    (rewind-stream %ecx)
    (write-stream 2 %ecx)
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

new-block-name:  # fn: (handle function) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # var n/ecx: int = len(fn->name) + 10 for an int + 2 for '$:'
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *eax 0/r32/eax  # Function-name
    8b/-> *eax 0/r32/eax  # String-length
    05/add-to-eax 0xd/imm32  # 10 + 2 for '$:'
    89/<- %ecx 0/r32/eax
    # var name/edx: (stream byte n)
    29/subtract %esp 1/r32/ecx
    ff 6/subop/push %ecx
    68/push 0/imm32/read
    68/push 0/imm32/write
    89/<- %edx 4/r32/esp
    (clear-stream %edx)
    # eax = fn->name
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *eax 0/r32/eax  # Function-name
    # construct result using Next-block-index (and increment it)
    (write %edx "$")
    (write %edx %eax)
    (write %edx ":")
    (print-int32 %edx *Next-block-index)
    ff 0/subop/increment *Next-block-index
    # var s/eax: slice = {name->data, name->data + name->write}  (clobbering edx)
    # . eax = name->write
    8b/-> *edx 0/r32/eax
    # . edx = name->data
    8d/copy-address *(edx+0xc) 2/r32/edx
    # . eax = name->write + name->data
    01/add %eax 2/r32/edx
    # . push {edx, eax}
    ff 6/subop/push %eax
    ff 6/subop/push %edx
    89/<- %eax 4/r32/esp
    # result->var = new literal(s)
    (new-literal Heap %eax)  # => eax
$new-block-name:end:
    # . reclaim locals
    81 0/subop/add %ecx 0xc/imm32  # name.{read/write/len}
    81 0/subop/add %ecx 8/imm32  # slice
    01/add %esp 1/r32/ecx
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

check-no-tokens-left:  # line: (addr stream byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # var s/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    #
    (next-mu-token *(ebp+8) %ecx)
    # if slice-empty?(s) return
    (slice-empty? %ecx)
    3d/compare-eax-and 0/imm32/false
    75/jump-if-!= $check-no-tokens-left:end/disp8
    # if (slice-starts-with?(s, '#') return
    # . eax = *s->start
    8b/-> *edx 0/r32/eax
    8a/copy-byte *eax 0/r32/AL
    81 4/subop/and %eax 0xff/imm32
    # . if (eax == '#') continue
    3d/compare-eax-and 0x23/imm32/hash
    74/jump-if-= $check-no-tokens-left:end/disp8
    # abort
    (write-buffered Stderr "'{' or '}' should be on its own line, but got '")
    (rewind-stream %ecx)
    (write-stream 2 %ecx)
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here
$check-no-tokens-left:end:
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-mu-named-block:  # name: (addr slice), in: (addr buffered-file), vars: (addr stack (handle var)), fn: (handle function) -> result/eax: (handle stmt)
    # pseudocode:
    #   var s: (addr array byte) = slice-to-string(name)
    #   var v: (handle var) = new-var(s, 0)
    #   v->block-depth = *Curr-block-depth  # containing block depth
    #   push(vars, v)
    #   result = parse-mu-block(in, vars, fn)
    #   pop(vars)
    #   result->name = s
    #   return result
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var v/ecx: (handle var)
    (new-literal Heap *(ebp+8))  # => eax
    89/<- %ecx 0/r32/eax
    # push(vars, v)
    (push *(ebp+0x10) %ecx)
    # eax = result
    (parse-mu-block *(ebp+0xc) *(ebp+0x10) *(ebp+0x14))  # => eax
    # pop the var
    50/push-eax
    (pop *(ebp+0x10))  # => eax
    58/pop-to-eax
    # result->tag = named-block
    c7 0/subop/copy *eax 0/imm32/block  # Stmt-tag
    # result->var = v
    89/<- *(eax+8) 1/r32/ecx  # Block-var
$parse-mu-named-block:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-mu-var-def:  # line: (addr stream byte), vars: (addr stack (handle var)) -> result/eax: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # var word-slice/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # var v/edx: (handle var) = parse-var-with-type(line)
    (next-mu-token *(ebp+8) %ecx)
    (parse-var-with-type %ecx *(ebp+8))  # => eax
    89/<- %edx 0/r32/eax
    # v->block-depth = *Curr-block-depth
    8b/-> *Curr-block-depth 0/r32/eax
    89/<- *(edx+8) 0/r32/eax
    #
    (push *(ebp+0xc) %edx)
    # either v has no register and there's no more to this line
    8b/-> *(edx+0x10) 0/r32/eax  # Var-register
    3d/compare-eax-and 0/imm32
    {
      75/jump-if-!= break/disp8
      # v->stack-offset = *Next-local-stack-offset
      8b/-> *Next-local-stack-offset 0/r32/eax
      89/<- *(edx+0xc) 0/r32/eax  # Var-offset
      # TODO: ensure that there's nothing else on this line
      (new-var-def Heap %edx)  # => eax
      eb/jump $parse-mu-var-def:end/disp8
    }
    # or v has a register and there's more to this line
    {
      74/jump-if-= break/disp8
      # ensure that the next word is '<-'
      (next-mu-token *(ebp+8) %ecx)
      (slice-equal? %ecx "<-")  # => eax
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= $parse-mu-var-def:abort/disp8
      #
      (new-reg-var-def Heap %edx)  # => eax
      (add-operation-and-inputs-to-stmt %eax *(ebp+8) *(ebp+0xc))
    }
$parse-mu-var-def:end:
    # *Next-local-stack-offset -= size-of(v)
    50/push-eax
    (size-of %edx)  # => eax
    29/subtract-from *Next-local-stack-offset 0/r32/eax
    58/pop-to-eax
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-mu-var-def:abort:
    (rewind-stream *(ebp+8))
    # error("register variable requires a valid instruction to initialize but got '" line "'\n")
    (write-buffered Stderr "register variable requires a valid instruction to initialize but got '")
    (flush Stderr)
    (write-stream 2 *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

test-parse-mu-var-def:
    # 'var n: int'
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "n: int\n")  # caller has consumed the 'var'
    c7 0/subop/copy *Curr-block-depth 1/imm32
    c7 0/subop/copy *Next-local-stack-offset -4/imm32
    # var vars/ecx: (stack (addr var) 4)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ecx 4/r32/esp
    (clear-stack %ecx)
    # convert
    (parse-mu-var-def _test-input-stream %ecx)  # => eax
    # check result
    (check-ints-equal *eax 2 "F - test-parse-mu-var-def/tag")  # Stmt-tag is var-def
    8b/-> *(eax+4) 0/r32/eax  # Vardef-var
    (check-strings-equal *eax "n" "F - test-parse-mu-var-def/var-name")  # Var-name
    (check-ints-equal *(eax+0x10) 0 "F - test-parse-mu-var-def/var-register")  # Var-register
    (check-ints-equal *(eax+8) 1 "F - test-parse-mu-reg-var-def/output-block-depth")  # Var-block-depth
    (check-ints-equal *(eax+0xc) -4 "F - test-parse-mu-reg-var-def/output-stack-offset")  # Var-offset
    # ensure type is int
    8b/-> *(eax+4) 0/r32/eax  # Var-type
    (check-ints-equal *eax 1 "F - test-parse-mu-var-def/var-type:0")  # Tree-left
    (check-ints-equal *(eax+4) 0 "F - test-parse-mu-var-def/var-type:0")  # Tree-right
    # globals
    (check-ints-equal *Next-local-stack-offset -8 "F - test-parse-mu-reg-var-def/Next-local-stack-offset")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-mu-reg-var-def:
    # 'var n/eax: int <- copy 0'
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "n/eax: int <- copy 0\n")  # caller has consumed the 'var'
    c7 0/subop/copy *Curr-block-depth 1/imm32
    c7 0/subop/copy *Next-local-stack-offset -4/imm32
    # var vars/ecx: (stack (addr var) 4)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ecx 4/r32/esp
    (clear-stack %ecx)
    # convert
    (parse-mu-var-def _test-input-stream %ecx)  # => eax
    # check result
    (check-ints-equal *eax 3 "F - test-parse-mu-reg-var-def/tag")  # Stmt-tag is reg-var-def
    8b/-> *(eax+0xc) 0/r32/eax  # Regvardef-outputs
    (check-ints-equal *(eax+4) 0 "F - test-parse-mu-reg-var-def/single-output")  # List-next
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    (check-strings-equal *eax "n" "F - test-parse-mu-reg-var-def/output-name")  # Var-name
    (check-strings-equal *(eax+0x10) "eax" "F - test-parse-mu-reg-var-def/output-register")  # Var-register
    (check-ints-equal *(eax+8) 1 "F - test-parse-mu-reg-var-def/output-block-depth")  # Var-block-depth
    (check-ints-equal *(eax+0xc) 0 "F - test-parse-mu-reg-var-def/output-stack-offset")  # Var-offset
    # ensure type is int
    8b/-> *(eax+4) 0/r32/eax  # Var-type
    (check-ints-equal *eax 1 "F - test-parse-mu-reg-var-def/output-type:0")  # Tree-left
    (check-ints-equal *(eax+4) 0 "F - test-parse-mu-reg-var-def/output-type:0")  # Tree-right
    # globals
    (check-ints-equal *Next-local-stack-offset -8 "F - test-parse-mu-reg-var-def/Next-local-stack-offset")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-mu-stmt:  # line: (addr stream byte), vars: (addr stack (handle var)), fn: (handle function) -> result/eax: (handle stmt)
    # pseudocode:
    #   var name: slice
    #   result = allocate(Heap, Stmt-size)
    #   if stmt-has-outputs?(line)
    #     while true
    #       name = next-mu-token(line)
    #       if (name == '<-') break
    #       assert(is-identifier?(name))
    #       var v: (handle var) = lookup-or-define-var(name, vars, fn)  # regular stmts may define vars in fn outputs
    #       result->outputs = append(result->outputs, v)
    #   add-operation-and-inputs-to-stmt(result, line, vars)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    57/push-edi
    # var name/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # var is-deref?/edx: boolean = false
    ba/copy-to-edx 0/imm32/false
    # result/edi: (handle stmt)
    (allocate Heap *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    89/<- %edi 0/r32/eax
    # result->tag = 1/stmt
    c7 0/subop/copy *edi 1/imm32/stmt1  # Stmt-tag
    {
      (stmt-has-outputs? *(ebp+8))
      3d/compare-eax-and 0/imm32/false
      0f 84/jump-if-= break/disp32
      {
$parse-mu-stmt:read-outputs:
        # name = next-mu-token(line)
        (next-mu-token *(ebp+8) %ecx)
        # if slice-empty?(word-slice) break
        (slice-empty? %ecx)  # => eax
        3d/compare-eax-and 0/imm32/false
        0f 85/jump-if-!= break/disp32
        # if (name == "<-") break
        (slice-equal? %ecx "<-")  # => eax
        3d/compare-eax-and 0/imm32/false
        0f 85/jump-if-!= break/disp32
        # is-deref? = false
        ba/copy-to-edx 0/imm32/false
        # if (slice-starts-with?(name, '*')) ++name->start and set is-deref?
        8b/-> *ecx 0/r32/eax  # Slice-start
        8a/copy-byte *eax 0/r32/AL
        81 4/subop/and %eax 0xff/imm32
        3d/compare-eax-and 0x2a/imm32/asterisk
        {
          75/jump-if-!= break/disp8
          ff 0/subop/increment *ecx
          ba/copy-to-edx 1/imm32/true
        }
        # assert(is-identifier?(name))
        (is-identifier? %ecx)  # => eax
        3d/compare-eax-and 0/imm32/false
        0f 84/jump-if-= $parse-mu-stmt:abort/disp32
        # result->outputs = new stmt-var(lookup(name, vars, fn), result->outputs, is-deref?)
        (lookup-or-define-var %ecx *(ebp+0xc) *(ebp+0x10))  # => eax
        (append-stmt-var Heap %eax *(edi+0xc) %edx)  # Stmt1-outputs => eax
        89/<- *(edi+0xc) 0/r32/eax  # Stmt1-outputs
        e9/jump loop/disp32
      }
    }
    (add-operation-and-inputs-to-stmt %edi *(ebp+8) *(ebp+0xc))
$parse-mu-stmt:end:
    # return result
    89/<- %eax 7/r32/edi
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$parse-mu-stmt:abort:
    # error("invalid identifier '" name "'\n")
    (write-buffered Stderr "invalid identifier '")
    (write-slice-buffered Stderr %ecx)
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

add-operation-and-inputs-to-stmt:  # stmt: (handle stmt), line: (addr stream byte), vars: (addr stack (handle var))
    # pseudocode:
    #   stmt->name = slice-to-string(next-mu-token(line))
    #   while true
    #     name = next-mu-token(line)
    #     v = lookup-var-or-literal(name)
    #     stmt->inouts = append(stmt->inouts, v)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    57/push-edi
    # edi = stmt
    8b/-> *(ebp+8) 7/r32/edi
    # var name/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # var is-deref?/edx: boolean = false
    ba/copy-to-edx 0/imm32/false
$add-operation-and-inputs-to-stmt:read-operation:
    (next-mu-token *(ebp+0xc) %ecx)
    (slice-to-string Heap %ecx)  # => eax
    89/<- *(edi+4) 0/r32/eax  # Stmt1-operation or Regvardef-operation
    # var is-get?/ebx: boolean = (name == "get")
    (slice-equal? %ecx "get")  # => eax
    89/<- %ebx 0/r32/eax
    {
$add-operation-and-inputs-to-stmt:read-inouts:
      # name = next-mu-token(line)
      (next-mu-token *(ebp+0xc) %ecx)
      # if slice-empty?(word-slice) break
      (slice-empty? %ecx)  # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= break/disp32
      # if (name == "<-") abort
      (slice-equal? %ecx "<-")
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= $add-operation-and-inputs-to-stmt:abort/disp32
      # if (is-get? && second operand) lookup or create offset
      {
        81 7/subop/compare %ebx 0/imm32/false
        74/jump-if-= break/disp8
        81 7/subop/compare *(edi+8) 0/imm32  # Stmt1-inouts or Regvardef-inouts
        74/jump-if-= break/disp8
        (lookup-or-create-constant *(edi+8) %ecx)  # Stmt1-inouts => eax
#?         (write-buffered Stderr "creating new output var ")
#?         (print-int32-buffered Stderr %eax)
#?         (write-buffered Stderr " for field called ")
#?         (write-slice-buffered Stderr %ecx)
#?         (write-buffered Stderr Newline)
#?         (flush Stderr)
        e9/jump $add-operation-and-inputs-to-stmt:save-var/disp32
      }
      # is-deref? = false
      ba/copy-to-edx 0/imm32/false
      # if (slice-starts-with?(name, '*')) ++name->start and set is-deref?
      8b/-> *ecx 0/r32/eax  # Slice-start
      8a/copy-byte *eax 0/r32/AL
      81 4/subop/and %eax 0xff/imm32
      3d/compare-eax-and 0x2a/imm32/asterisk
      {
        75/jump-if-!= break/disp8
$add-operation-and-inputs-to-stmt:inout-is-deref:
        ff 0/subop/increment *ecx
        ba/copy-to-edx 1/imm32/true
      }
      (lookup-var-or-literal %ecx *(ebp+0x10))  # => eax
$add-operation-and-inputs-to-stmt:save-var:
      (append-stmt-var Heap %eax *(edi+8) %edx)  # Stmt1-inouts or Regvardef-inouts => eax
      89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts or Regvardef-inouts
      e9/jump loop/disp32
    }
$add-operation-and-inputs-to-stmt:end:
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$add-operation-and-inputs-to-stmt:abort:
    # error("invalid statement '" line "'\n")
    (rewind-stream *(ebp+8))
    (write-buffered Stderr "invalid identifier '")
    (flush Stderr)
    (write-stream 2 *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

stmt-has-outputs?:  # line: (addr stream byte) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var word-slice/ecx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # result = false
    b8/copy-to-eax 0/imm32/false
    (rewind-stream *(ebp+8))
    {
      (next-mu-token *(ebp+8) %ecx)
      # if slice-empty?(word-slice) break
      (slice-empty? %ecx)
      3d/compare-eax-and 0/imm32/false
      b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
      0f 85/jump-if-!= break/disp32
      # if slice-starts-with?(word-slice, '#') break
      # . eax = *word-slice->start
      8b/-> *ecx 0/r32/eax
      8a/copy-byte *eax 0/r32/AL
      81 4/subop/and %eax 0xff/imm32
      # . if (eax == '#') break
      3d/compare-eax-and 0x23/imm32/hash
      b8/copy-to-eax 0/imm32/false/result  # restore result (if we're here it's still false)
      0f 84/jump-if-= break/disp32
      # if slice-equal?(word-slice, '<-') return true
      (slice-equal? %ecx "<-")
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= loop/disp8
      b8/copy-to-eax 1/imm32/true
    }
$stmt-has-outputs:end:
    (rewind-stream *(ebp+8))
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# if 'name' starts with a digit, create a new literal var for it
# otherwise return first 'name' from the top (back) of 'vars' and abort if not found
lookup-var-or-literal:  # name: (addr slice), vars: (addr stack (handle var)) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # esi = name
    8b/-> *(ebp+8) 6/r32/esi
    # if slice-empty?(name) abort
    (slice-empty? %esi)  # => eax
    3d/compare-eax-and 0/imm32/false
    0f 85/jump-if-!= $lookup-var-or-literal:abort/disp32
    # var c/ecx: byte = *name->start
    8b/-> *esi 1/r32/ecx
    8a/copy-byte *ecx 1/r32/CL
    81 4/subop/and %ecx 0xff/imm32
    # if is-decimal-digit?(c) return new var(name)
    {
      (is-decimal-digit? %ecx)  # => eax
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= break/disp8
      (new-literal-integer Heap %esi)  # => eax
      eb/jump $lookup-var-or-literal:end/disp8
    }
    # else if (c == '"') return new var(name)
    {
      81 7/subop/compare %ecx 0x22/imm32/dquote
      75/jump-if-!= break/disp8
      (new-literal Heap %esi)  # => eax
      eb/jump $lookup-var-or-literal:end/disp8
    }
    # otherwise return lookup-var(name, vars)
    {
      (lookup-var %esi *(ebp+0xc))  # => eax
    }
$lookup-var-or-literal:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$lookup-var-or-literal:abort:
    (write-buffered Stderr "empty variable!")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# return first 'name' from the top (back) of 'vars' and abort if not found
lookup-var:  # name: (addr slice), vars: (addr stack (handle var)) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # var target/eax: (handle array byte) = slice-to-string(name)
    (slice-to-string Heap *(ebp+8))  # => eax
    #
    (lookup-var-helper %eax *(ebp+0xc))  # => eax
    # if (result == 0) abort
    3d/compare-eax-and 0/imm32
    74/jump-if-= $lookup-var:abort/disp8
$lookup-var:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$lookup-var:abort:
    (write-buffered Stderr "unknown variable '")
    (write-slice-buffered Stderr *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# return first 'name' from the top (back) of 'vars', and 0/null if not found
lookup-var-helper:  # name: (addr array byte), vars: (addr stack (handle var)) -> result/eax: (handle var)
    # pseudocode:
    #   var curr: (addr handle var) = &vars->data[vars->top - 4]
    #   var min = vars->data
    #   while curr >= min
    #     var v: (handle var) = *curr
    #     if v->name == name
    #       return v
    #   return 0
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    52/push-edx
    53/push-ebx
    56/push-esi
    # esi = vars
    8b/-> *(ebp+0xc) 6/r32/esi
    # ebx = vars->top
    8b/-> *esi 3/r32/ebx
    # if (vars->top > vars->length) abort
    3b/compare 0/r32/eax *(esi+4)
    0f 8f/jump-if-> $lookup-var-helper:error1/disp32
    # var min/edx: (addr handle var) = vars->data
    8d/copy-address *(esi+8) 2/r32/edx
    # var curr/ebx: (addr handle var) = &vars->data[vars->top - 4]
    81 5/subop/subtract %ebx 4/imm32
    8d/copy-address *(esi+ebx+8) 3/r32/ebx
    {
      # if (curr < min) return 0
      39/compare %ebx 2/r32/edx
      b8/copy-to-eax 0/imm32
      0f 82/jump-if-addr< break/disp32
      # var v/eax: (handle var) = *curr
      8b/-> *ebx 0/r32/eax
      # if (v->name == name) return v
      (string-equal? *eax *(ebp+8))  # Var-name
      3d/compare-eax-and 0/imm32/false
      8b/-> *ebx 0/r32/eax
      75/jump-if-!= break/disp8
      # curr -= 4
      81 5/subop/subtract %ebx 4/imm32
      e9/jump loop/disp32
    }
$lookup-var-helper:end:
    # . restore registers
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$lookup-var-helper:error1:
    (write-buffered Stderr "malformed stack when looking up '")
    (write-slice-buffered Stderr *(ebp+8))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# return first 'name' from the top (back) of 'vars' and create a new var for a fn output if not found
lookup-or-define-var:  # name: (addr slice), vars: (addr stack (handle var)), fn: (handle function) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var target/ecx: (handle array byte) = slice-to-string(name)
    (slice-to-string Heap *(ebp+8))  # => eax
    89/<- %ecx 0/r32/eax
    #
    (lookup-var-helper %ecx *(ebp+0xc))  # => eax
    {
      # if (result != 0) return
      3d/compare-eax-and 0/imm32
      75/jump-if-!= break/disp8
      # if name is one of fn's outputs, return it
      {
        (find-in-function-outputs *(ebp+0x10) %ecx)  # => eax
        3d/compare-eax-and 0/imm32
        # otherwise abort
        0f 84/jump-if-!= $lookup-var:abort/disp32
      }
    }
$lookup-or-define-var:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-in-function-outputs:  # fn: (handle function), name: (handle array byte) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var curr/ecx: (handle list var) = fn->outputs
    8b/-> *(ebp+8) 1/r32/ecx
    8b/-> *(ecx+0xc) 1/r32/ecx
    # while curr != null
    {
      81 7/subop/compare %ecx 0/imm32
      74/jump-if-= break/disp8
      # var v: (handle var) = *curr
      8b/-> *ecx 0/r32/eax  # List-value
      # if (curr->name == name) return curr
      50/push-eax
      (string-equal? *eax *(ebp+0xc))
      3d/compare-eax-and 0/imm32/false
      58/pop-to-eax
      75/jump-if-!= $find-in-function-outputs:end/disp8
      # curr = curr->next
      8b/-> *(ecx+4) 1/r32/ecx  # List-next
      eb/jump loop/disp8
    }
    b8/copy-to-eax 0/imm32
$find-in-function-outputs:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-mu-stmt:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "increment n\n")
    # var vars/ecx: (stack (addr var) 4)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ecx 4/r32/esp
    (clear-stack %ecx)
    # var v/edx: var
    81 5/subop/subtract %esp 0x14/imm32  # Var-size
    89/<- %edx 4/r32/esp
    (zero-out %edx 0x14)  # Var-size
    # v->name = "n"
    c7 0/subop/copy *edx "n"/imm32  # Var-name
    #
    (push %ecx %edx)
    # convert
    (parse-mu-stmt _test-input-stream %ecx)  # => eax
    # check result
    (check-ints-equal *eax 1 "F - test-parse-mu-stmt/tag")  # Stmt-tag is Stmt1
    (check-strings-equal *(eax+4) "increment" "F - test-parse-mu-stmt/name")  # Stmt1-operation
    # edx: (handle list var) = result->inouts
    8b/-> *(eax+8) 2/r32/edx  # Stmt1-inouts
    # ebx: (handle var) = result->inouts->value
    8b/-> *edx 3/r32/ebx  # Stmt-var-value
    (check-strings-equal *ebx "n" "F - test-parse-mu-stmt/inout:0")  # Var-name
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-mu-stmt-with-comma:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (write _test-input-stream "copy-to n, 3\n")
    # var vars/ecx: (stack (addr var) 4)
    81 5/subop/subtract %esp 0x10/imm32
    68/push 0x10/imm32/length
    68/push 0/imm32/top
    89/<- %ecx 4/r32/esp
    (clear-stack %ecx)
    # var v/edx: var
    81 5/subop/subtract %esp 0x14/imm32  # Var-size
    89/<- %edx 4/r32/esp
    (zero-out %edx 0x14)  # Var-size
    # v->name = "n"
    c7 0/subop/copy *edx "n"/imm32  # Var-name
    #
    (push %ecx %edx)
    # convert
    (parse-mu-stmt _test-input-stream %ecx)  # => eax
    # check result
    (check-ints-equal *eax 1 "F - test-parse-mu-stmt-with-comma/tag")  # Stmt-tag is Stmt1
    (check-strings-equal *(eax+4) "copy-to" "F - test-parse-mu-stmt-with-comma/name")  # Stmt1-operation
    # edx: (handle list var) = result->inouts
    8b/-> *(eax+8) 2/r32/edx  # Stmt1-inouts
    # ebx: (handle var) = result->inouts->value
    8b/-> *edx 3/r32/ebx  # Stmt-var-value
    (check-strings-equal *ebx "n" "F - test-parse-mu-stmt-with-comma/inout:0")  # Var-name
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-function:  # ad: (addr allocation-descriptor), name: (addr array byte), subx-name: (addr array byte), inouts: (handle list var), outputs: (handle list var), body: (handle block), next: (handle function) -> result/eax: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Function-size)  # => eax
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *eax 1/r32/ecx  # Function-name
    8b/-> *(ebp+0x10) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Function-subx-name
    8b/-> *(ebp+0x14) 1/r32/ecx
    89/<- *(eax+8) 1/r32/ecx  # Function-inouts
    8b/-> *(ebp+0x18) 1/r32/ecx
    89/<- *(eax+0xc) 1/r32/ecx  # Function-outputs
    8b/-> *(ebp+0x1c) 1/r32/ecx
    89/<- *(eax+0x10) 1/r32/ecx  # Function-body
    8b/-> *(ebp+0x20) 1/r32/ecx
    89/<- *(eax+0x14) 1/r32/ecx  # Function-next
$new-function:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-var:  # ad: (addr allocation-descriptor), name: (addr array byte), type: (addr tree type-id), block: int, offset: int, register: (addr array byte) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Var-size)  # => eax
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *eax 1/r32/ecx  # Var-name
    8b/-> *(ebp+0x10) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Var-type
    8b/-> *(ebp+0x14) 1/r32/ecx
    89/<- *(eax+8) 1/r32/ecx  # Var-block-depth
    8b/-> *(ebp+0x18) 1/r32/ecx
    89/<- *(eax+0xc) 1/r32/ecx  # Var-offset
    8b/-> *(ebp+0x1c) 1/r32/ecx
    89/<- *(eax+0x10) 1/r32/ecx  # Var-register
$new-var:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-literal-integer:  # ad: (addr allocation-descriptor), name: (addr slice) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # if (!is-hex-int?(name)) abort
    (is-hex-int? *(ebp+0xc))  # => eax
    3d/compare-eax-and 0/imm32/false
    0f 84/jump-if-= $new-literal-integer:abort/disp32
    # var s/ecx: (addr array byte)
    (slice-to-string Heap *(ebp+0xc))  # => eax
    89/<- %ecx 0/r32/eax
    # result/ecx = new var(s)
    (allocate *(ebp+8) *Var-size)  # => eax
    (zero-out %eax *Var-size)
    89/<- *eax 1/r32/ecx  # Var-name
    89/<- %ecx 0/r32/eax
    # result->type = new type()
    (allocate *(ebp+8) *Tree-size)  # => eax
    (zero-out %eax *Tree-size)  # default type is 'literal'
    89/<- *(ecx+4) 0/r32/eax  # Var-type
    # move result to eax
    89/<- %eax 1/r32/ecx
$new-literal-integer:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$new-literal-integer:abort:
    (write-buffered Stderr "variable cannot begin with a digit '")
    (write-slice-buffered Stderr *(ebp+0xc))
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

new-literal:  # ad: (addr allocation-descriptor), name: (addr slice) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var s/ecx: (addr array byte)
    (slice-to-string Heap *(ebp+0xc))  # => eax
    89/<- %ecx 0/r32/eax
    # result->type = new type()
    (allocate *(ebp+8) *Tree-size)  # => eax
    (zero-out %eax *Tree-size)  # default type is 'literal'
    #
    (new-var *(ebp+8) %ecx %eax *Curr-block-depth 0 0)  # => eax
$new-literal:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-label:  # ad: (addr allocation-descriptor), name: (addr slice) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var s/ecx: (addr array byte)
    (slice-to-string Heap *(ebp+0xc))  # => eax
    89/<- %ecx 0/r32/eax
    #
    (allocate *(ebp+8) *Var-size)  # => eax
    89/<- *eax 1/r32/ecx  # Var-name
    89/<- %ecx 0/r32/eax
    (allocate *(ebp+8) *Tree-size)  # => eax
    (zero-out %eax *Tree-size)  # labels are literals
    89/<- *(ecx+4) 0/r32/eax  # Var-type
    89/<- %eax 1/r32/ecx
    c7 0/subop/copy *(eax+8) 0/imm32  # Var-block-depth
    c7 0/subop/copy *(eax+0xc) 0/imm32  # Var-offset
    c7 0/subop/copy *(eax+0x10) 0/imm32  # Var-register
$new-label:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-block:  # ad: (addr allocation-descriptor), data: (handle list stmt) -> result/eax: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    c7 0/subop/copy *eax 0/imm32/tag/block  # Stmt-tag
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Block-stmts
$new-block:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-var-def:  # ad: (addr allocation-descriptor), var: (handle var) -> result/eax: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    c7 0/subop/copy *eax 2/imm32/tag/var-on-stack  # Stmt-tag
    # result->var = var
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Vardef-var
$new-var-def:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

new-reg-var-def:  # ad: (addr allocation-descriptor), var: (handle var) -> result/eax: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    57/push-edi
    # ecx = var
    8b/-> *(ebp+0xc) 1/r32/ecx
    # edi = result
    (allocate *(ebp+8) *Stmt-size)  # => eax
    89/<- %edi 0/r32/eax
    (zero-out %edi *Stmt-size)
    # set tag
    c7 0/subop/copy *edi 3/imm32/tag/var-in-register  # Stmt-tag
    # set output
    (append-stmt-var Heap %ecx *(edi+0xc) 0)  # Regvardef-outputs => eax
    89/<- *(edi+0xc) 0/r32/eax  # Regvardef-outputs
$new-reg-var-def:end:
    89/<- %eax 7/r32/edi
    # . restore registers
    5f/pop-to-edi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

append-list:  # ad: (addr allocation-descriptor), value: _type, list: (handle list _type) -> result/eax: (handle list _type)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *List-size)  # => eax
    (zero-out %eax *List-size)
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *eax 1/r32/ecx  # List-value
    # if (list == null) return result
    81 7/subop/compare *(ebp+0x10) 0/imm32
    74/jump-if-= $append-list:end/disp8
    # otherwise append
    # var curr/ecx = list
    8b/-> *(ebp+0x10) 1/r32/ecx
    # while (curr->next != null) curr = curr->next
    {
      81 7/subop/compare *(ecx+4) 0/imm32  # List-next
      74/jump-if-= break/disp8
      # curr = curr->next
      8b/-> *(ecx+4) 1/r32/ecx
      eb/jump loop/disp8
    }
    # curr->next = result
    89/<- *(ecx+4) 0/r32/eax
    # return list
    8b/-> *(ebp+0x10) 0/r32/eax
$append-list:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

append-stmt-var:  # ad: (addr allocation-descriptor), v: (handle var), vars: (handle stmt-var), is-deref?: boolean -> result/eax: (handle stmt-var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Stmt-var-size)  # => eax
    (zero-out %eax *Stmt-var-size)
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *eax 1/r32/ecx  # Stmt-var-value
    8b/-> *(ebp+0x14) 1/r32/ecx
    89/<- *(eax+8) 1/r32/ecx  # Stmt-var-is-deref
    # if (list == null) return result
    81 7/subop/compare *(ebp+0x10) 0/imm32
    74/jump-if-= $append-stmt-var:end/disp8
    # otherwise append
    # var curr/ecx: (handle stmt-var) = vars
    8b/-> *(ebp+0x10) 1/r32/ecx
    # while (curr->next != null) curr = curr->next
    {
      81 7/subop/compare *(ecx+4) 0/imm32  # List-next
      74/jump-if-= break/disp8
      # curr = curr->next
      8b/-> *(ecx+4) 1/r32/ecx
      eb/jump loop/disp8
    }
    # curr->next = result
    89/<- *(ecx+4) 0/r32/eax
    # return vars
    8b/-> *(ebp+0x10) 0/r32/eax
$append-stmt-var:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

append-to-block:  # ad: (addr allocation-descriptor), block: (handle block), x: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    56/push-esi
    # esi = block
    8b/-> *(ebp+0xc) 6/r32/esi
    (append-list *(ebp+8) *(ebp+0x10) *(esi+4))  # ad, x, Block-stmts
    89/<- *(esi+4) 0/r32/eax  # Block-stmts
$append-to-block:end:
    # . restore registers
    5e/pop-to-esi
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

## Parsing types
# We need to create metadata on user-defined types, and we need to use this
# metadata as we parse instructions.
# However, we also want to allow types to be used before their definitions.
# This means we can't ever assume any type data structures exist.

lookup-or-create-constant:  # container: (handle stmt-var), field-name: (addr slice) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    56/push-esi
    # var container-type/esi: type-id
    (container-type *(ebp+8))  # => eax
#?     (write-buffered Stderr "lookup-or-create-constant: container type-id: ")
#?     (print-int32-buffered Stderr %eax)
#?     (write-buffered Stderr Newline)
#?     (flush Stderr)
    89/<- %esi 0/r32/eax
    # var typeinfo/eax: (addr typeinfo)
    (find-or-create-typeinfo %esi)  # => eax
#?     (write-buffered Stderr "lookup-or-create-constant: typeinfo: ")
#?     (print-int32-buffered Stderr %eax)
#?     (write-buffered Stderr Newline)
#?     (flush Stderr)
    # result = find-or-create-typeinfo-constant(typeinfo, field-name)
    (find-or-create-typeinfo-constant %eax *(ebp+0xc))  # => eax
$lookup-or-create-constant:end:
    # . restore registers
    5e/pop-to-esi
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# container->var->type->right->left->value
container-type:  # container: (handle stmt-var) -> result/eax: type-id
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    8b/-> *(eax+4) 0/r32/eax  # Var-type
    8b/-> *(eax+4) 0/r32/eax  # Tree-right
    8b/-> *eax 0/r32/eax  # Tree-left
    8b/-> *eax 0/r32/eax  # Atom-value
$container-type:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-or-create-typeinfo:  # t: type-id -> result/eax: (handle typeinfo)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # eax = find-typeinfo(t)
    (find-typeinfo *(ebp+8))  # => eax
    {
      # if (curr != 0) break
      3d/compare-eax-and 0/imm32
      75/jump-if-!= break/disp8
$find-or-create-typeinfo:create:
      (allocate Heap *Typeinfo-size)  # => eax
      (zero-out %eax *Typeinfo-size)
      # result->id = t
      8b/-> *(ebp+8) 1/r32/ecx
      89/<- *eax 1/r32/ecx  # Typeinfo-id
      # result->fields = new table
      50/push-eax
      (new-stream Heap 0x40 0xc)  # => eax
      89/<- %ecx 0/r32/eax
      58/pop-to-eax
      89/<- *(eax+4) 1/r32/ecx
      # result->next = Program->types
      8b/-> *_Program-types 1/r32/ecx
      89/<- *(eax+8) 1/r32/ecx  # Typeinfo-next
      # Program->types = result
      89/<- *_Program-types 0/r32/eax
    }
$find-or-create-typeinfo:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-typeinfo:  # t: type-id -> result/eax: (handle typeinfo)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # ecx = t
    8b/-> *(ebp+8) 1/r32/ecx
    # var curr/eax: (handle typeinfo) = Program->types
    8b/-> *_Program-types 0/r32/eax
    {
      # if (curr == 0) break
      3d/compare-eax-and 0/imm32
      74/jump-if-= break/disp8
      # if (curr->id == t) return curr
      39/compare *eax 1/r32/ecx  # Typeinfo-id
      0f 84/jump-if-= $find-or-create-typeinfo:end/disp32
      # curr = curr->next
      8b/-> *(eax+8) 0/r32/eax
      #
      eb/jump loop/disp8
    }
$find-typeinfo:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-or-create-typeinfo-constant:  # T: (handle typeinfo), f: (addr slice) -> result/eax: (handle var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    # esi = T->fields
    8b/-> *(ebp+8) 6/r32/esi
    8b/-> *(esi+4) 6/r32/esi  # Typeinfo-fields
    # esi = get-or-insert(T->fields, f)
    (leaky-get-or-insert-slice %esi *(ebp+0xc) 0xc)  # => eax
    89/<- %esi 0/r32/eax
    # if output var exists, return it
    81 7/subop/compare *(esi+4) 0/imm32  # output var
    8b/-> *(esi+4) 0/r32/eax  # output var
    75/jump-if-!= $find-or-create-typeinfo-constant:end/disp8
    # var type/ecx: (handle tree type-id) = new var("dummy name", constant type, -1 offset)
    (allocate Heap *Tree-size)  # => eax
    c7 0/subop/copy *eax 6/imm32/constant  # Atom-value
    c7 0/subop/copy *(eax+4) 0/imm32  # Tree-right
    (new-var Heap "field" %eax 0 -1 0)  # => eax
    # offset (constant value) isn't filled out yet
    # save output var in row
    89/<- *(esi+4) 0/r32/eax
$find-or-create-typeinfo-constant:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

populate-mu-type:  # in: (addr stream byte), t: (handle typeinfo)
    # pseudocode:
    #   var line: (stream byte 512)
    #   curr-offset = 0
    #   while true
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if line->write == 0
    #       abort
    #     word-slice = next-mu-token(line)
    #     if slice-empty?(word-slice)               # end of line
    #       continue
    #     if slice-equal?(word-slice, "}")
    #       break
    #     var v: (handle var) = parse-var-with-type(word-slice, line)
    #     var r: (addr {(handle var) (handle var)}) = get-or-insert(t, v->name, row-size=12)
    #     TODO: ensure that r->first is null
    #     r->first = v
    #     if r->second == 0
    #       r->second = new var("dummy name", constant type, -1 offset)
    #     r->second->offset = curr-offset
    #     curr-offset += size-of(existing)
    #     TODO: ensure nothing else in line
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # edi = t
    8b/-> *(ebp+0xc) 7/r32/edi
    # var line/ecx: (stream byte 512)
    81 5/subop/subtract %esp 0x200/imm32
    68/push 0x200/imm32/length
    68/push 0/imm32/read
    68/push 0/imm32/write
    89/<- %ecx 4/r32/esp
    # var word-slice/edx: slice
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %edx 4/r32/esp
    # var curr-offset/ebx: int = 0
    bb/copy-to-ebx 0/imm32
    {
$populate-mu-type:line-loop:
      (clear-stream %ecx)
      (read-line-buffered *(ebp+8) %ecx)
      # if (line->write == 0) abort
      81 7/subop/compare *ecx 0/imm32
      0f 84/jump-if-= $populate-mu-type:abort/disp32
#?       # dump line {{{
#?       (write 2 "parse-mu: ^")
#?       (write-stream 2 %ecx)
#?       (write 2 "$\n")
#?       (rewind-stream %ecx)
#?       # }}}
      (next-mu-token %ecx %edx)
      # if slice-empty?(word-slice) continue
      (slice-empty? %edx)
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-!= loop/disp32
      # if slice-equal?(word-slice, "}") break
      (slice-equal? %edx "}")
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-!= break/disp32
      # var v/esi: (handle var) = parse-var-with-type(word-slice, first-line)
      (parse-var-with-type %edx %ecx)  # => eax
      89/<- %esi 0/r32/eax
      # var r/eax: (addr {(handle var) (handle var)})
#?       (write-buffered Stderr "populate-mu-type: typeinfo: ")
#?       (print-int32-buffered Stderr %edi)
#?       (write-buffered Stderr Newline)
#?       (flush Stderr)
      (get-or-insert *(edi+4) *esi 0xc)  # Typeinfo-fields Var-name => eax
      # r->first = v
      89/<- *eax 6/r32/esi
      # if (r->second == 0) create a new var with some placeholder data
      {
        81 7/subop/compare *(eax+4) 0/imm32
        75/jump-if-!= break/disp8
        # temporarily spill r to esi
        89/<- %esi 0/r32/eax
        (new-literal Heap %edx)  # => eax
        89/<- *(esi+4) 0/r32/eax
        89/<- %eax 6/r32/esi
#?         (write-buffered Stderr "creating new output var ")
#?         (print-int32-buffered Stderr %eax)
#?         (write-buffered Stderr Newline)
#?         (flush Stderr)
      }
      # r->second->offset = curr-offset
      8b/-> *(eax+4) 0/r32/eax
#?       (write-buffered Stderr "writing offset ")
#?       (print-int32-buffered Stderr %ebx)
#?       (write-buffered Stderr " to output var ")
#?       (print-int32-buffered Stderr %eax)
#?       (write-buffered Stderr Newline)
#?       (flush Stderr)
      89/<- *(eax+0xc) 3/r32/ebx
      # curr-offset += size-of(v)
      50/push-eax
      (size-of %eax)  # => eax
      01/add-to %ebx 0/r32/eax
      58/pop-to-eax
      #
      e9/jump loop/disp32
    }
$populate-mu-type:end:
    # . reclaim locals
    81 0/subop/add %esp 0x214/imm32
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$populate-mu-type:abort:
    # error("unexpected top-level command: " word-slice "\n")
    (write-buffered Stderr "incomplete type definition '")
    (type-name *edi)  # Typeinfo-id => eax
    (write-buffered Stderr %eax)
    (write-buffered Stderr "\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

type-name:  # index: int -> result/eax: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (index Type-id *(ebp+8))
$type-name:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

index:  # arr: (addr stream (handle array byte)), index: int -> result/eax: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    56/push-esi
    # TODO: bounds-check index
    # esi = arr
    8b/-> *(ebp+8) 6/r32/esi
    # eax = index
    8b/-> *(ebp+0xc) 0/r32/eax
    # eax = *(arr + 12 + index)
    8b/-> *(esi+eax+0xc) 0/r32/eax
$index:end:
    # . restore registers
    5e/pop-to-esi
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

#######################################################
# Type-checking
#######################################################

check-mu-types:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
$check-mu-types:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

size-of:  # v: (addr var) -> result/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # if v is a literal, return 0
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *(eax+4) 0/r32/eax  # Var-type
    81 7/subop/compare *eax 0/imm32  # Tree-left
    b8/copy-to-eax 0/imm32
    74/jump-if-= $size-of:end/disp8
    # hard-coded since we only support 'int' types for now
    b8/copy-to-eax 4/imm32
$size-of:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

type-equal?:  # a: (handle tree type-id), b: (handle tree type-id) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # ecx = a
    8b/-> *(ebp+8) 1/r32/ecx
    # edx = b
    8b/-> *(ebp+0xc) 2/r32/edx
    # if (a == b) return true
    8b/-> %ecx 0/r32/eax  # Var-type
    39/compare %edx 0/r32/eax  # Var-type
    b8/copy-to-eax 1/imm32/true
    74/jump-if-= $type-equal?:end/disp8
    # if (a < MAX_TYPE_ID) return false
    81 7/subop/compare %ecx 0x10000/imm32
    b8/copy-to-eax 0/imm32/false
    72/jump-if-addr< $type-equal?:end/disp8
    # if (b < MAX_TYPE_ID) return false
    81 7/subop/compare %edx 0x10000/imm32
    b8/copy-to-eax 0/imm32/false
    72/jump-if-addr< $type-equal?:end/disp8
    # if (!type-equal?(a->left, b->left)) return false
    (type-equal? *ecx *edx)  # Tree-left, Tree-left => eax
    3d/compare-eax-and 0/imm32/false
    74/jump-if-= $type-equal?:end/disp8
    # return type-equal?(a->right, b->right)
    (type-equal? *(ecx+4) *(edx+4))  # Tree-right, Tree-right => eax
$type-equal?:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

== data

# not yet used, but it will be
Type-size:  # (stream int)
  0x18/imm32/write
  0/imm32/read
  0x100/imm32/length
  # data
  4/imm32  # literal
  4/imm32  # int
  4/imm32  # addr
  0/imm32  # array (logic elsewhere)
  8/imm32  # handle (fat pointer)
  4/imm32  # boolean
  0/imm32
  0/imm32
  # 0x20
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32
  0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32 0/imm32

== code

#######################################################
# Code-generation
#######################################################

emit-subx:  # out: (addr buffered-file)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    57/push-edi
    # edi = out
    8b/-> *(ebp+8) 7/r32/edi
    # var curr/ecx: (handle function) = *Program->functions
    8b/-> *_Program-functions 1/r32/ecx
    {
      # if (curr == null) break
      81 7/subop/compare %ecx 0/imm32
      0f 84/jump-if-= break/disp32
      (emit-subx-function %edi %ecx)
      # curr = curr->next
      8b/-> *(ecx+0x14) 1/r32/ecx  # Function-next
      e9/jump loop/disp32
    }
$emit-subx:end:
    # . restore registers
    5f/pop-to-edi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-function:  # out: (addr buffered-file), f: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    57/push-edi
    # edi = out
    8b/-> *(ebp+8) 7/r32/edi
    # ecx = f
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var vars/edx: (stack (addr var) 256)
    81 5/subop/subtract %esp 0x400/imm32
    68/push 0x400/imm32/length
    68/push 0/imm32/top
    89/<- %edx 4/r32/esp
    #
    (write-buffered %edi *ecx)
    (write-buffered %edi ":\n")
    # Important: each block's depth during code-generation should be identical
    # to what it was during parsing.
    c7 0/subop/copy *Curr-block-depth 1/imm32
    (emit-subx-prologue %edi)
    (emit-subx-block %edi *(ecx+0x10) %edx)  # Function-body
    (emit-subx-epilogue %edi)
$emit-subx-function:end:
    # . reclaim locals
    81 0/subop/add %esp 408/imm32
    # . restore registers
    5f/pop-to-edi
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-stmt-list:  # out: (addr buffered-file), stmts: (handle list stmt), vars: (addr stack (handle var))
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    # esi = stmts
    8b/-> *(ebp+0xc) 6/r32/esi
    # var var-seen?/edx: boolean <- copy false
    ba/copy-to-edx 0/imm32/false
    #
    {
$emit-subx-stmt-list:loop:
      81 7/subop/compare %esi 0/imm32
      0f 84/jump-if-= break/disp32
      # var curr-stmt/ecx = stmts->value
      8b/-> *esi 1/r32/ecx  # List-value
      {
$emit-subx-stmt-list:check-for-block:
        81 7/subop/compare *ecx 0/imm32/block  # Stmt-tag
        75/jump-if-!= break/disp8
$emit-subx-stmt-list:block:
        (emit-subx-block *(ebp+8) %ecx *(ebp+0x10))
      }
      {
$emit-subx-stmt-list:check-for-stmt:
        81 7/subop/compare *ecx 1/imm32/stmt1  # Stmt-tag
        0f 85/jump-if-!= break/disp32
$emit-subx-stmt-list:stmt1:
        {
          (is-mu-branch? %ecx)  # => eax
          3d/compare-eax-and 0/imm32/false
          0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:branch-stmt:
          # if !var-seen? break
          81 7/subop/compare %edx 0/imm32/false
          0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:branch-stmt-and-var-seen:
          # unconditional loops {{{
          {
            # if (!string-equal?(var->operation, "loop")) break
            (string-equal? *(ecx+4) "loop")  # Stmt1-operation => eax
            3d/compare-eax-and 0/imm32/false
            0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:unconditional-loop:
            81 7/subop/compare *(ecx+8) 0/imm32  # Stmt1-inouts
            # simple unconditional loops without a target
            {
              0f 85/jump-if-!= break/disp32
$emit-subx-stmt-list:zero-arg-unconditional-loop:
              (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth)
              (emit-indent *(ebp+8) *Curr-block-depth)
              (write-buffered *(ebp+8) "e9/jump loop/disp32")
              (write-buffered *(ebp+8) Newline)
              e9/jump $emit-subx-stmt-list:cleanup/disp32  # skip remaining statements; they're dead code
            }
            # unconditional loops with a target
            {
              0f 84/jump-if-= break/disp32
              (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
              e9/jump $emit-subx-stmt-list:cleanup/disp32
            }
          }
          # }}}
          # unconditional breaks {{{
          {
            # if (!string-equal?(curr-stmt->operation, "break")) break
            (string-equal? *(ecx+4) "break")  # Stmt1-operation => eax
            3d/compare-eax-and 0/imm32/false
            0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:unconditional-break:
            81 7/subop/compare *(ecx+8) 0/imm32  # Stmt1-inouts
            # simple unconditional breaks without a target
            0f 84/jump-if-= $emit-subx-stmt-list:emit-cleanup/disp32  # easy: just skip remaining statements
            # unconditional breaks with a target
            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
            e9/jump $emit-subx-stmt-list:cleanup/disp32
          }
          # }}}
          # simple conditional branches without a target {{{
          81 7/subop/compare *(ecx+8) 0/imm32  # Stmt1-inouts
          {
            0f 85/jump-if-!= break/disp32
$emit-subx-stmt-list:zero-arg-conditional-branch:
            # var old-block-depth/eax: int = Curr-block-depth - 1
            8b/-> *Curr-block-depth 3/r32/ebx
            # cleanup prologue
            (emit-indent *(ebp+8) *Curr-block-depth)
            (write-buffered *(ebp+8) "{\n")
            ff 0/subop/increment *Curr-block-depth
            #
            (emit-reverse-break *(ebp+8) %ecx)
            # clean up until old block depth
            (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) %ebx)
            # var target-block-depth/ebx: int = Curr-block-depth - 1
            4b/decrement-ebx
            # emit jump to target block
            (string-starts-with? *(ecx+4) "break")
            3d/compare-eax-and 0/imm32/false
            {
              74/jump-if-= break/disp8
              (emit-unconditional-jump-to-depth *(ebp+8) *(ebp+0x10) %ebx "break")
            }
            3d/compare-eax-and 0/imm32/false  # just in case the function call modified flags
            {
              75/jump-if-!= break/disp8
              (emit-unconditional-jump-to-depth *(ebp+8) *(ebp+0x10) %ebx "loop")
            }
            # cleanup epilogue
            ff 1/subop/decrement *Curr-block-depth
            (emit-indent *(ebp+8) *Curr-block-depth)
            (write-buffered *(ebp+8) "}\n")
            # continue
            e9/jump $emit-subx-stmt-list:continue/disp32
          }
          # }}}
          # conditional branches with an explicit target {{{
          {
            0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:conditional-branch-with-target:
            # cleanup prologue
            (emit-indent *(ebp+8) *Curr-block-depth)
            (write-buffered *(ebp+8) "{\n")
            ff 0/subop/increment *Curr-block-depth
            #
            (emit-reverse-break *(ebp+8) %ecx)
            (emit-subx-cleanup-and-unconditional-nonlocal-branch *(ebp+8) %ecx *(ebp+0x10))
            # cleanup epilogue
            ff 1/subop/decrement *Curr-block-depth
            (emit-indent *(ebp+8) *Curr-block-depth)
            (write-buffered *(ebp+8) "}\n")
            # continue
            e9/jump $emit-subx-stmt-list:continue/disp32
          }
          # }}}
        }
$emit-subx-stmt-list:1-to-1:
        (emit-subx-stmt *(ebp+8) %ecx Primitives *_Program-functions)
      }
      {
$emit-subx-stmt-list:check-for-var-def:
        81 7/subop/compare *ecx 2/imm32/var-def  # Stmt-tag
        75/jump-if-!= break/disp8
$emit-subx-stmt-list:var-def:
        (emit-subx-var-def *(ebp+8) %ecx)
        (push *(ebp+0x10) *(ecx+4))  # Vardef-var
        # var-seen? = true
        ba/copy-to-edx 1/imm32/true
      }
      {
$emit-subx-stmt-list:check-for-reg-var-def:
        81 7/subop/compare *ecx 3/imm32/reg-var-def  # Stmt-tag
        0f 85/jump-if-!= break/disp32
$emit-subx-stmt-list:reg-var-def:
        # TODO: ensure that there's exactly one output
        # var output/eax: (handle var) = curr-stmt->outputs->value
        8b/-> *(ecx+0xc) 0/r32/eax
        8b/-> *eax 0/r32/eax
        # ensure that output is in a register
        81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
        0f 84/jump-if-= $emit-subx-stmt-list:abort-reg-var-def-without-register/disp32
        # emit spill
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "ff 6/subop/push %")
        (write-buffered *(ebp+8) *(eax+0x10))
        (write-buffered *(ebp+8) Newline)
        # register variable definition
        (push *(ebp+0x10) %eax)
        # emit the instruction as usual
        (emit-subx-stmt *(ebp+8) %ecx Primitives *_Program-functions)
        # var-seen? = true
        ba/copy-to-edx 1/imm32/true
      }
$emit-subx-stmt-list:continue:
      # TODO: raise an error on unrecognized Stmt-tag
      8b/-> *(esi+4) 6/r32/esi  # List-next
      e9/jump loop/disp32
    }
$emit-subx-stmt-list:emit-cleanup:
    (emit-cleanup-code-until-depth *(ebp+8) *(ebp+0x10) *Curr-block-depth)
$emit-subx-stmt-list:cleanup:
    (clean-up-blocks *(ebp+0x10) *Curr-block-depth)
$emit-subx-stmt-list:end:
    # . restore registers
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$emit-subx-stmt-list:abort-reg-var-def-without-register:
    # error("var '" var->name "' initialized from an instruction must live in a register\n")
    (write-buffered Stderr "var '")
    (write-buffered Stderr *eax)  # Var-name
    (write-buffered Stderr "' initialized from an instruction must live in a register\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

emit-subx-cleanup-and-unconditional-nonlocal-branch:  # out: (addr buffered-file), stmt: (addr stmt1), vars: (addr stack (handle var))
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    # ecx = stmt
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var target/edx: (addr array byte) = curr-stmt->inouts->value->name
    8b/-> *(ecx+8) 2/r32/edx  # Stmt1-inouts
    8b/-> *edx 2/r32/edx  # Stmt-var-value
    8b/-> *edx 2/r32/edx  # Var-name
    # clean up until target block
    (emit-cleanup-code-until-target *(ebp+8) *(ebp+0x10) %edx)
    # emit jump to target block
    (emit-indent *(ebp+8) *Curr-block-depth)
    (write-buffered *(ebp+8) "e9/jump ")
    (write-buffered *(ebp+8) %edx)
    (string-starts-with? *(ecx+4) "break")
    3d/compare-eax-and 0/imm32/false
    {
      74/jump-if-= break/disp8
      (write-buffered *(ebp+8) ":break/disp32\n")
    }
    3d/compare-eax-and 0/imm32/false  # just in case the function call modified flags
    {
      75/jump-if-!= break/disp8
      (write-buffered *(ebp+8) ":loop/disp32\n")
    }
$emit-subx-cleanup-and-unconditional-nonlocal-branch:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

is-mu-branch?:  # stmt: (addr stmt1) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # ecx = stmt
    8b/-> *(ebp+8) 1/r32/ecx
    # if (stmt->operation starts with "loop") return true
    (string-starts-with? *(ecx+4) "loop")  # Stmt1-operation => eax
    3d/compare-eax-and 0/imm32/false
    75/jump-if-not-equal $is-mu-branch?:end/disp8
    # otherwise return (stmt->operation starts with "break")
    (string-starts-with? *(ecx+4) "break")  # Stmt1-operation => eax
$is-mu-branch?:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-reverse-break:  # out: (addr buffered-file), stmt: (addr stmt1)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # eax = stmt
    8b/-> *(ebp+0xc) 0/r32/eax
    #
    (get Reverse-branch *(eax+4) 8 "reverse-branch: ")  # Stmt1-operation => eax: (addr addr array byte)
    (emit-indent *(ebp+8) *Curr-block-depth)
    (write-buffered *(ebp+8) *eax)
    (write-buffered *(ebp+8) " break/disp32\n")
$emit-reverse-break:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

== data

Reverse-branch:  # (table string string)
  # a table is a stream
  0xa0/imm32/write
  0/imm32/read
  0xa0/imm32/length
  # data
  "break-if-="/imm32      "0f 85/jump-if-!="/imm32
  "loop-if-="/imm32       "0f 85/jump-if-!="/imm32
  "break-if-!="/imm32     "0f 84/jump-if-="/imm32
  "loop-if-!="/imm32      "0f 84/jump-if-="/imm32
  "break-if-<"/imm32      "0f 8d/jump-if->="/imm32
  "loop-if-<"/imm32       "0f 8d/jump-if->="/imm32
  "break-if->"/imm32      "0f 8e/jump-if-<="/imm32
  "loop-if->"/imm32       "0f 8e/jump-if-<="/imm32
  "break-if-<="/imm32     "0f 87/jump-if->"/imm32
  "loop-if-<="/imm32      "0f 87/jump-if->"/imm32
  "break-if->="/imm32     "0f 8c/jump-if-<"/imm32
  "loop-if->="/imm32      "0f 8c/jump-if-<"/imm32
  "break-if-addr<"/imm32  "0f 83/jump-if-addr>="/imm32
  "loop-if-addr<"/imm32   "0f 83/jump-if-addr>="/imm32
  "break-if-addr>"/imm32  "0f 86/jump-if-addr<="/imm32
  "loop-if-addr>"/imm32   "0f 86/jump-if-addr<="/imm32
  "break-if-addr<="/imm32 "0f 87/jump-if-addr>"/imm32
  "loop-if-addr<="/imm32  "0f 87/jump-if-addr>"/imm32
  "break-if-addr>="/imm32 "0f 82/jump-if-addr<"/imm32
  "loop-if-addr>="/imm32  "0f 82/jump-if-addr<"/imm32

== code

emit-unconditional-jump-to-depth:  # out: (addr buffered-file), vars: (addr stack (handle var)), depth: int, label-suffix: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # ecx = vars
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var eax: int = vars->top
    8b/-> *ecx 0/r32/eax
    # var min/ecx: (address (handle var)) = vars->data
    81 0/subop/add %ecx 8/imm32
    # var curr/eax: (address (handle var)) = &vars->data[vars->top - 4]
    81 5/subop/subtract %eax 4/imm32
    8d/copy-address *(ecx+eax) 0/r32/eax
    # edx = depth
    8b/-> *(ebp+0x10) 2/r32/edx
    {
$emit-unconditional-jump-to-depth:loop:
      # if (curr < min) break
      39/compare %eax 1/r32/ecx
      0f 82/jump-if-addr< break/disp32
      # var v/ebx: (handle var) = *curr
      8b/-> *eax 3/r32/ebx
      # if (v->block-depth < until-block-depth) break
      39/compare *(ebx+8) 2/r32/edx  # Var-block-depth
      0f 8c/jump-if-< break/disp32
      {
$emit-unconditional-jump-to-depth:check:
        # if v->block-depth != until-block-depth, continue
        39/compare *(ebx+8) 2/r32/edx  # Var-block-depth
        0f 85/jump-if-!= break/disp32
$emit-unconditional-jump-to-depth:depth-found:
        # if v is not a literal, continue
        # . var eax: int = size-of(v)
        50/push-eax
        (size-of %ebx)  # => eax
        # . if (eax != 0) continue
        3d/compare-eax-and 0/imm32
        58/pop-to-eax
        #
        0f 85/jump-if-!= break/disp32
$emit-unconditional-jump-to-depth:label-found:
        # emit unconditional jump, then return
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "e9/jump ")
        (write-buffered *(ebp+8) *ebx)  # Var-name
        (write-buffered *(ebp+8) ":")
        (write-buffered *(ebp+8) *(ebp+0x14))
        (write-buffered *(ebp+8) "/disp32\n")
        eb/jump $emit-unconditional-jump-to-depth:end/disp8
      }
      # curr -= 4
      2d/subtract-from-eax 4/imm32
      e9/jump loop/disp32
    }
    # TODO: error if no label at 'depth' was found
$emit-unconditional-jump-to-depth:end:
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# emit clean-up code for 'vars' until some block depth
# doesn't actually modify 'vars' so we need traverse manually inside the stack
emit-cleanup-code-until-depth:  # out: (addr buffered-file), vars: (addr stack (handle var)), until-block-depth: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # ecx = vars
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var eax: int = vars->top
    8b/-> *ecx 0/r32/eax
    # var min/ecx: (address (handle var)) = vars->data
    81 0/subop/add %ecx 8/imm32
    # var curr/eax: (address (handle var)) = &vars->data[vars->top - 4]
    81 5/subop/subtract %eax 4/imm32
    8d/copy-address *(ecx+eax) 0/r32/eax
    # edx = until-block-depth
    8b/-> *(ebp+0x10) 2/r32/edx
    {
$emit-cleanup-code-until-depth:loop:
      # if (curr < min) break
      39/compare %eax 1/r32/ecx
      0f 82/jump-if-addr< break/disp32
      # var v/ebx: (handle var) = *curr
      8b/-> *eax 3/r32/ebx
      # if (v->block-depth < until-block-depth) break
      39/compare *(ebx+8) 2/r32/edx  # Var-block-depth
      0f 8c/jump-if-< break/disp32
      # if v is in a register
      81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
      {
        74/jump-if-= break/disp8
$emit-cleanup-code-until-depth:reclaim-var-in-register:
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "8f 0/subop/pop %")
        (write-buffered *(ebp+8) *(ebx+0x10))
        (write-buffered *(ebp+8) Newline)
      }
      # otherwise v is on the stack
      {
        75/jump-if-!= break/disp8
$emit-cleanup-code-until-depth:reclaim-var-on-stack:
        50/push-eax
        (size-of %ebx)  # => eax
        # don't emit code for labels
        3d/compare-eax-and 0/imm32
        74/jump-if-= break/disp8
        #
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "81 0/subop/add %esp ")
        (print-int32-buffered *(ebp+8) %eax)
        (write-buffered *(ebp+8) "/imm32\n")
        58/pop-to-eax
      }
      # curr -= 4
      2d/subtract-from-eax 4/imm32
      e9/jump loop/disp32
    }
$emit-cleanup-code-until-depth:end:
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# emit clean-up code for 'vars' until a given label is encountered
# doesn't actually modify 'vars' so we need traverse manually inside the stack
emit-cleanup-code-until-target:  # out: (addr buffered-file), vars: (addr stack (handle var)), until-block-label: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # ecx = vars
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var eax: int = vars->top
    8b/-> *ecx 0/r32/eax
    # var min/ecx: (address (handle var)) = vars->data
    81 0/subop/add %ecx 8/imm32
    # var curr/edx: (address (handle var)) = &vars->data[vars->top - 4]
    81 5/subop/subtract %eax 4/imm32
    8d/copy-address *(ecx+eax) 2/r32/edx
    {
$emit-cleanup-code-until-target:loop:
      # if (curr < min) break
      39/compare %edx 1/r32/ecx
      0f 82/jump-if-addr< break/disp32
      # var v/ebx: (handle var) = *curr
      8b/-> *edx 3/r32/ebx
      # if (v->name == until-block-label) break
      (string-equal? *ebx *(ebp+0x10))  # => eax
      3d/compare-eax-and 0/imm32/false
      0f 85/jump-if-!= break/disp32
      # if v is in a register
      81 7/subop/compare *(ebx+0x10) 0/imm32  # Var-register
      {
        74/jump-if-= break/disp8
$emit-cleanup-code-until-target:reclaim-var-in-register:
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "8f 0/subop/pop %")
        (write-buffered *(ebp+8) *(ebx+0x10))
        (write-buffered *(ebp+8) Newline)
      }
      # otherwise v is on the stack
      {
        75/jump-if-!= break/disp8
$emit-cleanup-code-until-target:reclaim-var-on-stack:
        (size-of %ebx)  # => eax
        # don't emit code for labels
        3d/compare-eax-and 0/imm32
        74/jump-if-= break/disp8
        #
        (emit-indent *(ebp+8) *Curr-block-depth)
        (write-buffered *(ebp+8) "81 0/subop/add %esp ")
        (print-int32-buffered *(ebp+8) %eax)
        (write-buffered *(ebp+8) "/imm32\n")
      }
      # curr -= 4
      81 5/subop/subtract %edx 4/imm32
      e9/jump loop/disp32
    }
$emit-cleanup-code-until-target:end:
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# clean up global state for 'vars' until some block depth
clean-up-blocks:  # vars: (addr stack (handle var)), until-block-depth: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    # esi = vars
    8b/-> *(ebp+8) 6/r32/esi
    # ecx = until-block-depth
    8b/-> *(ebp+0xc) 1/r32/ecx
    {
$clean-up-blocks:reclaim-loop:
      # if (vars->top <= 0) break
      81 7/subop/compare *esi 0/imm32  # Stack-top
      7e/jump-if-<= break/disp8
      # var v/eax: (handle var) = top(vars)
      (top %esi)  # => eax
      # if (v->block-depth < until-block-depth) break
      39/compare *(eax+8) 1/r32/ecx  # Var-block-depth
      7c/jump-if-< break/disp8
      # if v is on the stack, update Next-local-stack-offset
      81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
      {
        75/jump-if-!= break/disp8
$clean-up-blocks:reclaim-var-on-stack:
        (size-of %eax)  # => eax
        01/add *Next-local-stack-offset 0/r32/eax
      }
      (pop %esi)
      e9/jump loop/disp32
    }
$clean-up-blocks:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-var-def:  # out: (addr buffered-file), stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # eax = stmt
    8b/-> *(ebp+0xc) 0/r32/eax
    # var n/eax: int = size-of(stmt->var)
    (size-of *(eax+4))  # Vardef-var => eax
    # while n > 0
    {
      3d/compare-eax-with 0/imm32
      7e/jump-if-<= break/disp8
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "68/push 0/imm32\n")
      # n -= 4
      2d/subtract-from-eax 4/imm32
      #
      eb/jump loop/disp8
    }
$emit-subx-var-def:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-stmt:  # out: (addr buffered-file), stmt: (handle stmt), primitives: (handle primitive), functions: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # handle some special cases
    # ecx = stmt
    8b/-> *(ebp+0xc) 1/r32/ecx
    # array length {{{
    {
      # if (!string-equal?(stmt->operation, "length")) break
      (string-equal? *(ecx+4) "length")  # Stmt1-operation => eax
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:array-length:
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "8b/copy-from *")
      # inouts[0]->register
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts or Regvardef-inouts
      8b/-> *eax 0/r32/eax  # Stmt-var-value
      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register => eax
      #
      (write-buffered *(ebp+8) " ")
      # outputs[0] "/r32"
      8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
      8b/-> *eax 0/r32/eax  # Stmt-var-value
      (get Registers *(eax+0x10) 8 "Registers")  # Var-register => eax
      (print-int32-buffered *(ebp+8) *eax)
      (write-buffered *(ebp+8) "/r32\n")
      e9/jump $emit-subx-stmt:end/disp32
    }
    # }}}
    # index into array {{{
    # TODO: support literal index
    {
      # if (!string-equal?(var->operation, "index")) break
      (string-equal? *(ecx+4) "index")  # Stmt1-operation => eax
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:index:
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "8d/copy-address *(")
      # inouts[0]->register " + "
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
      8b/-> *eax 0/r32/eax  # List-value
      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register => eax
      #
      (write-buffered *(ebp+8) " + ")
      # inouts[1]->register
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
      8b/-> *(eax+4) 0/r32/eax  # Stmt-var-next
      8b/-> *eax 0/r32/eax  # Stmt-var-value
      # TODO: handle Stmt-var-is-deref
      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register => eax
      #
      (write-buffered *(ebp+8) "<<2 + 4) ")
      # outputs[0] "/r32"
      8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
      8b/-> *eax 0/r32/eax  # List-value
      (get Registers *(eax+0x10) 8 "Registers")  # Var-register => eax
      (print-int32-buffered *(ebp+8) *eax)
      (write-buffered *(ebp+8) "/r32\n")
      e9/jump $emit-subx-stmt:end/disp32
    }
    # }}}
    # get field from record {{{
    {
      # if (!string-equal?(var->operation, "get")) break
      (string-equal? *(ecx+4) "get")  # Stmt1-operation => eax
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-= break/disp32
$emit-subx-stmt-list:get:
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "8d/copy-address *(")
      # inouts[0]->register " + "
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts
      8b/-> *eax 0/r32/eax  # Stmt-var-value
      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register => eax
      #
      (write-buffered *(ebp+8) " + ")
      (print-mu-get-offset *(ebp+8) %ecx)
      (write-buffered *(ebp+8) ") ")
      # outputs[0] "/r32"
      8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
      8b/-> *eax 0/r32/eax  # List-value
      (get Registers *(eax+0x10) 8 "Registers")  # Var-register => eax
      (print-int32-buffered *(ebp+8) *eax)
      (write-buffered *(ebp+8) "/r32\n")
      e9/jump $emit-subx-stmt:end/disp32
    }
    # }}}
    # if stmt matches a primitive, emit it
    {
$emit-subx-stmt:check-for-primitive:
      (find-matching-primitive *(ebp+0x10) *(ebp+0xc))  # primitives, stmt => curr/eax
      3d/compare-eax-and 0/imm32
      74/jump-if-= break/disp8
$emit-subx-stmt:primitive:
      (emit-subx-primitive *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
      e9/jump $emit-subx-stmt:end/disp32
    }
    # else if stmt matches a function, emit a call to it
    {
$emit-subx-stmt:check-for-call:
      (find-matching-function *(ebp+0x14) *(ebp+0xc))  # functions, stmt => curr/eax
      3d/compare-eax-and 0/imm32
      74/jump-if-= break/disp8
$emit-subx-stmt:call:
      (emit-subx-call *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
      e9/jump $emit-subx-stmt:end/disp32
    }
    # else assume it's a SubX function (TODO: how to type-check?!)
    (emit-hailmary-call *(ebp+8) *(ebp+0xc))
$emit-subx-stmt:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$emit-subx-stmt:abort:
    # error("couldn't translate '" stmt "'\n")
    (write-buffered Stderr "couldn't translate an instruction with operation '")
    8b/-> *(ebp+0xc) 0/r32/eax
    (write-buffered Stderr *(eax+4))  # Stmt1-operation
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

print-mu-get-offset:  # out: (addr buffered-file), stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # var second-inout/eax: (handle stmt-var) = stmt->inouts->next
    8b/-> *(ebp+0xc) 0/r32/eax
    8b/-> *(eax+8) 0/r32/eax  # Stmt1-inouts
    8b/-> *(eax+4) 0/r32/eax  # Stmt-var-next
    # var output-var/eax: (handle var) = second-inout->value
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    # print offset
#?     (write-buffered Stderr "emitting offset from output var ")
#?     (print-int32-buffered Stderr %eax)
#?     (write-buffered Stderr Newline)
#?     (flush Stderr)
    (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-offset
$emit-get-offset:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-block:  # out: (addr buffered-file), block: (handle block), vars: (addr stack (handle var))
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    # esi = block
    8b/-> *(ebp+0xc) 6/r32/esi
    # var stmts/eax: (handle list stmt) = block->statements
    8b/-> *(esi+4) 0/r32/eax  # Block-stmts
    #
    {
$emit-subx-block:check-empty:
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-= break/disp32
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "{\n")
      # var v/ecx: (addr array byte) = block->var->name
      8b/-> *(esi+8) 1/r32/ecx  # Block-var
      (write-buffered *(ebp+8) *ecx)  # Var-name
      (write-buffered *(ebp+8) ":loop:\n")
      ff 0/subop/increment *Curr-block-depth
      (push *(ebp+0x10) %ecx)
      (emit-subx-stmt-list *(ebp+8) %eax *(ebp+0x10))
      (pop *(ebp+0x10))  # => eax
      ff 1/subop/decrement *Curr-block-depth
      (emit-indent *(ebp+8) *Curr-block-depth)
      (write-buffered *(ebp+8) "}\n")
      (write-buffered *(ebp+8) *ecx)  # Var-name
      (write-buffered *(ebp+8) ":break:\n")
    }
$emit-subx-block:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# Primitives supported
# For each operation, put variants with hard-coded registers before flexible ones.
== data
Primitives:
# - increment/decrement
_Primitive-inc-eax:
    # var/eax <- increment => 40/increment-eax
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-eax/imm32/outputs
    "40/increment-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-ecx/imm32/next
_Primitive-inc-ecx:
    # var/ecx <- increment => 41/increment-ecx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ecx/imm32/outputs
    "41/increment-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-edx/imm32/next
_Primitive-inc-edx:
    # var/edx <- increment => 42/increment-edx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edx/imm32/outputs
    "42/increment-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-ebx/imm32/next
_Primitive-inc-ebx:
    # var/ebx <- increment => 43/increment-ebx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ebx/imm32/outputs
    "43/increment-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-esi/imm32/next
_Primitive-inc-esi:
    # var/esi <- increment => 46/increment-esi
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-esi/imm32/outputs
    "46/increment-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-edi/imm32/next
_Primitive-inc-edi:
    # var/edi <- increment => 47/increment-edi
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edi/imm32/outputs
    "47/increment-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-eax/imm32/next
_Primitive-dec-eax:
    # var/eax <- decrement => 48/decrement-eax
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-eax/imm32/outputs
    "48/decrement-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-ecx/imm32/next
_Primitive-dec-ecx:
    # var/ecx <- decrement => 49/decrement-ecx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ecx/imm32/outputs
    "49/decrement-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-edx/imm32/next
_Primitive-dec-edx:
    # var/edx <- decrement => 4a/decrement-edx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edx/imm32/outputs
    "4a/decrement-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-ebx/imm32/next
_Primitive-dec-ebx:
    # var/ebx <- decrement => 4b/decrement-ebx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ebx/imm32/outputs
    "4b/decrement-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-esi/imm32/next
_Primitive-dec-esi:
    # var/esi <- decrement => 4e/decrement-esi
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-esi/imm32/outputs
    "4e/decrement-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-edi/imm32/next
_Primitive-dec-edi:
    # var/edi <- decrement => 4f/decrement-edi
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edi/imm32/outputs
    "4f/decrement-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-mem/imm32/next
_Primitive-inc-mem:
    # increment var => ff 0/subop/increment *(ebp+__)
    "increment"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    0/imm32/no-outputs
    "ff 0/subop/increment"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-inc-reg/imm32/next
_Primitive-inc-reg:
    # var/reg <- increment => ff 0/subop/increment %__
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-some-register/imm32/outputs
    "ff 0/subop/increment"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-mem/imm32/next
_Primitive-dec-mem:
    # decrement var => ff 1/subop/decrement *(ebp+__)
    "decrement"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    0/imm32/no-outputs
    "ff 1/subop/decrement"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-dec-reg/imm32/next
_Primitive-dec-reg:
    # var/reg <- decrement => ff 1/subop/decrement %__
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-some-register/imm32/outputs
    "ff 1/subop/decrement"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-to-eax/imm32/next
# - add
_Primitive-add-to-eax:
    # var/eax <- add lit => 05/add-to-eax lit/imm32
    "add"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "05/add-to-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-reg-to-reg/imm32/next
_Primitive-add-reg-to-reg:
    # var1/reg <- add var2/reg => 01/add-to var1/rm32 var2/r32
    "add"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "01/add-to"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-reg-to-mem/imm32/next
_Primitive-add-reg-to-mem:
    # add-to var1 var2/reg => 01/add-to var1 var2/r32
    "add-to"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "01/add-to"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-mem-to-reg/imm32/next
_Primitive-add-mem-to-reg:
    # var1/reg <- add var2 => 03/add var2/rm32 var1/r32
    "add"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "03/add"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-lit-to-reg/imm32/next
_Primitive-add-lit-to-reg:
    # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
    "add"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 0/subop/add"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-add-lit-to-mem/imm32/next
_Primitive-add-lit-to-mem:
    # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32
    "add-to"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 0/subop/add"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-second-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-from-eax/imm32/next
# - subtract
_Primitive-subtract-from-eax:
    # var/eax <- subtract lit => 2d/subtract-from-eax lit/imm32
    "subtract"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "2d/subtract-from-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-reg-from-reg/imm32/next
_Primitive-subtract-reg-from-reg:
    # var1/reg <- subtract var2/reg => 29/subtract-from var1/rm32 var2/r32
    "subtract"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "29/subtract-from"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-reg-from-mem/imm32/next
_Primitive-subtract-reg-from-mem:
    # subtract-from var1 var2/reg => 29/subtract-from var1 var2/r32
    "subtract-from"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "29/subtract-from"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-mem-from-reg/imm32/next
_Primitive-subtract-mem-from-reg:
    # var1/reg <- subtract var2 => 2b/subtract var2/rm32 var1/r32
    "subtract"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "2b/subtract"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-lit-from-reg/imm32/next
_Primitive-subtract-lit-from-reg:
    # var1/reg <- subtract lit => 81 5/subop/subtract var1/rm32 lit/imm32
    "subtract"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 5/subop/subtract"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-subtract-lit-from-mem/imm32/next
_Primitive-subtract-lit-from-mem:
    # subtract-from var1, lit => 81 5/subop/subtract var1/rm32 lit/imm32
    "subtract-from"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 5/subop/subtract"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-with-eax/imm32/next
# - and
_Primitive-and-with-eax:
    # var/eax <- and lit => 25/and-with-eax lit/imm32
    "and"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "25/and-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-reg-with-reg/imm32/next
_Primitive-and-reg-with-reg:
    # var1/reg <- and var2/reg => 21/and-with var1/rm32 var2/r32
    "and"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "21/and-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-reg-with-mem/imm32/next
_Primitive-and-reg-with-mem:
    # and-with var1 var2/reg => 21/and-with var1 var2/r32
    "and-with"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "21/and-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-mem-with-reg/imm32/next
_Primitive-and-mem-with-reg:
    # var1/reg <- and var2 => 23/and var2/rm32 var1/r32
    "and"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "23/and"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-lit-with-reg/imm32/next
_Primitive-and-lit-with-reg:
    # var1/reg <- and lit => 81 4/subop/and var1/rm32 lit/imm32
    "and"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 4/subop/and"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-and-lit-with-mem/imm32/next
_Primitive-and-lit-with-mem:
    # and-with var1, lit => 81 4/subop/and var1/rm32 lit/imm32
    "and-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 4/subop/and"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-with-eax/imm32/next
# - or
_Primitive-or-with-eax:
    # var/eax <- or lit => 0d/or-with-eax lit/imm32
    "or"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "0d/or-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-reg-with-reg/imm32/next
_Primitive-or-reg-with-reg:
    # var1/reg <- or var2/reg => 09/or-with var1/rm32 var2/r32
    "or"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "09/or-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-reg-with-mem/imm32/next
_Primitive-or-reg-with-mem:
    # or-with var1 var2/reg => 09/or-with var1 var2/r32
    "or-with"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "09/or-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-mem-with-reg/imm32/next
_Primitive-or-mem-with-reg:
    # var1/reg <- or var2 => 0b/or var2/rm32 var1/r32
    "or"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "0b/or"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-lit-with-reg/imm32/next
_Primitive-or-lit-with-reg:
    # var1/reg <- or lit => 81 1/subop/or var1/rm32 lit/imm32
    "or"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 1/subop/or"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-or-lit-with-mem/imm32/next
_Primitive-or-lit-with-mem:
    # or-with var1, lit => 81 1/subop/or var1/rm32 lit/imm32
    "or-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 1/subop/or"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-second-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-with-eax/imm32/next
# - xor
_Primitive-xor-with-eax:
    # var/eax <- xor lit => 35/xor-with-eax lit/imm32
    "xor"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "35/xor-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-reg-with-reg/imm32/next
_Primitive-xor-reg-with-reg:
    # var1/reg <- xor var2/reg => 31/xor-with var1/rm32 var2/r32
    "xor"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "31/xor-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-reg-with-mem/imm32/next
_Primitive-xor-reg-with-mem:
    # xor-with var1 var2/reg => 31/xor-with var1 var2/r32
    "xor-with"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "31/xor-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-mem-with-reg/imm32/next
_Primitive-xor-mem-with-reg:
    # var1/reg <- xor var2 => 33/xor var2/rm32 var1/r32
    "xor"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "33/xor"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-lit-with-reg/imm32/next
_Primitive-xor-lit-with-reg:
    # var1/reg <- xor lit => 81 6/subop/xor var1/rm32 lit/imm32
    "xor"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 6/subop/xor"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-xor-lit-with-mem/imm32/next
_Primitive-xor-lit-with-mem:
    # xor-with var1, lit => 81 6/subop/xor var1/rm32 lit/imm32
    "xor-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 6/subop/xor"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-copy-to-eax/imm32/next
# - copy
_Primitive-copy-to-eax:
    # var/eax <- copy lit => b8/copy-to-eax lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "b8/copy-to-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-to-ecx/imm32/next
_Primitive-copy-to-ecx:
    # var/ecx <- copy lit => b9/copy-to-ecx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-ecx/imm32/outputs
    "b9/copy-to-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-to-edx/imm32/next
_Primitive-copy-to-edx:
    # var/edx <- copy lit => ba/copy-to-edx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-edx/imm32/outputs
    "ba/copy-to-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-to-ebx/imm32/next
_Primitive-copy-to-ebx:
    # var/ebx <- copy lit => bb/copy-to-ebx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-ebx/imm32/outputs
    "bb/copy-to-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-to-esi/imm32/next
_Primitive-copy-to-esi:
    # var/esi <- copy lit => be/copy-to-esi lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-esi/imm32/outputs
    "be/copy-to-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-to-edi/imm32/next
_Primitive-copy-to-edi:
    # var/edi <- copy lit => bf/copy-to-edi lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-edi/imm32/outputs
    "bf/copy-to-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-reg-to-reg/imm32/next
_Primitive-copy-reg-to-reg:
    # var1/reg <- copy var2/reg => 89/copy-to var1/rm32 var2/r32
    "copy"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "89/copy-to"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-reg-to-mem/imm32/next
_Primitive-copy-reg-to-mem:
    # copy-to var1 var2/reg => 89/copy-to var1 var2/r32
    "copy-to"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "89/copy-to"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-mem-to-reg/imm32/next
_Primitive-copy-mem-to-reg:
    # var1/reg <- copy var2 => 8b/copy-from var2/rm32 var1/r32
    "copy"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "8b/copy-from"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-lit-to-reg/imm32/next
_Primitive-copy-lit-to-reg:
    # var1/reg <- copy lit => c7 0/subop/copy var1/rm32 lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "c7 0/subop/copy"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-copy-lit-to-mem/imm32/next
_Primitive-copy-lit-to-mem:
    # copy-to var1, lit => c7 0/subop/copy var1/rm32 lit/imm32
    "copy-to"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "c7 0/subop/copy"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/no-disp32
    1/imm32/output-is-write-only
    _Primitive-compare-mem-with-reg/imm32/next
# - compare
_Primitive-compare-mem-with-reg:
    # compare var1 var2/reg => 39/compare-> var1/rm32 var2/r32
    "compare"/imm32/name
    Two-args-int-stack-int-reg/imm32/inouts
    0/imm32/outputs
    "39/compare->"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-compare-reg-with-mem/imm32/next
_Primitive-compare-reg-with-mem:
    # compare var1/reg var2 => 3b/compare<- var2/rm32 var1/r32
    "compare"/imm32/name
    Two-args-int-reg-int-stack/imm32/inouts
    0/imm32/outputs
    "3b/compare<-"/imm32/subx-name
    2/imm32/rm32-is-second-inout
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-compare-eax-with-literal/imm32/next
_Primitive-compare-eax-with-literal:
    # compare var1/eax n => 3d/compare-eax-with n/imm32
    "compare"/imm32/name
    Two-args-int-eax-int-literal/imm32/inouts
    0/imm32/outputs
    "3d/compare-eax-with"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    2/imm32/imm32-is-second-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-compare-reg-with-literal/imm32/next
_Primitive-compare-reg-with-literal:
    # compare var1/reg n => 81 7/subop/compare %reg n/imm32
    "compare"/imm32/name
    Int-var-in-register-and-literal/imm32/inouts
    0/imm32/outputs
    "81 7/subop/compare"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-second-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-compare-mem-with-literal/imm32/next
_Primitive-compare-mem-with-literal:
    # compare var1 n => 81 7/subop/compare *(ebp+___) n/imm32
    "compare"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 7/subop/compare"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-second-inout
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-multiply-reg-by-mem/imm32/next
# - multiply
_Primitive-multiply-reg-by-mem:
    # var1/reg <- multiply var2 => 0f af/multiply var2/rm32 var1/r32
    "multiply"/imm32/name
    Single-int-var-in-mem/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "0f af/multiply"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/output-is-write-only
    _Primitive-break-if-addr</imm32/next
# - branches
_Primitive-break-if-addr<:
    "break-if-addr<"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 82/jump-if-addr< break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-addr>=/imm32/next
_Primitive-break-if-addr>=:
    "break-if-addr>="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 83/jump-if-addr>= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-=/imm32/next
_Primitive-break-if-=:
    "break-if-="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 84/jump-if-= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-!=/imm32/next
_Primitive-break-if-!=:
    "break-if-!="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 85/jump-if-!= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-addr<=/imm32/next
_Primitive-break-if-addr<=:
    "break-if-addr<="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 86/jump-if-addr<= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-addr>/imm32/next
_Primitive-break-if-addr>:
    "break-if-addr>"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 87/jump-if-addr> break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-</imm32/next
_Primitive-break-if-<:
    "break-if-<"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8c/jump-if-< break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if->=/imm32/next
_Primitive-break-if->=:
    "break-if->="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8d/jump-if->= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-<=/imm32/next
_Primitive-break-if-<=:
    "break-if-<="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8e/jump-if-<= break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if->/imm32/next
_Primitive-break-if->:
    "break-if->"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8f/jump-if-> break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break/imm32/next
_Primitive-break:
    "break"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "e9/jump break/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-addr</imm32/next
_Primitive-loop-if-addr<:
    "loop-if-addr<"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 82/jump-if-addr< loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-addr>=/imm32/next
_Primitive-loop-if-addr>=:
    "loop-if-addr>="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 83/jump-if-addr>= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-=/imm32/next
_Primitive-loop-if-=:
    "loop-if-="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 84/jump-if-= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-!=/imm32/next
_Primitive-loop-if-!=:
    "loop-if-!="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 85/jump-if-!= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-addr<=/imm32/next
_Primitive-loop-if-addr<=:
    "loop-if-addr<="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 86/jump-if-addr<= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-addr>/imm32/next
_Primitive-loop-if-addr>:
    "loop-if-addr>"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 87/jump-if-addr> loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-</imm32/next
_Primitive-loop-if-<:
    "loop-if-<"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8c/jump-if-< loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if->=/imm32/next
_Primitive-loop-if->=:
    "loop-if->="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8d/jump-if->= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if-<=/imm32/next
_Primitive-loop-if-<=:
    "loop-if-<="/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8e/jump-if-<= loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop-if->/imm32/next
_Primitive-loop-if->:
    "loop-if->"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "0f 8f/jump-if-> loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-loop/imm32/next  # we probably don't need an unconditional break
_Primitive-loop:
    "loop"/imm32/name
    0/imm32/inouts
    0/imm32/outputs
    "e9/jump loop/disp32"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/no-disp32
    0/imm32/no-output
    _Primitive-break-if-addr<-named/imm32/next
# - branches to named blocks
_Primitive-break-if-addr<-named:
    "break-if-addr<"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 82/jump-if-addr<"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-addr>=-named/imm32/next
_Primitive-break-if-addr>=-named:
    "break-if-addr>="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 83/jump-if-addr>="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-=-named/imm32/next
_Primitive-break-if-=-named:
    "break-if-="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 84/jump-if-="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-!=-named/imm32/next
_Primitive-break-if-!=-named:
    "break-if-!="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 85/jump-if-!="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-addr<=-named/imm32/next
_Primitive-break-if-addr<=-named:
    "break-if-addr<="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 86/jump-if-addr<="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-addr>-named/imm32/next
_Primitive-break-if-addr>-named:
    "break-if-addr>"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 87/jump-if-addr>"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-<-named/imm32/next
_Primitive-break-if-<-named:
    "break-if-<"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8c/jump-if-<"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if->=-named/imm32/next
_Primitive-break-if->=-named:
    "break-if->="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8d/jump-if->="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if-<=-named/imm32/next
_Primitive-break-if-<=-named:
    "break-if-<="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8e/jump-if-<="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-if->-named/imm32/next
_Primitive-break-if->-named:
    "break-if->"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8f/jump-if->"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-break-named/imm32/next
_Primitive-break-named:
    "break"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "e9/jump"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-addr<-named/imm32/next
_Primitive-loop-if-addr<-named:
    "loop-if-addr<"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 82/jump-if-addr<"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-addr>=-named/imm32/next
_Primitive-loop-if-addr>=-named:
    "loop-if-addr>="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 83/jump-if-addr>="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-=-named/imm32/next
_Primitive-loop-if-=-named:
    "loop-if-="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 84/jump-if-="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-!=-named/imm32/next
_Primitive-loop-if-!=-named:
    "loop-if-!="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 85/jump-if-!="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-addr<=-named/imm32/next
_Primitive-loop-if-addr<=-named:
    "loop-if-addr<="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 86/jump-if-addr<="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-addr>-named/imm32/next
_Primitive-loop-if-addr>-named:
    "loop-if-addr>"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 87/jump-if-addr>"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-<-named/imm32/next
_Primitive-loop-if-<-named:
    "loop-if-<"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8c/jump-if-<"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if->=-named/imm32/next
_Primitive-loop-if->=-named:
    "loop-if->="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8d/jump-if->="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if-<=-named/imm32/next
_Primitive-loop-if-<=-named:
    "loop-if-<="/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8e/jump-if-<="/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-if->-named/imm32/next
_Primitive-loop-if->-named:
    "loop-if->"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "0f 8f/jump-if->"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    _Primitive-loop-named/imm32/next  # we probably don't need an unconditional break
_Primitive-loop-named:
    "loop"/imm32/name
    Single-lit-var/imm32/inouts
    0/imm32/outputs
    "e9/jump"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    1/imm32/disp32-is-first-inout
    0/imm32/no-output
    0/imm32/next

Single-int-var-in-mem:
    Int-var-in-mem/imm32
    0/imm32/next

Int-var-in-mem:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    1/imm32/some-stack-offset
    0/imm32/no-register

Two-args-int-stack-int-reg:
    Int-var-in-mem/imm32
    Single-int-var-in-some-register/imm32/next

Two-args-int-reg-int-stack:
    Int-var-in-some-register/imm32
    Single-int-var-in-mem/imm32/next

Two-args-int-eax-int-literal:
    Int-var-in-eax/imm32
    Single-lit-var/imm32/next

Int-var-and-literal:
    Int-var-in-mem/imm32
    Single-lit-var/imm32/next

Int-var-in-register-and-literal:
    Int-var-in-some-register/imm32
    Single-lit-var/imm32/next

Single-int-var-in-some-register:
    Int-var-in-some-register/imm32
    0/imm32/next

Int-var-in-some-register:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    Any-register/imm32

Single-int-var-in-eax:
    Int-var-in-eax/imm32
    0/imm32/next

Int-var-in-eax:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "eax"/imm32/register

Single-int-var-in-ecx:
    Int-var-in-ecx/imm32
    0/imm32/next

Int-var-in-ecx:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "ecx"/imm32/register

Single-int-var-in-edx:
    Int-var-in-edx/imm32
    0/imm32/next

Int-var-in-edx:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "edx"/imm32/register

Single-int-var-in-ebx:
    Int-var-in-ebx/imm32
    0/imm32/next

Int-var-in-ebx:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "ebx"/imm32/register

Single-int-var-in-esi:
    Int-var-in-esi/imm32
    0/imm32/next

Int-var-in-esi:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "esi"/imm32/register

Single-int-var-in-edi:
    Int-var-in-edi/imm32
    0/imm32/next

Int-var-in-edi:
    "arg1"/imm32/name
    Type-int/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    "edi"/imm32/register

Single-lit-var:
    Lit-var/imm32
    0/imm32/next

Lit-var:
    "literal"/imm32/name
    Type-literal/imm32
    1/imm32/some-block-depth
    0/imm32/no-stack-offset
    0/imm32/no-register

Type-int:
    1/imm32/left/int
    0/imm32/right/null

Type-literal:
    0/imm32/left/literal
    0/imm32/right/null

== code
emit-subx-primitive:  # out: (addr buffered-file), stmt: (handle stmt), primitive: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # ecx = primitive
    8b/-> *(ebp+0x10) 1/r32/ecx
    # emit primitive name
    (emit-indent *(ebp+8) *Curr-block-depth)
    (write-buffered *(ebp+8) *(ecx+0xc))  # Primitive-subx-name
    # emit rm32 if necessary
    (emit-subx-rm32 *(ebp+8) *(ecx+0x10) *(ebp+0xc))  # out, Primitive-subx-rm32, stmt
    # emit r32 if necessary
    (emit-subx-r32 *(ebp+8) *(ecx+0x14) *(ebp+0xc))  # out, Primitive-subx-r32, stmt
    # emit imm32 if necessary
    (emit-subx-imm32 *(ebp+8) *(ecx+0x18) *(ebp+0xc))  # out, Primitive-subx-imm32, stmt
    # emit disp32 if necessary
    (emit-subx-disp32 *(ebp+8) *(ecx+0x1c) *(ebp+0xc))  # out, Primitive-subx-disp32, stmt
    (write-buffered *(ebp+8) Newline)
$emit-subx-primitive:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-rm32:  # out: (addr buffered-file), l: arg-location, stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # if (l == 0) return
    81 7/subop/compare *(ebp+0xc) 0/imm32
    74/jump-if-= $emit-subx-rm32:end/disp8
    # var v/eax: (handle var)
    (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # => eax
    (emit-subx-var-as-rm32 *(ebp+8) %eax)
$emit-subx-rm32:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

get-stmt-operand-from-arg-location:  # stmt: (handle stmt), l: arg-location -> var/eax: (handle stmt-var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # eax = l
    8b/-> *(ebp+0xc) 0/r32/eax
    # ecx = stmt
    8b/-> *(ebp+8) 1/r32/ecx
    # if (l == 1) return stmt->inouts
    {
      3d/compare-eax-and 1/imm32
      75/jump-if-!= break/disp8
$get-stmt-operand-from-arg-location:1:
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts or Regvardef-inouts
      eb/jump $get-stmt-operand-from-arg-location:end/disp8
    }
    # if (l == 2) return stmt->inouts->next
    {
      3d/compare-eax-and 2/imm32
      75/jump-if-!= break/disp8
$get-stmt-operand-from-arg-location:2:
      8b/-> *(ecx+8) 0/r32/eax  # Stmt1-inouts or Regvardef-inouts
      8b/-> *(eax+4) 0/r32/eax  # Stmt-var-next
      eb/jump $get-stmt-operand-from-arg-location:end/disp8
    }
    # if (l == 3) return stmt->outputs
    {
      3d/compare-eax-and 3/imm32
      75/jump-if-!= break/disp8
$get-stmt-operand-from-arg-location:3:
      8b/-> *(ecx+0xc) 0/r32/eax  # Stmt1-outputs
      eb/jump $get-stmt-operand-from-arg-location:end/disp8
    }
    # abort
    e9/jump $get-stmt-operand-from-arg-location:abort/disp32
$get-stmt-operand-from-arg-location:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$get-stmt-operand-from-arg-location:abort:
    # error("invalid arg-location " eax)
    (write-buffered Stderr "invalid arg-location ")
    (print-int32-buffered Stderr %eax)
    (write-buffered Stderr Newline)
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

emit-subx-r32:  # out: (addr buffered-file), l: arg-location, stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # if (location == 0) return
    81 7/subop/compare *(ebp+0xc) 0/imm32
    0f 84/jump-if-= $emit-subx-r32:end/disp32
    # var v/eax: (handle var)
    (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # => eax
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    (maybe-get Registers *(eax+0x10) 8)  # Var-register => eax: (addr register-index)
    (write-buffered *(ebp+8) Space)
    (print-int32-buffered *(ebp+8) *eax)
    (write-buffered *(ebp+8) "/r32")
$emit-subx-r32:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-imm32:  # out: (addr buffered-file), l: arg-location, stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # if (location == 0) return
    81 7/subop/compare *(ebp+0xc) 0/imm32
    74/jump-if-= $emit-subx-imm32:end/disp8
    # var v/eax: (handle var)
    (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # => eax
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    (write-buffered *(ebp+8) Space)
    (write-buffered *(ebp+8) *eax)  # Var-name
    (write-buffered *(ebp+8) "/imm32")
$emit-subx-imm32:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-disp32:  # out: (addr buffered-file), l: arg-location, stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # if (location == 0) return
    81 7/subop/compare *(ebp+0xc) 0/imm32
    0f 84/jump-if-= $emit-subx-disp32:end/disp32
    # var v/eax: (handle var)
    (get-stmt-operand-from-arg-location *(ebp+0x10) *(ebp+0xc))  # => eax
    8b/-> *eax 0/r32/eax  # Stmt-var-value
    (write-buffered *(ebp+8) Space)
    (write-buffered *(ebp+8) *eax)  # Var-name
    # hack: if instruction operation starts with "break", emit ":break"
    # var name/ecx: (addr array byte) = stmt->operation
    8b/-> *(ebp+0x10) 0/r32/eax
    8b/-> *(eax+4) 1/r32/ecx
    {
      (string-starts-with? %ecx "break")  # => eax
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= break/disp8
      (write-buffered *(ebp+8) ":break")
    }
    # hack: if instruction operation starts with "loop", emit ":loop"
    {
      (string-starts-with? %ecx "loop")  # => eax
      3d/compare-eax-and 0/imm32/false
      74/jump-if-= break/disp8
      (write-buffered *(ebp+8) ":loop")
    }
    (write-buffered *(ebp+8) "/disp32")
$emit-subx-disp32:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-call:  # out: (addr buffered-file), stmt: (handle stmt), callee: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (emit-indent *(ebp+8) *Curr-block-depth)
    (write-buffered *(ebp+8) "(")
    # - emit function name
    8b/-> *(ebp+0x10) 1/r32/ecx
    (write-buffered *(ebp+8) *(ecx+4))  # Function-subx-name
    # - emit arguments
    # var curr/ecx: (handle stmt-var) = stmt->inouts
    8b/-> *(ebp+0xc) 1/r32/ecx
    8b/-> *(ecx+8) 1/r32/ecx  # Stmt1-inouts
    {
      # if (curr == null) break
      81 7/subop/compare %ecx 0/imm32
      74/jump-if-= break/disp8
      #
      (emit-subx-call-operand *(ebp+8) %ecx)
      # curr = curr->next
      8b/-> *(ecx+4) 1/r32/ecx  # Stmt-var-next
      eb/jump loop/disp8
    }
    #
    (write-buffered *(ebp+8) ")\n")
$emit-subx-call:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# like a function call, except we have no idea what function it is
# we hope it's defined in SubX and that the types are ok
emit-hailmary-call:  # out: (addr buffered-file), stmt: (handle stmt)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (emit-indent *(ebp+8) *Curr-block-depth)
    (write-buffered *(ebp+8) "(")
    # ecx = stmt
    8b/-> *(ebp+0xc) 1/r32/ecx
    # - emit function name
    (write-buffered *(ebp+8) *(ecx+4))  # Stmt1-operation
    # - emit arguments
    # var curr/ecx: (handle stmt-var) = stmt->inouts
    8b/-> *(ecx+8) 1/r32/ecx  # Stmt1-inouts
    {
      # if (curr == null) break
      81 7/subop/compare %ecx 0/imm32
      74/jump-if-= break/disp8
      #
      (emit-subx-call-operand *(ebp+8) %ecx)
      # curr = curr->next
      8b/-> *(ecx+4) 1/r32/ecx  # Stmt-var-next
      eb/jump loop/disp8
    }
    #
    (write-buffered *(ebp+8) ")\n")
$emit-hailmary-call:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-call-operand:  # out: (addr buffered-file), s: (handle stmt-var)
    # shares code with emit-subx-var-as-rm32
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    # ecx = s
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var operand/esi: (handle var) = s->value
    8b/-> *ecx 6/r32/esi  # Stmt-var-value
    # if (operand->register && s->is-deref?) emit "*__"
    {
$emit-subx-call-operand:check-for-register-indirect:
      81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
      74/jump-if-= break/disp8
      81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
      74/jump-if-= break/disp8
$emit-subx-call-operand:register-indirect:
      (write-buffered *(ebp+8) " *")
      (write-buffered *(ebp+8) *(esi+0x10))  # Var-register
      e9/jump $emit-subx-call-operand:end/disp32
    }
    # if (operand->register && !s->is-deref?) emit "%__"
    {
$emit-subx-call-operand:check-for-register-direct:
      81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
      74/jump-if-= break/disp8
      81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
      75/jump-if-!= break/disp8
$emit-subx-call-operand:register-direct:
      (write-buffered *(ebp+8) " %")
      (write-buffered *(ebp+8) *(esi+0x10))  # Var-register
      e9/jump $emit-subx-call-operand:end/disp32
    }
    # else if (operand->stack-offset) emit "*(ebp+__)"
    {
      81 7/subop/compare *(esi+0xc) 0/imm32  # Var-offset
      74/jump-if-= break/disp8
$emit-subx-call-operand:stack:
      (write-buffered *(ebp+8) Space)
      (write-buffered *(ebp+8) "*(ebp+")
      (print-int32-buffered *(ebp+8) *(esi+0xc))  # Var-offset
      (write-buffered *(ebp+8) ")")
      e9/jump $emit-subx-call-operand:end/disp32
    }
    # else if (operand->type == literal) emit "__"
    {
      8b/-> *(esi+4) 0/r32/eax  # Var-type
      81 7/subop/compare *eax 0/imm32  # Tree-left
      75/jump-if-!= break/disp8
$emit-subx-call-operand:literal:
      (write-buffered *(ebp+8) Space)
      (write-buffered *(ebp+8) *esi)
    }
$emit-subx-call-operand:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-var-as-rm32:  # out: (addr buffered-file), s: (handle stmt-var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    # ecx = s
    8b/-> *(ebp+0xc) 1/r32/ecx
    # var operand/esi: (handle var) = s->value
    8b/-> *ecx 6/r32/esi  # Stmt-var-value
    # if (operand->register && s->is-deref?) emit "*__"
    {
$emit-subx-var-as-rm32:check-for-register-indirect:
      81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
      74/jump-if-= break/disp8
      81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
      74/jump-if-= break/disp8
$emit-subx-var-as-rm32:register-indirect:
      (write-buffered *(ebp+8) " *")
      (write-buffered *(ebp+8) *(esi+0x10))  # Var-register
    }
    # if (operand->register && !s->is-deref?) emit "%__"
    {
$emit-subx-var-as-rm32:check-for-register-direct:
      81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
      74/jump-if-= break/disp8
      81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
      75/jump-if-!= break/disp8
$emit-subx-var-as-rm32:register-direct:
      (write-buffered *(ebp+8) " %")
      (write-buffered *(ebp+8) *(esi+0x10))  # Var-register
    }
    # else if (operand->stack-offset) emit "*(ebp+__)"
    {
      81 7/subop/compare *(esi+0xc) 0/imm32  # Var-offset
      74/jump-if-= break/disp8
$emit-subx-var-as-rm32:stack:
      (write-buffered *(ebp+8) Space)
      (write-buffered *(ebp+8) "*(ebp+")
      (print-int32-buffered *(ebp+8) *(esi+0xc))  # Var-offset
      (write-buffered *(ebp+8) ")")
    }
$emit-subx-var-as-rm32:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-matching-function:  # functions: (addr function), stmt: (handle stmt) -> result/eax: (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var curr/ecx: (handle function) = functions
    8b/-> *(ebp+8) 1/r32/ecx
    {
      # if (curr == null) break
      81 7/subop/compare %ecx 0/imm32
      74/jump-if-= break/disp8
      # if match(stmt, curr) return curr
      {
        (mu-stmt-matches-function? *(ebp+0xc) %ecx)  # => eax
        3d/compare-eax-and 0/imm32/false
        74/jump-if-= break/disp8
        89/<- %eax 1/r32/ecx
        eb/jump $find-matching-function:end/disp8
      }
      # curr = curr->next
      8b/-> *(ecx+0x14) 1/r32/ecx  # Function-next
      eb/jump loop/disp8
    }
    # return null
    b8/copy-to-eax 0/imm32
$find-matching-function:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

find-matching-primitive:  # primitives: (handle primitive), stmt: (handle stmt) -> result/eax: (handle primitive)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var curr/ecx: (handle primitive) = primitives
    8b/-> *(ebp+8) 1/r32/ecx
    {
$find-matching-primitive:loop:
      # if (curr == null) break
      81 7/subop/compare %ecx 0/imm32
      0f 84/jump-if-= break/disp32
#?       (write-buffered Stderr "prim: ")
#?       (write-buffered Stderr *ecx)  # Primitive-name
#?       (write-buffered Stderr " => ")
#?       (write-buffered Stderr *(ecx+0xc))  # Primitive-subx-name
#?       (write-buffered Stderr Newline)
#?       (flush Stderr)
      # if match(curr, stmt) return curr
      {
        (mu-stmt-matches-primitive? *(ebp+0xc) %ecx)  # => eax
        3d/compare-eax-and 0/imm32/false
        74/jump-if-= break/disp8
        89/<- %eax 1/r32/ecx
        eb/jump $find-matching-primitive:end/disp8
      }
$find-matching-primitive:next-primitive:
      # curr = curr->next
      8b/-> *(ecx+0x24) 1/r32/ecx  # Primitive-next
      e9/jump loop/disp32
    }
    # return null
    b8/copy-to-eax 0/imm32
$find-matching-primitive:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

mu-stmt-matches-function?:  # stmt: (handle stmt), function: (handle function) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # return function->name == stmt->operation
    8b/-> *(ebp+8) 1/r32/ecx
    8b/-> *(ebp+0xc) 0/r32/eax
    (string-equal? *(ecx+4) *eax)  # Stmt1-operation, Function-name => eax
$mu-stmt-matches-function?:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

mu-stmt-matches-primitive?:  # stmt: (handle stmt), primitive: (handle primitive) -> result/eax: boolean
    # A mu stmt matches a primitive if the name matches, all the inout vars
    # match, and all the output vars match.
    # Vars match if types match and registers match.
    # In addition, a stmt output matches a primitive's output if types match
    # and the primitive has a wildcard register.
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # ecx = stmt
    8b/-> *(ebp+8) 1/r32/ecx
    # edx = primitive
    8b/-> *(ebp+0xc) 2/r32/edx
    {
$mu-stmt-matches-primitive?:check-name:
      # if (primitive->name != stmt->operation) return false
      (string-equal? *(ecx+4) *edx)  # Stmt1-operation, Primitive-name => eax
      3d/compare-eax-and 0/imm32/false
      75/jump-if-!= break/disp8
      b8/copy-to-eax 0/imm32
      e9/jump $mu-stmt-matches-primitive?:end/disp32
    }
$mu-stmt-matches-primitive?:check-inouts:
    # for (curr/esi in stmt->inouts, curr2/edi in primitive->inouts)
    8b/-> *(ecx+8) 6/r32/esi  # Stmt1-inouts or Regvardef-inouts
    8b/-> *(edx+4) 7/r32/edi  # Primitive-inouts
    {
      # if (curr == 0 && curr2 == 0) move on to check outputs
      {
        81 7/subop/compare %esi 0/imm32
        75/jump-if-!= break/disp8
$mu-stmt-matches-primitive?:stmt-inout-is-null:
        {
          81 7/subop/compare %edi 0/imm32
          75/jump-if-!= break/disp8
          #
          e9/jump $mu-stmt-matches-primitive?:check-outputs/disp32
        }
        # return false
        b8/copy-to-eax 0/imm32/false
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # if (curr2 == 0) return false
      {
        81 7/subop/compare %edi 0/imm32
        75/jump-if-!= break/disp8
$mu-stmt-matches-primitive?:prim-inout-is-null:
        b8/copy-to-eax 0/imm32/false
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # if (curr != curr2) return false
      {
        (operand-matches-primitive? %esi *edi)  # List-value => eax
        3d/compare-eax-and 0/imm32/false
        75/jump-if-!= break/disp8
        b8/copy-to-eax 0/imm32/false
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # curr=curr->next
      8b/-> *(esi+4) 6/r32/esi  # Stmt-var-next
      # curr2=curr2->next
      8b/-> *(edi+4) 7/r32/edi  # Stmt-var-next
      eb/jump loop/disp8
    }
$mu-stmt-matches-primitive?:check-outputs:
    # for (curr/esi in stmt->outputs, curr2/edi in primitive->outputs)
    8b/-> *(ecx+0xc) 6/r32/esi  # Stmt1-outputs
    8b/-> *(edx+8) 7/r32/edi  # Primitive-outputs
    {
      # if (curr == 0) return (curr2 == 0)
      {
$mu-stmt-matches-primitive?:check-output:
        81 7/subop/compare %esi 0/imm32
        75/jump-if-!= break/disp8
        {
          81 7/subop/compare %edi 0/imm32
          75/jump-if-!= break/disp8
          # return true
          b8/copy-to-eax 1/imm32
          e9/jump $mu-stmt-matches-primitive?:end/disp32
        }
        # return false
        b8/copy-to-eax 0/imm32
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # if (curr2 == 0) return false
      {
        81 7/subop/compare %edi 0/imm32
        75/jump-if-!= break/disp8
        b8/copy-to-eax 0/imm32
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # if (curr != curr2) return false
      {
        (operand-matches-primitive? %esi *edi)  # List-value => eax
        3d/compare-eax-and 0/imm32/false
        75/jump-if-!= break/disp8
        b8/copy-to-eax 0/imm32
        e9/jump $mu-stmt-matches-primitive?:end/disp32
      }
      # curr=curr->next
      8b/-> *(esi+4) 6/r32/esi  # Stmt-var-next
      # curr2=curr2->next
      8b/-> *(edi+4) 7/r32/edi  # Stmt-var-next
      eb/jump loop/disp8
    }
$mu-stmt-matches-primitive?:return-true:
    b8/copy-to-eax 1/imm32
$mu-stmt-matches-primitive?:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

operand-matches-primitive?:  # s: (handle stmt-var), prim-var: (handle var) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    56/push-esi
    57/push-edi
    # ecx = s
    8b/-> *(ebp+8) 1/r32/ecx
    # var var/esi : (handle var) = s->value
    8b/-> *ecx 6/r32/esi  # Stmt-var-value
    # edi = prim-var
    8b/-> *(ebp+0xc) 7/r32/edi
$operand-matches-primitive?:check-type:
    # if (var->type != prim-var->type) return false
    (subx-type-equal? *(esi+4) *(edi+4))  # Var-type, Var-type => eax
    3d/compare-eax-and 0/imm32/false
    b8/copy-to-eax 0/imm32/false
    0f 84/jump-if-= $operand-matches-primitive?:end/disp32
    {
$operand-matches-primitive?:check-register:
      # if prim-var is in memory and var is in register but dereference, match
      {
        81 7/subop/compare *(edi+0x10) 0/imm32  # Var-register
        0f 85/jump-if-!= break/disp32
        81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
        74/jump-if-= break/disp8
        81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
        74/jump-if-= break/disp8
        e9/jump $operand-matches-primitive?:return-true/disp32
      }
      # if prim-var is in register and var is in register but dereference, no match
      {
        81 7/subop/compare *(edi+0x10) 0/imm32  # Var-register
        0f 84/jump-if-= break/disp32
        81 7/subop/compare *(esi+0x10) 0/imm32  # Var-register
        0f 84/jump-if-= break/disp32
        81 7/subop/compare *(ecx+8) 0/imm32/false  # Stmt-var-is-deref
        74/jump-if-= break/disp8
        e9/jump $operand-matches-primitive?:return-false/disp32
      }
      # return false if var->register doesn't match prim-var->register
      {
        # if register addresses are equal, it's a match
        8b/-> *(esi+0x10) 0/r32/eax  # Var-register
        39/compare *(edi+0x10) 0/r32/eax  # Var-register
        74/jump-if-= break/disp8
        # if either address is 0, return false
        3d/compare-eax-and 0/imm32
        74/jump-if-=  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
        81 7/subop/compare *(edi+0x10) 0/imm32  # Var-register
        74/jump-if-=  $operand-matches-primitive?:return-false/disp8
        # if prim-var->register is wildcard, it's a match
        (string-equal? *(edi+0x10) Any-register)  # Var-register => eax
        3d/compare-eax-and 0/imm32/false
        75/jump-if-!= break/disp8
        # if string contents aren't equal, return false
        (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register => eax
        3d/compare-eax-and 0/imm32/false
        74/jump-if-= $operand-matches-primitive?:return-false/disp8
      }
    }
$operand-matches-primitive?:return-true:
    b8/copy-to-eax 1/imm32/true
    eb/jump $operand-matches-primitive?:end/disp8
$operand-matches-primitive?:return-false:
    b8/copy-to-eax 0/imm32/false
$operand-matches-primitive?:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

subx-type-equal?:  # a: (handle tree type-id), b: (handle tree type-id) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # var alit/ecx: boolean = is-literal-type?(a)
    (is-literal-type? *(ebp+8))  # => eax
    89/<- %ecx 0/r32/eax
    # var blit/eax: boolean = is-literal-type?(b)
    (is-literal-type? *(ebp+0xc))  # => eax
    # return alit == blit
    39/compare %eax 1/r32/ecx
    74/jump-if-= $subx-type-equal?:true/disp8
$subx-type-equal?:false:
    b8/copy-to-eax 0/imm32/false
    eb/jump $subx-type-equal?:end/disp8
$subx-type-equal?:true:
    b8/copy-to-eax 1/imm32/true
$subx-type-equal?:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

is-literal-type?:  # a: (handle tree type-id) -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    8b/-> *(ebp+8) 0/r32/eax
    8b/-> *eax 0/r32/eax  # Atom-value
    3d/compare-eax-and 0/imm32/false
    74/jump-if-equal $is-literal-type?:end/disp8
    b8/copy-to-eax 1/imm32/true
$is-literal-type?:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-primitive:
    # Primitive operation on a variable on the stack.
    #   increment foo
    # =>
    #   ff 0/subop/increment *(ebp-8)
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   stack-offset: -8
    #
    # There's a primitive with this info:
    #   name: 'increment'
    #   inouts: int/mem
    #   value: 'ff 0/subop/increment'
    #
    # There's nothing in functions.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var
    68/push 0/imm32/no-register
    68/push -8/imm32/stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var operand/ebx: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %ebx 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    53/push-ebx/operands
    68/push "increment"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var primitives/ebx: primitive
    68/push 0/imm32/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 1/imm32/rm32-is-first-inout
    68/push "ff 0/subop/increment"/imm32/subx-name
    68/push 0/imm32/outputs
    53/push-ebx/inouts  # hack; in practice we won't have the same var in function definition and call
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi %ebx 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0xfffffff8)" "F - test-emit-subx-stmt-primitive")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-primitive-register:
    # Primitive operation on a variable in a register.
    #   foo <- increment
    # =>
    #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   register: 'eax'
    #
    # There's a primitive with this info:
    #   name: 'increment'
    #   out: int/reg
    #   value: 'ff 0/subop/increment'
    #
    # There's nothing in functions.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var operand/ebx: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %ebx 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    53/push-ebx/outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var formal-var/ebx: var in any register
    68/push Any-register/imm32
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "dummy"/imm32
    89/<- %ebx 4/r32/esp
    # var operand/ebx: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    53/push-ebx/formal-var
    89/<- %ebx 4/r32/esp
    # var primitives/ebx: primitive
    68/push 0/imm32/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 3/imm32/rm32-in-first-output
    68/push "ff 0/subop/increment"/imm32/subx-name
    53/push-ebx/outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi %ebx 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-stmt-primitive-register")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-select-primitive:
    # Select the right primitive between overloads.
    #   foo <- increment
    # =>
    #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   register: 'eax'
    #
    # There's two primitives, as follows:
    #   - name: 'increment'
    #     out: int/reg
    #     value: 'ff 0/subop/increment'
    #   - name: 'increment'
    #     inout: int/mem
    #     value: 'ff 0/subop/increment'
    #
    # There's nothing in functions.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var real-outputs/edi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var formal-var/ebx: var in any register
    68/push Any-register/imm32
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "dummy"/imm32
    89/<- %ebx 4/r32/esp
    # var formal-outputs/ebx: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    53/push-ebx/formal-var
    89/<- %ebx 4/r32/esp
    # var primitive1/ebx: primitive
    68/push 0/imm32/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 3/imm32/rm32-in-first-output
    68/push "ff 0/subop/increment"/imm32/subx-name
    53/push-ebx/outputs/formal-outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # var primitives/ebx: primitive
    53/push-ebx/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 1/imm32/rm32-is-first-inout
    68/push "ff 0/subop/increment"/imm32/subx-name
    68/push 0/imm32/outputs
    57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi %ebx 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-stmt-select-primitive")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-select-primitive-2:
    # Select the right primitive between overloads.
    #   foo <- increment
    # =>
    #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   register: 'eax'
    #
    # There's two primitives, as follows:
    #   - name: 'increment'
    #     out: int/reg
    #     value: 'ff 0/subop/increment'
    #   - name: 'increment'
    #     inout: int/mem
    #     value: 'ff 0/subop/increment'
    #
    # There's nothing in functions.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var inouts/edi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    57/push-edi/inouts
    68/push "increment"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var formal-var/ebx: var in any register
    68/push Any-register/imm32
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "dummy"/imm32
    89/<- %ebx 4/r32/esp
    # var operand/ebx: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    53/push-ebx/formal-var
    89/<- %ebx 4/r32/esp
    # var primitive1/ebx: primitive
    68/push 0/imm32/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 3/imm32/rm32-in-first-output
    68/push "ff 0/subop/increment"/imm32/subx-name
    53/push-ebx/outputs/formal-outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # var primitives/ebx: primitive
    53/push-ebx/next
    68/push 0/imm32/output-is-write-only
    68/push 0/imm32/no-disp32
    68/push 0/imm32/no-imm32
    68/push 0/imm32/no-r32
    68/push 1/imm32/rm32-is-first-inout
    68/push "ff 0/subop/increment"/imm32/subx-name
    68/push 0/imm32/outputs
    57/push-edi/inouts/real-outputs  # hack; in practice we won't have the same var in function definition and call
    68/push "increment"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi %ebx 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment %eax" "F - test-emit-subx-stmt-select-primitive-2")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-increment-register:
    # Select the right register between overloads.
    #   foo <- increment
    # =>
    #   50/increment-eax
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   register: 'eax'
    #
    # Primitives are the global definitions.
    #
    # There are no functions defined.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var real-outputs/edi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/operation
    68/push 1/imm32/regular-stmt
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "40/increment-eax" "F - test-increment-register")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-increment-var:
    # Select the right primitive between overloads.
    #   foo <- increment
    # =>
    #   ff 0/subop/increment %eax  # sub-optimal, but should suffice
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   register: 'eax'
    #
    # Primitives are the global definitions.
    #
    # There are no functions defined.
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var inouts/edi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    68/push 0/imm32/inouts
    68/push "increment"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "40/increment-eax" "F - test-increment-var")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-reg-to-reg:
    #   var1/reg <- add var2/reg
    # =>
    #   01/add %var1 var2
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var2/edx: var in ecx
    68/push "ecx"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "var2"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var outputs/edi: (handle stmt-var) = [var1, var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-var1
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    56/push-esi/inouts
    68/push "add"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "01/add-to %eax 0x00000001/r32" "F - test-add-reg-to-reg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-reg-to-mem:
    #   add-to var1 var2/reg
    # =>
    #   01/add *(ebp+__) var2
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var2/edx: var in ecx
    68/push "ecx"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "var2"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var inouts = (handle stmt-var) = [var1, var2]
    56/push-esi/next
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "add-to"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "01/add-to *(ebp+0x00000008) 0x00000001/r32" "F - test-add-reg-to-mem")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-mem-to-reg:
    #   var1/reg <- add var2
    # =>
    #   03/add *(ebp+__) var1
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var2/edx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "var2"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var outputs/edi = (handle stmt-var) = [var1]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-var1
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    56/push-esi/inouts
    68/push "add"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "03/add *(ebp+0x00000008) 0x00000000/r32" "F - test-add-mem-to-reg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-literal-to-eax:
    #   var1/eax <- add 0x34
    # =>
    #   05/add-to-eax 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var outputs/edi: (handle stmt-var) = [var1]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-var1
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    56/push-esi/inouts
    68/push "add"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "05/add-to-eax 0x34/imm32" "F - test-add-literal-to-eax")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-literal-to-reg:
    #   var1/ecx <- add 0x34
    # =>
    #   81 0/subop/add %ecx 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in ecx
    68/push "ecx"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var outputs/edi: (handle stmt-var) = [var1]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-var1
    89/<- %edi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    57/push-edi/outputs
    56/push-esi/inouts
    68/push "add"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "81 0/subop/add %ecx 0x34/imm32" "F - test-add-literal-to-reg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-add-literal-to-mem:
    #   add-to var1, 0x34
    # =>
    #   81 0/subop/add %eax 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var inouts = (handle stmt-var) = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi/next
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "add-to"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "81 0/subop/add *(ebp+0x00000008) 0x34/imm32" "F - test-add-literal-to-mem")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-compare-mem-with-reg:
    #   compare var1, var2/eax
    # =>
    #   39/compare *(ebp+___) 0/r32/eax
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var2/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var2"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var1/edx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "var1"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-var2
    89/<- %esi 4/r32/esp
    # inouts = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi
    52/push-edx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "compare"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "39/compare-> *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-mem-with-reg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-compare-reg-with-mem:
    #   compare var1/eax, var2
    # =>
    #   3b/compare *(ebp+___) 0/r32/eax
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var2/edx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    ff 6/subop/push *(ecx+4)  # Var-type
    68/push "var2"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # inouts = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "compare"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "3b/compare<- *(ebp+0x00000008) 0x00000000/r32" "F - test-compare-reg-with-mem")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-compare-mem-with-literal:
    #   compare var1, 0x34
    # =>
    #   81 7/subop/compare *(ebp+___) 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var
    68/push 0/imm32/no-register
    68/push 8/imm32/stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # inouts = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi/next
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "compare"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "81 7/subop/compare *(ebp+0x00000008) 0x34/imm32" "F - test-compare-mem-with-literal")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-compare-eax-with-literal:
    #   compare var1/eax 0x34
    # =>
    #   3d/compare-eax-with 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # inouts = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi/next
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "compare"/imm32/operation
    68/push 1/imm32/regular-stmt
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "3d/compare-eax-with 0x34/imm32" "F - test-compare-eax-with-literal")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-compare-reg-with-literal:
    #   compare var1/ecx 0x34
    # =>
    #   81 7/subop/compare %ecx 0x34/imm32
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-var1/ecx: var in ecx
    68/push "ecx"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    51/push-ecx
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var type/edx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %edx 4/r32/esp
    # var var-var2/edx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    52/push-edx
    68/push "0x34"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi: (handle stmt-var) = [var2]
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # inouts = [var1, var2]
    68/push 0/imm32/is-deref:false
    56/push-esi/next
    51/push-ecx/var-var1
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "compare"/imm32/operation
    68/push 1/imm32/regular-stmt
    89/<- %esi 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "81 7/subop/compare %ecx 0x34/imm32" "F - test-compare-reg-with-literal")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-function-call:
    # Call a function on a variable on the stack.
    #   f foo
    # =>
    #   (f2 *(ebp-8))
    # (Changing the function name supports overloading in general, but here it
    # just serves to help disambiguate things.)
    #
    # There's a variable on the var stack as follows:
    #   name: 'foo'
    #   type: int
    #   stack-offset: -8
    #
    # There's nothing in primitives.
    #
    # There's a function with this info:
    #   name: 'f'
    #   inout: int/mem
    #   value: 'f2'
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = int
    68/push 0/imm32/right/null
    68/push 1/imm32/left/int
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var
    68/push 0/imm32/no-register
    68/push -8/imm32/stack-offset
    68/push 0/imm32/block-depth
    51/push-ecx
    68/push "foo"/imm32
    89/<- %ecx 4/r32/esp
    # var inouts/esi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "f"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var functions/ebx: function
    68/push 0/imm32/next
    68/push 0/imm32/body
    68/push 0/imm32/outputs
    51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
    68/push "f2"/imm32/subx-name
    68/push "f"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi 0 %ebx)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "(f2 *(ebp+0xfffffff8))" "F - test-emit-subx-stmt-function-call")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-emit-subx-stmt-function-call-with-literal-arg:
    # Call a function on a literal.
    #   f 34
    # =>
    #   (f2 34)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var type/ecx: (handle tree type-id) = literal
    68/push 0/imm32/right/null
    68/push 0/imm32/left/literal
    89/<- %ecx 4/r32/esp
    # var var-foo/ecx: var literal
    68/push 0/imm32/no-register
    68/push 0/imm32/no-stack-offset
    68/push 0/imm32/block-depth
    51/push-ecx
    68/push "34"/imm32
    89/<- %ecx 4/r32/esp
    # var inouts/esi: (handle stmt-var)
    68/push 0/imm32/is-deref:false
    68/push 0/imm32/next
    51/push-ecx/var-foo
    89/<- %esi 4/r32/esp
    # var stmt/esi: statement
    68/push 0/imm32/next
    68/push 0/imm32/outputs
    56/push-esi/inouts
    68/push "f"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # var functions/ebx: function
    68/push 0/imm32/next
    68/push 0/imm32/body
    68/push 0/imm32/outputs
    51/push-ecx/inouts  # hack; in practice we won't have the same var in function definition and call
    68/push "f2"/imm32/subx-name
    68/push "f"/imm32/name
    89/<- %ebx 4/r32/esp
    # convert
    c7 0/subop/copy *Curr-block-depth 0/imm32
    (emit-subx-stmt _test-output-buffered-file %esi 0 %ebx)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "(f2 34)" "F - test-emit-subx-stmt-function-call-with-literal-arg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-indent:  # out: (addr buffered-file), n: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # var i/eax: int = n
    8b/-> *(ebp+0xc) 0/r32/eax
    {
      # if (i <= 0) break
      3d/compare-eax-with 0/imm32
      7e/jump-if-<= break/disp8
      (write-buffered *(ebp+8) "  ")
      48/decrement-eax
      eb/jump loop/disp8
    }
$emit-indent:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-prologue:  # out: (addr buffered-file)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (write-buffered *(ebp+8) "  # . prologue\n")
    (write-buffered *(ebp+8) "  55/push-ebp\n")
    (write-buffered *(ebp+8) "  89/<- %ebp 4/r32/esp\n")
$emit-subx-prologue:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-epilogue:  # out: (addr buffered-file)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (write-buffered *(ebp+8) "  # . epilogue\n")
    (write-buffered *(ebp+8) "  89/<- %esp 5/r32/ebp\n")
    (write-buffered *(ebp+8) "  5d/pop-to-ebp\n")
    (write-buffered *(ebp+8) "  c3/return\n")
$emit-subx-epilogue:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return
n> class="LineNr">569 </span> restart r <span class="Comment"># should have no effect</span> <span id="L570" class="LineNr">570 </span> <span class="Normal">x</span>:num<span class="Special"> &lt;- </span>copy <span class="Constant">0</span> <span class="Comment"># give f time to be scheduled (though it shouldn't be)</span> <span id="L571" class="LineNr">571 </span>] <span id="L572" class="LineNr">572 </span><span class="muRecipe">def</span> f [ <span id="L573" class="LineNr">573 </span> <span class="Constant">1</span>:num/<span class="Special">raw &lt;- </span>copy <span class="Constant">1</span> <span id="L574" class="LineNr">574 </span>] <span id="L575" class="LineNr">575 </span><span class="Comment"># shouldn't crash</span> <span id="L576" class="LineNr">576 </span> <span id="L577" class="LineNr">577 </span><span class="Delimiter">:(scenario restart_blocked_routine)</span> <span id="L578" class="LineNr">578 </span><span class="Special">% Scheduling_interval = 1;</span> <span id="L579" class="LineNr">579 </span><span class="muRecipe">def</span> <a href='000organization.cc.html#L113'>main</a> [ <span id="L580" class="LineNr">580 </span> local-scope <span id="L581" class="LineNr">581 </span> <span class="Normal">r</span>:num/routine-id<span class="Special"> &lt;- </span>start-running f <span id="L582" class="LineNr">582 </span> wait-<span class="Normal">for</span>-routine-to-block r <span class="Comment"># get past the block in f below</span> <span id="L583" class="LineNr">583 </span> restart r <span id="L584" class="LineNr">584 </span> wait-<span class="Normal">for</span>-routine-to-block r <span class="Comment"># should run f to completion</span> <span id="L585" class="LineNr">585 </span>] <span id="L586" class="LineNr">586 </span><span class="Comment"># function with one block</span> <span id="L587" class="LineNr">587 </span><span class="muRecipe">def</span> f [ <span id="L588" class="LineNr">588 </span> current-routine-is-blocked <span id="L589" class="LineNr">589 </span> <span class="Comment"># 8 instructions of padding, many more than 'main' above</span> <span id="L590" class="LineNr">590 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L591" class="LineNr">591 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L592" class="LineNr">592 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L593" class="LineNr">593 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L594" class="LineNr">594 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L595" class="LineNr">595 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L596" class="LineNr">596 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L597" class="LineNr">597 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L598" class="LineNr">598 </span> <span class="Constant">1</span>:num<span class="Special"> &lt;- </span>add <span class="Constant">1</span>:num<span class="Delimiter">,</span> <span class="Constant">1</span> <span id="L599" class="LineNr">599 </span>] <span id="L600" class="LineNr">600 </span><span class="Comment"># make sure all of f ran</span> <span id="L601" class="LineNr">601 </span><span class="traceContains">+mem: storing 8 in location 1</span> </pre> </body> </html> <!-- vim: set foldmethod=manual : -->