about summary refs log blame commit diff stats
path: root/subx/apps/survey.subx
blob: 737205c3760c6fa50a25af6b574cb9fe8e705af5 (plain) (tree)
1
2
3
4
5
6
7
8
9
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828



                                                                          
                                                                         


                                                             
 






























                                                                                                                                                 
                              






                                                                                                                                                                  






                                                                                                                                                                  
 














































                                                                                                                                                                                      




                                                                            
                                                             
 
                                                                                 

                                                         
                                                        




                                              



                                                                                                                                                                       







                                                                                                                                                                         


                                                                                                                                                                         


















                                                                                                                                                                       
                                                                                                                                                                  






                                                                                                                                                                     

























                                                                                                                                                                             













































                                                                                                                                                                     









                                                                                                                                                                      

                      
                                                                                                                                                                  
                         

                 




                                                                                                                                                                       


                                
              























































                                                                                                                                                                       







                                                                                                                                                                  








































                                                                                                                                                                  































                                                                                                                                                                  




                                                                                                                                                                       


                                                              
                                   
         
                                      




                               
                                     

               


       
                                                                                                                                                    








                           
                 
                                                
                                     
                                                               


                                                                 
                                                               
                                        

                                                                 
                                                                  
                      
                                                  

                                                                
                                                           
                                                                               


                                                              
                     

                                           
                     
                                                              
                                                                
                                             
                                                                                             
                                



                                                                    
                                                 
                                                                                        
                                                

                                                                                          
                
                                                        

                                      




                                                                                                                                                                       





               

                                                                                                                                                                 
                     
                                                                                                                                                                                                                   
                        
                                                                                                                                                                                                                   


                                   

                        
                       
              
                              
                      
                                                                                                                                                                  

                                                                                                                                                                       





                                                                                                                                                                  
                                      




                                                                                                                                                                     
                                 
                                                                                                                                                                        
                               
                                                                
                           
                      






                                                                                                                                                                  
















































































                                                                                                                                                                      





                                                                                                                                                                  


                                                              





                                                                                                                                                                  

                               
                                                              

                                                             





                                                                                                                                                                  
                                    
                               
                                                           

                                                                                                                                                                   
                                                                   
                                                                       
                   
                                
               
                                                                                                                                                                      
              
                                 
                      
                                                                                                                                                                  
                                                



                                                                                                                                                                                 
                              
                                                                                                                                                                            





                                                                                                                                                                             
                                                                                
                   
                      
               
                                
               
                              
              
                               
                      
                                                                                                                                                                  
                                        

                                              



                                                                                                                                                                  














































                                                                                                                                                                      







                                                      
                      




                                                                                                                                                                       
                               
                                                      

                                              



                                                                                                                                                                  


                                              


                                                                                                                                                                  


                                                          
                                                                       
                   
                                
               
                                                                                                                                                                      
              
                                 

                                                                                                                                                                  

                                                                                                                                                                       


                                               




                                                                                                                                                                        
                                     
                                                                                                                                                                                 
                                                                                                                                                                            
                                                                                               
                   


                                         
               
                              
              
                               

                                                                                                                                                                  

                                                                                                                                                                                                
           
                                             










                                                                                                                                                                  
                                                         

                                                                                                                                                                          
                                                                        




                                                                                                                                                                      
                                             

                                                                                                                                                                  
                                   

                                                                                                                                                                        










                                                                                                                                                                  




                                                                                                                                                                                        
















                                                                                                                                                                        
                              

                                                    
               

                                         
                      

                                                                                                                                                                  
                                                                                                                                                                                      
                          
                                                                                                                                                                                














































                                                                                                                                                                                    
                                             
                                 






















                                                                                                                                                                                 
                                                                            









                                                                                                                                                                  


                         





                 



                                                                                                                                                                       













                                                                                                                                                                  





                      
          
          
            

                                    





                                             




                                                                                                                                                                       




































































                                                                                                                                                                         







                                                                                                                                                                  















                                                                                                                                                                  
                                                                  


                   



                                            
                                                                                                                                                                 

























                                                                                                                                                                             
                 
                                                                                 

                                               
                                                                 



                                                                                                                                                                  
                                                                       

                                               
                                                        



                                                                                                                                                                  
                                                                                 

                                               
                                                                 



                                                                                                                                                                  
                                                                        

                                               
                                                        



                                                                                                                                                                  
                                                                    

                                               
                                                    



                                                                                                                                                                  
                                                                               

                                               
                                                               

                                        

                                                                                                                                                                  








                                                                                                                                                                  






                                                                                                                                                                       
                                                      
                                              

                                                      
                  
                                
                                           

                                                                    
                                                                         

                                                  

                                          

                                                          
                                                                                   


                                                                         




                                                                                                                                                                       




               
               

                                                                                                                                                                            






                                                                                                                                                                                                       

                                                                                                                                                                          



                                                                                                                                                                        
                            

                                                                                                                                                                            
                                          
                                                                                                                                                                           

                                                            
                                                                                                                                                                            

                                                                                                                                                                          

                                                
                                                                                                                                                                            

                                                                                                                                                                          
                                 
                                                                                                                                                                                 
                                                                                                                                                                            
                                                                          










                                                                                                                                                                  


                                                  

























                                                                                                                                                                             

                                                                                                                                                                             

                                                                                                                                                                          



                                                                                                                                                                        
                            
                                                                                                                                                                            
                                                                          

























                                                                                                                                                                     

                                                                                                                                                                        

























                                                                                                                                                                     
                                                                                   

                
                                                  


                                
                                                                                                                                                                     
              
                       

                                                                                                                                                                  



                                                                                                                                                                       

                                                                                                                                                                        
                                 
                                                                                                                                                                           

                                                                                                                                                                             










                                                                                                                                                                  
                               
                             
                                                 
                       
                         
                 




                 





                                                                                                                                                                       







                                
     



                                                                                 

                                                                              




                                                                                                                                                                       





















                                                                                                                                                                         
                                                
                                 

                                       





                                                                                                                                                                  
                                                 
                                   
                                


























                                                                                                                                                                  
                          







                                                                                                                                                                  
            

























                                                                                                                                                                             
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                         

                                                 








                                                                                                                                                                  



                                                                                                                                                                  








                                                                                                                                                                  




                                                                                                                                                                       


                                                                                                                                                                               































                                                                                                                                                                                 

                                          
                  











                                                                                            


                                                                                      



                                                   
                                                                                  
                                           













                                                               







                                          



                                                                                                                                                                       




















































































































































































































































                                                                                                                                                                               

                                                      




                                                                                                                                                                      
                             








































































































































                                                                                                                                                                             
                   
                      
                                                                                                                                                                  
                         





                 




                                                                                                                                                                       













































                                                                                                                                                                  


                   
                        

                     
                        



                 

                                  
               

                                     


                   
                      



















































                                                                                                                                                                         
                                                     
                   
                                     




















                                                                                                                                                                  
                                                     
                   
                                     




























                                                                                                                                                                  
                                                   


                                       
                                      




                                                                                                                                                                  
                                                   


                                       
                                      




                                                                                                                                                                  
                                                    

                                       
                                      





                                                                                                                                                                  
                                                   

                                       

                                      
















                                                                                                                                                                  

































                                                                                                                                                                     

                                             
                                 




                                                                                                                                                                  
                                                                                 

                                             
                                    




                                                                                                                                                                  
                                                                     

                                             
                        




                                                                                                                                                                  
                                                                     

                                             
                        









                                                                                                                                                                       













                                                                                                                                                                       











                                                                                                                                                                      
                                                                                                                                                                             








                                                                                                                                                                            





































































                                                                                                                                                                     








                                                                                                                                                                  
                                      
                  
                         

                 






                                                                                                                                                                       
                                                    
                                                                   
                                       




                                                                                                                                                                       


                                                    
                                                  

                                                                                                                                                                             
                                                          


                                
               
              
                       
























                                                                                                                                                                                                                           
                     
                         


                 




                                                                                                                                                                       
                                                                                 









                                                 
                                                     




                                                                                                                                                                       


                        
                                                                                                                                                                            













































                                                                                                                                                                                 
                                   
                         

                 




                                                                                                                                                                       

                          









                                                                                                                                                                       
                                                                                                                                                                            



































































                                                                                                                                                                              




                                                                                           
                                    





















































                                                                                                                                                                          



                                                                                                                                                                                 






































































































                                                                                                                                                                             



                                                                                                                                                                                 






































































































                                                                                                                                                                             



                                                                                                                                                                                 
























































































































                                                                                                                                                                             



                                                                                                                                                                                 

























































                                                                                                                                                                             



















































































































                                                                                                                                                                                 
 



















                                                                                                                                                                       





                                                                                                                                                                 

                          


























                                                                                                                                                                        






                                                                                                                                                                     




































































                                                                                                                                                                     
                 



































                                                                                                                                                                               
                 














                                                                                                                                                                       
               






                                                                                                                                                                     
                      
                                                                                                                                                                  
                         


                 





















































































































































































































































































































































                                                                                                                                                                       




                                                                                                                                                                       




                  



                                                                           


            


































                                                                                


            













                                                                                                     
         




                                                                        
# Assign addresses (co-ordinates) to instructions (landmarks) in a program
# (landscape).
# Use the addresses assigned to:
#   a) replace labels
#   b) add segment headers with addresses and offsets correctly filled in
#
# To build (from the subx/ directory):
#   $ ./subx translate *.subx apps/survey.subx -o apps/survey
#
# The expected input is a stream of bytes with segment headers, comments and
# some interspersed labels.
#   $ cat x
#   == code 0x1
#   l1:
#   aa bb l1/imm8
#   cc dd l2/disp32
#   l2:
#   ee foo/imm32
#   == data 0x10
#   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  |./subx run apps/assort
#   ...ELF header bytes...
#   # ELF header above will specify that code segment begins at this offset
#   aa bb nn  # some computed address
#   cc dd nn nn nn nn  # some computed displacement
#   ee nn nn nn nn  # some computed address
#   # ELF header above will specify that data segment begins at this offset
#   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:
    # Heap = new-segment(64KB)
    # . . push args
    68/push  Heap/imm32
    68/push  0x10000/imm32/64KB
    # . . 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(256KB)
    # . . push args
    68/push  0x40000/imm32/256KB
    # . . call
    e8/call  initialize-trace-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP

    # run tests if necessary, convert stdin if not
    # . prolog
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # initialize heap
    # - if argc > 1 and argv[1] == "test", then return run_tests()
    # . argc > 1
    81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
    7e/jump-if-lesser-or-equal  $run-main/disp8
    # . 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
    # . check result
    3d/compare-EAX-and  1/imm32
    75/jump-if-not-equal  $run-main/disp8
    # . run-tests()
    e8/call  run-tests/disp32
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
    eb/jump  $main:end/disp8
$run-main:
    # - otherwise convert stdin
    # var ed/EAX : exit-descriptor
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
    89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
    # configure ed to really exit()
    # . ed->target = 0
    c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
    # return convert(Stdin, 1/stdout, 2/stderr, ed)
    # . . push args
    50/push-EAX/ed
    68/push  Stderr/imm32
    68/push  Stdout/imm32
    68/push  Stdin/imm32
    # . . call
    e8/call  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    # . syscall(exit, 0)
    bb/copy-to-EBX  0/imm32
$main:end:
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8

# data structures:
#   segment-info: {address, file-offset, size}            (12 bytes)
#   segments: (address stream {string, segment-info})     (16 bytes per row)
#   label-info: {segment-name, segment-offset, address}   (12 bytes)
#   labels: (address stream {string, label-info})         (16 bytes per row)
# these are all inefficient; use sequential scans for lookups

convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
    # pseudocode
    #   var segments = new-stream(10 rows, 16 bytes each)
    #   var labels = new-stream(512 rows, 16 bytes each)
    #   compute-offsets(in, segments, labels)
    #   compute-addresses(segments, labels)
    #   rewind-stream(in)
    #   emit-output(in, out, segments, labels)
    #
    # . prolog
    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
    # var segments/ECX = stream(10 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
    68/push  0xa0/imm32/length
    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 labels/EDX = stream(512 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
    68/push  0x2000/imm32/length
    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
    # compute-offsets(in, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  compute-offsets/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # compute-addresses(segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    # . . call
    e8/call  compute-addresses/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x8/imm32         # add to ESP
    # rewind-stream(in)
    # . . 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
#?     # 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
#?     # }}}
#?     # dump labels->write {{{
#?     # . write(2/stderr, "labels->write after rewinding input: ")
#?     # . . push args
#?     68/push  "labels->write after rewinding input: "/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+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . print-int32-buffered(Stderr, labels)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  2/rm32/EDX    .           .             .           .           .               .                 # push *EDX
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  print-int32-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(in, out, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-output/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
$convert:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x214/imm32       # add to ESP
    # . restore registers
    5a/pop-to-EDX
    59/pop-to-ECX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-convert-computes-addresses:
    # input:
    #   == code 0x1
    #   Entry:
    #   ab x/imm32
    #   == data 0x1000
    #   x:
    #     01
    #
    # trace contains (in any order):
    #   label x is at address 0x1079
    #   segment code starts at address 0x74
    #   segment code has size 5
    #   segment data starts at address 0x1079
    #
    # . prolog
    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+4)
    # . . push args
    b8/copy-to-EAX  _test-input-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . 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+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . 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 0x1\n")
    # . . push args
    68/push  "== code 0x1\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 0x1000\n")
    # . . push args
    68/push  "== data 0x1000\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
    # convert(_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  convert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # check trace
    # . check-trace-contains("label 'x' is at address 0x1079", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/0"/imm32
    68/push  "label 'x' is at address 0x1079"/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-trace-contains("segment 'code' starts at address 0x74", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/1"/imm32
    68/push  "segment 'code' starts at address 0x74"/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-trace-contains("segment 'code' has size 0x5", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/2"/imm32
    68/push  "segment 'code' has size 0x5"/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-trace-contains("segment 'data' starts at address 0x1079", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/3"/imm32
    68/push  "segment 'data' starts at address 0x1079"/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
    # . epilog
    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 compute-offsets in the data segment
== data

compute-offsets:file-offset:  # int
  0/imm32
compute-offsets:segment-offset:  # int
  0/imm32
compute-offsets:word-slice:
  0/imm32/start
compute-offsets:word-slice:end:
  0/imm32/end
compute-offsets:segment-tmp:  # slice
  0/imm32/start
  0/imm32/end

== code

compute-offsets:  # in : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # skeleton:
    #   for lines in 'in'
    #     for words in line
    #       switch word
    #         case 1
    #         case 2
    #         ...
    #         default
    #
    # pseudocode:
    #   curr-segment-name : (address string) = 0
    #   var line = new-stream(512, 1)
    #   while true                                  # line loop
    #     clear-stream(line)
    #     read-line-buffered(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
    #         continue
    #       else if slice-equal?(word-slice, "==")
    #         if curr-segment-name != 0
    #           seg = get-or-insert(segments, curr-segment-name)
    #           seg->size = *file-offset - seg->file-offset
    #           trace("segment '", curr-segment-name, "' has size ", seg->size)
    #         segment-tmp = next-word(line)
    #         curr-segment-name = slice-to-string(segment-tmp)
    #         if empty?(curr-segment-name)
    #           abort
    #         segment-tmp = next-word(line)
    #         if slice-empty?(segment-tmp)
    #           abort
    #         seg = get-or-insert(segments, curr-segment-name)
    #         seg->starting-address = parse-hex-int(segment-tmp)
    #         seg->file-offset = *file-offset
    #         trace("segment '", curr-segment-name, "' is at file offset ", seg->file-offset)
    #         segment-offset = 0
    #         break  (next line)
    #       else if is-label?(word-slice)
    #         strip trailing ':' from word-slice
    #         x : (address label-info) = get-or-insert(labels, name)
    #         x->segment-name = curr-segment-name
    #         trace("label '", word-slice, "' is in segment '", curr-segment-name, "'.")
    #         x->segment-offset = segment-offset
    #         trace("label '", word-slice, "' is at segment offset ", segment-offset, ".")
    #         # labels occupy no space, so no need to increment offsets
    #       else
    #         width = compute-width-of-slice(word-slice)
    #         *segment-offset += width
    #         *file-offset += width
    #
    # . prolog
    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
    # curr-segment-name/ESI = 0
    31/xor                          3/mod/direct    6/rm32/ESI    .           .             .           6/r32/ESI   .               .                 # clear ESI
    # file-offset = 0
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:file-offset/disp32  0/imm32               # copy to *compute-offsets:word-slice
    # segment-offset = 0
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           compute-offsets:segment-offset/disp32  0/imm32            # copy to *compute-offsets:word-slice
    # line/ECX = new-stream(512, 1)
    # . EAX = new-stream(512, 1)
    # . . push args
    68/push  1/imm32
    68/push  0x200/imm32
    68/push  Heap/imm32
    # . . call
    e8/call  new-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . line/ECX = EAX
    89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # copy EAX to ECX
$compute-offsets:line-loop:
    # clear-stream(line/ECX)
    51/push-ECX
    e8/call  clear-stream/disp32
    # . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # read-line-buffered(in, line/ECX)
    51/push-ECX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    e8/call  read-line-buffered/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-equal  $compute-offsets:break-line-loop/disp32
$compute-offsets:word-loop:
    # EDX = word-slice
    ba/copy-to-EDX  compute-offsets:word-slice/imm32
    # next-word(line/ECX, word-slice/EDX)
    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
#?     # dump word-slice and maybe curr-segment-name {{{
#?     # . 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+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . 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
#?     # . if (curr-segment-name == 0) print curr-segment-name
#?     81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
#?     74/jump-if-equal  $compute-offsets:check0/disp8
#?     # . write(2/stderr, "segment at start of word: ")
#?     # . . push args
#?     68/push  "segment at start of word: "/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-buffered(Stderr, curr-segment-name)
#?     # . . push args
#?     56/push-ESI
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-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
#?     # }}}
$compute-offsets:check0:
    # if slice-empty?(word/EDX) break
    # . EAX = slice-empty?(word/EDX)
    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 != 0) break
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $compute-offsets:line-loop/disp32
    # if slice-starts-with?(word-slice, "#") continue
    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 != 0) continue
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $compute-offsets:word-loop/disp32
$compute-offsets:case-segment-header:
    # if (!slice-equal?(word-slice/EDX, "==")) goto next case
    # . EAX = slice-equal?(word-slice/EDX, "==")
    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 == 0) goto next case
    3d/compare-EAX-and  0/imm32
    0f 84/jump-if-equal  $compute-offsets:case-label/disp32
    # if (curr-segment-name == 0) goto construct-next-segment
    81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
    74/jump-if-equal  $compute-offsets:construct-next-segment/disp8
    # seg/EAX = get-or-insert(segments, curr-segment-name, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    56/push-ESI
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  get-or-insert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # seg->size = file-offset - seg->file-offset
    # . save ECX
    51/push-ECX
    # . EBX = *file-offset
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:file-offset/disp32 # copy *file-offset to EBX
    # . ECX = seg->file-offset
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EAX+4) to ECX
    # . EBX -= ECX
    29/subtract                     3/mod/direct    3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EBX
    # . seg->size = EBX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy EBX to *(EAX+8)
    # . restore ECX
    59/pop-to-ECX
    # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".")
    # . . push args
    68/push  "."/imm32
    53/push-EBX
    68/push  "' has size "/imm32
    56/push-ESI
    68/push  "segment '"/imm32
    # . . call
    e8/call  trace-sssns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
$compute-offsets:construct-next-segment:
    # next-word(line/ECX, segment-tmp)
    68/push  compute-offsets:segment-tmp/imm32
    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
#?     # dump curr-segment-name if not null (clobbering EAX) {{{
#?     # . if (curr-segment-name == 0) goto update-curr-segment-name
#?     81          7/subop/compare     3/mod/direct    6/rm32/ESI    .           .             .           .           .               0/imm32           # compare ESI
#?     74/jump-if-equal  $compute-offsets:update-curr-segment-name/disp8
#?     # . write(2/stderr, "setting segment to: ")
#?     # . . push args
#?     68/push  "setting segment to: "/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+4)
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . write-buffered(Stderr, curr-segment-name)
#?     # . . push args
#?     56/push-ESI
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  write-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
#?     # }}}
$compute-offsets:update-curr-segment-name:
    # curr-segment-name = slice-to-string(segment-tmp)
    # . EAX = slice-to-string(Heap, segment-tmp)
    # . . push args
    68/push  compute-offsets:segment-tmp/imm32
    68/push  Heap/imm32
    # . . call
    e8/call  slice-to-string/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . curr-segment-name = EAX
    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
    # if empty?(curr-segment-name) abort
    # . if (EAX == 0) abort
    3d/compare-EAX-and  0/imm32
    0f 84/jump-if-equal  $compute-offsets:abort/disp32
    # next-word(line/ECX, segment-tmp)
    68/push  compute-offsets:segment-tmp/imm32
    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
    # if slice-empty?(segment-tmp) abort
    # . EAX = slice-empty?(segment-tmp)
    68/push  compute-offsets:segment-tmp/imm32
    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) abort
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $compute-offsets:abort/disp32
    # seg/EBX = get-or-insert(segments, curr-segment-name, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    56/push-ESI
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  get-or-insert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . EBX = EAX
    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
    # seg->address = parse-hex-int(segment-tmp)
    # . EAX = parse-hex-int(segment-tmp)
    68/push  compute-offsets:segment-tmp/imm32
    e8/call  parse-hex-int/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . seg->address = EAX
    89/copy                         0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to *EBX
    # seg->file-offset = *file-offset
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:file-offset/disp32 # copy *file-offset to EAX
    89/copy                         1/mod/*+disp8   3/rm32/EBX    .           .             .           0/r32/EAX   4/disp8         .                 # copy EAX to *(EBX+4)
    # trace-sssns("segment '", curr-segment-name, "' is at file offset ", seg->file-offset, "")
    # . . push args
    68/push  "."/imm32
    50/push-EAX
    68/push  "' is at file offset "/imm32
    56/push-ESI
    68/push  "segment '"/imm32
    # . . call
    e8/call  trace-sssns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # segment-offset = 0
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .     compute-offsets:segment-offset/disp32  0/imm32           # copy to *segment-offset
    # break
    e9/jump $compute-offsets:line-loop/disp32
$compute-offsets:case-label:
    # if (!is-label?(word-slice/EDX)) goto next case
    # . EAX = is-label?(word-slice/EDX)
    # . . 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 == 0) goto next case
    3d/compare-EAX-and  0/imm32
    74/jump-if-equal  $compute-offsets:case-default/disp8
    # strip trailing ':' from word-slice
    ff          1/subop/decrement   1/mod/*+disp8   2/rm32/EDX    .           .             .           .           4/disp8         .                 # decrement *(EDX+4)
    # x/EAX = leaky-get-or-insert-slice(labels, word-slice, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    52/push-EDX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    # . . call
    e8/call  leaky-get-or-insert-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
$compute-offsets:save-label-offset:
    # x->segment-name = curr-segment-name
    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           6/r32/ESI   .               .                 # copy ESI to *EAX
    # trace-slsss("label '" word-slice/EDX "' is in segment '" current-segment-name "'.")
    # . . push args
    68/push  "'."/imm32
    56/push-ESI
    68/push  "' is in segment '"/imm32
    52/push-EDX
    68/push  "label '"/imm32
    # . . call
    e8/call  trace-slsss/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # x->segment-offset = segment-offset
    # . EBX = segment-offset
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:segment-offset/disp32  # copy *segment-offset to EBX
    # . x->segment-offset = EBX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   4/disp8         .                 # copy EBX to *(EAX+4)
    # trace-slsns("label '" word-slice/EDX "' is at segment offset " *segment-offset/EAX ".")
    # . . EAX = file-offset
    b8/copy-to-EAX compute-offsets:segment-offset/imm32
    # . . EAX = *file-offset/EAX
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # copy *EAX to EAX
    # . . push args
    68/push  "."/imm32
    50/push-EAX
    68/push  "' is at segment offset "/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
    # segment-offset += width
    01/add                          0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:segment-offset/disp32 # add EAX to *segment-offset
    # file-offset += width
    01/add                          0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   compute-offsets:file-offset/disp32 # add EAX to *file-offset
#?     # 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+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . print-int32-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  print-int32-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
#?     # }}}
    e9/jump $compute-offsets:word-loop/disp32
$compute-offsets:break-line-loop:
    # seg/EAX = get-or-insert(segments, curr-segment-name, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    56/push-ESI
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    # . . call
    e8/call  get-or-insert/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # seg->size = file-offset - seg->file-offset
    # . save ECX
    51/push-ECX
    # . EBX = *file-offset
    8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   compute-offsets:file-offset/disp32 # copy *file-offset to EBX
    # . ECX = seg->file-offset
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           1/r32/ECX   4/disp8         .                 # copy *(EAX+4) to ECX
    # . EBX -= ECX
    29/subtract                     3/mod/direct    3/rm32/EBX    .           .             .           1/r32/ECX   .               .                 # subtract ECX from EBX
    # . seg->size = EBX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy EBX to *(EAX+8)
    # . restore ECX
    59/pop-to-ECX
    # trace-sssns("segment '", curr-segment-name, "' has size ", seg->size, ".")
    # . trace-sssns("segment '", curr-segment-name, "' has size ", EBX, ".")
    # . . push args
    68/push  "."/imm32
    53/push-EBX
    68/push  "' has size "/imm32
    56/push-ESI
    68/push  "segment '"/imm32
    # . . call
    e8/call  trace-sssns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
$compute-offsets:end:
    # . reclaim locals
    # . 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
    # . epilog
    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
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

test-compute-offsets:
    # input:
    #   == code 0x1
    #   ab x/imm32
    #   == data 0x1000
    #   00
    #   x:
    #     34
    #
    # trace contains (in any order):
    #   segment 'code' is at file offset 0x0.
    #   segment 'code' has size 0x5.
    #   segment 'data' is at file offset 0x5.
    #   segment 'data' has size 0x2.
    #   label 'x' is in segment 'data'.
    #   label 'x' is at segment offset 0x1.
    #
    # . prolog
    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+4)
    # . . push args
    b8/copy-to-EAX  _test-input-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . 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+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # var segments/ECX = stream(2 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x20/imm32        # subtract from ESP
    68/push  0x20/imm32/length
    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 labels/EDX = stream(2 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x20/imm32        # subtract from ESP
    68/push  0x20/imm32/length
    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 0x1\n")
    # . . push args
    68/push  "== code 0x1\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 0x1000\n")
    # . . push args
    68/push  "== data 0x1000\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-buffered-file, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  compute-offsets/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/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
    # . check-trace-contains("segment 'code' is at file offset 0x00000000.", msg)
    # . . push args
    68/push  "F - test-compute-offsets/0"/imm32
    68/push  "segment 'code' is at file offset 0x00000000."/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-trace-contains("segment 'code' has size 0x00000005", msg)
    # . . push args
    68/push  "F - test-compute-offsets/1"/imm32
    68/push  "segment 'code' has size 0x00000005."/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-trace-contains("segment 'data' is at file offset 0x00000005.", msg)
    # . . push args
    68/push  "F - test-compute-offsets/2"/imm32
    68/push  "segment 'data' is at file offset 0x00000005."/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-trace-contains("segment 'data' has size 0x00000002.", msg)
    # . . push args
    68/push  "F - test-compute-offsets/3"/imm32
    68/push  "segment 'data' has size 0x00000002."/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-trace-contains("label 'x' is in segment 'data'.", msg)
    # . . push args
    68/push  "F - test-compute-offsets/4"/imm32
    68/push  "label 'x' is in segment 'data'."/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-trace-contains("label 'x' is at segment offset 0x00000001.", msg)
    # . . push args
    68/push  "F - test-compute-offsets/5"/imm32
    68/push  "label 'x' is at segment offset 0x00000001."/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, 0x10, msg)
    # . . push args
    68/push  "F - test-compute-offsets-maintains-labels-write-index"/imm32
    68/push  0x10/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

compute-addresses:  # segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # pseudocode:
    #   srow : (address segment-info) = segments->data
    #   max = segments->data + segments->write
    #   num-segments = segments->write / 16
    #   starting-offset = 0x34 + (num-segments * 0x20)
    #   while true
    #     if (srow >= max) break
    #     s->file-offset += starting-offset
    #     s->address &= 0xfffff000  # clear last 12 bits for p_align
    #     s->address += (s->file-offset & 0x00000fff)
    #     trace-sssns("segment " s->key " starts at address " s->address)
    #     srow += 16  # row-size
    #   lrow : (address label-info) = labels->data
    #   max = labels->data + labels->write
    #   while true
    #     if (lrow >= max) break
    #     seg-name : (address string) = lrow->segment-name
    #     label-seg : (address segment-info) = get(segments, seg-name, row-size=16)
    #     lrow->address = label-seg->address + lrow->segment-offset
    #     trace-sssns("label " lrow->key " is at address " lrow->address)
    #     lrow += 16  # row-size
    #
    # . prolog
    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
    # ESI = segments
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # starting-offset/EDI = 0x34 + (num-segments * 0x20)  # make room for ELF headers
    # . EDI = segments->write / 16 (row-size)
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           7/r32/EDI   .               .                 # copy *ESI to EDI
    c1/shift    5/subop/logic-right 3/mod/direct    7/rm32/EDI    .           .             .           .           .               4/imm8            # shift EDI right by 4 bits, while padding zeroes
    # . EDI = (EDI * 0x20) + 0x34
    c1/shift    4/subop/left        3/mod/direct    7/rm32/EDI    .           .             .           .           .               5/imm8            # shift EDI left by 5 bits
    81          0/subop/add         3/mod/direct    7/rm32/EDI    .           .             .           .           .               0x34/imm32        # add to EDI
    # srow/EAX = segments->data
    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
    # max/ECX = segments->data + segments->write
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
$compute-addresses:segment-loop:
    # if (srow >= max) break
    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
    73/jump-if-greater-or-equal-unsigned  $compute-addresses:segment-break/disp8
    # srow->file-offset += starting-offset
    01/add                          1/mod/*+disp8   0/rm32/EAX    .           .             .           7/r32/EDI   8/disp8         .                 # add EDI to *(EAX+8)
    # clear last 12 bits of srow->address for p_align=0x1000
    # . EDX = srow->address
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *(EAX+4) to EDX
    # . EDX &= 0xfffff000
    81          4/subop/and         3/mod/direct    2/rm32/EDX    .           .             .           .           .               0xfffff000/imm32  # bitwise and of EDX
    # update last 12 bits from srow->file-offset
    # . EBX = srow->file-offset
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # copy *(EAX+8) to EBX
    # . EBX &= 0xfff
    81          4/subop/and         3/mod/direct    3/rm32/EBX    .           .             .           .           .               0x00000fff/imm32  # bitwise and of EBX
    # . srow->address = EDX | EBX
    09/or                           3/mod/direct    2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # EDX = bitwise OR with EBX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy EDX to *(EAX+4)
    # trace-sssns("segment " srow " starts at address " srow->address ".")
    # . . push args
    68/push  "."/imm32
    52/push-EDX
    68/push  "' starts at address "/imm32
    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
    68/push  "segment '"/imm32
    # . . call
    e8/call  trace-sssns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # srow += 16  # size of row
    05/add-to-EAX  0x10/imm32
    eb/jump  $compute-addresses:segment-loop/disp8
$compute-addresses:segment-break:
#?     # 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
#?     # }}}
    # ESI = labels
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   0xc/disp8       .                 # copy *(EBP+12) to ESI
    # lrow/EAX = labels->data
    8d/copy-address                 1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy ESI+12 to EAX
    # max/ECX = labels->data + labels->write
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           1/r32/ECX   .               .                 # copy *ESI to ECX
    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           6/r32/ESI   .               .                 # add ESI to ECX
$compute-addresses:label-loop:
    # if (lrow >= max) break
    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
    0f 83/jump-if-greater-or-equal-unsigned  $compute-addresses:end/disp32
#?     # dump lrow->key {{{
#?     # . write(2/stderr, "label: ")
#?     # . . push args
#?     68/push  "label: "/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(2/stderr, lrow->key)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
#?     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(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
#?     # }}}
    # seg-name/EDX = lrow->segment-name
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           2/r32/EDX   4/disp8         .                 # copy *EAX to EDX
#?     # dump seg-name {{{
#?     # . write(2/stderr, "compute-addresses: seg-name: ")
#?     # . . push args
#?     68/push  "seg-name: "/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(2/stderr, seg-name)
#?     # . . push args
#?     52/push-EDX
#?     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(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
#?     # }}}
    # label-seg/EDX : (address segment-info) = get(segments, seg-name, row-size=16)
    # . save EAX
    50/push-EAX
    # . EAX = get(segments, seg-name, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    52/push-EDX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  get/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . EDX = EAX
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EDX
    # . restore EAX
    58/pop-to-EAX
    # EBX = label-seg->address
    8b/copy                         0/mod/indirect  2/rm32/EDX    .           .             .           3/r32/EBX   .               .                 # copy *EDX to EBX
    # EBX += lrow->segment-offset
    03/add                          1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   8/disp8         .                 # add *(EAX+8) to EBX
    # lrow->address = EBX
    89/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           3/r32/EBX   0xc/disp8       .                 # copy EBX to *(EAX+12)
    # trace-sssns("label " lrow->key " is at address " lrow->address ".")
    # . . push args
    68/push  "."/imm32
    53/push-EBX
    68/push  "' is at address "/imm32
    ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
    68/push  "label '"/imm32
    # . . call
    e8/call  trace-sssns/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # lrow += 16  # size of row
    05/add-to-EAX  0x10/imm32
    e9/jump  $compute-addresses:label-loop/disp32
$compute-addresses:end:
    # . 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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-compute-addresses:
    # input:
    #   segments:
    #     - 'a': {0x1000, 0, 5}
    #     - 'b': {0x2018, 5, 1}
    #     - 'c': {0x5444, 6, 12}
    #   labels:
    #     - 'l1': {'a', 3, 0}
    #     - 'l2': {'b', 0, 0}
    #
    # trace contains in any order (comments in parens):
    #   segment 'a' starts at address 0x00001094.  (0x34 + 0x20 for each segment)
    #   segment 'b' starts at address 0x00002099.  (0x018 discarded)
    #   segment 'c' starts at address 0x0000509a.  (0x444 discarded)
    #   label 'l1' is at address 0x00001097.       (0x1094 + segment-offset 3)
    #   label 'l2' is at address 0x00002099.       (0x2099 + segment-offset 0)
    #
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # setup
    # . var segments/ECX = stream(10 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
    68/push  0xa0/imm32/length
    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 labels/EDX = stream(512 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
    68/push  0x2000/imm32/length
    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
    # . stream-add4(segments, "a", 0x1000, 0, 5)
    68/push  5/imm32/segment-size
    68/push  0/imm32/file-offset
    68/push  0x1000/imm32/start-address
    68/push  "a"/imm32/segment-name
    51/push-ECX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(segments, "b", 0x2018, 5, 1)
    68/push  1/imm32/segment-size
    68/push  5/imm32/file-offset
    68/push  0x2018/imm32/start-address
    68/push  "b"/imm32/segment-name
    51/push-ECX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(segments, "c", 0x5444, 6, 12)
    68/push  0xc/imm32/segment-size
    68/push  6/imm32/file-offset
    68/push  0x5444/imm32/start-address
    68/push  "c"/imm32/segment-name
    51/push-ECX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(labels, "l1", "a", 3, 0)
    68/push  0/imm32/label-address
    68/push  3/imm32/segment-offset
    68/push  "a"/imm32/segment-name
    68/push  "l1"/imm32/label-name
    52/push-EDX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(labels, "l2", "b", 0, 0)
    68/push  0/imm32/label-address
    68/push  0/imm32/segment-offset
    68/push  "b"/imm32/segment-name
    68/push  "l2"/imm32/label-name
    52/push-EDX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # component under test
    # . compute-addresses(segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    # . . call
    e8/call  compute-addresses/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # checks
#?     # 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("segment 'a' starts at address 0x00001094.", msg)
    # . . push args
    68/push  "F - test-compute-addresses/0"/imm32
    68/push  "segment 'a' starts at address 0x00001094."/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-trace-contains("segment 'b' starts at address 0x00002099.", msg)
    # . . push args
    68/push  "F - test-compute-addresses/1"/imm32
    68/push  "segment 'b' starts at address 0x00002099."/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-trace-contains("segment 'c' starts at address 0x0000509a.", msg)
    # . . push args
    68/push  "F - test-compute-addresses/2"/imm32
    68/push  "segment 'c' starts at address 0x0000509a."/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-trace-contains("label 'l1' is at address 0x00001097.", msg)
    # . . push args
    68/push  "F - test-compute-addresses/3"/imm32
    68/push  "label 'l1' is at address 0x00001097."/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-trace-contains("label 'l2' is at address 0x00002099.", msg)
    # . . push args
    68/push  "F - test-compute-addresses/4"/imm32
    68/push  "label 'l2' is at address 0x00002099."/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, 0x20, msg)
    # . . push args
    68/push  "F - test-compute-addresses-maintains-labels-write-index"/imm32
    68/push  0x20/imm32/2-entries
    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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

emit-output:  # in : (address buffered-file), out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # pseudocode:
    #   emit-headers(out, segments, labels)
    #   emit-segments(in, out, segments, labels)
    #
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # emit-headers(out, segments, labels)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8       .                # push *(EBP+20)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8       .                # push *(EBP+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8        .                # push *(EBP+12)
    # . . call
    e8/call  emit-headers/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # emit-segments(in, out, segments, labels)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8       .                # push *(EBP+20)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8       .                # push *(EBP+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-segments/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
$emit-output:end:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

emit-segments:  # in : (address buffered-file), out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # pseudocode:
    #   var offset-of-next-instruction = 0
    #   var line = new-stream(512, 1)
    #   line-loop:
    #   while true
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if (line->write == 0) break               # end of file
    #     offset-of-next-instruction += num-bytes(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
    #       datum = next-token-from-slice(word-slice->start, word-slice->end, "/")
    #       info = get-slice(labels, datum)
    #       if has-metadata?(word-slice, "imm8")
    #         abort  # label should never go to imm8
    #       else if has-metadata?(word-slice, "imm32")
    #         emit(out, info->address, 4)
    #       else if has-metadata?(word-slice, "disp8")
    #         value = info->offset - offset-of-next-instruction
    #         emit(out, value, 1)
    #       else if has-metadata?(word-slice, "disp32")
    #         value = info->offset - offset-of-next-instruction
    #         emit(out, value, 4)
    #       else
    #         abort
    #     write-buffered(out, "\n")
    #
    # registers:
    #   line: ECX
    #   word-slice: EDX
    #   offset-of-next-instruction: EBX
    #   datum: EDI
    #   info: ESI (inner loop only)
    #   temporaries: EAX, ESI (outer loop)
    #
    # . prolog
    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 : (address stream byte) = stream(512)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x200/imm32       # subtract from ESP
    68/push  0x200/imm32/length
    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 = {0, 0}
    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/EDI = {0, 0}
    68/push  0/imm32/end
    68/push  0/imm32/start
    89/copy                         3/mod/direct    7/rm32/EDI    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDI
    # offset-of-next-instruction/EBX = 0
    31/xor                          3/mod/direct    3/rm32/EBX    .           .             .           3/r32/EBX   .               .                 # clear EBX
$emit-segments: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-buffered(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-buffered/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-segments:check0:
    # if (line->write == 0) break
    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
    0f 84/jump-if-equal  $emit-segments:end/disp32
    # offset-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
    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # copy EAX to EBX
$emit-segments: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-segments:check1:
    # 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
    0f 85/jump-if-not-equal  $emit-segments:next-line/disp32
$emit-segments: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-equal  $emit-segments:next-line/disp32
$emit-segments: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 != 0) break
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $emit-segments:line-loop/disp32
$emit-segments: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 != 0) break
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $emit-segments:line-loop/disp32
$emit-segments:2-character:
    # if (length(word-slice) != 2) goto next check
    # . EAX = length(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-not-equal  $emit-segments: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  " "/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-segments:word-loop/disp32
$emit-segments:check-metadata:
    # - if we get here, 'word-slice' must be a label to be looked up
    # datum/EDI = next-token-from-slice(word-slice->start, word-slice->end, "/")
    # . . push args
    57/push-EDI
    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 word-slice {{{
#?     # . 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, word-slice)
#?     # . . push args
#?     57/push-EDI
#?     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
#?     # }}}
    # info/ESI = get-slice(labels, datum, row-size=16)
    # . EAX = get-slice(labels, datum, row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    57/push-EDI
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
    # . . call
    e8/call  get-slice/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . ESI = EAX
    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy EAX to ESI
$emit-segments:check-for-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 != 0) abort
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $emit-segments:imm8-abort/disp32
$emit-segments:check-for-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 == 0) goto next check
    3d/compare-EAX-and  0/imm32
    74/jump-if-equal  $emit-segments:check-for-disp8/disp8
#?     # dump info->address {{{
#?     # . write(2/stderr, "info->address: ")
#?     # . . push args
#?     68/push  "info->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
#?     # . print-int32-buffered(Stderr, info->address)
#?     # . . push args
#?     ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  print-int32-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-hex(out, info->address, 4)
    # . . push args
    68/push  4/imm32
    ff          6/subop/push        1/mod/*+disp8   6/rm32/ESI    .           .             .           .           8/disp8         .                 # push *(ESI+8)
    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-segments:word-loop/disp32
$emit-segments:check-for-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 == 0) goto next check
    3d/compare-EAX-and  0/imm32
    74/jump-if-equal  $emit-segments:check-for-disp32/disp8
    # emit-hex(out, info->offset - offset-of-next-instruction, 1)
    # . . push args
    68/push  1/imm32
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) 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-segments:word-loop/disp32
$emit-segments:check-for-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 == 0) abort
    3d/compare-EAX-and  0/imm32
    74/jump-if-equal  $emit-segments:abort/disp8
    # emit-hex(out, info->offset - offset-of-next-instruction, 4)
    # . . push args
    68/push  4/imm32
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) 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-segments:word-loop/disp32
$emit-segments: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-segments:line-loop/disp32
$emit-segments:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x21c/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

$emit-segments:imm8-abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "emit-segments: unexpected /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
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$emit-segments:abort:
    # print(stderr, "missing metadata in " word-slice)
    # . _write(2/stderr, "missing metadata in word ")
    # . . push args
    68/push  "emit-segments: 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
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

test-emit-segments:
    # input:
    #   in:
    #     == code 0x1000
    #     ab cd ef gh
    #     ij x/imm32
    #     == data 0x2000
    #     00
    #     x:
    #       34
    #   segments:
    #     - 'code': {0x1074, 0, 5}
    #     - 'data': {0x2079, 5, 1}
    #   labels:
    #     - 'l1': {'code', 3, 0x1077}
    #     - 'x': {'data', 1, 0x207a}
    #
    # output:
    #   ab cd ef gh
    #   ij 7a 20 00 00
    #   00
    #   34
    #
    # . prolog
    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+4)
    # . . push args
    b8/copy-to-EAX  _test-input-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . 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+4)
    # . . push args
    b8/copy-to-EAX  _test-output-buffered-file/imm32
    05/add-to-EAX  4/imm32
    50/push-EAX
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . var segments/ECX = stream(10 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xa0/imm32        # subtract from ESP
    68/push  0xa0/imm32/length
    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 labels/EDX = stream(512 * 16)
    81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x2000/imm32      # subtract from ESP
    68/push  0x2000/imm32/length
    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 0x1000\n")
    # . . push args
    68/push  "== code 0x1000\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/imm32\n")
    # . . push args
    68/push  "ij 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 0x2000\n")
    # . . push args
    68/push  "== data 0x2000\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
    # . stream-add4(segments, "code", 0x1074, 0, 5)
    68/push  5/imm32/segment-size
    68/push  0/imm32/file-offset
    68/push  0x1074/imm32/start-address
    68/push  "code"/imm32/segment-name
    51/push-ECX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(segments, "data", 0x2079, 5, 1)
    68/push  1/imm32/segment-size
    68/push  5/imm32/file-offset
    68/push  0x2079/imm32/start-address
    68/push  "data"/imm32/segment-name
    51/push-ECX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(labels, "l1", "code", 3, 0x1077)
    68/push  0x1077/imm32/label-address
    68/push  3/imm32/segment-offset
    68/push  "code"/imm32/segment-name
    68/push  "l1"/imm32/label-name
    52/push-EDX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # . stream-add4(labels, "x", "data", 1, 0x207a)
    68/push  0x207a/imm32/label-address
    68/push  1/imm32/segment-offset
    68/push  "data"/imm32/segment-name
    68/push  "x"/imm32/label-name
    52/push-EDX
    # . . call
    e8/call  stream-add4/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x14/imm32        # add to ESP
    # component under test
    # . emit-segments(_test-input-buffered-file, _test-output-buffered-file, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    68/push  _test-output-buffered-file/imm32
    68/push  _test-input-buffered-file/imm32
    # . . call
    e8/call  emit-segments/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
    # checks
#?     # 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-segments/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 7a 20 00 00 ", msg)
    # . . push args
    68/push  "F - test-emit-segments/1"/imm32
    68/push  "ij 7a 20 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
    # . check-next-stream-line-equal(_test-output-stream, "00 ", msg)
    # . . push args
    68/push  "F - test-emit-segments/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-segments/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

emit-headers:  # out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # pseudocode:
    #   emit-elf-header(out, segments, labels)
    #   curr-segment = segments->data
    #   max = segments->data + segments->write
    #   while true
    #     if (curr-segment >= max) break
    #     emit-elf-program-header-entry(curr-segment)
    #     curr-segment += 20                        # size of a row
    #
    # . prolog
    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
    # emit-elf-header(out, segments, labels)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-elf-header/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # EAX = segments
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
    # ECX = segments->write
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy *EAX to ECX
    # curr-segment/EAX = segments->data
    8d/copy-address                 1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy EAX+12 to EAX
    # max/ECX = segments->data + segments->write
    01/add                          3/mod/direct    1/rm32/ECX    .           .             .           0/r32/EAX   .               .                 # add EAX to ECX
$emit-headers:loop:
    # if (curr-segment >= max) break
    39/compare                      3/mod/direct    0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # compare EAX with ECX
    0f 83/jump-if-greater-or-equal-unsigned  $emit-headers:end/disp32
#?     # dump curr-segment->name {{{
#?     # . write(2/stderr, "about to emit ph entry: segment->name: ")
#?     # . . push args
#?     68/push  "about to emit ph entry: segment->name: "/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+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . print-int32-buffered(Stderr, &curr-segment)
#?     # . . push args
#?     50/push-EAX
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  print-int32-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, " -> ")
#?     # . . 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
#?     # . print-int32-buffered(Stderr, curr-segment->name)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  0/rm32/EAX    .           .             .           .           .               .                 # push *EAX
#?     68/push  Stderr/imm32
#?     # . . call
#?     e8/call  print-int32-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-elf-program-header-entry(curr-segment)
    # . . push args
    50/push-EAX
    # . . call
    e8/call  emit-elf-program-header-entry/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # curr-segment += 20                        # size of a row
    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x14/imm32        # add to EAX
    e9/jump  $emit-headers:loop/disp32
$emit-headers:end:
    # . restore registers
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

emit-elf-header:  # out : (address buffered-file), segments : (address stream {string, segment-info}), labels : (address stream {string, label-info})
    # pseudocode
    #   *Elf_e_entry = get(labels, "Entry")->address
    #   *Elf_e_phnum = segments->write / 20         # size of a row
    #   emit-hex-array(out, Elf_header)
    #
    # . prolog
    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  # just because we need to call idiv
    # *Elf_e_entry = get(labels, "Entry")->address
    # . EAX = labels
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0x10/disp8      .                 # copy *(EBP+16) to EAX
    # . label-info/EAX = get(labels, "Entry", row-size=16)
    # . . push args
    68/push  0x10/imm32/row-size
    68/push  "Entry"/imm32
    50/push-EAX
    # . . call
    e8/call  get/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . EAX = label-info->address
    8b/copy                         1/mod/*+disp8   0/rm32/EAX    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(EAX+8) to EAX
    # . *Elf_e_entry = EAX
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_e_entry/disp32                # copy EAX to *Elf_e_entry
    # *Elf_e_phnum = segments->write / 0x20
    # . EAX = segments
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(EBP+12) to EAX
    # . len/EAX = segments->write
    8b/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           0/r32/EAX   .               .                 # copy *EAX to EAX
    # . EAX = len / 0x20  (destroying EDX)
    b9/copy-to-ECX  0x20/imm32
    31/xor                          3/mod/direct    2/rm32/EDX    .           .             .           2/r32/EDX   .               .                 # clear EDX
    f7          7/subop/idiv        3/mod/direct    1/rm32/ECX    .           .             .           .           .               .                 # divide EDX:EAX by ECX, storing quotient in EAX and remainder in EDX
    # . *Elf_e_phnum = EAX
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_e_phnum/disp32                # copy EAX to *Elf_e_phnum
    # emit-hex-array(out, Elf_header)
    # . . push args
    68/push  Elf_header/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-hex-array/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$emit-elf-header:end:
    # . restore registers
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

emit-elf-program-header-entry:  # curr-segment : (address {string, segment-info})
    # pseudocode:
    #   *Elf_p_offset = curr-segment->file-offset
    #   *Elf_p_vaddr = curr-segment->address
    #   *Elf_p_paddr = curr-segment->address
    #   *Elf_p_filesz = curr-segment->size
    #   *Elf_p_memsz = curr-segment->size
    #   if curr-segment->name == "code"
    #     *Elf_p_flags = 5  # r-x
    #   else
    #     *Elf_p_flags = 6  # rw-
    #   emit-hex-array(out, Elf_program_header_entry)
    #
    # . prolog
    55/push-EBP
    89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
    # . save registers
    50/push-EAX
    56/push-ESI
    # ESI = curr-segment
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
    # *Elf_p_offset = curr-segment->file-offset
    # . EAX = curr-segment->file-offset
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   8/disp8         .                 # copy *(ESI+8) to EAX
    # . *Elf_p_offset = EAX
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_offset/disp32               # copy EAX to *Elf_p_offset
    # *Elf_p_vaddr = curr-segment->address
    # . EAX = curr-segment->address
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   4/disp8         .                 # copy *(ESI+4) to EAX
    # . *Elf_p_vaddr = EAX
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_vaddr/disp32                # copy EAX to *Elf_p_vaddr
    # *Elf_p_paddr = curr-segment->address
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_paddr/disp32                # copy EAX to *Elf_p_paddr
    # *Elf_p_filesz = curr-segment->size
    # . EAX = curr-segment->size
    8b/copy                         1/mod/*+disp8   6/rm32/ESI    .           .             .           0/r32/EAX   0xc/disp8       .                 # copy *(ESI+12) to EAX
    # . *Elf_p_filesz = EAX
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_filesz/disp32               # copy EAX to *Elf_p_filesz
    # *Elf_p_memsz = curr-segment->size
    89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Elf_p_memsz/disp32                # copy EAX to *Elf_p_memsz
    # if (!string-equal?(curr-segment->name, "code") goto next check
    # . EAX = curr-segment->name
    8b/copy                         0/mod/indirect  6/rm32/ESI    .           .             .           0/r32/EAX   .               .                 # copy *ESI to EAX
    # . EAX = string-equal?(curr-segment->name, "code")
    # . . push args
    68/push  "code"/imm32
    50/push-EAX
    # . . call
    e8/call  string-equal?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . if (EAX == 0) goto next check
    3d/compare-EAX-and  0/imm32
    74/jump-if-equal  $emit-elf-program-header-entry:data/disp8
    # *Elf_p_flags = rw-
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Elf_p_flags/disp32  6/imm32       # copy to *Elf_p_flags
$emit-elf-program-header-entry:data:
    # otherwise *Elf_p_flags = r-x
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Elf_p_flags/disp32  5/imm32       # copy to *Elf_p_flags
    # emit-hex-array(out, Elf_program_header_entry)
    # . . push args
    68/push  Elf_program_header_entry/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-hex-array/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
$emit-elf-program-header-entry:end:
    # . restore registers
    5e/pop-to-ESI
    58/pop-to-EAX
    # . epilog
    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-add4:  # in : (address stream byte), key : address, val1 : address, val2 : address, val3 : address
    # . prolog
    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->length
    # . EDX = in->length
    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-greater-or-equal-unsigned  $stream-add4:abort/disp8
    # *curr = key
    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-greater-or-equal-unsigned  $stream-add4:abort/disp8
    # *curr = val1
    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-greater-or-equal-unsigned  $stream-add4:abort/disp8
    # *curr = val2
    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
    # 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-greater-or-equal-unsigned  $stream-add4:abort/disp8
    # *curr = val3
    8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .                         1/r32/ECX   0x18/disp8      .                 # copy *(EBP+24) to ECX
    89/copy                         0/mod/indirect  0/rm32/EAX    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EAX
    # in->write += 16
    81          0/subop/add         0/mod/indirect  6/rm32/ESI    .           .             .           .           .               0x10/imm32        # add to *ESI
$stream-add4:end:
    # . restore registers
    5e/pop-to-ESI
    5a/pop-to-EDX
    59/pop-to-ECX
    58/pop-to-EAX
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

$stream-add4:abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "overflow in stream-add4\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
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # 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: (address string)
#   l: (address slice)
# one gotcha: 's5' must not be empty

trace-sssns:  # s1 : (address string), s2 : (address string), s3 : (address string), n4 : int, s5 : (address string)
    # . prolog
    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(*Trace-stream, s2)
    # . . 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/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
    # print-int32(*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  print-int32/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-sssns:end:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-trace-sssns:
    # . prolog
    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
    # trace-sssns("A" "b" "c " 3 " e")
    # . . push args
    68/push  " e"/imm32
    68/push  3/imm32
    68/push  "c "/imm32
    68/push  "b"/imm32
    68/push  "A"/imm32
    # . . call
    e8/call  trace-sssns/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-sssns"/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

trace-snsns:  # s1 : (address string), n2 : int, s3 : (address string), n4 : int, s5 : (address string)
    # . prolog
    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
    # print-int32(*Trace-stream, n2)
    # . . 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  print-int32/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
    # print-int32(*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  print-int32/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-snsns:end:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-trace-snsns:
    # . prolog
    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
    # trace-snsns("A " 2 " c " 3 " e")
    # . . push args
    68/push  " e"/imm32
    68/push  3/imm32
    68/push  " c "/imm32
    68/push  2/imm32
    68/push  "A "/imm32
    # . . call
    e8/call  trace-snsns/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-snsns"/imm32
    68/push  "A 0x00000002 c 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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

trace-slsls:  # s1 : (address string), l2 : (address slice), s3 : (address string), l4 : (address slice), s5 : (address string)
    # . prolog
    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-slice(*Trace-stream, l4)
    # . . 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-slice/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-slsls:end:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-trace-slsls:
    # . prolog
    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 : (address slice) = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    3/rm32/EBX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBX
    # (EAX..ECX) = "d"
    b8/copy-to-EAX  "d"/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 d/EDX : (address slice) = {EAX, ECX}
    51/push-ECX
    50/push-EAX
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
    # trace-slsls("A" b "c" d "e")
    # . . push args
    68/push  "e"/imm32
    52/push-EDX
    68/push  "c"/imm32
    53/push-EBX
    68/push  "A"/imm32
    # . . call
    e8/call  trace-slsls/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("Abcde")
    # . . push args
    68/push  "F - test-trace-slsls"/imm32
    68/push  "Abcde"/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

trace-slsns:  # s1 : (address string), l2 : (address slice), s3 : (address string), n4 : int, s5 : (address string)
    # . prolog
    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
    # print-int32(*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  print-int32/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:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-trace-slsns:
    # . prolog
    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 : (address 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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

trace-slsss:  # s1 : (address string), l2 : (address slice), s3 : (address string), s4 : (address string), s5 : (address string)
    # . prolog
    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(*Trace-stream, s4)
    # . . 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/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-slsss:end:
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

test-trace-slsss:
    # . prolog
    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 : (address 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-slsss("A" b "c" "d" "e")
    # . . push args
    68/push  "e"/imm32
    68/push  "d"/imm32
    68/push  "c"/imm32
    53/push-EBX
    68/push  "A"/imm32
    # . . call
    e8/call  trace-slsss/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("Abcde")
    # . . push args
    68/push  "F - test-trace-slsss"/imm32
    68/push  "Abcde"/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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

num-bytes:  # line : (address stream) -> 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
    #
    # . prolog
    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 = {0, 0}
    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+4)
#?     # . . save EAX
#?     50/push-EAX
#?     # . . push args
#?     b8/copy-to-EAX  Stderr/imm32
#?     05/add-to-EAX  4/imm32
#?     50/push-EAX
#?     # . . call
#?     e8/call  clear-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # . . restore EAX
#?     58/pop-to-EAX
#?     # . 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 != 0) break
    3d/compare-EAX-and  0/imm32
    # . restore result now that ZF is set
    58/pop-to-EAX
    75/jump-if-not-equal  $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-equal  $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-equal  $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 != 0) break
    3d/compare-EAX-and  0/imm32
    # . restore result now that ZF is set
    58/pop-to-EAX
    75/jump-if-not-equal  $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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    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
    # . prolog
    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
    # . epilog
    89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
    5d/pop-to-EBP
    c3/return

== data

Segment-size:
  0x1000/imm32/4KB

# This block of bytes gets copied to the start of the output ELF file, with
# some fields filled in.
# http://www.sco.com/developers/gabi/latest/ch4.eheader.html
Elf_header:
  # - length
  0x34/imm32
  # - data
$e_ident:
  7f 45/E 4c/L 46/F
  01/32-bit  01/little-endian  01/file-version  00/no-os-extensions
  00 00 00 00 00 00 00 00  # 8 bytes of padding
$e_type:
  02 00
$e_machine:
  03 00
$e_version:
  1/imm32
Elf_e_entry:
  0x09000000/imm32  # approximate default; must be updated
$e_phoff:
  0x34/imm32  # offset for the 'program header table' containing segment headers
$e_shoff:
  0/imm32  # no sections
$e_flags:
  0/imm32  # unused
$e_ehsize:
  0x34 00
$e_phentsize:
  0x20 00
Elf_e_phnum:
  00 00  # number of segments; must be updated
$e_shentsize:
  00 00  # no sections
$e_shnum:
  00 00
$e_shstrndx:
  00 00

# This block of bytes gets copied after the Elf_header once for each segment.
# Some fields need filling in each time.
# https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html
Elf_program_header_entry:
  # - length
  0x20/imm32
  # - data
$p_type:
  1/imm32/PT_LOAD
Elf_p_offset:
  0/imm32  # byte offset in the file at which a segment begins; must be updated
Elf_p_vaddr:
  0/imm32  # starting address to store the segment at before running the program
Elf_p_paddr:
  0/imm32  # should have same value as Elf_p_vaddr
Elf_p_filesz:
  0/imm32
Elf_p_memsz:
  0/imm32  # should have same value as Elf_p_filesz
Elf_p_flags:
  6/imm32/rw-  # read/write/execute permissions for the segment; must be updated for the code segment
$p_align:
  # we hold this constant; changing it will require adjusting the way we
  # compute the starting address for each segment
  0x1000/imm32

# . . vim:nowrap:textwidth=0