about summary refs log blame commit diff stats
path: root/subx/apps/survey.subx
blob: 42c113515aec0acab024deb1f8c40022688bfdba (plain) (tree)
1
2
3
4
5
6
7
8
9
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738



                                                                          
                                                                         


                                                             
 























                                                                                 



                                                                              






                                                                                                                                                 
                                   

                       
                                                                                                                                                                       



                                                                                                                                                                  






                                                                                                                                                                  
 
























                                                                                                                                                                                      
                            
                   




                           
                                                                                                                                                                  







                                                                                                                                                                             





                                




                                                                            
                                                             
 
                                                                                     
                

                                                     
                                                         
                                                        




                                              



                                                                                                                                                                       

               
               





                                                                                                                                                                         





                                                                                                                                                                                     
                         
             

                                                                                                                                                                       
                                         
            
                                                                                                                                                                                     
              
                                                                                                                                                                        
            
                         
             

                                                                                                                                                                       














































                                                                                                                                                                     








                                                                                                                                                                  







                                                                                                                                                                     













































                                                                                                                                                                     
































                                                                                                                                                                     








                                                                                                                                                                  



                                           
               



                                                                                                                                                                  








                                                                                                                                                                  






                                         
                                                                                                                                                                  
                          
                      
                  
                 
                                    

                                                                                                                                                                     

















































































                                                                                                                                                                                
                      



                                                                                                                                                                         
                 
                                  
                         

                                                                                                                                                                     
                      
                                                                                                                                                                         



                                                                                                                                                                     

                      
                                                                                                                                                                  
                         
                 

                 




                                                                                                                                                                       


                                
              























































                                                                                                                                                                       







                                                                                                                                                                  








































                                                                                                                                                                  


























                                                                                                                                                                             

                                                          
                                                        



                                                                                                                                                                  
                                                                                 

                                                          
                                                                 



                                                                                                                                                                  
                                                                        

                                                          
                                                        



                                                                                                                                                                  
                                                                                 

                                                          
                                                                 



                                                                                                                                                                  




                                                                                                                                                                       


                                                              
                                   
         
                                      


                           
             
                                     

               


       
                                                                                                                                             








                           
                 
                                                
                                     
                                                               
                            
                             
                                                                 
                                                               
                                        

                                                                 
                                                                  
                                                                 
                                                  

                                                                
                                                           
                                                                               


                                                              
                     

                                           
                     
                                                              
                                                                
                                             
                                                                                             
                                



                                                                    
                                                 
                                                                                        
                                                

                                                                                          
                
                                                        

                                      




                                                                                                                                                                       





               

                                                                                                                                                                 
                     
                                                                                                                                                                                                                   
                        
                                                                                                                                                                                                                   


                                   

                        
                       
              
                              
                      
                                                                                                                                                                  

                                                                                                                                                                       





                                                                                                                                                                  
                             

                                                                                                                                                                     
                             

                                                                                                                                                                  
                                 
                                                                                                                                                                        
                               
                                                                
































                                                                                                                                                                     
                           
                      






                                                                                                                                                                  
                                                        
                                 
                      
                           










































                                                                                                                                                                      
                                                          































                                                                                                                                                                     
                            





                                                                                                                                                                  


                                                              
                              





                                                                                                                                                                  
                           
                               
                                                              

                                                             





                                                                                                                                                                  
                                    
                               
                                                           

                                                                                                                                                                   
                                                                   
                                                                       
                   
                                
               
                                                                                                                                                                      
              
                                 
                      
                                                                                                                                                                  
                                                



                                                                                                                                                                                 
                              
                                                                                                                                                                            





                                                                                                                                                                             
                                                                                
                   
                      
               
                                
               
                              
              
                               
                      
                                                                                                                                                                  
                                        

                                              



                                                                                                                                                                  














































                                                                                                                                                                      







                                                      
                      




                                                                                                                                                                       
                               
                                                      

                                              



                                                                                                                                                                  


                                              


                                                                                                                                                                  


                                                          
                                                                       
                   
                                
               
                                                                                                                                                                      
              
                                 

                                                                                                                                                                  

                                                                                                                                                                       


                                               




                                                                                                                                                                        
                                     
                                                                                                                                                                                 
                                                                                                                                                                            
                                                                                               
                   


                                         
               
                              
              
                               

                                                                                                                                                                  

                                                                                                                                                                                                
           
                                             










                                                                                                                                                                  
                                                         

                                                                                                                                                                          
                                                                        




                                                                                                                                                                      
                                             

                                                                                                                                                                  
                                   

                                                                                                                                                                        










                                                                                                                                                                  




                                                                                                                                                                                        
















                                                                                                                                                                        
                              

                                                    
               

                                         
                      

                                                                                                                                                                  
                                                                                                                                                                                      
                          
                                                                                                                                                                                














































                                                                                                                                                                                    
                                             
                                 






















                                                                                                                                                                                 
                                                                            









                                                                                                                                                                  


                         





                 



                                                                                                                                                                       













                                                                                                                                                                  



                     
                                  
                      
          
          
            

                                    





                                             




                                                                                                                                                                       






                                                                                                                                                                  




















                                                                                                                                                                         
                                                                 
                   
                                                 












                                                                                                                                                                  







                                                                                                                                                                  















                                                                                                                                                                  
                                                           


                   
                                     


                                   
                                                                                                                                                                 

























                                                                                                                                                                             
                 
                                                                                 

                                               
                                                                 



                                                                                                                                                                  
                                                                       

                                               
                                                        



                                                                                                                                                                  
                                                                                 

                                               
                                                                 



                                                                                                                                                                  
                                                                        

                                               
                                                        



                                                                                                                                                                  
                                                                    

                                               
                                                    



                                                                                                                                                                  
                                                                               

                                               
                                                               

                                        

                                                                                                                                                                  








                                                                                                                                                                  






                                                                                                                                                                       
                                                      
                                              

                                                      
                  
                                
                                           

                                                                    
                                                                         

                                                  

                                          

                                                          
                                                                                   


                                                                         




                                                                                                                                                                       




               
               

                                                                                                                                                                            






                                                                                                                                                                                                       

                                                                                                                                                                          



                                                                                                                                                                        
                            

                                                                                                                                                                            
                                          
                                                                                                                                                                           

                                                            
                                                                                                                                                                            

                                                                                                                                                                          

                                                
                                                                                                                                                                            

                                                                                                                                                                          
                                 
                                                                                                                                                                                 
                                                                                                                                                                            






















                                                                                                                                                                     
                                                                          










                                                                                                                                                                  


                                                  

























                                                                                                                                                                             

                                                                                                                                                                             

                                                                                                                                                                          



                                                                                                                                                                        
                            
                                                                                                                                                                            
                                                                          

























                                                                                                                                                                     

                                                                                                                                                                        

























                                                                                                                                                                     
                                                                                   

                
                                                  


                                
                                                                                                                                                                     
              
                       

                                                                                                                                                                  



                                                                                                                                                                       

                                                                                                                                                                        
                                 
                                                                                                                                                                           

                                                                                                                                                                             










                                                                                                                                                                  
                               
                             
                                                 
                       
                         
                 




                 





                                                                                                                                                                       







                                
     



                                                                                 

                                                                              




                                                                                                                                                                       





















                                                                                                                                                                         
                                                
                                 

                                       





                                                                                                                                                                  
                                                 
                                   
                                


























                                                                                                                                                                  
                          







                                                                                                                                                                  
            

























                                                                                                                                                                             
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                              

                                                 
                                                              



                                                                                                                                                                  
                                                                         

                                                 








                                                                                                                                                                  



                                                                                                                                                                  

                                                  
                                                                            





                                                                                                                                                                  




                                                                                                                                                                       

































































































                                                                                                                                                                         
                                                                                                                                                                        

                                           




                                                                                                                                                                       








                                                                                                                                                                     








                                                                                                                                                                      








                                                                                                                                                                     















                                                                                                                                                                       
                                                                                                                                                                          
                 

                                          
                  

                            
                             








                                                                                            


                                                                                      



                                                   
                                                                                  
                                           







                                                                                                    












                                                               







                                          



                                                                                                                                                                       





























                                                                                                                                                                         
                         



                                                                                                                                                                     
                             


































                                                                                                                                                                     
                                      










                                                                                                                                                                    

                                                                                                                                                                      









































                                                                                                                                                                     
                                     




















































































































                                                                                                                                                                               

                                                      




                                                                                                                                                                      
                             



                                                                                                                                                                       













































































                                                                                                                                                                      











                                                                                                                                                                  
                                          










                                                                                                                                                                  
                                                                     
































                                                                                                                                                                        
                                     










                                                                                                                                                                      
                                          










                                                                                                                                                                  

                                                                      












                                                                                                                                                                             
                                           










                                                                                                                                                                  

                                                    























                                                                                                                                                                             
                   
                      
                                                                                                                                                                  
                         





                 




                                                                                                                                                                       














                                                                                                                                                                  


                               
                                                                          









































                                                                                                                                                                  


                                                                                   

            
                        
                     
                     
                        



                 

                                  
               
                                    


                   
                      













                                                                                                                                                                       




























                                                                                                                                                                         
                                                     
                   
                                     












                                                                                                                                                                  
                                                  
                   
                                  




                                                                                                                                                                  
                                                     
                   
                                     




























                                                                                                                                                                  

                                                   

                                       
                                      




                                                                                                                                                                  
                                                   


                                       
                                      




                                                                                                                                                                  
                                                   

                                       

                                      





                                                                                                                                                                  
                                                                                       



                                             
                                     




                                                                                                                                                                  






                                                                                                                                                                  

































                                                                                                                                                                     
                   
                                                             
                                 




                                                                                                                                                                  
                                                                                 
                   
                                                             
                                    




                                                                                                                                                                  
                                                                     
                   
                                                             
                        




                                                                                                                                                                  
                                                                     
                   
                                                             
                        









                                                                                                                                                                       























































































































































































































































































































































































































                                                                                                                                                                         






                                                                                                                                                  
                                                          
                                                                   




                                                                                                                                                                       

               








                                                                                                                                                                     









                                                                                                                                                                      
                                                                                                                                                                             








                                                                                                                                                                            
                                                                     













































































                                                                                                                                                                     
                                                      

                   
                                                                                                                                                                     


                                                 
                                                                                                                                                                  

                                                                                                                                                                  
                                      
                  
                         

                 






                                                                                                                                                                       
                                                    
                                                                   
                                       
                                 




                                                                                                                                                                       


                                                    
                                                  

                                                                                                                                                                             
                                                          


                                
               
              
                       





                                                                                                                                                                                
                                           



                                                                                                                                                                             

                                          











                                                                                                                                                                                                                           







                                                                                                                                                                     
                     
                         


                 




                                                                                                                                                                       
                                                                                                                









                                                 
                                                     
                                 




                                                                                                                                                                       


                        
                                                                                                                                                                             
































                                                                                                                                                                                 
                        
                                                                                                                                                                            




                                                                                                                                                                            







                                                                                                                                                                     







                                                                                                                                                                     
                                   
                         

                 




                                                                                                                                                                       

                          









                                                                                                                                                                       
                                                                                                                                                                            



































































                                                                                                                                                                              




                                                                                           
                                    





















































                                                                                                                                                                          



                                                                                                                                                                                 






































































































                                                                                                                                                                             



                                                                                                                                                                                 






































































































                                                                                                                                                                             



                                                                                                                                                                                 
























































































































                                                                                                                                                                             



                                                                                                                                                                                 

























































                                                                                                                                                                             



















































































































                                                                                                                                                                                 
 



















                                                                                                                                                                       





                                                                                                                                                                 

                          


























                                                                                                                                                                        






                                                                                                                                                                     




































































                                                                                                                                                                     
                 



































                                                                                                                                                                               
                 














                                                                                                                                                                       
               






                                                                                                                                                                     
                      
                                                                                                                                                                  
                         


                 





















































































































































































































































































































































                                                                                                                                                                       




                                                                                                                                                                       

       



                                                                           


            


































                                                                                


            













                                                                                                     
         




                                                                        
# 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
#
# The ELF format has some persnickety constraints on the starting addresses of
# segments, so input headers are treated as guidelines and adjusted in the
# output.

== 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(Heap-size)
    # . . push args
    68/push  Heap/imm32
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
    # . . call
    e8/call  new-segment/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # initialize-trace-stream(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
    # convert(Stdin, Stdout)
    # . . push args
    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    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . syscall(exit, 0)
    bb/copy-to-EBX  0/imm32
$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:  # infile : (address buffered-file), out : (address buffered-file) -> <void>
    # pseudocode
    #   var in : (address stream byte) = stream(4096)
    #   slurp(infile, in)
    #   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
    56/push-ESI
    # 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(1024 * 16)
    # . data
    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/ESP   Max-labels/disp32                 # subtract *Max-labels from ESP
    # . length
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Max-labels/disp32                 # push *Max-labels
    # . read
    68/push  0/imm32/read
    # . write
    68/push  0/imm32/write
    89/copy                         3/mod/direct    2/rm32/EDX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EDX
    # var in/ESI = stream(Input-size * 1)
    # . data
    2b/subtract                     0/mod/indirect  5/rm32/.disp32            .             .           4/r32/ESP   Input-size/disp32                 # subtract *Input-size from ESP
    # . length
    ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Input-size/disp32                 # push *Input-size
    # . read
    68/push  0/imm32/read
    # . write
    68/push  0/imm32/write
    89/copy                         3/mod/direct    6/rm32/ESI    .           .             .           4/r32/ESP   .               .                 # copy ESP to ESI
#?     # dump labels->write {{{
#?     # . write(2/stderr, "labels->write right after initialization: ")
#?     # . . push args
#?     68/push  "labels->write right after initialization: "/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->write)
#?     # . . push args
#? $watch-1:
#?     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
#?     # }}}
    # write(2/stderr, "slurp in\n") {{{
    # . . push args
    68/push  "slurp in\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
    # }}}
    # slurp(infile, in)
    # . . push args
    56/push-ESI
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  slurp/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # dump labels->write {{{
#?     # . write(2/stderr, "labels->write after slurp: ")
#?     # . . push args
#?     68/push  "labels->write after slurp: "/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->write)
#?     # . . 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
#?     # }}}
#?     # dump in {{{
#?     # . write(2/stderr, "in: ")
#?     # . . push args
#?     68/push  "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-stream(2/stderr, in)
#?     # . . push args
#?     56/push-ESI
#?     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(in)
#?     # . . push args
#?     56/push-ESI
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
    # write(2/stderr, "compute-offsets\n") {{{
    # . . push args
    68/push  "compute-offsets\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(in, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    56/push-ESI
    # . . call
    e8/call  compute-offsets/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # write(2/stderr, "compute-addresses\n") {{{
    # . . push args
    68/push  "compute-addresses\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-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
#?     56/push-ESI
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#? #?     # write(2/stderr, "emit-output\n") {{{
#? #?     # . . push args
#? #?     68/push  "emit-output\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 *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)
#?     56/push-ESI
#?     # . . call
#?     e8/call  emit-output/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
#?     # flush(out)
#?     # . . push args
#?     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
#?     # . . call
#?     e8/call  flush/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
$convert:end:
    # . reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x30a0/imm32      # add to ESP
    # . restore registers
    5e/pop-to-ESI
    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
#?     # dump *Trace-stream {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, *Trace-stream)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Trace-stream/disp32               # push *Trace-stream
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # }}}
    # . check-trace-contains("label 'x' is at address 0x00001079.", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/0"/imm32
    68/push  "label 'x' is at address 0x00001079."/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 0x00000074.", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/1"/imm32
    68/push  "segment 'code' starts at address 0x00000074."/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-convert-computes-addresses/2"/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' starts at address 0x00001079.", msg)
    # . . push args
    68/push  "F - test-convert-computes-addresses/3"/imm32
    68/push  "segment 'data' starts at address 0x00001079."/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
  0/imm32/end
compute-offsets:segment-tmp:  # slice
  0/imm32/start
  0/imm32/end

== code

compute-offsets:  # in : (address stream), 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(in, line)
    #     if (line->write == 0) break               # end of file
    #     while true                                # word loop
    #       word-slice = next-word(line)
    #       if slice-empty?(word-slice)             # end of line
    #         break
    #       else if slice-starts-with?(word-slice, "#")  # comment
    #         break                                 # end of line
    #       else if slice-equal?(word-slice, "==")
    #         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(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/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
#?     # dump line {{{
#?     # . write(2/stderr, "LL: ")
#?     # . . push args
#?     68/push  "LL: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # write-stream(2/stderr, line)
#?     # . . push args
#?     51/push-ECX
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . rewind-stream(line)
#?     # . . push args
#?     51/push-ECX
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
$compute-offsets:word-loop:
    # 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, "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
#?     # . 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:case-empty/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:case-empty:
    # 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
$compute-offsets:case-comment:
    # 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) break
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $compute-offsets:line-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  # skip comment
    #   == 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
    # 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  # skip comment\n")
    # . . push args
    68/push  "ab x/imm32  # skip comment\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "== data 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-stream, segments, labels)
    # . . push args
    52/push-EDX
    51/push-ECX
    68/push  _test-input-stream/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)
    # . print-int32-buffered(Stderr, srow->address)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
    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
    # . write-buffered(Stderr, "\n")
    # . . push args
    68/push  Newline/imm32
    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
    # 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

test-compute-addresses-large-segments:
    # input:
    #   segments:
    #     - 'a': {0x1000, 0, 0x5604}
    #     - 'b': {0x2018, 0x5604, 1}
    #   labels:
    #     - 'l1': {'a', 3, 0}
    #
    # trace contains in any order (comments in parens):
    #   segment 'a' starts at address 0x00001074.  (0x34 + 0x20 for each segment)
    #   segment 'b' starts at address 0x00002678.  (0x018 discarded; last 3 nibbles from 0x1074 + 0x5604)
    #   label 'l1' is at address 0x00001077.       (0x1074 + segment-offset 3)
    #
    # . 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, 0x5604)
    68/push  0x5604/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, 0x5604, 1)
    68/push  1/imm32/segment-size
    68/push  0x5604/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(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
    # 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
    # . check-trace-contains("segment 'a' starts at address 0x00001074.", msg)
    # . . push args
    68/push  "F - test-compute-addresses-large-segments/0"/imm32
    68/push  "segment 'a' starts at address 0x00001074."/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 0x00002678.", msg)
    # . . push args
    68/push  "F - test-compute-addresses-large-segments/1"/imm32
    68/push  "segment 'b' starts at address 0x00002678."/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 0x00001077.", msg)
    # . . push args
    68/push  "F - test-compute-addresses-large-segments/3"/imm32
    68/push  "label 'l1' is at address 0x00001077."/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

emit-output:  # in : (address stream), 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
#?     # write(2/stderr, "emit-headers\n") {{{
#?     # . . push args
#?     68/push  "emit-headers\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-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
#?     # write(2/stderr, "emit-segments\n") {{{
#?     # . . push args
#?     68/push  "emit-segments\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(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 stream), 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(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 !string-equal?(info->segment-name, "code")
    #         if has-metadata?(word-slice, "disp8")
    #           abort
    #         if has-metadata?(word-slice, "imm8")
    #           abort
    #         emit(out, info->address, 4)  # global variables always translate to absolute addresses
    #       # code segment cases
    #       else 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(in, line)
    # . . push args
    51/push-ECX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  read-line/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # dump line {{{
#?     # . write(2/stderr, "LL: ")
#?     # . . push args
#?     68/push  "LL: "/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # write-stream(2/stderr, line)
#?     # . . push args
#?     51/push-ECX
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . rewind-stream(line)
#?     # . . push args
#?     51/push-ECX
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
$emit-segments:check-for-end-of-input:
    # if (line->write == 0) break
    81          7/subop/compare     0/mod/indirect  1/rm32/ECX    .           .             .           .           .               0/imm32           # compare *ECX
    0f 84/jump-if-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
    01/add                          3/mod/direct    3/rm32/EBX    .           .             .           0/r32/EAX   .               .                 # add 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:check-for-end-of-line:
    # if (slice-empty?(word-slice)) break
    # . EAX = slice-empty?(word-slice)
    # . . push args
    52/push-EDX
    # . . call
    e8/call  slice-empty?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
    # . if (EAX != 0) break
    3d/compare-EAX-and  0/imm32
    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-global-variable:
#?     # dump info->segment-name {{{
#?     # . write(2/stderr, "aa: label segment: ")
#?     # . . push args
#?     68/push  "aa: label segment: "/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, info->segment-name)
#?     # . . push args
#?     ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
#?     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
#?     # }}}
    # if string-equal?(info->segment-name, "code") goto code label checks
    # . EAX = string-equal?(info->segment-name, "code")
    # . . push args
    68/push  "code"/imm32
    ff          6/subop/push        0/mod/indirect  6/rm32/ESI    .           .             .           .           .               .                 # push *ESI
    # . . 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 code label checks
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $emit-segments:check-code-label-for-imm8/disp32
$emit-segments:check-global-variable-for-disp8:
    # if has-metadata?(word-slice, "disp8") abort
    # . EAX = has-metadata?(word-slice, "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) abort
    3d/compare-EAX-and  0/imm32
    0f 85/jump-if-not-equal  $emit-segments:global-variable-abort/disp32
$emit-segments:check-global-variable-for-imm8:
    # if has-metadata?(word-slice, "imm8") abort
    # . EAX = has-metadata?(word-slice, "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:global-variable-abort/disp32
$emit-segments:emit-global-variable:
    # 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-code-label-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-code-label-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-code-label-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-segments:emit-code-label-imm32:
    # 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-code-label-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-code-label-for-disp32/disp8
$emit-segments:emit-code-label-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-code-label-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
    0f 84/jump-if-equal  $emit-segments:abort/disp32
$emit-segments:emit-code-label-disp32:
    # 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:global-variable-abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "emit-segments: must refer to global variables with /disp32 or /imm32"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    b8/copy-to-EAX  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

$emit-segments:imm8-abort:
    # . _write(2/stderr, error)
    # . . push args
    68/push  "emit-segments: cannot refer to code labels with /imm8"/imm32
    68/push  2/imm32/stderr
    # . . call
    e8/call  _write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . syscall(exit, 1)
    bb/copy-to-EBX  1/imm32
    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-global-variable:
    # global variables always convert to absolute addresses, regardless of metadata
    #
    # input:
    #   in:
    #     == code 0x1000
    #     ab cd ef gh
    #     ij x/disp32
    #     == data 0x2000
    #     00
    #     x:
    #       34
    #   segments:
    #     - 'code': {0x1074, 0, 9}
    #     - 'data': {0x2079, 5, 2}
    #   labels:
    #     - '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-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/disp32\n")
    # . . push args
    68/push  "ij x/disp32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "== data 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, 9)
    68/push  9/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, 2)
    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, "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-stream, _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-stream/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
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-segments-global-variable/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-global-variable/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-global-variable/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-global-variable/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

test-emit-segments-code-label:
    # labels usually convert to displacements
    #
    # input:
    #   in:
    #     == code 0x1000
    #     ab cd
    #     l1:
    #       ef gh
    #       ij l1/disp32
    #   segments:
    #     - 'code': {0x1054, 0, 9}
    #   labels:
    #     - 'l1': {'code', 2, 0x1056}
    #
    # output:
    #   ab cd
    #   ef gh
    #   ij f9 ff ff ff  # -7
    #
    # . 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
    # . 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\n")
    # . . push args
    68/push  "ab cd\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "l1:\n")
    # . . push args
    68/push  "l1:\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "  ef gh\n")
    # . . push args
    68/push  "  ef gh\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "  ij l1/disp32\n")
    # . . push args
    68/push  "  ij l1/disp32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . stream-add4(segments, "code", 0x1054, 0, 9)
    68/push  9/imm32/segment-size
    68/push  0/imm32/file-offset
    68/push  0x1054/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(labels, "l1", "code", 2, 0x1056)
    68/push  0x1056/imm32/label-address
    68/push  2/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
    # component under test
    # . emit-segments(_test-input-stream, _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-stream/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
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label/0"/imm32
    68/push  "ab cd "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label/1"/imm32
    68/push  "ef gh "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label/2"/imm32
    68/push  "ij f9 ff ff ff "/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

test-emit-segments-code-label-absolute:
    # labels can also convert to absolute addresses
    #
    # input:
    #   in:
    #     == code 0x1000
    #     ab cd
    #     l1:
    #       ef gh
    #       ij l1/imm32
    #   segments:
    #     - 'code': {0x1054, 0, 9}
    #   labels:
    #     - 'l1': {'code', 2, 0x1056}
    #
    # output:
    #   ab cd
    #   ef gh
    #   ij 56 10 00 00
    #
    # . 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
    # . 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\n")
    # . . push args
    68/push  "ab cd\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "l1:\n")
    # . . push args
    68/push  "l1:\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "  ef gh\n")
    # . . push args
    68/push  "  ef gh\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . write(_test-input-stream, "  ij l1/imm32\n")
    # . . push args
    68/push  "  ij l1/imm32\n"/imm32
    68/push  _test-input-stream/imm32
    # . . call
    e8/call  write/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # . stream-add4(segments, "code", 0x1054, 0, 9)
    68/push  9/imm32/segment-size
    68/push  0/imm32/file-offset
    68/push  0x1054/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(labels, "l1", "code", 2, 0x1056)
    68/push  0x1056/imm32/label-address
    68/push  2/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
    # component under test
    # . emit-segments(_test-input-stream, _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-stream/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
    # . flush(_test-output-buffered-file)
    # . . push args
    68/push  _test-output-buffered-file/imm32
    # . . call
    e8/call  flush/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # dump output {{{
#?     # . write(2/stderr, "result: ^")
#?     # . . push args
#?     68/push  "result: ^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write-stream(2/stderr, _test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
#?     # . rewind-stream(_test-output-stream)
#?     # . . push args
#?     68/push  _test-output-stream/imm32
#?     # . . call
#?     e8/call  rewind-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
#?     # }}}
    # . check-next-stream-line-equal(_test-output-stream, "ab cd ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label-absolute/0"/imm32
    68/push  "ab cd "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . check-next-stream-line-equal(_test-output-stream, "ef gh ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label-absolute/1"/imm32
    68/push  "ef gh "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . check-next-stream-line-equal(_test-output-stream, "ij f9 ff ff ff ", msg)
    # . . push args
    68/push  "F - test-emit-segments-code-label-absolute/2"/imm32
    68/push  "ij 56 10 00 00 "/imm32
    68/push  _test-output-stream/imm32
    # . . call
    e8/call  check-next-stream-line-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
    # . 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(out, curr-segment)
    #     curr-segment += 16                        # 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
#?     # write(2/stderr, "emit-elf-header\n") {{{
#?     # . . push args
#?     68/push  "emit-elf-header\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-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
#?     # }}}
#?     # write(2/stderr, "emit-segment-header\n") {{{
#?     # . . push args
#?     68/push  "emit-segment-header\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(out, curr-segment)
    # . . push args
    50/push-EAX
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  emit-elf-program-header-entry/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
    # curr-segment += 16                        # size of a row
    81          0/subop/add         3/mod/direct    0/rm32/EAX    .           .             .           .           .               0x10/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 / 16         # size of a row
    #   emit-hex-array(out, Elf_header)
    #   write-buffered(out, "\n")
    #
    # . 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 / 0x10
    # . 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 / 0x10  (destroying EDX)
    b9/copy-to-ECX  0x10/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
    # write-buffered(out, "\n")
    # . . push args
    68/push  "\n"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  write-buffered/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:  # out : (address buffered-file), 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)
    #   write-buffered(out, "\n")
    #
    # . 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   0xc/disp8       .                 # copy *(EBP+12) 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 = r-x
    c7          0/subop/copy        0/mod/indirect  5/rm32/.disp32            .             .           .           Elf_p_flags/disp32  5/imm32       # copy to *Elf_p_flags
    eb/jump  $emit-elf-program-header-entry:really-emit/disp8
$emit-elf-program-header-entry:data:
    # otherwise *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:really-emit:
    # 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
    # write-buffered(out, "\n")
    # . . push args
    68/push  "\n"/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
    # . . call
    e8/call  write-buffered/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

# 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