about summary refs log blame commit diff stats
path: root/apps/mu.subx
blob: 7c056d46cb098fadc65bcd04706ffad735d35bf4 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                     




                                                  


                                                                              

                                                                         
                                                                         




                                                        

                         








                                                                             


                                                                             
                           
                                                        



                                                                             


                                                                           



                                                                            

                                                                            
 
                                                                           

















                                                                            

                                    
                                                                      
                                                                                        













                                                                              
 



                                                                              


                                           
                               
                                                                             

                           
                                  


                           
                        

                                         









                                             
                                         


                                 


                                    


                                             

                                         


                                              


                                         


                           

                                         
 
                                    
                                                                         

                                                                            





                                                                                   
                                                                     







                                                                                       
               

                            
                               

                                                 
                                       
                                   

                                       
                                                                   

                                                                            
 









                                                                                
                               


                                         
                                    
 





























                                                                                  
                               
                                             
                                                                   
                                    


                                   
                                   




                          
 
                        











                                                                    


                              

       
                                       

         

              
                   
         
                                     
         
                                      
           
                                
            
                                   
            
              
               
 

               
                                      
         
                                       
         
                                           
           


                                         
            

                                          
                                       
            

                                    
               
               
 


         
                                            

         
                                       
         
                                  
         
                                   

           
                                   
         
                                     

         
                                      
         
                                        
         
                                          
           



                                           


                 
                                                  

         
          
            
 

         

         
          

                 
           
             
            

            
 





                    






           




                        
                                 




                                            
                                    






































                                                                          
                                                     
                                      
                                                      


























                                                                              
                                                     
                                      
                                                      





















































                                                                                                                     
                                                     
                                      
                                                      




































                                                                                                                                
                               

                           





                              








                                     
                                                     
                                      
                                                      
     
                                                   










                                                                     


































                                                                                                                     
                                                     
                                      
                                                      
























                                                                                                                                                




                        






















































                                                                                                                                                 
                                     
                               
                                                  
























                                                      
                                                                             





























                                                                                                                                                    





















































































































                                                                                                                                                   























































































                                                                                                                                                            



                                                       

                                         
                                                                 

                                        



                                                                 





                                                                 




                                                                     



                                               







                        
               
               
                                          




                                        
                                      


                         
                                                                   
                                




                                                  







                                        
                                



                              
















                                                            
                                  
                                        
                                                                                                 
                                                
                                      
                          

                                                      






                                                           
                                     


                      
                                   

                         
                 







                        
                 

                                                             
                                      







                                












                                                                                










                                   
                                                                                                                                
                 

                                   

                                         
                                 






                                      
                                                                      

                                       
                                   
                                              
                       




                                          
                                                                      
                                     
                                                

             





                        

               


                              
                                      


                         

                             
                        
                             



                                      
                                                                      


                                       
                                                                      

                                      
                              
                                                                      


                                         

                                                  
                          
     
                                             

                                        

                                        
                                                                      


                                         
                                          


                                        
                                                                        
                                                                              

                                                   
                                   

                                                                        
                                     
                                                    
                                 

                              
       
                                                                
                                                 
                             
       

                         
                           
     
                                   
                               


                                        
                                          


                                         
                                                                        


                                        
                                                                        
       





                                                                    
                                                    

                         
                                  
                                   




                                 

                 






                        
                                    
                                                        

                                                                                                  









                                 



























                                                               







                                                
                                 


                                          




                                                 
             
                                                              
                  
                                                                                              
                                              
                                               
                                                
                                      
                                                                                          













                                                                                              
                                    


                                          




                                                 
             
                                                              
                  
                                                     
                                              
                                               
                                                
                                                
                                      
                                                                                                    
                                                                                                        

                                         
                                                

                                       
                                                                                                    
                                                                                                        

                                         
                                                

                                             
                                                                                                    
                                                                                                        












                                                                                         
                                    


                                          




                                                 
             
                                                              
                  
                                                     
                                              
                                               
                                                
                                      
                                                                                                    




                                                                                              
                                                                                                    




                                                                                              
                                                                                                    
                                                                                              
                                               
                                                  
                                                 
                                      
                                                                                                     
                                                                                               
                                                                                                                 



                                         
                                                                                                     
                                                                                               
                                                                                                                 




                        


                                 

                                             
                                                                                                            
                 

                                                       
                                                             
                                         

                                    

                                    

                                    
                                                     



                                    


                                          
                                     

                                     
                                       














                                          
                                                              

                                       


                            
                             


                         
                               













                                                                                  









                                              
                                


                                         
                                   
                                                                          



















                                              

                                                            





                                                                             



                                                 
                                   











                                                             

                                  

                                
                                      
                                     











                                                               





                                        

                               
























                                                                    
                                                                 







































                                                            
                                                                               



                        



                                             
















                                          
                                              




                                       

                                    
                                                 
                                    
                                                                















                                                           
                                              




                                         

                                      
                                                 
                                    
                                                                             
                                               
                                                                                   






                                                                        



                                        

                             


                                          
                                              








                                                 
                                                                               
                                               
                                                                              






                                                                          




























                                                                                                  










                                                               
                                   






































                                                  
                                              



















                                                             
                                              



















                                                                 
                                              



















                                                        
                                              



















                                                        
                                              



















                                                        
                                              











                                                        
                                     







                                          
                                              










                                                        









                                          
                                              




















                                                        
                                              




















                                                               
                                              



















                                                                       
                                              




















                                                                        
                                              










                                                             
                                                                                                                        





                        
               

                            

                              
                                                         
                                                    
                     
                                                

                               
                 







                                                                                   
                                                                                                                                           
                 

                                        











                                                                 
                                      
                                                



                                                   
                                                                              

                                                 
                                                  

                                            
                                                


                                         



                        


               
               
                                          




                                        
                                      


                         
                  
                                        
                              
                        
                 
                          


                                        



                                         




                                      



                                           
                                            
                         
                                
                                         






                                                        

                                        
       



                                    
                                   
                                    
                                                                  

                                                
       


                                              
                                











                                                                         
                                                                                  









                                                
                                                    




                                                
                                                           
                                      
                         
                     

                        
                    


                                   
                 


                 




                        
                      











                                                                             
                                                     





                        
                             






































                                                                             
                                                                                                                                                                                    
                 

                                        












                                                                 
                                            



                                                   
                                                                    

                                                 
                                                  

                                            
                                                














                                         
                                                                                                                    













                         
                                                                                                                                         
                 
                              





                                          
                                                                   



                                                        
                                         
                                                    




                        

               
                                


                         
                                

                                        
                        

                                                  




















                                                       
                                                                    





















                                                                  
                                                       



                                                             
                   

                        
                      
                               
                         































                                                  
                                                                          




                        
                                      






































                                                                                            
















































                                                                                                                
                                                                          
                                                                                                     


























                                                                  
                                                                            
                                                                                                                 






                                                                    
                
     



                        


               





                                         
                                                          





                                                                      
                                
                               

                                                

                                        

                                               
                                
                          
                                      

                                      

                         
                       
                         


                 




                        
                          









                                                               
                                                                                                     
































































                                                                                                                                       







                                              













                                                   
             
                                           

                                                                                               
                                              
                                            
                                                

                                                                               




                        
                                                                                                                                                                                                                                                        


























                                                  
                                                                                                                                                                                 
























                                                  






































                                                                                                              
                                                                                                                  






                                            
                              










                                                      
                                                                                                                                                                          






                                            
                              














                                                             
                                                                                                                           






                                            
                              












                                                             
                                                                                                                                                              






                                            
                              














                                                    
                                                                                                                                                    






                                            
                              












                                                      
                                                                                                                             


















                                            
                                                                                                                                


































                                                      
                                                                                                



                        




                                                                          

                         
                 




                        



                                                       




                        
                    




                        











                                                          



                                                       









                                           
                                                

                            
                               

                                      
                                    
                         
                                                  











                         
                                                                           














                               
                                                       










                             
                                                                         


                        
                                                            

                                                
     
     
                             
                                     
                                      
                                     
       
                      

                                       
                                                               



                                             

                                     





                        
                                                                                                                                                


                        


                      
                                          
     
                               
                                                                                      
                                
                                  
                                                                       



                                                        
                          
                                                                                    
                                
                                  
                                                                  
                                             
     

                                             
                         



















                                                  
                      
                                                                                  

           
                       
                   
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                

                                 
                                              






                                       
                                
                                 
                   







                                                     
                                
                                 



                                                      
                                                 



                                          
                                









                                                     
                                









                                                      
                                


                                    
                                                   






                                       
                                

                                        
                                                              


                                                 
                               


                                
                                

                                        
                                                     


                                                            
                               


                               
                                

                                        
                                                       


                                                 
                            


                               
                                









                                                               
                                


                                                            
                       





                                    
                                


                                           
                                                               






                                          
                                

                                               
                                                                          


                                                 
                                      


                                
                                

                                               
                                                                   


                                                            
                                      


                               
                                









                                                                 
                                









                                                                         
                                









                                                                        
                                










                                                     
                                

                                          
                                                                


                                                 
                                 


                                
                                

                                          
                                                         


                                                            
                                 


                               
                                









                                                       
                                









                                                               
                                









                                                              
                                










                                                   
                                

                                         
                                                              


                                                 
                                


                                
                                

                                         
                                                       


                                                            
                                


                               
                                









                                                     
                                









                                                             
                                









                                                            
                                










                                                     
                                

                                          
                                                                


                                                 
                                 


                                
                                

                                          
                                                         


                                                            
                                 


                               
                                









                                                       
                                









                                                               
                                









                                                              
                                

























































































































                                                                 












                             

                                            
                                              




                             
                                






                                  
                           

                      

































































                            










                            
       
                                                                                                               





                        
                     
                               



                                                                                      

                                                                                    

                                                                                        








                         
                                                                                             









                                                                                     
                                                     







                         
                                                                                                                 









                                          
     


                                      
                                              

                                                           
     




                                                
                                              








                                                           
                                                 
























                                                            
                                                                                            





                        
                               







                                                                                         








                         
                                                                                              





                        




                                                                                     


                                              
                     







                         
                                                                                                       





                        

                                 
                          
                               
                                                            
                      
                                                     
                              
                                            







                                            
                        
     

                                 








                         
                                                                                     




                        
                   
                              

















                                                     
                                                                                    






                              



                                                            
                                






                                                               
                             





                                                                    
                           






                         
                                                                                                                      




                        
                                                  
                            



                                     
                                        
       
                                                             





                                                 
                                                  











                            
                                                                                                                         




                        
                                                    

                            
                              

                                     






                                                                  





                                                              
                                                  
       
                                        
                         
                                                   
                         










                             
                                                                                                             




                        
                                              

                              
                                                                          
                               

                         




                        
                                                                                                                




                                                                            




                        




                
                            




                                                            
                                                                             





                                                    
                                                                    
                                            

                                                
                                                             


                                        
                                               


                                          

                                                                  

                      
                                    





                                                      

                                               






                                                        
                                    


                                                      
                                              
                         

                                              

                                          
                                                                      
                                               



                                                 
                                         





















                                                        
                                                        





                                                      
                                              
                         
                                              
                        


                                        

                                



                 





                        
                                                                                                  







                            
                    
                              
                                                   



                                                              
                                                                    









                                                                                                              
                                                 











                                                                          
                                







                         
                                   
                                                     



                                     
                                                     

                   
                        
     
                                         
                         
                       

                                     
                                   





                                      
                                                      
                                 

                                 

                               

                        
                                      

                        
                        
                                    

                           
                        
                                       
                   
                        
                                          
                        
                                        



                                                  
                           
                                                                                                      


                                  
                                                                







                                           
                                                                                                                                        

















                                                                    
                    








                                      
                                                      
                                        





                                   
                                      

                        
                        
                                    
                        
                       

                                       
                   
                        
                                                    





                                   
                                      


                          
                                          
                        
                                        








                                                  
                                                                







                                           
                                                                                                                                    






























                                                                    
                                                      
                                        





                                   
                                           


                        
                                    



                                       
                   
                        
                                                    





                                   
                                                               


                          
                                          
                        
                                        







                                                  
                                          
                    
                                        








                                                                                                                   
                                                                







                                           
                                                                                                                                  






























                                                                    
                                                      
                                        





                                   
                                     


                        
                                    



                                       
                   
                        
                                                    





                                   
                                      


                          
                                    
                        
                                        







                                                  
                                          
                    
                                        








                                                                                                                   
                                                                







                                           
                                                                                                                                    




                        
                        


                                                   
                        














                                                     
                                                      
                                        





                                   
                                           


                        
                                    



                                       
                   

                        
                                                                      







                                           
                                                                                                       




                        
                   


















                                                                    
                                                      
                                        





                                   
                                     


                        
                                    



                                       
                   

                        
                                                                      







                                           








                                                                                                           
                         





                                      
                                                      
                                         





                                   
                                         





                                   
                                      


                        
                                       


                        
                                    



                                 
                   

                        
                                                                      







                                           
                                                                                                                




                        


                            
                             





                                      
                                                      
                                  





                                
                                         





                                   
                                      


                        
                                       


                        
                                    






                                    
                                                                      







                                           
                                                                                                                             







                            
                             





                                      
                                                      
                                         





                                   
                                  





                                
                                      


                        
                                       


                        
                                    






                                 
                                                                      







                                           
                                                                                                                          




                        
                        

                            
                                





                                      
                                                      
                                         





                                   
                                          





                                   
                                      


                        
                                       


                        
                                    



                                 
                   

                        
                                                                      







                                           
























































                                                                                                                     




                        









                                      
                                                      
                                  





                                
                                          





                                   
                                      


                        
                                         


                        
                                    






                                    
                                                                      













                                                                                                                                  

                                                 
             

                     

                                                                              

                                                     
                   
                 
                        
     











                                        
                                                      
                                 

                                 


                               
                        
                                       


                        
                                    

                           
                      
                               
                   
                        
                                        
                        



                                                                                                      


                          
                                                                
                                      





                                           
                  
                                                                                                                            




                        










                                                        
                                                      
                                         





                                   
                                       


                        
                                    



                               
                   
                        
                                        







                                                                                                      
                                                                













                                                                                                                              







                                                      
                        













                                                      
                        



                        
# The Mu computer's level-2 language, also called Mu.
# http://akkartik.name/post/mu-2019-2
#
# To run:
#   $ ./ntranslate init.linux 0*.subx apps/mu.subx

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

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

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

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

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

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

# The rest is straightforward.

== data

Program:  # (address (handle function))
  0/imm32

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

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

Stmt-tag:
  0/imm32

Block-statements:  # (handle list statement)
  4/imm32

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

Vardef-name:  # (handle array byte)
  4/imm32
Vardef-type:  # (handle tree type-id)
  8/imm32

Regvardef-name:  # (handle array byte)
  4/imm32
Regvardef-type:  # (handle tree type-id)
  8/imm32
Regvardef-register:  # (handle array byte)
  0xc/imm32
Regvardef-operation:  # (handle array byte)
  0x10/imm32
Regvardef-inputs:  # (handle list var)
  0x14/imm32

Named-block-name:
  4/imm32
Named-block-statements:  # (handle list statement)
  8/imm32

Stmt-size:
  0x18/imm32

Var-name:
  0/imm32
Var-type:
  4/imm32
Var-block:
  8/imm32
Var-stack-offset:
  0xc/imm32
Var-register:
  0x10/imm32
Var-size:
  0x14/imm32

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

List-value:
  0/imm32
List-next:
  4/imm32
List-size:
  8/imm32

== code

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

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

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

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

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

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

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

test-convert-function-distinguishes-args:
    # function with two args refers to second one in body
    #   fn foo a: int, b: int {
    #     increment b
    #   }
    # =>
    #   foo:
    #     # . prologue
    #     55/push-ebp
    #     89/<- %ebp 4/r32/esp
    #     {
    #       ff 0/subop/increment *(ebp+0xc)
    #     }
    #     # . epilogue
    #     89/<- %esp 5/r32/ebp
    #     5d/pop-to-ebp
    #     c3/return
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-input-stream)
    (clear-stream $_test-input-buffered-file->buffer)
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    #
    (write _test-input-stream "fn foo a: int, b: int {\n")
    (write _test-input-stream "  increment b\n")
    (write _test-input-stream "}\n")
    # convert
    (convert-mu _test-input-buffered-file _test-output-buffered-file)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "foo:"                  "F - test-convert-function-distinguishes-args/0")
    (check-next-stream-line-equal _test-output-stream "# . prologue"          "F - test-convert-function-distinguishes-args/1")
    (check-next-stream-line-equal _test-output-stream "55/push-ebp"           "F - test-convert-function-distinguishes-args/2")
    (check-next-stream-line-equal _test-output-stream "89/<- %ebp 4/r32/esp"  "F - test-convert-function-distinguishes-args/3")
    (check-next-stream-line-equal _test-output-stream "{"                     "F - test-convert-function-distinguishes-args/4")
    (check-next-stream-line-equal _test-output-stream "ff 0/subop/increment *(ebp+0x0000000c)"  "F - test-convert-function-distinguishes-args/5")
    (check-next-stream-line-equal _test-output-stream "}"                     "F - test-convert-function-distinguishes-args/6")
    (check-next-stream-line-equal _test-output-stream "# . epilogue"          "F - test-convert-function-distinguishes-args/7")
    (check-next-stream-line-equal _test-output-stream "89/<- %esp 5/r32/ebp"  "F - test-convert-function-distinguishes-args/8")
    (check-next-stream-line-equal _test-output-stream "5d/pop-to-ebp"         "F - test-convert-function-distinguishes-args/9")
    (check-next-stream-line-equal _test-output-stream "c3/return"             "F - test-convert-function-distinguishes-args/10")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# format for variables with types
#   x : int
#   x: int
#   x: int,
# ignores at most one trailing colon or comma
parse-var-with-type:  # name: (address slice), first-line: (address stream byte) -> result/eax: (handle var)
    # pseudocode:
    #   var v : (handle var) = allocate(Heap, Var-size)
    #   var s : (ref slice)
    #   next-token-from-slice(name->start, name->end, '/', s)
    #   var end : (address byte) = s->end
    #   if (slice-ends-with(s, ":"))
    #     decrement s->end
    #   if (slice-ends-with(s, ","))
    #     decrement s->end
    #   v->name = slice-to-string(s)
    #   ## register
    #   next-token-from-slice(end, name->end, '/', s)
    #   if (slice-ends-with(s, ":"))
    #     decrement s->end
    #   if (slice-ends-with(s, ","))
    #     decrement s->end
    #   if (!slice-empty?(s))
    #     v->register = slice-to-string(s)
    #   ## type
    #   s = next-mu-token(first-line)
    #   assert(s not in '{' '}' '->')
    #   if (slice-empty?(s)) {
    #     s = next-mu-token(first-line)
    #     assert(type not in '{' '}' '->')
    #   }
    #   type = type-for(s)
    #   v->type = type
    #   return v
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # var result/edi : (handle var) = allocate(Heap, Var-size)
    (allocate Heap *Var-size)  # => eax
    (zero-out %eax *Var-size)
    89/<- %edi 0/r32/eax
    # esi = name
    8b/-> *(ebp+8) 6/r32/esi
    # var s/ecx : (ref slice)
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
$parse-var-with-type:save-name:
    # save v->name
    (next-token-from-slice *esi *(esi+4) 0x2f %ecx)  # Slice-start, Slice-end, '/'
    # . end/edx = s->end
    8b/-> *(ecx+4) 2/r32/edx
    # . if s ends with ':', decrement s->end
    {
      8b/-> *(ecx+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x3a/imm32/colon
      75/jump-if-not-equal break/disp8
      89/<- *(ecx+4) 0/r32/eax
    }
    # . if s ends with ',', decrement s->end
    {
      8b/-> *(ecx+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x2c/imm32/comma
      75/jump-if-not-equal break/disp8
      89/<- *(ecx+4) 0/r32/eax
    }
$parse-var-with-type:write-name:
    (slice-to-string Heap %ecx)  # => eax
    89/<- *edi 0/r32/eax  # Var-name
    # save v->register
$parse-var-with-type:save-register:
    (next-token-from-slice %edx *(esi+4) 0x2f %ecx)  # end, name->end, '/'
    # . if s ends with ':', decrement s->end
    {
      8b/-> *(ecx+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x3a/imm32/colon
      75/jump-if-not-equal break/disp8
      89/<- *(ecx+4) 0/r32/eax
    }
    # . if s ends with ',', decrement s->end
    {
      8b/-> *(ecx+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x2c/imm32/comma
      75/jump-if-not-equal break/disp8
      89/<- *(ecx+4) 0/r32/eax
    }
    # if (!slice-empty?(s)) v->register = slice-to-string(s)
    {
$parse-var-with-type:write-register:
      # HACK: s->end can be less than s->start with all the decrements above
      # That's probably a sign we have the wrong algorithm for this function.
      8b/-> *ecx 0/r32/eax
      39/compare 0/r32/eax *(ecx+4)
      76/jump-if-lesser-or-equal break/disp8
      (slice-to-string Heap %ecx)
      89/<- *(edi+0x10) 0/r32/eax  # Var-register
    }
    # save v->type
    (next-mu-token *(ebp+0xc) %ecx)
    # if (word-slice == '{') abort
    (slice-equal? %ecx "{")   # => eax
    3d/compare-eax-and 0/imm32
    0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
    # if (word-slice == '->') abort
    (slice-equal? %ecx "->")   # => eax
    3d/compare-eax-and 0/imm32
    0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
    # if (word-slice == '}') abort
    (slice-equal? %ecx "}")   # => eax
    3d/compare-eax-and 0/imm32
    0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
    # if (slice-empty?(type)) skip
    (slice-empty? %ecx)
    {
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-equal break/disp32
      (next-mu-token *(ebp+0xc) %ecx)
      # if (word-slice == '{') abort
      (slice-equal? %ecx "{")   # => eax
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
      # if (word-slice == '->') abort
      (slice-equal? %ecx "->")   # => eax
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
      # if (word-slice == '}') abort
      (slice-equal? %ecx "}")   # => eax
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-not-equal $parse-var-with-type:abort/disp32
    }
    (type-for %ecx)
    89/<- *(edi+4) 0/r32/eax  # Var-type
$parse-var-with-type:end:
    # return result
    89/<- %eax 7/r32/edi
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

next-mu-token:  # in: (address stream byte), out: (address slice)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    57/push-edi
    # edi = out
    8b/-> *(ebp+0xc) 7/r32/edi
    #
    (next-word *(ebp+8) %edi)  # TODO: support s-expressions
    # if out ends with ':', decrement out->end
    {
      8b/-> *(edi+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x3a/imm32/colon
      75/jump-if-not-equal break/disp8
      89/<- *(edi+4) 0/r32/eax
    }
    # if out ends with ',', decrement out->end
    {
      8b/-> *(edi+4) 0/r32/eax
      48/decrement-eax
      8a/copy-byte *eax 3/r32/BL
      81 4/subop/and %ebx 0xff/imm32
      81 7/subop/compare %ebx 0x2c/imm32/comma
      75/jump-if-not-equal break/disp8
      89/<- *(edi+4) 0/r32/eax
    }
$next-mu-token:end:
    b8/copy-to-eax 1/imm32/int
    # . restore registers
    5f/pop-to-edi
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

type-for:  # name: (address slice) -> result/eax: (handle s-expression type-id)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
#?     (write-buffered Stderr "type: ")
#?     (write-slice-buffered Stderr *(ebp+8))
#?     (write-buffered Stderr Newline)
#?     (flush Stderr)
$type-for:end:
    b8/copy-to-eax 1/imm32/int
    # . restore registers
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

populate-mu-function-body:  # in : (address buffered-file), out : (handle function), vars : (address stack (handle var))
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    56/push-esi
    57/push-edi
    # esi = in
    8b/-> *(ebp+8) 6/r32/esi
    # edi = out
    8b/-> *(ebp+0xc) 7/r32/edi
    # var eax : (handle block) = parse-mu-block(in, vars)
    (parse-mu-block %esi *(ebp+0x10) %edi)  # => eax
    # out->body = eax
    89/<- *(edi+0x10) 0/r32/eax  # Function-body
$populate-mu-function-body:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

parse-mu-named-block:  # name : (address slice), first-line : (address stream byte), in : (address buffered-file), vars : (address stack (handle var)) -> result/eax : (handle stmt)
    # pseudocode:
    #   var line : (ref stream byte 512)
    #   var word-slice : (ref slice)
    #   result/eax = allocate(Heap, Stmt-size)
    #   result->tag = 4/Named-block
    #   result->name = name
    #   assert(next-word(first-line) == "{")
    #   assert(no-tokens-in(first-line))
    #   while true                                  # line loop
    #     clear-stream(line)
    #     read-line-buffered(in, line)
    #     if (line->write == 0) break               # end of file
    #     word-slice = next-word(line)
    #     if slice-empty?(word-slice)               # end of line
    #       break
    #     else if slice-equal?(word-slice, "{")
    #       block = parse-mu-block(in, vars)
    #       append-to-block(result, block)
    #     else if slice-equal?(word-slice, "}")
    #       break
    #     else if slice-ends-with?(word-slice, ":")
    #       named-block = parse-mu-named-block(word-slice, in, vars)
    #       append-to-block(result, named-block)
    #     else if slice-equal?(word-slice, "var")
    #       var-def = parse-mu-var-def(line, vars)
    #       append-to-block(result, var-def)
    #     else
    #       stmt = parse-mu-stmt(line, vars, fn)
    #       append-to-block(result, stmt)
    #   return result
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
$parse-mu-named-block:end:
    # . reclaim locals
    # . restore registers
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-mu-var-def:  # line : (address stream byte), vars : (address stack (handle var)) -> result/eax : (handle stmt)
    # pseudocode:
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
$parse-mu-var-def:end:
    # . reclaim locals
    # . restore registers
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-mu-stmt:  # line : (address stream byte), vars : (address stack (handle var)), fn : (handle function) -> result/eax : (handle stmt)
    # pseudocode:
    #   var name : (ref slice)
    #   result = allocate(Heap, Stmt-size)
    #   if stmt-has-outputs?(line)
    #     while true
    #       name = next-word(line)
    #       if (name == '<-') break
    #       assert(is-identifier?(name))
    #       var v : (handle var) = lookup-or-define-var(name, vars)
    #       result->outputs = append(result->outputs, v)
    #   result->name = slice-to-string(next-word(line))
    #   while true
    #     name = next-word-or-string(line)
    #     v = lookup-var-or-literal(name)
    #     result->inouts = append(result->inouts, v)
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    57/push-edi
    # var name/ecx : (ref slice)
    68/push 0/imm32/end
    68/push 0/imm32/start
    89/<- %ecx 4/r32/esp
    # result/edi : (handle stmt)
    (allocate Heap *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    89/<- %edi 0/r32/eax
    # result->tag = 1/stmt
    c7 0/subop/copy *edi 1/imm32/stmt1  # Stmt-tag
    {
      (stmt-has-outputs? *(ebp+8))
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-equal break/disp32
      {
$parse-mu-stmt:read-outputs:
        # name = next-word(line)
        (next-word *(ebp+8) %ecx)
        # if slice-empty?(word-slice) break
        (slice-empty? %ecx)
        3d/compare-eax-and 0/imm32
        0f 85/jump-if-not-equal break/disp32
        # if (name == "<-") break
        (slice-equal? %ecx "<-")
        3d/compare-eax-and 0/imm32
        75/jump-if-not-equal break/disp8
        # assert(is-identifier?(name))
        (is-identifier? %ecx)
        3d/compare-eax-and 0/imm32
        0f 84/jump-if-equal $parse-mu-stmt:abort/disp32
        #
        (lookup-or-define-var %ecx *(ebp+0xc) *(ebp+0x10))  # => eax
        (append-list Heap %eax *(edi+0xc))  # Stmt1-outputs => eax
        89/<- *(edi+0xc) 0/r32/eax  # Stmt1-outputs
        e9/jump loop/disp32
      }
    }
$parse-mu-stmt:read-operation:
    (next-word *(ebp+8) %ecx)
    (slice-to-string Heap %ecx)
    89/<- *(edi+4) 0/r32/eax  # Stmt1-operation
    {
$parse-mu-stmt:read-inouts:
      # name = next-word-or-string(line)
      (next-word-or-string *(ebp+8) %ecx)
      # if slice-empty?(word-slice) break
      (slice-empty? %ecx)
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-not-equal break/disp32
      # if (name == "<-") abort
      (slice-equal? %ecx "<-")
      3d/compare-eax-and 0/imm32
      0f 85/jump-if-not-equal $parse-mu-stmt:abort2/disp32
      #
      (lookup-var-or-literal %ecx *(ebp+0xc))  # => eax
      (append-list Heap %eax *(edi+8))  # Stmt1-inouts => eax
      89/<- *(edi+8) 0/r32/eax  # Stmt1-inouts
      e9/jump loop/disp32
    }
$parse-mu-stmt:end:
    # return result
    89/<- %eax 7/r32/edi
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

new-stmt:  # ad: (address allocation-descriptor), operation: (address array byte), inouts: (handle list var), outputs: (handle list var) -> result/eax: (handle statement)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    c7 0/subop/copy *eax 1/imm32/tag/regular-stmt  # Stmt-tag
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Stmt1-operation
    8b/-> *(ebp+0x10) 1/r32/ecx
    89/<- *(eax+8) 1/r32/ecx  # Stmt1-inouts
    8b/-> *(ebp+0x14) 1/r32/ecx
    89/<- *(eax+0xc) 1/r32/ecx  # Stmt1-outputs
$new-stmt:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

new-regvardef:  # ad: (address allocation-descriptor), name: (address array byte), type: int, register: (address array byte) -> result/eax: (handle statement)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *Stmt-size)  # => eax
    (zero-out %eax *Stmt-size)
    c7 0/subop/copy *eax 3/imm32/tag/var-in-register
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # Regvardef-name
    8b/-> *(ebp+0x10) 1/r32/ecx
    89/<- *(eax+8) 1/r32/ecx  # Regvardef-type
    8b/-> *(ebp+0x14) 1/r32/ecx
    89/<- *(eax+0xc) 1/r32/ecx  # Regvardef-register
$new-regvardef:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

new-list:  # ad: (address allocation-descriptor), value: _type, next: (handle list _type) -> result/eax : (handle list _type)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    #
    (allocate *(ebp+8) *List-size)  # => eax
    8b/-> *(ebp+0xc) 1/r32/ecx
    89/<- *eax 1/r32/ecx  # List-value
    8b/-> *(ebp+0x10) 1/r32/ecx
    89/<- *(eax+4) 1/r32/ecx  # List-next
$new-list:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

size-of:  # n : (address var)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # hard-coded since we only support 'int' types for now
    b8/copy-to-eax 4/imm32
$size-of:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

emit-subx-function:  # out : (address buffered-file), f : (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    57/push-edi
    # edi = out
    8b/-> *(ebp+8) 7/r32/edi
    # ecx = f
    8b/-> *(ebp+0xc) 1/r32/ecx
    #
    (write-buffered %edi *ecx)
    (write-buffered %edi ":\n")
    (emit-subx-prologue %edi)
    (emit-subx-block %edi *(ecx+0x10))  # Function-body
    (emit-subx-epilogue %edi)
$emit-subx-function:end:
    # . restore registers
    5f/pop-to-edi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-block:  # out : (address buffered-file), block : (handle block)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # curr/esi : (handle list statement) = block->statements
    8b/-> *(ebp+0xc) 6/r32/esi
    8b/-> *(esi+4) 6/r32/esi  # Block-statements
    #
    {
$emit-subx-block:check-empty:
      81 7/subop/compare %esi 0/imm32
      0f 84/jump-if-equal break/disp32
      (write-buffered *(ebp+8) "{\n")
      {
$emit-subx-block:stmt:
        81 7/subop/compare %esi 0/imm32
        74/jump-if-equal break/disp8
        (emit-subx-statement *(ebp+8) *esi Primitives *Program)
        (write-buffered *(ebp+8) Newline)
        8b/-> *(esi+4) 6/r32/esi  # List-next
        eb/jump loop/disp8
      }
      (write-buffered *(ebp+8) "}\n")
    }
$emit-subx-block:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-statement:  # out : (address buffered-file), stmt : (handle statement), primitives : (handle primitive), functions : (handle function)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # if stmt matches a primitive, emit it
    {
$emit-subx-statement:primitive:
      (find-matching-primitive *(ebp+0x10) *(ebp+0xc))  # primitives, stmt => curr/eax
      3d/compare-eax-and 0/imm32
      74/jump-if-equal break/disp8
      (emit-subx-primitive *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
      e9/jump $emit-subx-statement:end/disp32
    }
    # else if stmt matches a function, emit a call to it
    {
$emit-subx-statement:call:
      (find-matching-function *(ebp+0x14) *(ebp+0xc))  # functions, stmt => curr/eax
      3d/compare-eax-and 0/imm32
      74/jump-if-equal break/disp8
      (emit-subx-call *(ebp+8) *(ebp+0xc) %eax)  # out, stmt, curr
      e9/jump $emit-subx-statement:end/disp32
    }
    # else abort
    e9/jump $emit-subx-statement:abort/disp32
$emit-subx-statement:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

$emit-subx-statement:abort:
    # error("couldn't translate '" stmt "'\n")
    (write-buffered Stderr "couldn't translate '")
#?     (emit-string Stderr *(ebp+0xc))  # TODO
    (write-buffered Stderr "'\n")
    (flush Stderr)
    # . syscall(exit, 1)
    bb/copy-to-ebx  1/imm32
    b8/copy-to-eax  1/imm32/exit
    cd/syscall  0x80/imm8
    # never gets here

# Primitives supported
# For each operation, put variants with hard-coded registers before flexible ones.
== data
Primitives:
# - increment/decrement
_Primitive-inc-eax:
    # var/eax <- increment => 40/increment-eax
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-eax/imm32/outputs
    "40/increment-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-ecx/imm32/next
_Primitive-inc-ecx:
    # var/ecx <- increment => 41/increment-ecx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ecx/imm32/outputs
    "41/increment-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-edx/imm32/next
_Primitive-inc-edx:
    # var/edx <- increment => 42/increment-edx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edx/imm32/outputs
    "42/increment-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-ebx/imm32/next
_Primitive-inc-ebx:
    # var/ebx <- increment => 43/increment-ebx
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ebx/imm32/outputs
    "43/increment-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-esi/imm32/next
_Primitive-inc-esi:
    # var/esi <- increment => 46/increment-esi
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-esi/imm32/outputs
    "46/increment-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-edi/imm32/next
_Primitive-inc-edi:
    # var/edi <- increment => 47/increment-edi
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edi/imm32/outputs
    "47/increment-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-eax/imm32/next
_Primitive-dec-eax:
    # var/eax <- decrement => 48/decrement-eax
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-eax/imm32/outputs
    "48/decrement-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-ecx/imm32/next
_Primitive-dec-ecx:
    # var/ecx <- decrement => 49/decrement-ecx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ecx/imm32/outputs
    "49/decrement-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-edx/imm32/next
_Primitive-dec-edx:
    # var/edx <- decrement => 4a/decrement-edx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edx/imm32/outputs
    "4a/decrement-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-ebx/imm32/next
_Primitive-dec-ebx:
    # var/ebx <- decrement => 4b/decrement-ebx
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-ebx/imm32/outputs
    "4b/decrement-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-esi/imm32/next
_Primitive-dec-esi:
    # var/esi <- decrement => 4e/decrement-esi
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-esi/imm32/outputs
    "4e/decrement-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-edi/imm32/next
_Primitive-dec-edi:
    # var/edi <- decrement => 4f/decrement-edi
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-edi/imm32/outputs
    "4f/decrement-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-mem/imm32/next
_Primitive-inc-mem:
    # increment var => ff 0/subop/increment *(ebp+__)
    "increment"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    0/imm32/no-outputs
    "ff 0/subop/increment"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-inc-reg/imm32/next
_Primitive-inc-reg:
    # var/reg <- increment => ff 0/subop/increment %__
    "increment"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-some-register/imm32/outputs
    "ff 0/subop/increment"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-mem/imm32/next
_Primitive-dec-mem:
    # decrement var => ff 1/subop/decrement *(ebp+__)
    "decrement"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    0/imm32/no-outputs
    "ff 1/subop/decrement"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-dec-reg/imm32/next
_Primitive-dec-reg:
    # var/reg <- decrement => ff 1/subop/decrement %__
    "decrement"/imm32/name
    0/imm32/no-inouts
    Single-int-var-in-some-register/imm32/outputs
    "ff 1/subop/decrement"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-add-to-eax/imm32/next
# - add
_Primitive-add-to-eax:
    # var/eax <- add lit => 05/add-to-eax lit/imm32
    "add"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "05/add-to-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-add-reg-to-reg/imm32/next
_Primitive-add-reg-to-reg:
    # var1/reg <- add var2/reg => 01/add-to var1/rm32 var2/r32
    "add"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "01/add-to"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-add-reg-to-mem/imm32/next
_Primitive-add-reg-to-mem:
    # add-to var1 var2/reg => 01/add-to var1 var2/r32
    "add-to"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "01/add-to"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-add-mem-to-reg/imm32/next
_Primitive-add-mem-to-reg:
    # var1/reg <- add var2 => 03/add var2/rm32 var1/r32
    "add"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "03/add"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-add-lit-to-reg/imm32/next
_Primitive-add-lit-to-reg:
    # var1/reg <- add lit => 81 0/subop/add var1/rm32 lit/imm32
    "add"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 0/subop/add"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-add-lit-to-mem/imm32/next
_Primitive-add-lit-to-mem:
    # add-to var1, lit => 81 0/subop/add var1/rm32 lit/imm32
    "add-to"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 0/subop/add"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-subtract-from-eax/imm32/next
# - subtract
_Primitive-subtract-from-eax:
    # var/eax <- subtract lit => 2d/subtract-from-eax lit/imm32
    "subtract"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "2d/subtract-from-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-subtract-reg-from-reg/imm32/next
_Primitive-subtract-reg-from-reg:
    # var1/reg <- subtract var2/reg => 29/subtract-from var1/rm32 var2/r32
    "subtract"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "29/subtract-from"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-subtract-reg-from-mem/imm32/next
_Primitive-subtract-reg-from-mem:
    # subtract-from var1 var2/reg => 29/subtract-from var1 var2/r32
    "subtract-from"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "29/subtract-from"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-subtract-mem-from-reg/imm32/next
_Primitive-subtract-mem-from-reg:
    # var1/reg <- subtract var2 => 2b/subtract var2/rm32 var1/r32
    "subtract"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "2b/subtract"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-subtract-lit-from-reg/imm32/next
_Primitive-subtract-lit-from-reg:
    # var1/reg <- subtract lit => 81 5/subop/subtract var1/rm32 lit/imm32
    "subtract"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 5/subop/subtract"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-subtract-lit-from-mem/imm32/next
_Primitive-subtract-lit-from-mem:
    # subtract-from var1, lit => 81 5/subop/subtract var1/rm32 lit/imm32
    "subtract-from"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 5/subop/subtract"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-and-with-eax/imm32/next
# - and
_Primitive-and-with-eax:
    # var/eax <- and lit => 25/and-with-eax lit/imm32
    "and"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "25/and-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-and-reg-with-reg/imm32/next
_Primitive-and-reg-with-reg:
    # var1/reg <- and var2/reg => 21/and-with var1/rm32 var2/r32
    "and"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "21/and-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-and-reg-with-mem/imm32/next
_Primitive-and-reg-with-mem:
    # and-with var1 var2/reg => 21/and-with var1 var2/r32
    "and-with"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "21/and-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-and-mem-with-reg/imm32/next
_Primitive-and-mem-with-reg:
    # var1/reg <- and var2 => 23/and var2/rm32 var1/r32
    "and"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "23/and"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-and-lit-with-reg/imm32/next
_Primitive-and-lit-with-reg:
    # var1/reg <- and lit => 81 4/subop/and var1/rm32 lit/imm32
    "and"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 4/subop/and"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-and-lit-with-mem/imm32/next
_Primitive-and-lit-with-mem:
    # and-with var1, lit => 81 4/subop/and var1/rm32 lit/imm32
    "and-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 4/subop/and"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-or-with-eax/imm32/next
# - or
_Primitive-or-with-eax:
    # var/eax <- or lit => 0d/or-with-eax lit/imm32
    "or"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "0d/or-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-or-reg-with-reg/imm32/next
_Primitive-or-reg-with-reg:
    # var1/reg <- or var2/reg => 09/or-with var1/rm32 var2/r32
    "or"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "09/or-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-or-reg-with-mem/imm32/next
_Primitive-or-reg-with-mem:
    # or-with var1 var2/reg => 09/or-with var1 var2/r32
    "or-with"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "09/or-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-or-mem-with-reg/imm32/next
_Primitive-or-mem-with-reg:
    # var1/reg <- or var2 => 0b/or var2/rm32 var1/r32
    "or"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "0b/or"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-or-lit-with-reg/imm32/next
_Primitive-or-lit-with-reg:
    # var1/reg <- or lit => 81 1/subop/or var1/rm32 lit/imm32
    "or"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 4/subop/or"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-or-lit-with-mem/imm32/next
_Primitive-or-lit-with-mem:
    # or-with var1, lit => 81 1/subop/or var1/rm32 lit/imm32
    "or-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 4/subop/or"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-xor-with-eax/imm32/next
# - xor
_Primitive-xor-with-eax:
    # var/eax <- xor lit => 35/xor-with-eax lit/imm32
    "xor"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "35/xor-with-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-xor-reg-with-reg/imm32/next
_Primitive-xor-reg-with-reg:
    # var1/reg <- xor var2/reg => 31/xor-with var1/rm32 var2/r32
    "xor"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "31/xor-with"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-xor-reg-with-mem/imm32/next
_Primitive-xor-reg-with-mem:
    # xor-with var1 var2/reg => 31/xor-with var1 var2/r32
    "xor-with"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "31/xor-with"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-xor-mem-with-reg/imm32/next
_Primitive-xor-mem-with-reg:
    # var1/reg <- xor var2 => 33/xor var2/rm32 var1/r32
    "xor"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "33/xor"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    0/imm32/output-is-write-only
    _Primitive-xor-lit-with-reg/imm32/next
_Primitive-xor-lit-with-reg:
    # var1/reg <- xor lit => 81 6/subop/xor var1/rm32 lit/imm32
    "xor"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "81 4/subop/xor"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-xor-lit-with-mem/imm32/next
_Primitive-xor-lit-with-mem:
    # xor-with var1, lit => 81 6/subop/xor var1/rm32 lit/imm32
    "xor-with"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "81 4/subop/xor"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    0/imm32/output-is-write-only
    _Primitive-copy-to-eax/imm32/next
# - copy
_Primitive-copy-to-eax:
    # var/eax <- copy lit => b8/copy-to-eax lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-eax/imm32/outputs
    "b8/copy-to-eax"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-to-ecx/imm32/next
_Primitive-copy-to-ecx:
    # var/ecx <- copy lit => b9/copy-to-ecx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-ecx/imm32/outputs
    "b9/copy-to-ecx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-to-edx/imm32/next
_Primitive-copy-to-edx:
    # var/edx <- copy lit => ba/copy-to-edx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-edx/imm32/outputs
    "ba/copy-to-edx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-to-ebx/imm32/next
_Primitive-copy-to-ebx:
    # var/ebx <- copy lit => bb/copy-to-ebx lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-ebx/imm32/outputs
    "bb/copy-to-ebx"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-to-esi/imm32/next
_Primitive-copy-to-esi:
    # var/esi <- copy lit => be/copy-to-esi lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-esi/imm32/outputs
    "be/copy-to-esi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-to-edi/imm32/next
_Primitive-copy-to-edi:
    # var/edi <- copy lit => bf/copy-to-edi lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-edi/imm32/outputs
    "bf/copy-to-edi"/imm32/subx-name
    0/imm32/no-rm32
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-reg-to-reg/imm32/next
_Primitive-copy-reg-to-reg:
    # var1/reg <- copy var2/reg => 89/copy-to var1/rm32 var2/r32
    "copy"/imm32/name
    Single-int-var-in-some-register/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "89/copy-to"/imm32/subx-name
    3/imm32/rm32-is-first-output
    1/imm32/r32-is-first-inout
    0/imm32/no-imm32
    1/imm32/output-is-write-only
    _Primitive-copy-reg-to-mem/imm32/next
_Primitive-copy-reg-to-mem:
    # copy-to var1 var2/reg => 89/copy-to var1 var2/r32
    "copy-to"/imm32/name
    Int-var-and-second-int-var-in-some-register/imm32/inouts
    0/imm32/outputs
    "89/copy-to"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    2/imm32/r32-is-second-inout
    0/imm32/no-imm32
    1/imm32/output-is-write-only
    _Primitive-copy-mem-to-reg/imm32/next
_Primitive-copy-mem-to-reg:
    # var1/reg <- copy var2 => 8b/copy-from var2/rm32 var1/r32
    "copy"/imm32/name
    Single-int-var-on-stack/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "8b/copy-from"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    3/imm32/r32-is-first-output
    0/imm32/no-imm32
    1/imm32/output-is-write-only
    _Primitive-copy-lit-to-reg/imm32/next
_Primitive-copy-lit-to-reg:
    # var1/reg <- copy lit => c7 0/subop/copy var1/rm32 lit/imm32
    "copy"/imm32/name
    Single-lit-var/imm32/inouts
    Single-int-var-in-some-register/imm32/outputs
    "c7 0/subop/copy"/imm32/subx-name
    3/imm32/rm32-is-first-output
    0/imm32/no-r32
    1/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    _Primitive-copy-lit-to-mem/imm32/next
_Primitive-copy-lit-to-mem:
    # copy-to var1, lit => c7 0/subop/copy var1/rm32 lit/imm32
    "copy-to"/imm32/name
    Int-var-and-literal/imm32/inouts
    0/imm32/outputs
    "c7 0/subop/copy"/imm32/subx-name
    1/imm32/rm32-is-first-inout
    0/imm32/no-r32
    2/imm32/imm32-is-first-inout
    1/imm32/output-is-write-only
    0/imm32/next

Single-int-var-on-stack:
    Int-var-on-stack/imm32
    0/imm32/next

Int-var-on-stack:
    "arg1"/imm32/name
    1/imm32/type-int
    1/imm32/some-block-depth
    1/imm32/some-stack-offset
    0/imm32/no-register

Int-var-and-second-int-var-in-some-register:
    Int-var-on-stack/imm32
    Single-int-var-in-some-register/imm32/next

Int-var-and-literal:
    Int-var-on-stack/imm32
    Single-lit-var/imm32/next

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

emit-subx-call-operand:  # out : (address buffered-file), operand : (handle variable)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # eax = operand
    8b/-> *(ebp+0xc) 0/r32/eax
    # if non-literal, emit appropriately
    (emit-subx-var-as-rm32 *(ebp+8) %eax)
    # else if (operand->type == literal) emit "__"
    {
      81 7/subop/compare *(eax+4) 0/imm32  # Var-type
      75/jump-if-not-equal break/disp8
$emit-subx-call-operand:literal:
      (write-buffered *(ebp+8) Space)
      (write-buffered *(ebp+8) *eax)
    }
$emit-subx-call-operand:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

emit-subx-var-as-rm32:  # out : (address buffered-file), operand : (handle variable)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # eax = operand
    8b/-> *(ebp+0xc) 0/r32/eax
    # if (operand->register) emit "%__"
    {
      81 7/subop/compare *(eax+0x10) 0/imm32  # Var-register
      74/jump-if-equal break/disp8
$emit-subx-var-as-rm32:register:
      (write-buffered *(ebp+8) " %")
      (write-buffered *(ebp+8) *(eax+0x10))  # Var-register
    }
    # else if (operand->stack-offset) emit "*(ebp+__)"
    {
      81 7/subop/compare *(eax+0xc) 0/imm32  # Var-stack-offset
      74/jump-if-equal break/disp8
$emit-subx-var-as-rm32:stack:
      (write-buffered *(ebp+8) Space)
      (write-buffered *(ebp+8) "*(ebp+")
      8b/-> *(ebp+0xc) 0/r32/eax
      (print-int32-buffered *(ebp+8) *(eax+0xc))  # Var-stack-offset
      (write-buffered *(ebp+8) ")")
    }
$emit-subx-var-as-rm32:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

operand-matches-primitive?:  # var : (handle var), prim-var : (handle var) => result/eax : boolean
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    56/push-esi
    57/push-edi
    # esi = var
    8b/-> *(ebp+8) 6/r32/esi
    # edi = prim-var
    8b/-> *(ebp+0xc) 7/r32/edi
    # if (var->type != prim-var->type) return false
    8b/-> *(esi+4) 0/r32/eax  # Var-type
    39/compare *(edi+4) 0/r32/eax  # Var-type
    b8/copy-to-eax 0/imm32/false
    75/jump-if-not-equal $operand-matches-primitive?:end/disp8
    # return false if var->register doesn't match prim-var->register
    {
      # if addresses are equal, don't return here
      8b/-> *(esi+0x10) 0/r32/eax
      39/compare *(edi+0x10) 0/r32/eax
      74/jump-if-equal break/disp8
      # if either address is 0, return false
      3d/compare-eax-and 0/imm32
      74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
      81 7/subop/compare *(edi+0x10) 0/imm32
      74/jump-if-equal  $operand-matches-primitive?:end/disp8  # eax goes from meaning var->register to result
      # if prim-var->register is "*", return true
      (string-equal? *(edi+0x10) "*")  # Var-register
      3d/compare-eax-and 0/imm32
      b8/copy-to-eax 1/imm32/true
      75/jump-if-not-equal $operand-matches-primitive?:end/disp8
      # if string contents don't match, return false
      (string-equal? *(esi+0x10) *(edi+0x10))  # Var-register Var-register
      3d/compare-eax-and 0/imm32
      b8/copy-to-eax 0/imm32/false
      74/jump-if-equal $operand-matches-primitive?:end/disp8
    }
    # return true
    b8/copy-to-eax 1/imm32/true
$operand-matches-primitive?:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

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

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

test-add-reg-to-reg:
    #   var1/reg <- add var2/reg
    # =>
    #   01/add %var1 var2
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # setup
    (clear-stream _test-output-stream)
    (clear-stream $_test-output-buffered-file->buffer)
    # var var-var1/ecx : (ref var) in eax
    68/push "eax"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    68/push 1/imm32/type-int
    68/push "var1"/imm32
    89/<- %ecx 4/r32/esp
    # var var-var2/edx : (ref var) in ecx
    68/push "ecx"/imm32/register
    68/push 0/imm32/no-stack-offset
    68/push 1/imm32/block-depth
    68/push 1/imm32/type-int
    68/push "var2"/imm32
    89/<- %edx 4/r32/esp
    # var inouts/esi : (ref list var2)
    68/push 0/imm32/next
    52/push-edx/var-var2
    89/<- %esi 4/r32/esp
    # var outputs/edi : (ref list var1)
    68/push 0/imm32/next
    51/push-ecx/var-var1
    89/<- %edi 4/r32/esp
    # var stmt/esi : (ref statement)
    68/push 0/imm32/next
    57/push-edi/outputs
    56/push-esi/inouts
    68/push "add"/imm32/operation
    68/push 1/imm32
    89/<- %esi 4/r32/esp
    # convert
    (emit-subx-statement _test-output-buffered-file %esi Primitives 0)
    (flush _test-output-buffered-file)
#?     # dump _test-output-stream {{{
#?     (write 2 "^")
#?     (write-stream 2 _test-output-stream)
#?     (write 2 "$\n")
#?     (rewind-stream _test-output-stream)
#?     # }}}
    # check output
    (check-next-stream-line-equal _test-output-stream "01/add-to %eax 0x00000001/r32" "F - test-add-reg-to-reg")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

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

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

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

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

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

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

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

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

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