about summary refs log blame commit diff stats
path: root/apps/survey_baremetal.subx
blob: fbb79b68c72a17ecdb33e4ab9a3a1890a864b7d7 (plain) (tree)
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719



































































































                                                                                                                                                                                      



                                                                       

                                                             
                         
                                    







                                                                                                                                                                       

                                                                                                  


























                                                                                                                                                                                     
                                 

                   



                                   







                                                                                                                                                                  
                                  

                   




                                                                                                                                                                      
                                                                                                                                                                  








                                                                                                                                                                      












                                                                                                                                                                              
               

                  
               



                                    
                                    

































                                                                                                                                                                       
                                              
                   
                              




















                                                                                                                                                                  
                                              
                   
                              























































                                                                                                                                                                             
                                                                        

                                                              
                                                        








                                                                                                                                                                       
                                                                                                
                 
                                    
                                   









                                                                  

                                                                                

                                                
                                                                                


                                                                       
                                      










                                                                                                                                                                       

                                           











                                                                                                                                                                         
                   
               
              
                                
                      

                                                                                                                                                                  
                   

                                                                                                                                                                     
              
                             
                      



                                                                                                                                                                        
                                                


















































                                                                                                                                                                     
                                                  








                                                                                                                                                                  
                                             





                                                                                                                                                                  
                               
                                     
                                                       













                                                                                                                                                                          
                                                                                    

                       
                               
               
                                                                                                                                                                      



                                                                                                                                                                  


                                                                                                                                                                        

                      

                                     















                                                                                                                                                                  

                                                                                                                                                                       









































                                                                                                                                                                                    

                      
                                                                                                                                                                  



























                                                                                                                                                                       
               
                                  
               




                                    
                                       











                                                                                                                                                                       


                                                                                                                                                                         



                                                                                                                                                                       
                                              
                   
                              












                                                                                                                                                                  
                                              
                   
                              




























                                                                                                                                                                  
                                                 

                   



                                     
                                                                                                                                                                 

























                                                                                                                                                                             
                                                                        
                   

                                                        



                                                                                                                                                                  
                                                 

                                                                          
                              




                                                                                                                                                                  

                                                                                                                                                                  




                                                                                                                                                                       









                                                                                                                       
                 


                                                
                  













































                                                                                                    










                                                                                                                                                                       















                                                                                                                                                                         
              
                                
                      

                                                                                                                                                                  
                   

                                                                                                                                                                     
              
                             
                      


                                                                                                                                                                  
                      
                            




                                                                                                                                                                     
                                     
                      
                  












                                                                                                                                                                     






                                                                                                                                                                     
            






































                                                                                                                                                                      
                      
                           




                                                                                                                                                                     
                                                   
                      

                            
                 
                                           

                                                                                                                                                                     






                                                                                                                                                                     








                                                                                                                                                                     




































                                                                                                                                                                               
                   

                       
              
                                

                                                                                                                                                                  










































                                                                                                                                                                              
                      
                               




                                                                                                                                                                     
                                              
                      

                                       
                 
                                           

                                                                                                                                                                     






                                                                                                                                                                     








                                                                                                                                                                     

                                                                                    
                   



                                                                                                                                                                      
              
                             

                                                                                                                                                                  
































































                                                                                                                                                                       
                                                                                                                                                                 












                                                                                                                                                                      
              
                                 

                                                                                                                                                                  




                                                              
                   
                    
                                                                                                                                                                        
                                                                                                                                                                             
               
                                                                                                                                                                      
              
                            
                      
























































                                                                                                                                                                             



                                                                                                                                                                       




























































                                                                                                                                                                  
            






                     
               
                           
     




                      




                                                                                                                                                                       




















                                                                                                                                                                  









                                                                                                                                                                         

                                              
                   

                                     
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      



                                                                                                                                                                  
               
                      







                                                                                                                                                                     
                               
                      
                                                                                                                                                                  
                          
                                                                           

                   

                                             
              
                               
                      
                                                                                                                                                                  
            








                                                                                                                                                                  
                      
                                 




                                                                                                                                                                     
                                                      
                      
                                         












                                                                                                                                                                     






                                                                                                                                                                     
            
                                                                              
                   


                                                                
              
                                                
                      

                                                                                                                                                                  
                   


                                                                
              
                                                
                      

                                                                                                                                                                  
                   


                                                                
              
                                                
                      

                                                                                                                                                                  
                   


                                                                
              
                                                






                                                                                                                                                                       


                                                                              
            




                      
               
                        
     



                                             




                                                                                                                                                                       




















                                                                                                                                                                  









                                                                                                                                                                         

                                              
                   

                                     
              
                         
                      




                                                                                                                                                                  
              
                         
                      

                                                                                                                                                                  
                   

                                     
              
                         
                      




                                                                                                                                                                  
              
                         
                      



                                                                                                                                                                  









                                                                                                                                                                     
                               
                      
                                                                                                                                                                  
                          
                                                                           
                   
               

                                             
              
                               
                      


                                                                                                                                                                  
                   
                                             
              
                         
                      


                                                                                                                                                                  
                      
                                 




                                                                                                                                                                     
                                                      
                      
                                         












                                                                                                                                                                     






                                                                                                                                                                     
            
                                                                        
                   


                                                      
              
                                                
                      


















                                                                                                                                                                  




                                                                                                                                                                       
















                                                   


                                                                                                                                                                       

                                        
                   

































                                                                                                                                                                         



                                                                                                                                                                  
                                            
                   

                                     
              
                         

                                                                                                                                                                  
                                            
                   

                                     



                                                                                                                                                                  
                                                  
                   

                                     
              
                         

                                                                                                                                                                  











                                                                                                                                                                     
              
                               
                      


                                                                                                                                                                  
                   
               

                                             
              
                               
                      










                                                                                                                                                                  
                      
                                 




                                                                                                                                                                     
                                                      
                      
                                         












                                                                                                                                                                     






                                                                                                                                                                     
            
























































                                                                                                                                                                            
                   

               
              
                             

                                                                                                                                                                  

                                                
                   
               
              






















                                                                                                                                                                     

                                                                                                                                                                  




                                                          
                   

                       
              
                                

                                                                                                                                                                  




                                                          
                   

                       
              
                                

                                                                                                                                                                  




                                                 
                   

               
              
                             

                                                                                                                                                                  

                                                
                   
               
              
                                

                                                                                                                                                                  






















                                                                                                                                                                               




                                                                                                                                                                       


                                                             


                                                                                                                                                                       
                      
               












































                                                                                                                                                                              




                                                                                                                                                                       





















                                                                                                                                                                  


























                                                                                                                                                                          
                                        



                                                                                                                                                                          
                                   








                                                                                                                                                                      
                 




                                                                                                                                                                       
                 















                                                                                                                                                                                 
                                    
                   


                       


                      
                               



























                                                                                                                                                                             
                                              
                   

                                         



































































































































































































































































































































































































































































































































































                                                                                                                                                                               
                            
# Assign addresses (co-ordinates) to instructions (landmarks) in a program
# (landscape).
# Use the addresses assigned to replace labels.
#
# To build:
#   $ ./bootstrap translate init.linux [01]*.subx apps/subx-params.subx apps/survey_baremetal.subx  -o apps/survey_baremetal
#
# The expected input is a stream of bytes and some interspersed labels.
# Comments and '==' segment headers are allowed, but ignored. The emitted code
# will all lie in a single header, and start at address 0x8800.
#   $ cat x
#   == code
#   l1:
#   aa bb l1/imm8
#   cc dd l2/disp32
#   l2:
#   ee foo/imm32
#   == data
#   foo:
#     00
#
# The output is the stream of bytes without segment headers or label definitions,
# and with label references replaced with numeric values/displacements.
#
#   $ cat x  |./bootstrap run apps/survey_baremetal
#   aa bb nn  # some computed address
#   cc dd nn nn nn nn  # some computed displacement
#   ee nn nn nn nn  # address right after this instruction
#   # data segment interleaved with code
#   00

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes

Entry:  # run tests if necessary, convert stdin if not
    # . prologue
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp

    # Heap = new-segment(Heap-size)
    # . . push args
    68/push  Heap/imm32
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
    # . . call
    e8/call  new-segment/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # initialize-trace-stream(Trace-size)
    # . . push args
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-size/disp32                 # push *Heap-size
    # . . call
    e8/call  initialize-trace-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp

    # - if argc > 1 and argv[1] == "test", then return run_tests()
    # if (argc <= 1) goto interactive
    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
    7e/jump-if-<=  $subx-survey-main:interactive/disp8
    # if (!kernel-string-equal?(argv[1], "test")) goto interactive
    # . eax = kernel-string-equal?(argv[1], "test")
    # . . push args
    68/push  "test"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  kernel-string-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) goto interactive
    3d/compare-eax-and  0/imm32/false
    74/jump-if-=  $subx-survey-main:interactive/disp8
    # run-tests()
    e8/call  run-tests/disp32
    # syscall(exit, *Num-test-failures)
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/ebx   Num-test-failures/disp32          # copy *Num-test-failures to ebx
    eb/jump  $subx-survey-main:end/disp8
$subx-survey-main:interactive:
    # - otherwise convert stdin
    # subx-survey(Stdin, Stdout)
    # . . push args
    68/push  Stdout/imm32
    68/push  Stdin/imm32
    # . . call
    e8/call  subx-survey/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # syscall(exit, 0)
    bb/copy-to-ebx  0/imm32
$subx-survey-main:end:
    e8/call  syscall_exit/disp32

subx-survey:  # infile: (addr buffered-file), out: (addr buffered-file)
    # pseudocode
    #   var in: (stream byte Input-size)
    #   slurp(infile, in)
    #   var labels: (stream {label-name, address} Max-labels)
    #   compute-offsets(in, labels)
    #   rewind-stream(in)
    #   emit-output(in, out, labels)
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    51/push-ecx
    52/push-edx
    56/push-esi
    # var labels/edx: (stream {label-name, address} Max-labels)
    # (we get more rows than Max-labels advertises because row size is smaller than in survey_elf)
    # . data
    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Max-labels/disp32                 # subtract *Max-labels from esp
    # . size
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Max-labels/disp32                 # push *Max-labels
    # . read
    68/push  0/imm32/read
    # . write
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # var in/esi: (stream byte Input-size)
    # . data
    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Input-size/disp32                 # subtract *Input-size from esp
    # . size
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Input-size/disp32                 # push *Input-size
    # . read
    68/push  0/imm32/read
    # . write
    68/push  0/imm32/write
    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           4/r32/esp   .               .                 # copy esp to esi
    # slurp(infile, in)
    # . . push args
    56/push-esi
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  slurp/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # compute-offsets(in, labels)
    # . . push args
    52/push-edx
    56/push-esi
    # . . call
    e8/call  compute-offsets/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # rewind-stream(in)
    # . . push args
    56/push-esi
    # . . call
    e8/call  rewind-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # emit-output(in, out, labels)
    # . . push args
    52/push-edx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    56/push-esi
    # . . call
    e8/call  emit-output/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # flush(out)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
$subx-survey:end:
    # . reclaim locals
    03/add                          0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Max-labels/disp32                 # add *Max-labels to esp
    03/add                          0/mod/indirect  5/rm32/.disp32            .             .           4/r32/esp   Input-size/disp32                 # add *Input-size to esp
    # . restore registers
    5e/pop-to-esi
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-subx-survey-computes-addresses:
    # input:
    #   == code
    #   Entry:
    #   ab x/imm32
    #   == data
    #   x:
    #     01
    #
    # trace contains (in any order):
    #   label x is at address 0x8805
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-input-buffered-file->buffer)
    # . . push args
    68/push  $_test-input-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-output-buffered-file->buffer)
    # . . push args
    68/push  $_test-output-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "== code\n")
    # . . push args
    68/push  "== code\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "Entry:\n")
    # . . push args
    68/push  "Entry:\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ab x/imm32\n")
    # . . push args
    68/push  "ab x/imm32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "== data\n")
    # . . push args
    68/push  "== data\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "x:\n")
    # . . push args
    68/push  "x:\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "01\n")
    # . . push args
    68/push  "01\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # subx-survey(_test-input-buffered-file, _test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  subx-survey/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # check trace
#?     # dump *Trace-stream {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, *Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # . check-trace-contains("label 'x' is at address 0x00008805.", msg)
    # . . push args
    68/push  "F - test-subx-survey-computes-addresses/0"/imm32
    68/push  "label 'x' is at address 0x00008805."/imm32
    # . . call
    e8/call  check-trace-contains/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

compute-offsets:  # in: (addr stream byte), labels: (addr stream {(handle array byte), address})
    # pseudocode:
    #   var current-address = 0x8800
    #   var line: (stream byte 512)
    #   while true                                  # line loop
    #     clear-stream(line)
    #     read-line(in, line)
    #     if (line->write == 0) break               # end of file
    #     while true                                # word loop
    #       word-slice = next-word(line)
    #       if slice-empty?(word-slice)             # end of line
    #         break
    #       else if slice-starts-with?(word-slice, "#")  # comment
    #         break                                 # end of line
    #       else if slice-equal?(word-slice, "==")  # no need for segment header
    #         break
    #       else if is-label?(word-slice)
    #         strip trailing ':' from word-slice
    #         trace("label '" word-slice "' is at address " current-address ".")
    #         # labels occupy no space, so no need to increment offsets
    #       else
    #         width = compute-width-of-slice(word-slice)
    #         current-address += width
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # var current-address/esi: int = 0x8800
    be/copy-to-esi  0x8800/imm32
    # var line/ecx: (stream byte 512)
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
    68/push  0x200/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    # var word-slice/edx: (addr slice)
    68/push  0/imm32
    68/push  0/imm32
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
$compute-offsets:line-loop:
    # clear-stream(line)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # read-line(in, line)
    # . . push args
    51/push-ecx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  read-line/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # if (line->write == 0) break
    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
    3d/compare-eax-and  0/imm32
    0f 84/jump-if-=  $compute-offsets:end/disp32
#?     # dump line {{{
#?     # . write(2/stderr, "LL: ")
#?     # . . push args
#?     68/push  "LL: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # write-stream(2/stderr, line)
#?     # . . push args
#?     51/push-ecx
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . rewind-stream(line)
#?     # . . push args
#?     51/push-ecx
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # }}}
$compute-offsets:word-loop:
    # next-word(line, word-slice)
    52/push-edx
    51/push-ecx
    e8/call  next-word/disp32
    # . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
$compute-offsets:case-empty:
    # if slice-empty?(word-slice) break
    # . eax = slice-empty?(word-slice)
    52/push-edx
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $compute-offsets:line-loop/disp32
$compute-offsets:case-comment:
    # if slice-starts-with?(word-slice, "#") break
    68/push  "#"/imm32
    52/push-edx
    e8/call  slice-starts-with?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $compute-offsets:line-loop/disp32
$compute-offsets:case-segment-header:
    # if slice-equal?(word-slice, "==") break
    # . eax = slice-equal?(word-slice, "==")
    68/push  "=="/imm32
    52/push-edx
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $compute-offsets:line-loop/disp32
$compute-offsets:case-label:
    # if (!is-label?(word-slice)) goto next case
    # . eax = is-label?(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call  is-label?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax == false) goto next case
    3d/compare-eax-and  0/imm32/false
    0f 84/jump-if-=  $compute-offsets:case-default/disp32
    # strip trailing ':' from word-slice
    ff          1/subop/decrement   1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # decrement *(edx+4)
    # var tmp/eax: (addr int) = get-or-insert-slice(labels, word-slice, row-size=12)
    # . . push args
    68/push  Heap/imm32
    68/push  0xc/imm32/row-size
    52/push-edx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  get-or-insert-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # *tmp = current-address
    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           6/r32/esi   .               .                 # copy esi to *eax
    # trace-slsns("label '" word-slice "' is at address " current-address ".")
    # . . push args
    68/push  "."/imm32
    56/push-esi
    68/push  "' is at address "/imm32
    52/push-edx
    68/push  "label '"/imm32
    # . . call
    e8/call  trace-slsns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x14/imm32        # add to esp
    # continue
    e9/jump  $compute-offsets:word-loop/disp32
$compute-offsets:case-default:
    # width/eax = compute-width-of-slice(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call compute-width-of-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # current-address += width
    01/add                          3/mod/direct    6/rm32/esi    .           .             .           0/r32/eax   .               .                 # add eax to *esi
#?     # dump segment-offset {{{
#?     # . write(2/stderr, "segment-offset: ")
#?     # . . push args
#?     68/push  "segment-offset: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . clear-stream($Stderr->buffer)
#?     # . . push args
#?     68/push  $Stderr->buffer/imm32
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write-int32-hex-buffered(Stderr, segment-offset)
#?     # . . push args
#?     52/push-edx
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:segment-offset/disp32  # push *segment-offset
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-int32-hex-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write(2/stderr, "\n")
#?     # . . push args
#?     68/push  Newline/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    e9/jump $compute-offsets:word-loop/disp32
$compute-offsets:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
    # . 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/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

$compute-offsets:abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "'==' must be followed by segment name and segment-start\n"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    e8/call  syscall_exit/disp32
    # never gets here

test-compute-offsets:
    # input:
    #   == code
    #   ab x/imm32  # skip comment
    #   == data
    #   00
    #   x:
    #     34
    #
    # trace contains (in any order):
    #   label 'x' is at address 0x8806.
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # var labels/edx: (stream byte 2*12)
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x18/imm32        # subtract from esp
    68/push  0x18/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # initialize input
    # . write(_test-input-stream, "== code\n")
    # . . push args
    68/push  "== code\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ab x/imm32  # skip comment\n")
    # . . push args
    68/push  "ab x/imm32  # skip comment\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "== data\n")
    # . . push args
    68/push  "== data\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "00\n")
    # . . push args
    68/push  "00\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "x:\n")
    # . . push args
    68/push  "x:\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "34\n")
    # . . push args
    68/push  "34\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # compute-offsets(_test-input-stream, labels)
    # . . push args
    52/push-edx
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  compute-offsets/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32          # add to esp
#?     # dump *Trace-stream {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, *Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # . check-trace-contains("label 'x' is at address 0x00008806.", msg)
    # . . push args
    68/push  "F - test-compute-offsets"/imm32
    68/push  "label 'x' is at address 0x00008806."/imm32
    # . . call
    e8/call  check-trace-contains/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . check-ints-equal(labels->write, 0xc, msg)
    # . . push args
    68/push  "F - test-compute-offsets-maintains-labels-write-index"/imm32
    68/push  0xc/imm32/1-entry
    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x24/imm32        # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# global scratch space for emit-output
== data

emit-output:datum:  # slice
  0/imm32/start
  0/imm32/end

== code

emit-output:  # in: (addr stream byte), out: (addr buffered-file), labels: (addr stream {(handle array byte), address})
    # pseudocode:
    #   var address-of-next-instruction = 0x8800
    #   var line: (stream byte 512)
    #   line-loop:
    #   while true
    #     clear-stream(line)
    #     read-line(in, line)
    #     if (line->write == 0) break               # end of file
    #     address-of-next-instruction += num-bytes(line)
    #     var is-far-jump-or-call? = is-far-jump-or-call?(line)
    #     rewind-stream(line)
    #     while true
    #       var word-slice = next-word(line)
    #       if slice-empty?(word-slice)             # end of line
    #         break
    #       if slice-starts-with?(word-slice, "#")  # comment
    #         break
    #       if is-label?(word-slice)                # no need for label declarations anymore
    #         goto line-loop                        # don't insert empty lines
    #       if slice-equal?(word-slice, "==")       # no need for segment header lines
    #         goto line-loop                        # don't insert empty lines
    #       if length(word-slice) == 2
    #         write-slice-buffered(out, word-slice)
    #         write-buffered(out, " ")
    #         continue
    #       var datum: (addr slice) = next-token-from-slice(word-slice->start, word-slice->end, "/")
    #       var address: (addr int) = get-slice(labels, datum)
    #       if has-metadata?(word-slice, "imm8")
    #         abort
    #       else if has-metadata?(word-slice, "imm32")
    #         emit(out, *address, 4)
    #       else if has-metadata?(word-slice, "disp8")
    #         value = *address - address-of-next-instruction
    #         emit(out, value, 1)
    #       else if has-metadata?(word-slice, "disp32")
    #         if is-far-jump-or-call?
    #           value = *address - address-of-next-instruction
    #         else
    #           value = *address
    #         emit(out, value, 4)
    #       else
    #         abort
    #     write-buffered(out, "\n")
    #
    # registers:
    #   line: ecx
    #   word-slice: edx
    #   address-of-next-instruction: ebx
    #   is-far-jump-or-call?: edi
    #   address: esi (inner loop only)
    #   temporaries: eax, esi (outer loop)
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . 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    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
    68/push  0x200/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
    # var word-slice/edx: slice
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # var address-of-next-instruction/ebx = 0x8800
    bb/copy-to-ebx  0x8800/imm32
$emit-output:line-loop:
    # clear-stream(line)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # read-line(in, line)
    # . . push args
    51/push-ecx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  read-line/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # dump line {{{
#?     # . write(2/stderr, "LL: ")
#?     # . . push args
#?     68/push  "LL: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # write-stream(2/stderr, line)
#?     # . . push args
#?     51/push-ecx
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . rewind-stream(line)
#?     # . . push args
#?     51/push-ecx
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # }}}
$emit-output:check-for-end-of-input:
    # if (line->write == 0) break
    81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
    0f 84/jump-if-=  $emit-output:end/disp32
    # address-of-next-instruction += num-bytes(line)
    # . eax = num-bytes(line)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . ebx += eax
    01/add                          3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # add eax to ebx
    # var is-far-jump-or-call?/edi: boolean = is-far-jump-or-call?(line)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  is-far-jump-or-call?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # rewind-stream(line)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  rewind-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
$emit-output:word-loop:
    # next-word(line, word-slice)
    # . . push args
    52/push-edx
    51/push-ecx
    # . . call
    e8/call  next-word/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # dump word-slice {{{
#?     # . write(2/stderr, "w: ")
#?     # . . push args
#?     68/push  "w: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-slice-buffered(Stderr, word-slice)
#?     # . . push args
#?     52/push-edx
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-slice-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
$emit-output:check-for-end-of-line:
    # if (slice-empty?(word-slice)) break
    # . eax = slice-empty?(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != 0) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $emit-output:next-line/disp32
$emit-output:check-for-comment:
    # if (slice-starts-with?(word-slice, "#")) break
    # . start/esi = word-slice->start
    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           6/r32/esi   .               .                 # copy *edx to esi
    # . c/eax = *start
    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
    8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
    # . if (eax == '#') break
    3d/compare-eax-and  0x23/imm32/hash
    0f 84/jump-if-=  $emit-output:next-line/disp32
$emit-output:check-for-label:
    # if is-label?(word-slice) break
    # . eax = is-label?(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call  is-label?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $emit-output:line-loop/disp32
$emit-output:check-for-segment-header:
    # if (slice-equal?(word-slice, "==")) break
    # . eax = slice-equal?(word-slice, "==")
    # . . push args
    68/push  "=="/imm32
    52/push-edx
    # . . call
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $emit-output:line-loop/disp32
$emit-output:2-character:
    # if (size(word-slice) != 2) goto next check
    # . eax = size(word-slice)
    8b/copy                         1/mod/*+disp8   2/rm32/edx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(edx+4) to eax
    2b/subtract                     0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # subtract *edx from eax
    # . if (eax != 2) goto next check
    3d/compare-eax-and  2/imm32
    75/jump-if-!=  $emit-output:check-metadata/disp8
    # write-slice-buffered(out, word-slice)
    # . . push args
    52/push-edx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write-slice-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write-buffered(out, " ")
    # . . push args
    68/push  Space/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # continue
    e9/jump  $emit-output:word-loop/disp32
$emit-output:check-metadata:
    # - if we get here, 'word-slice' must be a label to be looked up
    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
    # . . push args
    68/push  emit-output:datum/imm32
    68/push  0x2f/imm32/slash
    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    # . . call
    e8/call  next-token-from-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
#?     # dump datum {{{
#?     # . write(2/stderr, "datum: ")
#?     # . . push args
#?     68/push  "datum: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-slice-buffered(Stderr, datum)
#?     # . . push args
#?     68/push  emit-output:datum/imm32
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-slice-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # address/esi: (addr int) = get-slice(labels, datum, row-size=12, "label table")
    # . eax = get-slice(labels, datum, row-size=24, "label table")
    # . . push args
    68/push  "label table"/imm32
    68/push  0xc/imm32/row-size
    68/push  emit-output:datum/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # . esi = eax
    89/copy                         3/mod/direct    6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy eax to esi
$emit-output:check-imm8:
    # if (has-metadata?(word-slice, "imm8")) abort
    # . eax = has-metadata?(edx, "imm8")
    # . . push args
    68/push  "imm8"/imm32
    52/push-edx
    # . . call
    e8/call  has-metadata?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) abort
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $emit-output:imm8-abort/disp32
$emit-output:check-imm32:
    # if (!has-metadata?(word-slice, "imm32")) goto next check
    # . eax = has-metadata?(edx, "imm32")
    # . . push args
    68/push  "imm32"/imm32
    52/push-edx
    # . . call
    e8/call  has-metadata?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) goto next check
    3d/compare-eax-and  0/imm32/false
    74/jump-if-=  $emit-output:check-disp8/disp8
#?     # dump *address {{{
#?     # . write(2/stderr, "*address: ")
#?     # . . push args
#?     68/push  "*address: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-int32-hex-buffered(Stderr, *address)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-int32-hex-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
$emit-output:emit-imm32:
    # emit-hex(out, *address, 4)
    # . . push args
    68/push  4/imm32
    ff          6/subop/push        0/mod/indirect  6/rm32/esi    .           .             .           .           .               .                 # push *esi
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  emit-hex/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # continue
    e9/jump  $emit-output:word-loop/disp32
$emit-output:check-disp8:
    # if (!has-metadata?(word-slice, "disp8")) goto next check
    # . eax = has-metadata?(edx, "disp8")
    # . . push args
    68/push  "disp8"/imm32
    52/push-edx
    # . . call
    e8/call  has-metadata?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) goto next check
    3d/compare-eax-and  0/imm32/false
    74/jump-if-=  $emit-output:check-disp32/disp8
$emit-output:emit-disp8:
    # emit-hex(out, *address - address-of-next-instruction, 1)
    # . . push args
    68/push  1/imm32
    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
    50/push-eax
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  emit-hex/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # continue
    e9/jump  $emit-output:word-loop/disp32
$emit-output:check-disp32:
    # if (!has-metadata?(word-slice, "disp32")) abort
    # . eax = has-metadata?(edx, "disp32")
    # . . push args
    68/push  "disp32"/imm32
    52/push-edx
    # . . call
    e8/call  has-metadata?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) abort
    3d/compare-eax-and  0/imm32/false
    0f 84/jump-if-=  $emit-output:abort/disp32
$emit-output:emit-disp32:
    # var value/eax = *address
    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
    # if (is-far-jump-or-call?) value -= address-of-next-instruction
    81          7/subop/compare     3/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32/false     # compare edi
    74/jump-if-=  $emit-output:really-emit-disp32/disp8
    29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # subtract ebx from eax
$emit-output:really-emit-disp32:
    # emit-hex(out, value, 4)
    # . . push args
    68/push  4/imm32
    50/push-eax
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  emit-hex/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # continue
    e9/jump  $emit-output:word-loop/disp32
$emit-output:next-line:
    # write-buffered(out, "\n")
    # . . push args
    68/push  Newline/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    # . . call
    e8/call  write-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # loop
    e9/jump  $emit-output:line-loop/disp32
$emit-output:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
    # . 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/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

$emit-output:global-variable-abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "emit-output: must refer to global variables with /disp32 or /imm32"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    e8/call  syscall_exit/disp32
    # never gets here

$emit-output:imm8-abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "emit-output: cannot refer to code labels with /imm8"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    e8/call  syscall_exit/disp32
    # never gets here

$emit-output:abort:
    # print(stderr, "missing metadata in " word-slice)
    # . _write(2/stderr, "missing metadata in word ")
    # . . push args
    68/push  "emit-output: missing metadata in "/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write-slice-buffered(Stderr, word-slice)
    # . . push args
    52/push-edx
    68/push  Stderr/imm32
    # . . call
    e8/call  write-slice-buffered/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . flush(Stderr)
    # . . push args
    68/push  Stderr/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    e8/call  syscall_exit/disp32
    # never gets here

test-emit-output-non-far-control-flow:
    # labels turn into absolute addresses if opcodes are not far jumps or calls
    #
    # input:
    #   in:
    #     == code
    #     ab cd ef gh
    #     ij x/disp32
    #     == data
    #     00
    #     34
    #   labels:
    #     - 'x': 0x11223344
    #
    # output:
    #   ab cd ef gh
    #   ij 44 33 22 11
    #   00
    #   34
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-output-buffered-file->buffer)
    # . . push args
    68/push  $_test-output-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . var labels/edx: (stream byte 8*24)
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
    68/push  0xc0/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # . var h/ebx: (handle array byte)
    68/push  0/imm32
    68/push  0/imm32
    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    # initialize input
    # . write(_test-input-stream, "== code\n")
    # . . push args
    68/push  "== code\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ab cd ef gh\n")
    # . . push args
    68/push  "ab cd ef gh\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ij x/disp32\n")
    # . . push args
    68/push  "ij x/disp32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "== data\n")
    # . . push args
    68/push  "== data\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "00\n")
    # . . push args
    68/push  "00\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "34\n")
    # . . push args
    68/push  "34\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . stream-add2(labels, "x", 0x11223344)
    68/push  0x11223344/imm32/label-address
    # . . push handle for "x"
    53/push-ebx
    68/push  "x"/imm32
    68/push  Heap/imm32
    e8/call  copy-array/disp32
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
    # . . push labels
    52/push-edx
    # . . call
    e8/call  stream-add2/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # component under test
    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
    # . . push args
    52/push-edx
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  emit-output/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # checks
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-output-non-far-control-flow/0"/imm32
    68/push  "ab cd ef gh "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "ij 44 33 22 11 ", msg)
    # . . push args
    68/push  "F - test-emit-output-non-far-control-flow/1"/imm32
    68/push  "ij 44 33 22 11 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
    # . . push args
    68/push  "F - test-emit-output-non-far-control-flow/2"/imm32
    68/push  "00 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "34 ", msg)
    # . . push args
    68/push  "F - test-emit-output-non-far-control-flow/3"/imm32
    68/push  "34 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-emit-output-code-label:
    # labels turn into PC-relative addresses if opcodes are far jumps or calls
    #
    # input:
    #   in:
    #     == code
    #     ab cd
    #     ef gh
    #     e8 l1/disp32
    #   labels:
    #     - 'l1': 0x8810
    #
    # output:
    #   ab cd
    #   ef gh
    #   e8 07 00 00 00  # 0x8810 - 0x8809 = 7
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-output-buffered-file->buffer)
    # . . push args
    68/push  $_test-output-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . var labels/edx: (stream byte 8*24)
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
    68/push  0xc0/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # . var h/ebx: (handle array byte)
    68/push  0/imm32
    68/push  0/imm32
    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    # initialize input
    # . write(_test-input-stream, "== code\n")
    # . . push args
    68/push  "== code\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ab cd\n")
    # . . push args
    68/push  "ab cd\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ef gh\n")
    # . . push args
    68/push  "ef gh\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "e8 l1/disp32\n")
    # . . push args
    68/push  "e8 l1/disp32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . stream-add2(labels, "l1", 0x8810)
    68/push  0x8810/imm32/label-address
    # . . push handle for "l1"
    53/push-ebx
    68/push  "l1"/imm32
    68/push  Heap/imm32
    e8/call  copy-array/disp32
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
    # . . push labels
    52/push-edx
    # . . call
    e8/call  stream-add2/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # component under test
    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
    # . . push args
    52/push-edx
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  emit-output/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # checks
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label/0"/imm32
    68/push  "ab cd "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label/1"/imm32
    68/push  "ef gh "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "e8 07 00 00 00 ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label/2"/imm32
    68/push  "e8 07 00 00 00 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-emit-output-code-label-absolute:
    # labels can also convert to absolute addresses
    #
    # input:
    #   in:
    #     == code
    #     ab cd
    #     ef gh
    #     ij l1/imm32
    #   labels:
    #     - 'l1': 0x1056
    #
    # output:
    #   ab cd
    #   ef gh
    #   ij 56 10 00 00
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream($_test-output-buffered-file->buffer)
    # . . push args
    68/push  $_test-output-buffered-file->buffer/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . var labels/edx: (stream byte 8*24)
    81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc0/imm32        # subtract from esp
    68/push  0xc0/imm32/size
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # . var h/ebx: (handle array byte)
    68/push  0/imm32
    68/push  0/imm32
    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    # initialize input
    # . write(_test-input-stream, "== code 0x1000\n")
    # . . push args
    68/push  "== code\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ab cd\n")
    # . . push args
    68/push  "ab cd\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ef gh\n")
    # . . push args
    68/push  "ef gh\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . write(_test-input-stream, "ij l1/imm32\n")
    # . . push args
    68/push  "ij l1/imm32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . stream-add2(labels, "l1", 0x1056)
    68/push  0x1056/imm32/label-address
    # . . push handle for "l1"
    53/push-ebx
    68/push  "l1"/imm32
    68/push  Heap/imm32
    e8/call  copy-array/disp32
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    ff          6/subop/push        1/mod/*+disp8   3/rm32/ebx    .           .             .           .           4/disp8         .                 # push *(ebx+4)
    ff          6/subop/push        0/mod/indirect  3/rm32/ebx    .           .             .           .           .               .                 # push *ebx
    # . . push labels
    52/push-edx
    # . . call
    e8/call  stream-add2/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # component under test
    # . emit-output(_test-input-stream, _test-output-buffered-file, labels)
    # . . push args
    52/push-edx
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  emit-output/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # checks
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label-absolute/0"/imm32
    68/push  "ab cd "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label-absolute/1"/imm32
    68/push  "ef gh "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
    # . . push args
    68/push  "F - test-emit-output-code-label-absolute/2"/imm32
    68/push  "ij 56 10 00 00 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# reads line to make some checks
# don't assume the read state of line after calling this function
is-far-jump-or-call?:  # line: (addr stream byte) -> result/edi: boolean
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # ecx = line
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    # var word-slice/edx: slice
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
    # var datum-slice/ebx: slice
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    # result = false
    bf/copy-to-edi  0/imm32/false
$is-far-jump-or-call?:check-first-word:
    # next-word(line, word-slice)
    # . . push args
    52/push-edx
    51/push-ecx
    # . . call
    e8/call  next-word/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # if (slice-empty?(word-slice)) return false
    # . eax = slice-empty?(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != 0) return
    3d/compare-eax-and  0/imm32/false
    0f 85/jump-if-!=  $is-far-jump-or-call?:end/disp32
    # datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
    # . . push args
    53/push-ebx
    68/push  0x2f/imm32/slash
    ff          6/subop/push        1/mod/*+disp8   2/rm32/edx    .           .             .           .           4/disp8         .                 # push *(edx+4)
    ff          6/subop/push        0/mod/indirect  2/rm32/edx    .           .             .           .           .               .                 # push *edx
    # . . call
    e8/call  next-token-from-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # if (datum-slice == "e8") return true
    # . eax = slice-equal?(datum-slice, "e8")
    # . . push args
    68/push  "e8"/imm32
    53/push-ebx
    # . . call
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) return true
    3d/compare-eax-and  0/imm32/false
    75/jump-if-!=  $is-far-jump-or-call?:return-true/disp8
    # if (datum-slice == "e9") return true
    # . eax = slice-equal?(datum-slice, "e9")
    # . . push args
    68/push  "e9"/imm32
    53/push-ebx
    # . . call
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) return true
    3d/compare-eax-and  0/imm32/false
    75/jump-if-!=  $is-far-jump-or-call?:return-true/disp8
    # if (datum-slice != "0f") return false
    # . eax = slice-equal?(datum-slice, "0f")
    # . . push args
    68/push  "0f"/imm32
    53/push-ebx
    # . . call
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax == false) return
    3d/compare-eax-and  0/imm32/false
    74/jump-if-=  $is-far-jump-or-call?:end/disp8
$is-far-jump-or-call?:check-second-word:
    # next-word(line, word-slice)
    # . . push args
    52/push-edx
    51/push-ecx
    # . . call
    e8/call  next-word/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # if (slice-empty?(word-slice)) return false
    # . eax = slice-empty?(word-slice)
    # . . push args
    52/push-edx
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != 0) return
    3d/compare-eax-and  0/imm32/false
    75/jump-if-!=  $is-far-jump-or-call?:end/disp8
    # if datum of word-slice does not start with "8", return false
    # . start/eax = word-slice->start
    8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy *edx to eax
    # . c/eax = *start
    8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           0/r32/AL    .               .                 # copy byte at *eax to AL
    81          4/subop/and         3/mod/direct    0/rm32/eax    .           .             .           .           .               0xff/imm32        # bitwise and of eax
    # . if (eax != '8') return
    3d/compare-eax-and  0x38/imm32/8
    75/jump-if-!=  $is-far-jump-or-call?:end/disp8
    # otherwise return true
$is-far-jump-or-call?:return-true:
    bf/copy-to-edi  1/imm32/true
$is-far-jump-or-call?:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# - some helpers for tests

stream-add2:  # in: (addr stream byte), key: handle, val: int
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    56/push-esi
    # esi = in
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
    # curr/eax = &in->data[in->write]
    # . eax = in->write
    8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
    # . eax = esi+eax+12
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy esi+eax+12 to eax
    # max/edx = &in->data[in->size]
    # . edx = in->size
    8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   8/disp8         .                 # copy *(esi+8) to edx
    # . edx = esi+edx+12
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy esi+edx+12 to edx
    # if (curr >= max) abort
    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
    73/jump-if-addr>=  $stream-add2:abort/disp8
    # *curr = key->alloc-id
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
    # curr += 4
    05/add-to-eax  4/imm32
    # if (curr >= max) abort
    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
    73/jump-if-addr>=  $stream-add2:abort/disp8
    # *curr = key->payload
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x10/disp8      .                 # copy *(ebp+16) to ecx
    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
    # curr += 4
    05/add-to-eax  4/imm32
    # if (curr >= max) abort
    39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax with edx
    73/jump-if-addr>=  $stream-add2:abort/disp8
    # *curr = val
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   0x14/disp8      .                 # copy *(ebp+20) to ecx
    89/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy ecx to *eax
    # in->write += 0xc
    81          0/subop/add         0/mod/indirect  6/rm32/esi    .           .             .           .           .               0xc/imm32         # add to *esi
$stream-add2:end:
    # . restore registers
    5e/pop-to-esi
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

$stream-add2:abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "overflow in stream-add2\n"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    e8/call  syscall_exit/disp32
    # never gets here

# some variants of 'trace' that take multiple arguments in different combinations of types:
#   n: int
#   c: character [4-bytes, will eventually be UTF-8]
#   s: (addr array byte)
#   l: (addr slice)
# one gotcha: 's5' must not be empty

trace-slsns:  # s1: (addr array byte), l2: (addr slice), s3: (addr array byte), n4: int, s5: (addr array byte)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # write(*Trace-stream, s1)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write-slice(*Trace-stream, l2)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
    # . . call
    e8/call  write-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write(*Trace-stream, s3)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # write-int32-hex(*Trace-stream, n4)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x14/disp8      .                 # push *(ebp+20)
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
    # . . call
    e8/call  write-int32-hex/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # trace(s5)  # implicitly adds a newline and finalizes the trace line
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x18/disp8      .                 # push *(ebp+24)
    # . . call
    e8/call  trace/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
$trace-slsns:end:
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-trace-slsns:
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . *Trace-stream->write = 0
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/eax   Trace-stream/disp32               # copy *Trace-stream to eax
    c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # clear *eax
    # (eax..ecx) = "b"
    b8/copy-to-eax  "b"/imm32
    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
    05/add-to-eax  4/imm32
    # var b/ebx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
    # trace-slsls("A" b "c " 3 " e")
    # . . push args
    68/push  " e"/imm32
    68/push  3/imm32
    68/push  "c "/imm32
    53/push-ebx
    68/push  "A"/imm32
    # . . call
    e8/call  trace-slsns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x14/imm32        # add to esp
#?     # dump *Trace-stream {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, *Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # check-trace-contains("Abc 0x00000003 e")
    # . . push args
    68/push  "F - test-trace-slsls"/imm32
    68/push  "Abc 0x00000003 e"/imm32
    # . . call
    e8/call  check-trace-contains/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

num-bytes:  # line: (addr stream byte) -> eax: int
    # pseudocode:
    #   result = 0
    #   while true
    #     var word-slice = next-word(line)
    #     if slice-empty?(word-slice)             # end of line
    #       break
    #     if slice-starts-with?(word-slice, "#")  # comment
    #       break
    #     if is-label?(word-slice)                # no need for label declarations anymore
    #       break
    #     if slice-equal?(word-slice, "==")
    #       break                                 # no need for segment header lines
    #     result += compute-width(word-slice)
    #   return result
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    # var result/eax = 0
    31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
    # var word-slice/ecx: slice
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
#?     # dump line {{{
#?     # . write(2/stderr, "LL: ")
#?     # . . push args
#?     68/push  "LL: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # write-stream(2/stderr, line)
#?     # . . push args
#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # . rewind-stream(line)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  rewind-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
$num-bytes:loop:
    # next-word(line, word-slice)
    # . . push args
    51/push-ecx
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  next-word/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # dump word-slice {{{
#?     # . write(2/stderr, "AA: ")
#?     # . . push args
#?     68/push  "AA: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . clear-stream($Stderr->buffer)
#?     # . . push args
#?     68/push  $Stderr->buffer/imm32
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write-slice-buffered(Stderr, word-slice)
#?     # . . push args
#?     51/push-ecx
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-slice-buffered/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . flush(Stderr)
#?     # . . push args
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
$num-bytes:check0:
    # if (slice-empty?(word-slice)) break
    # . save result
    50/push-eax
    # . eax = slice-empty?(word-slice)
    # . . push args
    51/push-ecx
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    # . restore result now that ZF is set
    58/pop-to-eax
    75/jump-if-!=  $num-bytes:end/disp8
$num-bytes:check-for-comment:
    # if (slice-starts-with?(word-slice, "#")) break
    # . start/edx = word-slice->start
    8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
    # . c/ebx = *start
    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
    8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           3/r32/BL    .               .                 # copy byte at *edx to BL
    # . if (ebx == '#') break
    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x23/imm32/hash   # compare ebx
    74/jump-if-=  $num-bytes:end/disp8
$num-bytes:check-for-label:
    # if (slice-ends-with?(word-slice, ":")) break
    # . end/edx = word-slice->end
    8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
    # . c/ebx = *(end-1)
    31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
    8a/copy-byte                    1/mod/*+disp8   2/rm32/edx    .           .             .           3/r32/BL    -1/disp8        .                 # copy byte at *ecx to BL
    # . if (ebx == ':') break
    81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x3a/imm32/colon  # compare ebx
    74/jump-if-=  $num-bytes:end/disp8
$num-bytes:check-for-segment-header:
    # if (slice-equal?(word-slice, "==")) break
    # . push result
    50/push-eax
    # . eax = slice-equal?(word-slice, "==")
    # . . push args
    68/push  "=="/imm32
    51/push-ecx
    # . . call
    e8/call  slice-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . if (eax != false) break
    3d/compare-eax-and  0/imm32/false
    # . restore result now that ZF is set
    58/pop-to-eax
    75/jump-if-!=  $num-bytes:end/disp8
$num-bytes:loop-body:
    # result += compute-width-of-slice(word-slice)
    # . copy result to edx
    89/copy                         3/mod/direct    2/rm32/edx    .           .             .           0/r32/eax   .               .                 # copy eax to edx
    # . eax = compute-width-of-slice(word-slice)
    # . . push args
    51/push-ecx
    # . . call
    e8/call compute-width-of-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . eax += result
    01/add                          3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # add edx to eax
    e9/jump  $num-bytes:loop/disp32
$num-bytes:end:
    # . rewind-stream(line)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  rewind-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-handles-empty-string:
    # if a line starts with '#', return 0
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # no contents in input
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-num-bytes-handles-empty-string"/imm32
    68/push  0/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-ignores-comments:
    # if a line starts with '#', return 0
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "# abcd")
    # . . push args
    68/push  "# abcd"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-num-bytes-ignores-comments"/imm32
    68/push  0/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-ignores-labels:
    # if the first word ends with ':', return 0
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "ab: # cd")
    # . . push args
    68/push  "ab: # cd"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-num-bytes-ignores-labels"/imm32
    68/push  0/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-ignores-segment-headers:
    # if the first word is '==', return 0
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "== ab cd")
    # . . push args
    68/push  "== ab cd"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-num-bytes-ignores-segment-headers"/imm32
    68/push  0/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-counts-words-by-default:
    # without metadata, count words
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "ab cd ef")
    # . . push args
    68/push  "ab cd ef"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 3, msg)
    # . . push args
    68/push  "F - test-num-bytes-counts-words-by-default"/imm32
    68/push  3/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-ignores-trailing-comment:
    # trailing comments appropriately ignored
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "ab cd # ef")
    # . . push args
    68/push  "ab cd # ef"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 2, msg)
    # . . push args
    68/push  "F - test-num-bytes-ignores-trailing-comment"/imm32
    68/push  2/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-num-bytes-handles-imm32:
    # if a word has the /imm32 metadata, count it as 4 bytes
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # setup
    # . clear-stream(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # . clear-stream(_test-output-stream)
    # . . push args
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # initialize input
    # . write(_test-input-stream, "ab cd/imm32 ef")
    # . . push args
    68/push  "ab cd/imm32 ef"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # eax = num-bytes(_test-input-stream)
    # . . push args
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  num-bytes/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 6, msg)
    # . . push args
    68/push  "F - test-num-bytes-handles-imm32"/imm32
    68/push  6/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# . . vim:nowrap:textwidth=0