summaryrefslogblamecommitdiffstats
path: root/src/code-generation.c
blob: 725ee5417ee47e6e3188a57b9a726fa0a7575e43 (plain) (tree)
1
2
3
4
5
6
7
8
9




                            
                     

                   
                     

                   
                   
 
                                                                              
 
                                                       





                                                                        
                                                                              
                      









                                                                          




                                                                 











                                                                                     
                                              


                                                  











                                                                                       
                                              


                                                   



                                  



                              

 
                                                 
                                 

 




                                                                   
             



                                                 
                                                                   

                                                                  






                                                                                   
                                                                  


                                                            
                                  
                                         






                                                          
                                                       

                                                    

                                                                

                                                              
                              

                                
                                                   

 



                                                
                    



                                                                          
                                                                               

                                                                                
                                                      








                                                                            

























                                                                                 
         
            
                                                       
                                                                  









                                                                          














                                                           
                           






                                                                                 
                                                       

















                                                                
                                      
                                                       
                                             

                          



                                                                         



         



                                                                          
                                                                               

                                                                                 
                                                      








                                                                           

























                                                                                 
         
            

                                                                









                                                                         













                                                                                     
                           






                                                                                 
                                                       

















                                                                
                                      
                                                       
                                             

                         



                                                                         


         
              
 

                                                              

                                             


                         





                                                          
                                    






                                                        
                                                                     
                                                              





                                                                   

                                                          
                                                                     
                                                              





                                                                   




                              

                                                          
 









                                                                            
                                              


                                                                           
                                                          


                                                                           
                                


                                                                           
                                  
                                                  



                                                             





                                                                             








                                                                           
                                                                          

             

                                                                           



                                            


                                                                           

                                                                   
                                                             
                                               
                                      
                                                                    













                                                                    

                                                                


                                              
                                          
                                                                
                                                  





                                                         

                                                          
              
                                                                 






























                                                                  
                                                 
                                              
                                                  






                                                                 
                                            




                                                                           


                                                                           
     
                                                        
                                                
                                                                       

 
                                                                  
                                               
 

                                               


                                                                  
                                                                  

                                                      
                                                                             



                                                               
                              


                                  
                                                





                                               
                                                       




                                                                 



                                                               
                                                          
                                                                  
                                               
                                                                       
            

                                                                      
                


                                               
                                                           
















                                                                     










                                                                              


                                                                     
                                                                




                                                          
                                       




                                          

                                                        


                                                                           
                                                                




                                                          
                                       




                                           

                                                        


                                        



                                                                              
                                  




                                                       


                                                         
                                                                
                                                        
                                                                                    
                                                               
                                                                                   

                              


                                                                
                                                         
                                                                                    
                                                                
                                                                                   

                              
                                                        

         



                                                                  






                                                                      
                                                      






                                                                      
                                                      






                                                                      
                                                      





                                                                      
                                                                      
                                                      





                                                                                         

                                                                                    





                                                                                         

                                                                                    





                                                                                         

                                                                                    





                                                                                         

                                                                                    





                                                                                         

                                                                                    





                                                                                         

                                                                                    













                                                               
                                   

                                    
                                   








                                              

                                             

                                    
                                   














                                                               
                                   

                                    
                                   








                                              

                                             

                                    
                                   




                                    





                                                                  
 




                                                                                     




                                                            

                                                                    















                                                                               
 

                                                                  





                                                             
                                                                                            
                    
                                                                                       




                                                                                

                                                                            



                              



                                                                  
 




                                                                                     









                   
                                                                          
                                                                  

                        



                                                              

                                                    


                                  

                                   

                                                         


                                  

                                   
     
                                                   


                
                                                                           
                                                
 
                     

                                                                                  
                                                        
                                                                 

 

                                                              
                               
                                                               
 
                                              
               
                                                  
                                                           
               



                                                   



                                                      

                                                                                      
 
                              
                                                               

                                                                             





                                                                 
                               




                                          
                               
                                

                                                                          

                         
                                                          
                                       

                                      
                            



                            


















































                                                                                      
                                    
                                                        
                                                                    
                  


                                                          

                                                                                      

                           

                                                                             





                                                                 
                               




                                          
                               
                                    

                                                                          

                      
                                                          
                                       
 
                                                                    

                                        
                                                                        





                                                          
                                



                                    

                                                                             
                                                                    


                    
                                           
                                                               

                                    






                                                                                      



                                                                                    
                                                                                 

                                                                              
                                                           

                                                                                   
                                                                                  

                                                                                  
 





                                                                                   
                    

                                                       

                                                                                     
                     



                                                                           



                          









                                                                        







                                                                          

                                         

                                                    
                      






                              










                                                                      

                                                              
 

                                                      


                                          
                             


                                                                                         
                                                   
                              



                                                                           
                    

                                               

                                                                          

             

                                                                                         



                                                                                     
                                                            
     
                          



                                                                       
                    

                                           

                                                                      

         






                                                                    


                                                                             
                                       
                                    


                                   


                                     


                     
                                                                         







                                                                                
                                                     
                                               
                              
                   

                                   
                              


                     












                                                                        
                                      

                                                    





                              
                                             
 

                                    
                                                  
                                                             


                                              
                            

                                         
 

                               
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "code-generation.h"
#include "register.h"

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void generate_global_variable(CcmmcAst *global_decl, CcmmcState *state)
{
    fputs("\t.data\n\t.align\t2\n", state->asm_output);
    for (CcmmcAst *var_decl = global_decl->child->right_sibling;
         var_decl != NULL; var_decl = var_decl->right_sibling) {
        CcmmcSymbol *var_sym = ccmmc_symbol_table_retrieve(state->table,
            var_decl->value_id.name);
        switch (var_decl->value_id.kind) {
            case CCMMC_KIND_ID_NORMAL:
                fprintf(state->asm_output, "\t.comm\t%s, 4\n", var_sym->name);
                break;
            case CCMMC_KIND_ID_ARRAY: {
                size_t total_elements = 1;
                assert(var_sym->type.array_dimension > 0);
                for (size_t i = 0; i < var_sym->type.array_dimension; i++)
                    total_elements *= var_sym->type.array_size[i];
                fprintf(state->asm_output, "\t.comm\t%s, %zu\n",
                    var_sym->name, total_elements * 4);
                } break;
            case CCMMC_KIND_ID_WITH_INIT: {
                CcmmcAst *init_value = var_decl->child;
                fprintf(state->asm_output,
                    "\t.type\t%s, %%object\n"
                    "\t.size\t%s, 4\n"
                    "\t.global\t%s\n",
                    var_sym->name, var_sym->name, var_sym->name);
                if (var_sym->type.type_base == CCMMC_AST_VALUE_INT) {
                    int int_value;
                    if (init_value->type_node == CCMMC_AST_NODE_CONST_VALUE) {
                        assert(init_value->value_const.kind == CCMMC_KIND_CONST_INT);
                        int_value = init_value->value_const.const_int;
                    } else if (init_value->type_node == CCMMC_AST_NODE_EXPR) {
                        assert(ccmmc_ast_expr_get_is_constant(init_value));
                        assert(ccmmc_ast_expr_get_is_int(init_value));
                        int_value = ccmmc_ast_expr_get_int(init_value);
                    } else {
                        assert(false);
                    }
                    fprintf(state->asm_output,
                        "%s:\n"
                        "\t.word\t%d\n",
                        var_sym->name, int_value);
                } else if (var_sym->type.type_base == CCMMC_AST_VALUE_FLOAT) {
                    float float_value;
                    if (init_value->type_node == CCMMC_AST_NODE_CONST_VALUE) {
                        assert(init_value->value_const.kind == CCMMC_KIND_CONST_FLOAT);
                        float_value = init_value->value_const.const_float;
                    } else if (init_value->type_node == CCMMC_AST_NODE_EXPR) {
                        assert(ccmmc_ast_expr_get_is_constant(init_value));
                        assert(ccmmc_ast_expr_get_is_float(init_value));
                        float_value = ccmmc_ast_expr_get_float(init_value);
                    } else {
                        assert(false);
                    }
                    fprintf(state->asm_output,
                        "%s:\n"
                        "\t.float\t%.9g\n",
                       var_sym->name, float_value);
                } else {
                    assert(false);
                }
                } break;
            default:
                assert(false);
        }
    }
}

static inline bool safe_immediate(uint64_t imm) {
    return false; // imm <= 4096;
}

static void generate_expression(CcmmcAst *expr, CcmmcState *state,
    CcmmcTmp *result, uint64_t *current_offset);
static void calc_array_offset(CcmmcAst *ref, CcmmcSymbolType *type,
    CcmmcState *state, CcmmcTmp *result, uint64_t *current_offset)
{
    size_t i;
    CcmmcAst *index_node;
    CcmmcTmp *index, *mul;
    const char *result_reg, *index_reg, *mul_reg;

    generate_expression(ref->child, state, result, current_offset);
    index = ccmmc_register_alloc(state->reg_pool, current_offset);
    mul = ccmmc_register_alloc(state->reg_pool, current_offset);
    for (i = 1, index_node = ref->child->right_sibling;
         index_node != NULL || i < type->array_dimension;
         i++, index_node = index_node == NULL ? NULL : index_node->right_sibling) {
        if (index_node != NULL) {
            generate_expression(index_node, state, index, current_offset);
            index_reg = ccmmc_register_lock(state->reg_pool, index);
        }
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        mul_reg = ccmmc_register_lock(state->reg_pool, mul);
        fprintf(state->asm_output,
            "\tldr\t%s, =%zu\n"
            "\tmul\t%s, %s, %s\n",
            mul_reg, type->array_size[i],
            result_reg, result_reg, mul_reg);
        if (index_node != NULL) {
            fprintf(state->asm_output,
                "\tadd\t%s, %s, %s\n",
                result_reg, result_reg, index_reg);
            ccmmc_register_unlock(state->reg_pool, index);
        }
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, mul);
    }
    ccmmc_register_free(state->reg_pool, index, current_offset);
    ccmmc_register_free(state->reg_pool, mul, current_offset);

    result_reg = ccmmc_register_lock(state->reg_pool, result);
    fprintf(state->asm_output,
        "\tlsl\t%s, %s, #2\n",
        result_reg, result_reg);
    ccmmc_register_unlock(state->reg_pool, result);
}

static inline int calc_arg_offset(int arg_num) {
    return 16 + (arg_num - 8) * 8;
}

#define REG_TMP "x9"
static void load_variable(CcmmcAst *id, CcmmcState *state, CcmmcTmp *dist,
    uint64_t *current_offset)
{
    const char *dist_reg, *var_name = id->value_id.name;
    CcmmcSymbol *var_sym = ccmmc_symbol_table_retrieve(state->table, var_name);

    fprintf(state->asm_output, "\t/* var load, line %zu */\n", id->line_number);
    if (ccmmc_symbol_attr_is_global(&var_sym->attr)) {
        if (id->value_id.kind != CCMMC_KIND_ID_ARRAY) {
            dist_reg = ccmmc_register_lock(state->reg_pool, dist);
            fprintf(state->asm_output,
                "\tadrp\t" REG_TMP ", %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", #:lo12:%s\n"
                "\tldr\t%s, [" REG_TMP "]\n", var_name, var_name, dist_reg);
            ccmmc_register_unlock(state->reg_pool, dist);
        }
        else {
            CcmmcTmp *offset;
            const char *offset_reg;
            char extend[8];

            offset = ccmmc_register_alloc(state->reg_pool, current_offset);
            calc_array_offset(id, &var_sym->type, state, offset, current_offset);

            dist_reg = ccmmc_register_lock(state->reg_pool, dist);
            offset_reg = ccmmc_register_lock(state->reg_pool, offset);

            ccmmc_register_extend_name(offset, extend);
            fprintf(state->asm_output,
                "\tadrp\t" REG_TMP ", %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", #:lo12:%s\n"
                "\tsxtw\t%s, %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", %s\n"
                "\tldr\t%s, [" REG_TMP "]\n",
                var_name,
                var_name,
                extend, offset_reg,
                extend,
                dist_reg);

            ccmmc_register_unlock(state->reg_pool, dist);
            ccmmc_register_unlock(state->reg_pool, offset);
            ccmmc_register_free(state->reg_pool, offset, current_offset);
        }
    } else {
        if (id->value_id.kind != CCMMC_KIND_ID_ARRAY) {
            dist_reg = ccmmc_register_lock(state->reg_pool, dist);
            if (var_sym->attr.is_arg) {
                if (var_sym->attr.arg_num < 8)
                    fprintf(state->asm_output,
                        "\tmov\t%s, w%d\n",
                        dist_reg, var_sym->attr.arg_num);
                else
                    fprintf(state->asm_output,
                        "\tldr\t%s, [fp, #%d]\n",
                        dist_reg, calc_arg_offset(var_sym->attr.arg_num));
            } else if (safe_immediate(var_sym->attr.addr)) {
                fprintf(state->asm_output,
                    "\tldr\t%s, [fp, #-%" PRIu64 "]\n",
                    dist_reg, var_sym->attr.addr);
            } else {
                fprintf(state->asm_output,
                    "\tldr\t" REG_TMP ", =%" PRIu64 "\n"
                    "\tsub\t" REG_TMP ", fp, " REG_TMP "\n"
                    "\tldr\t%s, [" REG_TMP "]\n",
                    var_sym->attr.addr, dist_reg);
            }
            ccmmc_register_unlock(state->reg_pool, dist);
        }
        else {
            CcmmcTmp *offset;
            const char *offset_reg;
            char extend[8];

            offset = ccmmc_register_alloc(state->reg_pool, current_offset);
            calc_array_offset(id, &var_sym->type, state, offset, current_offset);

            dist_reg = ccmmc_register_lock(state->reg_pool, dist);
            offset_reg = ccmmc_register_lock(state->reg_pool, offset);

            ccmmc_register_extend_name(offset, extend);

            if (var_sym->attr.is_arg) {
                if (var_sym->attr.arg_num < 8)
                    fprintf(state->asm_output,
                        "\tmov\t" REG_TMP ", x%d\n",
                        var_sym->attr.arg_num);
                else
                    fprintf(state->asm_output,
                        "\tldr\t" REG_TMP ", [fp, #%d]\n",
                        calc_arg_offset(var_sym->attr.arg_num));
            } else {
                fprintf(state->asm_output,
                    "\tldr\t" REG_TMP ", =%" PRIu64 "\n"
                    "\tsub\t" REG_TMP ", fp, " REG_TMP "\n"
                    "\tsxtw\t%s, %s\n",
                    var_sym->attr.addr,
                    extend, offset_reg);
            }
            fprintf(state->asm_output,
                "\tadd\t" REG_TMP ", " REG_TMP ", %s\n"
                "\tldr\t%s, [" REG_TMP "]\n",
                extend,
                dist_reg);

            ccmmc_register_unlock(state->reg_pool, dist);
            ccmmc_register_unlock(state->reg_pool, offset);
            ccmmc_register_free(state->reg_pool, offset, current_offset);
        }
    }
}

static void store_variable(CcmmcAst *id, CcmmcState *state, CcmmcTmp *src,
    uint64_t *current_offset)
{
    const char *src_reg, *var_name = id->value_id.name;
    CcmmcSymbol *var_sym = ccmmc_symbol_table_retrieve(state->table, var_name);

    fprintf(state->asm_output, "\t/* var store, line %zu */\n", id->line_number);
    if (ccmmc_symbol_attr_is_global(&var_sym->attr)) {
        if (id->value_id.kind != CCMMC_KIND_ID_ARRAY) {
            src_reg = ccmmc_register_lock(state->reg_pool, src);
            fprintf(state->asm_output,
                "\tadrp\t" REG_TMP ", %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", #:lo12:%s\n"
                "\tstr\t%s, [" REG_TMP "]\n", var_name, var_name, src_reg);
            ccmmc_register_unlock(state->reg_pool, src);
        }
        else {
            CcmmcTmp *offset;
            const char *offset_reg;
            char extend[8];

            offset = ccmmc_register_alloc(state->reg_pool, current_offset);
            calc_array_offset(id, &var_sym->type, state, offset, current_offset);

            src_reg = ccmmc_register_lock(state->reg_pool, src);
            offset_reg = ccmmc_register_lock(state->reg_pool, offset);

            ccmmc_register_extend_name(offset, extend);
            fprintf(state->asm_output,
                "\tadrp\t" REG_TMP ", %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", #:lo12:%s\n"
                "\tsxtw\t%s, %s\n"
                "\tadd\t" REG_TMP ", " REG_TMP ", %s\n"
                "\tstr\t%s, [" REG_TMP "]\n",
                var_name,
                var_name,
                extend, offset_reg,
                extend,
                src_reg);

            ccmmc_register_unlock(state->reg_pool, src);
            ccmmc_register_unlock(state->reg_pool, offset);
            ccmmc_register_free(state->reg_pool, offset, current_offset);
        }
    } else {
        if (id->value_id.kind != CCMMC_KIND_ID_ARRAY) {
            src_reg = ccmmc_register_lock(state->reg_pool, src);
            if (var_sym->attr.is_arg) {
                if (var_sym->attr.arg_num < 8)
                    fprintf(state->asm_output,
                        "\tmov\tw%d, %s\n",
                        var_sym->attr.arg_num, src_reg);
                else
                    fprintf(state->asm_output,
                        "\tstr\t%s, [fp, #%d]\n",
                        src_reg, calc_arg_offset(var_sym->attr.arg_num));
            } else if (safe_immediate(var_sym->attr.addr)) {
                fprintf(state->asm_output,
                    "\tstr\t%s, [fp, #-%" PRIu64 "]\n", src_reg, var_sym->attr.addr);
            } else {
                fprintf(state->asm_output,
                    "\tldr\t" REG_TMP ", =%" PRIu64 "\n"
                    "\tsub\t" REG_TMP ", fp, " REG_TMP "\n"
                    "\tstr\t%s, [" REG_TMP "]\n",
                    var_sym->attr.addr, src_reg);
            }
            ccmmc_register_unlock(state->reg_pool, src);
        }
        else {
            CcmmcTmp *offset;
            const char *offset_reg;
            char extend[8];

            offset = ccmmc_register_alloc(state->reg_pool, current_offset);
            calc_array_offset(id, &var_sym->type, state, offset, current_offset);

            src_reg = ccmmc_register_lock(state->reg_pool, src);
            offset_reg = ccmmc_register_lock(state->reg_pool, offset);

            ccmmc_register_extend_name(offset, extend);

            if (var_sym->attr.is_arg) {
                if (var_sym->attr.arg_num < 8)
                    fprintf(state->asm_output,
                        "\tmov\t" REG_TMP ", x%d\n",
                        var_sym->attr.arg_num);
                else
                    fprintf(state->asm_output,
                        "\tldr\t" REG_TMP ", [fp, #%d]\n",
                        calc_arg_offset(var_sym->attr.arg_num));
            } else {
                fprintf(state->asm_output,
                    "\tldr\t" REG_TMP ", =%" PRIu64 "\n"
                    "\tsub\t" REG_TMP ", fp, " REG_TMP "\n"
                    "\tsxtw\t%s, %s\n",
                    var_sym->attr.addr,
                    extend, offset_reg);
            }
            fprintf(state->asm_output,
                "\tadd\t" REG_TMP ", " REG_TMP ", %s\n"
                "\tstr\t%s, [" REG_TMP "]\n",
                extend,
                src_reg);

            ccmmc_register_unlock(state->reg_pool, src);
            ccmmc_register_unlock(state->reg_pool, offset);
            ccmmc_register_free(state->reg_pool, offset, current_offset);
        }
    }
}
#undef REG_TMP

static const char *call_write(CcmmcAst *id, CcmmcState *state,
    uint64_t *current_offset)
{
    CcmmcAst *arg = id->right_sibling->child;
    CcmmcTmp *dist;
    const char *dist_reg;

    if (arg->type_value == CCMMC_AST_VALUE_CONST_STRING) {
        size_t label_str = state->label_number++;
        fprintf(state->asm_output,
            "\t.section .rodata\n"
            "\t.align 2\n"
            ".LC%zu:\n"
            "\t.ascii \"%s\\000\"\n"
            "\t.text\n"
            "\tadr\tx0, .LC%zu\n",
            label_str,
            arg->value_const.const_string,
            label_str);
        return "_write_str";
    } else if (arg->type_value == CCMMC_AST_VALUE_INT) {
        dist = ccmmc_register_alloc(state->reg_pool, current_offset);
        generate_expression(arg, state, dist, current_offset);
        dist_reg = ccmmc_register_lock(state->reg_pool, dist);
        fprintf(state->asm_output,
            "\tmov\tw0, %s\n",
            dist_reg);
        ccmmc_register_unlock(state->reg_pool, dist);
        ccmmc_register_free(state->reg_pool, dist, current_offset);
        return "_write_int";
    } else if (arg->type_value == CCMMC_AST_VALUE_FLOAT) {
        dist = ccmmc_register_alloc(state->reg_pool, current_offset);
        generate_expression(arg, state, dist, current_offset);
        dist_reg = ccmmc_register_lock(state->reg_pool, dist);
        fprintf(state->asm_output,
            "\tfmov\ts0, %s\n",
            dist_reg);
        ccmmc_register_unlock(state->reg_pool, dist);
        ccmmc_register_free(state->reg_pool, dist, current_offset);
        return "_write_float";
    }
    abort();
}

static void call_function(CcmmcAst *id, CcmmcState *state,
    uint64_t *current_offset)
{
    // XXX: We should have a better way to find the function in which we are
    CcmmcAst *in_func = id->parent;
    for (; in_func->type_node != CCMMC_AST_NODE_DECL ||
           in_func->value_decl.kind != CCMMC_KIND_DECL_FUNCTION;
           in_func = in_func->parent);
    // XXX: We should not search scopes other than the global scope
    CcmmcSymbol *in_func_sym = ccmmc_symbol_table_retrieve(
        state->table, in_func->child->right_sibling->value_id.name);
    size_t stored_param_count = in_func_sym->type.param_count;

    const char *func_name = id->value_id.name;
    if (strcmp(func_name, "write") == 0) {
        ccmmc_register_save_arguments(state->reg_pool, stored_param_count);
        ccmmc_register_caller_save(state->reg_pool);
        func_name = call_write(id, state, current_offset);
    } else if (strcmp(func_name, "read") == 0) {
        ccmmc_register_save_arguments(state->reg_pool, stored_param_count);
        ccmmc_register_caller_save(state->reg_pool);
        func_name = "_read_int";
    } else if (strcmp(func_name, "fread") == 0) {
        ccmmc_register_save_arguments(state->reg_pool, stored_param_count);
        ccmmc_register_caller_save(state->reg_pool);
        func_name = "_read_float";
    } else if (id->right_sibling->child != NULL) {
        CcmmcSymbol *func_sym = ccmmc_symbol_table_retrieve(
            state->table, func_name);
        size_t call_param_count = func_sym->type.param_count;

        CcmmcAst *arg;
        CcmmcTmp *dists[call_param_count];
        size_t i;
        for (i = 0; i < call_param_count; i++)
            dists[i] = ccmmc_register_alloc(state->reg_pool, current_offset);
        for (i = 0, arg = id->right_sibling->child; i < call_param_count;
             i++, arg = arg->right_sibling) {
            if (ccmmc_symbol_type_is_array(func_sym->type.param_list[i])) {
                assert(arg->type_node == CCMMC_AST_NODE_ID);
                CcmmcSymbolType var_type = ccmmc_symbol_table_retrieve(
                    state->table, arg->value_id.name)->type;
                if (arg->child != NULL)
                    calc_array_offset(arg, &var_type, state,
                        dists[i], current_offset);
            } else {
                generate_expression(arg, state, dists[i], current_offset);
            }
        }
        ccmmc_register_save_arguments(state->reg_pool, stored_param_count);
        ccmmc_register_caller_save(state->reg_pool);
        if (call_param_count > 8)
            fprintf(state->asm_output,
                "\tsub\tsp, sp, %zu\n",
                (call_param_count - 8) * 8);
        for (i = 0, arg = id->right_sibling->child; i < call_param_count;
             i++, arg = arg->right_sibling) {
            if (ccmmc_symbol_type_is_array(func_sym->type.param_list[i])) {
                CcmmcSymbol *var_sym = ccmmc_symbol_table_retrieve(
                    state->table, arg->value_id.name);
                const char *offset_reg = ccmmc_register_lock(
                    state->reg_pool, dists[i]);
                char offset_extend[8];
                ccmmc_register_extend_name(dists[i], offset_extend);

#define REG_TMP "x9"
                if (var_sym->attr.is_arg) {
                    if (var_sym->attr.arg_num < 8)
                        fprintf(state->asm_output,
                            "\tmov\t" REG_TMP ", x%d\n",
                            var_sym->attr.arg_num);
                    else
                        fprintf(state->asm_output,
                            "\tldr\t" REG_TMP ", [fp, #%d]\n",
                            calc_arg_offset(var_sym->attr.arg_num));
                } else {
                    fprintf(state->asm_output,
                        "\tldr\t" REG_TMP ", =%" PRIu64 "\n"
                        "\tsub\t" REG_TMP ", fp, " REG_TMP "\n",
                        var_sym->attr.addr);
                }
                if (arg->child != NULL)
                    fprintf(state->asm_output,
                        "\tsxtw\t%s, %s\n"
                        "\tadd\t" REG_TMP ", " REG_TMP ", %s\n",
                        offset_extend, offset_reg,
                        offset_extend);
                if (i < 8)
                    fprintf(state->asm_output,
                        "\tmov\tx%zu, " REG_TMP "\n", i);
                else
                    fprintf(state->asm_output,
                        "\tstr\t" REG_TMP ", [sp, %zu]\n",
                        (i - 8) * 8);
#undef REG_TMP
                ccmmc_register_unlock(state->reg_pool, dists[i]);
            } else {
                CcmmcAstValueType arg_type = arg->type_value;
                CcmmcAstValueType expected_type =
                    func_sym->type.param_list[i].type_base;
                const char *dist_reg = ccmmc_register_lock(
                    state->reg_pool, dists[i]);
                char dist_extend[8];
                ccmmc_register_extend_name(dists[i], dist_extend);
#define FPREG_TMP "s16"
                if (arg_type == CCMMC_AST_VALUE_FLOAT &&
                    expected_type == CCMMC_AST_VALUE_INT)
                    fprintf(state->asm_output,
                        "\tfmov\t%s, %s\n"
                        "\tfcvtas\t%s, %s\n",
                        FPREG_TMP, dist_reg,
                        dist_reg, FPREG_TMP);
                else if (arg_type == CCMMC_AST_VALUE_INT &&
                    expected_type == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output,
                        "\tscvtf\t%s, %s\n"
                        "\tfmov\t%s, %s\n",
                        FPREG_TMP, dist_reg,
                        dist_reg, FPREG_TMP);
#undef FPREG_TMP
                if (i < 8)
                    fprintf(state->asm_output,
                        "\tsxtw\tx%zu, %s\n",
                        i, dist_reg);
                else
                    fprintf(state->asm_output,
                        "\tsxtw\t%s, %s\n"
                        "\tstr\t%s, [sp, %zu]\n",
                        dist_extend, dist_reg,
                        dist_extend, (i - 8) * 8);
                ccmmc_register_unlock(state->reg_pool, dists[i]);
            }
        }
        fprintf(state->asm_output, "\tbl\t%s\n", func_name);
        if (call_param_count > 8)
            fprintf(state->asm_output,
                "\tadd\tsp, sp, %zu\n",
                (call_param_count - 8) * 8);
        ccmmc_register_caller_load(state->reg_pool);
        ccmmc_register_load_arguments(state->reg_pool, stored_param_count);
        for (i = 0; i < call_param_count; i++)
            ccmmc_register_free(state->reg_pool, dists[i], current_offset);
        return;
    } else {
        ccmmc_register_save_arguments(state->reg_pool, stored_param_count);
        ccmmc_register_caller_save(state->reg_pool);
    }
    fprintf(state->asm_output, "\tbl\t%s\n", func_name);
    ccmmc_register_caller_load(state->reg_pool);
    ccmmc_register_load_arguments(state->reg_pool, stored_param_count);
}

static void generate_expression(CcmmcAst *expr, CcmmcState *state,
    CcmmcTmp *result, uint64_t *current_offset)
{
    const char *result_reg, *op1_reg, *op2_reg;

    if (expr->type_node == CCMMC_AST_NODE_CONST_VALUE) {
        fprintf(state->asm_output,
            "\t/* const value, line %zu */\n", expr->line_number);
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        if (expr->type_value == CCMMC_AST_VALUE_INT) {
            fprintf(state->asm_output,
                "\tldr\t%s, =%d\n", result_reg, expr->value_const.const_int);
        } else if (expr->type_value == CCMMC_AST_VALUE_FLOAT) {
            fprintf(state->asm_output,
                "\tldr\t%s, .LC%zu\n"
                "\t.section .rodata\n"
                "\t.align 2\n"
                ".LC%zu:\n"
                "\t.float\t%.9g\n"
                "\t.text\n",
                result_reg, state->label_number,
                state->label_number,
                expr->value_const.const_float);
            state->label_number++;
        } else {
            assert(false);
        }
        ccmmc_register_unlock(state->reg_pool, result);
        return;
    }

    if (expr->type_node == CCMMC_AST_NODE_STMT &&
        expr->value_stmt.kind == CCMMC_KIND_STMT_FUNCTION_CALL) {
        const char *func_name = expr->child->value_id.name;
        CcmmcSymbol *func_sym = ccmmc_symbol_table_retrieve(
            state->table, func_name);
        CcmmcAstValueType func_type = func_sym->type.type_base;
        call_function(expr->child, state, current_offset);
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        if (func_type == CCMMC_AST_VALUE_FLOAT)
            fprintf(state->asm_output, "\tfmov\t%s, s0\n", result_reg);
        else
            fprintf(state->asm_output, "\tmov\t%s, w0\n", result_reg);
        ccmmc_register_unlock(state->reg_pool, result);
        return ;
    }

    if (expr->type_node == CCMMC_AST_NODE_ID) {
        load_variable(expr, state, result, current_offset);
        return;
    }

    assert(expr->type_node == CCMMC_AST_NODE_EXPR);

#define FPREG_RESULT  "s16"
#define FPREG_OP1     "s17"
#define FPREG_OP2     "s18"

    if (expr->value_expr.kind == CCMMC_KIND_EXPR_BINARY_OP) {
        CcmmcAst *left = expr->child;
        CcmmcAst *right = expr->child->right_sibling;
        size_t label_short = state->label_number;

        // evaluate the left expression
        fprintf(state->asm_output,
            "\t/* expr binary op, line %zu */\n", expr->line_number);
        generate_expression(left, state, result, current_offset);
        CcmmcTmp *op1 = ccmmc_register_alloc(state->reg_pool, current_offset);
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        op1_reg = ccmmc_register_lock(state->reg_pool, op1);
        fprintf(state->asm_output,
            "\t/* mov op1_reg, result_reg */\n"
            "\tmov\t%s, %s\n",
            op1_reg, result_reg);
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, op1);

        if (expr->value_expr.op_binary == CCMMC_KIND_OP_BINARY_AND) {
            // logical AND needs short-curcuit evaluation
            label_short = state->label_number++;
            op1_reg = ccmmc_register_lock(state->reg_pool, op1);
            if (left->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output,
                    "\tfmov\t%s, %s\n"
                    "\tfcmp\t%s, #0.0\n"
                    "\tb.eq\t.LC%zu\n",
                    FPREG_OP1, op1_reg,
                    FPREG_OP1,
                    label_short);
            else
                fprintf(state->asm_output,
                    "\tcbz\t%s, .LC%zu\n",
                    op1_reg, label_short);
            ccmmc_register_unlock(state->reg_pool, op1);
        } else if (expr->value_expr.op_binary == CCMMC_KIND_OP_BINARY_OR) {
            // logical OR needs short-curcuit evaluation
            label_short = state->label_number++;
            op1_reg = ccmmc_register_lock(state->reg_pool, op1);
            if (left->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output,
                    "\tfmov\t%s, %s\n"
                    "\tfcmp\t%s, #0.0\n"
                    "\tb.ne\t.LC%zu\n",
                    FPREG_OP1, op1_reg,
                    FPREG_OP1,
                    label_short);
            else
                fprintf(state->asm_output,
                    "\tcbnz\t%s, .LC%zu\n",
                    op1_reg, label_short);
            ccmmc_register_unlock(state->reg_pool, op1);
        }

        // evaluate the right expression
        generate_expression(right, state, result, current_offset);
        CcmmcTmp *op2 = ccmmc_register_alloc(state->reg_pool, current_offset);
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        op2_reg = ccmmc_register_lock(state->reg_pool, op2);
        fprintf(state->asm_output,
            "\t/* mov op2_reg, result_reg */\n"
            "\tmov\t%s, %s\n",
            op2_reg, result_reg);
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, op2);

        if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
            right->type_value == CCMMC_AST_VALUE_FLOAT) {
            op1_reg = ccmmc_register_lock(state->reg_pool, op1);
            if (left->type_value == CCMMC_AST_VALUE_INT)
                fprintf(state->asm_output, "\tscvtf\t%s, %s\n", FPREG_OP1, op1_reg);
            else if (left->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output, "\tfmov\t%s, %s\n", FPREG_OP1, op1_reg);
            else
                assert(false);
            ccmmc_register_unlock(state->reg_pool, op1);

            op2_reg = ccmmc_register_lock(state->reg_pool, op2);
            if (right->type_value == CCMMC_AST_VALUE_INT)
                fprintf(state->asm_output, "\tscvtf\t%s, %s\n", FPREG_OP2, op2_reg);
            else if (right->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output, "\tfmov\t%s, %s\n", FPREG_OP2, op2_reg);
            else
                assert(false);
            ccmmc_register_unlock(state->reg_pool, op2);
        }

        result_reg = ccmmc_register_lock(state->reg_pool, result);
        op1_reg = ccmmc_register_lock(state->reg_pool, op1);
        op2_reg = ccmmc_register_lock(state->reg_pool, op2);

        switch (expr->value_expr.op_binary) {
            case CCMMC_KIND_OP_BINARY_ADD:
                if (expr->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfadd\t%s, %s, %s\n",
                        FPREG_RESULT, FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tadd\t%s, %s, %s\n",
                        result_reg, op1_reg, op2_reg);
                break;
            case CCMMC_KIND_OP_BINARY_SUB:
                if (expr->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfsub\t%s, %s, %s\n",
                        FPREG_RESULT, FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tsub\t%s, %s, %s\n",
                        result_reg, op1_reg, op2_reg);
                break;
            case CCMMC_KIND_OP_BINARY_MUL:
                if (expr->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfmul\t%s, %s, %s\n",
                        FPREG_RESULT, FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tmul\t%s, %s, %s\n",
                        result_reg, op1_reg, op2_reg);
                break;
            case CCMMC_KIND_OP_BINARY_DIV:
                if (expr->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfdiv\t%s, %s, %s\n",
                        FPREG_RESULT, FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tsdiv\t%s, %s, %s\n",
                        result_reg, op1_reg, op2_reg);
                break;
            case CCMMC_KIND_OP_BINARY_EQ:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, eq\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_GE:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, ge\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_LE:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, le\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_NE:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, ne\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_GT:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, gt\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_LT:
                if (left->type_value == CCMMC_AST_VALUE_FLOAT ||
                    right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, %s\n", FPREG_OP1, FPREG_OP2);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, %s\n", op1_reg, op2_reg);
                fprintf(state->asm_output, "\tcset\t%s, lt\n", result_reg);
                break;
            case CCMMC_KIND_OP_BINARY_AND: {
                size_t label_exit = state->label_number++;
                if (right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output,
                        "\tfcmp\t%s, #0.0\n"
                        "\tb.eq\t.LC%zu\n"
                        "\tmov\t%s, #1\n"
                        "\tb\t.LC%zu\n"
                        ".LC%zu:\n"
                        "\tmov\t%s, #0\n"
                        ".LC%zu:\n",
                        FPREG_OP2,
                        label_short,
                        result_reg,
                        label_exit,
                        label_short,
                        result_reg,
                        label_exit);
                else
                    fprintf(state->asm_output,
                        "\tcbz\t%s, .LC%zu\n"
                        "\tmov\t%s, #1\n"
                        "\tb\t.LC%zu\n"
                        ".LC%zu:\n"
                        "\tmov\t%s, #0\n"
                        ".LC%zu:\n",
                        op2_reg, label_short,
                        result_reg,
                        label_exit,
                        label_short,
                        result_reg,
                        label_exit);
                } break;
            case CCMMC_KIND_OP_BINARY_OR: {
                size_t label_exit = state->label_number++;
                if (right->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output,
                        "\tfcmp\t%s, #0.0\n"
                        "\tb.ne\t.LC%zu\n"
                        "\tmov\t%s, #0\n"
                        "\tb\t.LC%zu\n"
                        ".LC%zu:\n"
                        "\tmov\t%s, #1\n"
                        ".LC%zu:\n",
                        FPREG_OP2,
                        label_short,
                        result_reg,
                        label_exit,
                        label_short,
                        result_reg,
                        label_exit);
                else
                    fprintf(state->asm_output,
                        "\tcbnz\t%s, .LC%zu\n"
                        "\tmov\t%s, #0\n"
                        "\tb\t.LC%zu\n"
                        ".LC%zu:\n"
                        "\tmov\t%s, #1\n"
                        ".LC%zu:\n",
                        op2_reg, label_short,
                        result_reg,
                        label_exit,
                        label_short,
                        result_reg,
                        label_exit);
                } break;
            default:
                assert(false);
        }
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, op1);
        ccmmc_register_unlock(state->reg_pool, op2);

        ccmmc_register_free(state->reg_pool, op1, current_offset);
        ccmmc_register_free(state->reg_pool, op2, current_offset);

        if (expr->type_value == CCMMC_AST_VALUE_FLOAT) {
            result_reg = ccmmc_register_lock(state->reg_pool, result);
            fprintf(state->asm_output, "\tfmov\t%s, %s\n", result_reg, FPREG_RESULT);
            ccmmc_register_unlock(state->reg_pool, result);
        }
        return;
    }

    if (expr->value_expr.kind == CCMMC_KIND_EXPR_UNARY_OP) {
        CcmmcAst *arg = expr->child;
        fprintf(state->asm_output,
            "\t/* expr unary op, line %zu */\n", expr->line_number);
        generate_expression(arg, state, result, current_offset);
        CcmmcTmp *op1 = ccmmc_register_alloc(state->reg_pool, current_offset);
        result_reg = ccmmc_register_lock(state->reg_pool, result);
        op1_reg = ccmmc_register_lock(state->reg_pool, op1);
        fprintf(state->asm_output,
            "\t/* mov op1_reg, result_reg */\n"
            "\tmov\t%s, %s\n",
            op1_reg, result_reg);
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, op1);

        if (arg->type_value == CCMMC_AST_VALUE_FLOAT) {
            op1_reg = ccmmc_register_lock(state->reg_pool, op1);
            fprintf(state->asm_output, "\tfmov\t%s, %s\n", FPREG_OP1, op1_reg);
            ccmmc_register_unlock(state->reg_pool, op1);
        }

        result_reg = ccmmc_register_lock(state->reg_pool, result);
        op1_reg = ccmmc_register_lock(state->reg_pool, op1);
        switch (expr->value_expr.op_unary) {
            case CCMMC_KIND_OP_UNARY_POSITIVE:
                fputs("\t/* nop */\n", state->asm_output);
                break;
            case CCMMC_KIND_OP_UNARY_NEGATIVE:
                if (arg->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfneg\t%s, %s\n", FPREG_RESULT, FPREG_OP1);
                else
                    fprintf(state->asm_output, "\tneg\t%s, %s\n", result_reg, op1_reg);
                break;
            case CCMMC_KIND_OP_UNARY_LOGICAL_NEGATION:
                if (arg->type_value == CCMMC_AST_VALUE_FLOAT)
                    fprintf(state->asm_output, "\tfcmp\t%s, #0.0\n", FPREG_OP1);
                else
                    fprintf(state->asm_output, "\tcmp\t%s, wzr\n", op1_reg);
                fprintf(state->asm_output, "\tcset\t%s, eq\n", result_reg);
                break;
            default:
                assert(false);
        }
        ccmmc_register_unlock(state->reg_pool, result);
        ccmmc_register_unlock(state->reg_pool, op1);

        ccmmc_register_free(state->reg_pool, op1, current_offset);

        if (expr->type_value == CCMMC_AST_VALUE_FLOAT) {
            result_reg = ccmmc_register_lock(state->reg_pool, result);
            fprintf(state->asm_output, "\tfmov\t%s, %s\n", result_reg, FPREG_RESULT);
            ccmmc_register_unlock(state->reg_pool, result);
        }
        return;
    }

#undef FPREG_RESULT
#undef FPREG_OP1
#undef FPREG_OP2

    assert(false);
}

static void calc_expression_result(CcmmcAst *expr, CcmmcAstValueType type,
    CcmmcState *state, CcmmcTmp *result, uint64_t *current_offset)
{
#define FPREG_TMP  "s16"
    const char *result_reg;
    generate_expression(expr, state, result, current_offset);

    result_reg = ccmmc_register_lock(state->reg_pool, result);
    if (expr->type_value == CCMMC_AST_VALUE_FLOAT &&
        type == CCMMC_AST_VALUE_INT) {
        fprintf(state->asm_output,
            "\tfmov\t%s, %s\n"
            "\tfcvtas\t%s, %s\n",
            FPREG_TMP, result_reg,
            result_reg, FPREG_TMP);
    } else if (expr->type_value == CCMMC_AST_VALUE_INT &&
        type == CCMMC_AST_VALUE_FLOAT) {
        fprintf(state->asm_output,
            "\tscvtf\t%s, %s\n"
            "\tfmov\t%s, %s\n",
            FPREG_TMP, result_reg,
            result_reg, FPREG_TMP);
    }
    ccmmc_register_unlock(state->reg_pool, result);
#undef FPREG_TMP
}

static void calc_and_save_expression_result(CcmmcAst *lvar, CcmmcAst *expr,
    CcmmcState *state, uint64_t *current_offset)
{
    CcmmcTmp *result;
    result = ccmmc_register_alloc(state->reg_pool, current_offset);
    calc_expression_result(expr, lvar->type_value, state, result, current_offset);
    store_variable(lvar, state, result, current_offset);
    ccmmc_register_free(state->reg_pool, result, current_offset);
}

static void generate_block(CcmmcAst *block, CcmmcState *state,
    uint64_t current_offset, bool scope_already_open);
static void generate_statement(
    CcmmcAst *stmt, CcmmcState *state, uint64_t current_offset)
{
    if (stmt->type_node == CCMMC_AST_NODE_NUL)
        return;
    if (stmt->type_node == CCMMC_AST_NODE_BLOCK) {
        generate_block(stmt, state, current_offset, false);
        return;
    }

    assert(stmt->type_node == CCMMC_AST_NODE_STMT);
    switch(stmt->value_stmt.kind) {
        case CCMMC_KIND_STMT_WHILE: {
#define FPREG_TMP  "s16"
            size_t label_cmp = state->label_number++;
            size_t label_exit = state->label_number++;
            const char *result_reg;
            CcmmcTmp *result = ccmmc_register_alloc(state->reg_pool, &current_offset);

            // while condition
            fprintf(state->asm_output, ".LC%zu:\n", label_cmp);
            generate_expression(stmt->child, state, result, &current_offset);
            result_reg = ccmmc_register_lock(state->reg_pool, result);
            if (stmt->child->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output,
                    "\tfmov\t%s, %s\n"
                    "\tfcmp\t%s, #0.0\n"
                    "\tb.e\t.LC%zu\n",
                    FPREG_TMP,
                    result_reg,
                    FPREG_TMP,
                    label_exit);
            else
                fprintf(state->asm_output,
                    "\tcbz\t%s, .LC%zu\n",
                    result_reg,
                    label_exit);
            ccmmc_register_unlock(state->reg_pool, result);
            ccmmc_register_free(state->reg_pool, result, &current_offset);

            // while body
            generate_statement(stmt->child->right_sibling,
                state, current_offset);
            fprintf(state->asm_output,
                "\tb\t.LC%zu\n"
                ".LC%zu:\n",
                label_cmp,
                label_exit);
#undef FPREG_TMP
            } break;
        case CCMMC_KIND_STMT_FOR: {
#define FPREG_TMP  "s16"
            size_t label_cmp = state->label_number++;
            size_t label_exit = state->label_number++;
            const char *result_reg;
            CcmmcTmp *result = ccmmc_register_alloc(state->reg_pool, &current_offset);

            for (CcmmcAst *assign = stmt->child->child; assign != NULL;
                    assign = assign->right_sibling) {
                generate_statement(assign, state, current_offset);
            }

            // for condition
            fprintf(state->asm_output, ".LC%zu:\n", label_cmp);
            for (CcmmcAst *expr = stmt->child->right_sibling->child; expr != NULL;
                    expr = expr->right_sibling) {
                generate_expression(expr, state, result, &current_offset);
            }
            result_reg = ccmmc_register_lock(state->reg_pool, result);
            if (stmt->child->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output,
                    "\tfmov\t%s, %s\n"
                    "\tfcmp\t%s, #0.0\n"
                    "\tb.e\t.LC%zu\n",
                    FPREG_TMP,
                    result_reg,
                    FPREG_TMP,
                    label_exit);
            else
                fprintf(state->asm_output,
                    "\tcbz\t%s, .LC%zu\n",
                    result_reg,
                    label_exit);
            ccmmc_register_unlock(state->reg_pool, result);
            ccmmc_register_free(state->reg_pool, result, &current_offset);

            // for body
            generate_statement(
                stmt->child->right_sibling->right_sibling->right_sibling,
                state, current_offset);
            for (CcmmcAst *assign = stmt->child->right_sibling->right_sibling->child;
                    assign != NULL; assign = assign->right_sibling) {
                generate_statement(assign, state, current_offset);
            }
            fprintf(state->asm_output,
                "\tb\t.LC%zu\n"
                ".LC%zu:\n",
                label_cmp,
                label_exit);
#undef FPREG_TMP
            } break;
        case CCMMC_KIND_STMT_ASSIGN:
            calc_and_save_expression_result(stmt->child,
                stmt->child->right_sibling, state, &current_offset);
            break;
        case CCMMC_KIND_STMT_IF: {
#define FPREG_TMP  "s16"
            size_t label_cross_if = state->label_number++;
            const char *result_reg;
            CcmmcTmp *result = ccmmc_register_alloc(state->reg_pool, &current_offset);

            // if condition
            generate_expression(stmt->child, state, result, &current_offset);
            result_reg = ccmmc_register_lock(state->reg_pool, result);
            if (stmt->child->type_value == CCMMC_AST_VALUE_FLOAT)
                fprintf(state->asm_output,
                    "\tfmov\t%s, %s\n"
                    "\tfcmp\t%s, #0.0\n"
                    "\tb.e\t.LC%zu\n",
                    FPREG_TMP,
                    result_reg,
                    FPREG_TMP,
                    label_cross_if);
            else
                fprintf(state->asm_output,
                    "\tcbz\t%s, .LC%zu\n",
                    result_reg,
                    label_cross_if);
            ccmmc_register_unlock(state->reg_pool, result);
            ccmmc_register_free(state->reg_pool, result, &current_offset);

            // if body
            generate_statement(stmt->child->right_sibling,
                state, current_offset);

            if (stmt->child->right_sibling->right_sibling->type_node
                == CCMMC_AST_NODE_NUL) {
                // no else
                fprintf(state->asm_output, ".LC%zu:\n", label_cross_if);
            }
            else {
                // jump across else
                size_t label_exit = state->label_number++;
                fprintf(state->asm_output,
                    "\tb\t.LC%zu\n"
                    ".LC%zu:\n",
                    label_exit,
                    label_cross_if);

                // else body
                generate_statement(stmt->child->right_sibling->right_sibling,
                    state, current_offset);
                fprintf(state->asm_output, ".LC%zu:\n", label_exit);
#undef FPREG_TMP
            }
            } break;
        case CCMMC_KIND_STMT_FUNCTION_CALL:
            call_function(stmt->child, state, &current_offset);
            break;
        case CCMMC_KIND_STMT_RETURN:
            for (CcmmcAst *func = stmt->parent; ; func = func->parent) {
                if (func->type_node == CCMMC_AST_NODE_DECL &&
                    func->value_decl.kind == CCMMC_KIND_DECL_FUNCTION) {
                    const char *func_name = func->child->right_sibling->value_id.name;
                    CcmmcSymbol *func_sym =
                        ccmmc_symbol_table_retrieve(state->table, func_name);
                    CcmmcAstValueType func_type = func_sym->type.type_base;
                    const char *result_reg;
                    CcmmcTmp *result;

                    result = ccmmc_register_alloc(state->reg_pool, &current_offset);
                    calc_expression_result(stmt->child, func_sym->type.type_base,
                        state, result, &current_offset);
                    result_reg = ccmmc_register_lock(state->reg_pool, result);
                    if (func_type == CCMMC_AST_VALUE_FLOAT)
                        fprintf(state->asm_output, "\tfmov\ts0, %s\n", result_reg);
                    else
                        fprintf(state->asm_output, "\tmov\tw0, %s\n", result_reg);
                    ccmmc_register_unlock(state->reg_pool, result);
                    ccmmc_register_free(state->reg_pool, result, &current_offset);

                    // XXX: We should fix the location of the return label
                    // instead of copying code and modifying sp here.
                    if (safe_immediate(current_offset)) {
                        fprintf(state->asm_output, "\tadd\tsp, sp, #%" PRIu64 "\n",
                            current_offset);
                    } else {
#define REG_TMP "x9"
                        fprintf(state->asm_output,
                            "\tldr\t%s, =%" PRIu64 "\n"
                            "\tadd\tsp, sp, %s\n", REG_TMP, current_offset, REG_TMP);
#undef REG_TMP
                    }
                    fprintf(state->asm_output, "\tb\t.LR_%s\n", func_name);
                    break;
                }
            }
            break;
        default:
            assert(false);
    }
}

static uint64_t generate_local_variable(
    CcmmcAst *local_decl, CcmmcState *state, uint64_t current_offset)
{
    for (CcmmcAst *var_decl = local_decl->child->right_sibling;
         var_decl != NULL; var_decl = var_decl->right_sibling) {
        CcmmcSymbol *var_sym = ccmmc_symbol_table_retrieve(state->table,
            var_decl->value_id.name);
        switch (var_decl->value_id.kind) {
            case CCMMC_KIND_ID_ARRAY: {
                size_t total_elements = 1;
                assert(var_sym->type.array_dimension > 0);
                for (size_t i = 0; i < var_sym->type.array_dimension; i++)
                    total_elements *= var_sym->type.array_size[i];
                current_offset += total_elements * 4;
                var_sym->attr.addr = current_offset;
                } break;
            case CCMMC_KIND_ID_NORMAL:
            case CCMMC_KIND_ID_WITH_INIT:
                current_offset += 4;
                var_sym->attr.addr = current_offset;
                break;
            default:
                assert(false);
        }
    }
    return current_offset;
}

static void init_local_variable(
    CcmmcAst *local_decl, CcmmcState *state, uint64_t current_offset)
{
    for (CcmmcAst *var_decl = local_decl->child->right_sibling;
         var_decl != NULL; var_decl = var_decl->right_sibling) {
        if (var_decl->value_id.kind == CCMMC_KIND_ID_WITH_INIT)
            calc_and_save_expression_result(var_decl, var_decl->child,
                state, &current_offset);
    }
}

static void generate_block(CcmmcAst *block, CcmmcState *state,
    uint64_t current_offset, bool scope_already_open)
{
    if (!scope_already_open)
        ccmmc_symbol_table_reopen_scope(state->table);

    CcmmcAst *child = block->child;
    uint64_t orig_offset = current_offset;
    uint64_t offset_diff = 0;
    if (child != NULL && child->type_node == CCMMC_AST_NODE_VARIABLE_DECL_LIST) {
        for (CcmmcAst *local = child->child; local != NULL; local = local->right_sibling)
            current_offset = generate_local_variable(local, state, current_offset);
        offset_diff = current_offset - orig_offset;
        if (offset_diff > 0) {
            if (safe_immediate(offset_diff)) {
                fprintf(state->asm_output, "\tsub\tsp, sp, #%" PRIu64 "\n",
                    offset_diff);
            } else {
#define REG_TMP "x9"
                fprintf(state->asm_output,
                    "\tldr\t%s, =%" PRIu64 "\n"
                    "\tsub\tsp, sp, %s\n", REG_TMP, offset_diff, REG_TMP);
#undef REG_TMP
            }
        }
        for (CcmmcAst *local = child->child; local != NULL; local = local->right_sibling)
            init_local_variable(local, state, current_offset);
        child = child->right_sibling;
    }
    if (child != NULL && child->type_node == CCMMC_AST_NODE_STMT_LIST) {
        for (CcmmcAst *stmt = child->child; stmt != NULL; stmt = stmt->right_sibling)
            generate_statement(stmt, state, current_offset);
    }
    if (offset_diff > 0) {
        if (safe_immediate(offset_diff)) {
            fprintf(state->asm_output, "\tadd\tsp, sp, #%" PRIu64 "\n",
                offset_diff);
        } else {
#define REG_TMP "x9"
            fprintf(state->asm_output,
                "\tldr\t%s, =%" PRIu64 "\n"
                "\tadd\tsp, sp, %s\n", REG_TMP, offset_diff, REG_TMP);
#undef REG_TMP
        }
    }

    ccmmc_symbol_table_close_scope(state->table);
}

static void generate_function(CcmmcAst *function, CcmmcState *state)
{
    fputs("\t.text\n\t.align\t2\n", state->asm_output);
    const char *func_name = function->child->right_sibling->value_id.name;
    const char *symbol_name = func_name;
    // XXX: We have to rewrite some names to workaround TA's broken toolchain
    if (strcmp(func_name, "MAIN") == 0)
        symbol_name = "_start_MAIN";
    fprintf(state->asm_output,
        "\t.type\t%s, %%function\n"
        "\t.global\t%s\n"
        "%s:\n"
        "\tstp\tlr, fp, [sp, -16]!\n"
        "\tmov\tfp, sp\n",
        symbol_name,
        symbol_name,
        symbol_name);
    CcmmcAst *param_node = function->child->right_sibling->right_sibling;
    ccmmc_symbol_table_reopen_scope(state->table);
    CcmmcAst *arg_node = param_node->child;
    for (int i = 0; arg_node != NULL; arg_node = arg_node->right_sibling, i++) {
        CcmmcSymbol *arg_sym = ccmmc_symbol_table_retrieve(state->table,
            arg_node->child->right_sibling->value_id.name);
        arg_sym->attr.is_arg = true;
        arg_sym->attr.arg_num = i;
    }
    CcmmcAst *block_node = param_node->right_sibling;
    generate_block(block_node, state, 0, true);
    fprintf(state->asm_output,
        ".LR_%s:\n"
        "\tldp\tlr, fp, [sp], 16\n"
        "\tret\tlr\n"
        "\t.size\t%s, .-%s\n",
        func_name,
        symbol_name,
        symbol_name);
}

static void generate_program(CcmmcState *state)
{
    for (CcmmcAst *global_decl = state->ast->child; global_decl != NULL;
         global_decl = global_decl->right_sibling) {
        switch (global_decl->value_decl.kind) {
            case CCMMC_KIND_DECL_VARIABLE:
                generate_global_variable(global_decl, state);
                break;
            case CCMMC_KIND_DECL_FUNCTION:
                generate_function(global_decl, state);
                break;
            case CCMMC_KIND_DECL_TYPE:
                break;
            case CCMMC_KIND_DECL_FUNCTION_PARAMETER:
            default:
                assert(false);
        }
    }
}

void ccmmc_code_generation(CcmmcState *state)
{
    state->table->this_scope = NULL;
    state->table->current = NULL;
    ccmmc_symbol_table_reopen_scope(state->table);
    state->reg_pool = ccmmc_register_init(state->asm_output);
    fputs(
        "fp\t.req\tx29\n"
        "lr\t.req\tx30\n", state->asm_output);
    generate_program(state);
    ccmmc_register_fini(state->reg_pool);
    state->reg_pool = NULL;
}

// vim: set sw=4 ts=4 sts=4 et: