OVERVIEW OF CODE GENERATION .. ASTs...-> [ Static Analysis ] | | IR v [ Code Generation ] | | Machine Code | v SSM Virtual Machine Execution The IR (= Intermediate Representation) records IR TREES An IR is a tree structure, contains information from parsing, and static analysis, including attributes Helps in modularizing compilers and code generation WITHOUT IR WITH IR Java ------>x86 Java ->x86 \/ ||| \ / C ------>MIPS C \\ /-->MIPS \\/ || ->IR C++ ------>Sparc C++ // \-->Sparc \\\/ / \ C# ------>A1 C#/ \>A1 OUR CHOICES FOR AN IR To keep things simple, we will use a modified AST type as an IR Parser: - records structure of programs (ASTs) - provides placeholders for attributes Static analysis: - records attributes needed for code generation (in the AST) GENERAL STRATEGY FOR CODE GENERATION Don't try to optimize! Follow the grammar of the ASTs attributes filled in during scope checking Trust the recursion! but keep the recursion simple. FOLLOWING THE GRAMMAR Code resembles the grammar that describes the input data When the grammar is recursive the code should also be recursive When the grammar has alternatives the code has conditionals or a switch TARGET: CODE SEQUENCES Need lists of machine code instructions (we will use linked lists) Why? because it often takes many instructions to accomplish one thing from the language THE CODE TYPE // file code.h #include "instruction.h" // machine code instructions typedef struct code_s { struct code_s *next; bin_instr_t instr; } code; // Code creation functions below extern code *code_nop(); extern code *code_add(reg_num_type t, offset_type ot, reg_num_type s, offset_type os); extern code *code_sub(reg_num_type t, offset_type ot, reg_num_type s, offset_type os); // ... REPRESENTING CODE SEQUENCES IN C // file code_seq.h #include "code.h" // code sequences typedef code *code_seq; extern code_seq code_seq_empty(); extern code_seq code_seq_singleton( code *c); extern bool code_seq_is_empty( code_seq seq); // Requires: !code_seq_is_empty(seq) // Return the first element... extern code *code_seq_first(code_seq seq); // Requires: !code_seq_is_empty(seq) // Return the rest of the given sequence extern code_seq code_seq_rest( code_seq seq); // Return the size (number of words) extern unsigned int code_seq_size( code_seq seq); // ... STRATEGIES FOR DESIGNING CODE SEQUENCES 0. Figure out what you want to do (for the PL thing) 1. Work backwards a. select the instruction that does that b. generate code to set up the stack or memory, etc. EXAMPLE: EXPRESSION EVALUATION Example: (E1 + E2) - (E3 / E4). Constraints: - Expressions have a result value - Binary operations (+, -, *, /) in the SSM Where should the result be stored? Can it be a register? No. Have an unbounded number of subexpressions but only finite number of registers. So we'll put results on top of the runtime stack Are there side-effects in SPL expressions? No, there aren't any. Can order of evaluating sub-expressions matter? No. Code to evaluate E2 op E3 [code to evaluate E3, pushing value v3 on stack] [code to evaluate E2, pushing value v2 on stack] [instruction to compute v2 op v3 and put result at SP+1] [deallocate 1 word from the stack] E.g., for E2 - E3 [code to evaluate E3, pushing value v3 on stack] [code to evaluate E2, pushing value v2 on stack] SUB $sp,1,$sp,1 # subtracts v3 from v2 ARI $sp,1 // gen_code_bin_op_expr would have code like: // ... determine we are doing a subtraction... code_seq ret = gen_code_expr(/* E3 */); ret = code_seq_concat(ret, gen_code_expr(/* E2 */)); ret = code_seq_add_to_end(ret, code_sub(SP,1,SP,1)); ret = code_seq_add_concat(ret, code_utils_deallocate_stack_space(1)); ADDRESSING VARIABLES Consider an expression x where x is a variable How to get x's value on the top of stack? [allocate a word on top of the stack] [copy x's value from it's location to top of the stack] Need levels outwards from current scope and the offset How to find the offset? it's in the attributes need the id_use for the name x (we will assume that is in the AST for x) We need the base address for x's AR USE OF REGISTERS IN A REGISTER-BASED ISA For a register-based ISA: What if the target register is already in use? e.g., in x := y + z Strategies: - use a different register - save and restore GENERAL STRATEGY FOR EXPRESSIONS Each expression's value goes To operate on an expression's value BACKGROUND: SSM INSTRUCTIONS ADD t,ot,s,os "M[GPR[t]+ot] = M[GPR[SP]] + M[GPR[s]+os]" SUB t,ot,s,os "M[GPR[t]+ot] = M[GPR[SP]] - M[GPR[s]+os]" MUL s,o "(HI,LO) = M[GPR[SP]] * M[GPR[s]+ o]" DIV s,o "HI = M[GPR[SP]] % M[GPR[s] + o]" and "LO = M[GPR[SP]] / M[GPR[s] + o]" CPW t,ot,s,os "M[GPR[t+ot] = M[GPR[s]+os]" ADDI r,o,i "M[GPR[r]+o] = M[GPR[r]+o] + sgnExt(i)" What limitations on immediate operands? What if the literal doesn't fit? LITERAL TABLE IDEA - Store literal values in - Keep mapping from - Initialize LITERAL TABLE IN EXPRESSION EVALUATION Idea for code for numeric expression, N: 1. Look up N in global table, 2. Receive N's 3. generate a copy (CPW) instruction to copy that to LITERAL TABLE AND BOF DATA SECTION How to get the literals into memory with the assumed offsets? LAYOUT OF AN ACTIVATION RECORD Must save SP, FP, static link, RA and register $r3 Can't have offset of static link at a varying offset from FP Layout 1: offset FP --> [ saved SP ] 0 [ registers FP ]-1 [ static link ]-2 [ RA ]-3 [ local constants ]-4 [ ... ] [ local variables ] [ ... ] [ temporary storage ] SP -->[ ... ] Layout 2: offset [ ... ] [ local variables ] [ ... ] FP -->[ local constants ] 0 [ saved SP ]-1 [ registers FP ]-2 [ static link ]-3 [ RA ]-4 [ temporary storage ] SP -->[ ... ] offset Layout 3: [ saved SP ] 4 [ registers FP ] 3 [ static link ] 2 [ RA ] 1 FP -->[ local constants ] 0 [ ... ] [ local variables ] [ ... ] [ temporary storage ] SP -->[ ... ] Advantages of layout 1: Advantages of layout 2: Advantages of layout 3: TRANSLATION SCHEME FOR SPL DECLARATIONS const c = n; var x; When do blocks start executing? What should be done then? How do we know how much space to allocate? How to initialize constants? How to initialize variables? TRANSLATION SCHEME FOR NUMERIC LITERALS TRANSLATION SCHEME FOR VARIABLE NAMES (AND CONSTANTS) TRANSLATING EXPRESSIONS Abstract syntax of expressions in SPL E ::= E1 o E2 | x | n o ::= + | - | * | / Simplest cases are: TRANSLATION SCHEME FOR BASIC STATEMENTS x := E read x print E GRAMMAR FOR CONDITIONS ::= divisible by | ::= == | != | < | <= | > | >= So the code recursion structure is? Code looks like: RELATIONAL OPERATOR CONDITIONS ::= A design for conditions: Goal: put true of false on top of stack for the value of the condition Consider E1 != E2 [Evaluate E2 to top of stack] [Evaluate E1 to top of stack] # jump past 2 instrs, # if memory[GPR[$sp]] != GPR[$at] BNE $sp, 1, 2 # put 0 (false) on stack SUB $sp, 0, $sp, 0 # jump over next instr JREL 1 # put 1 (true) on stack LIT $sp, 0, 1 # now top of stack has truth value Consider E1 >= E2 [Evaluate E2 to top of stack] [Evaluate E1 to top of stack] SUB $sp, 0, $sp, 1 # SP = E1 - E2 # jump past 2 instrs, if geq BGEZ $sp, 0, 2 # skip 2 instrs # put 0 (false) on stack LIT $sp, 0, 0 # jump over next instr JREL 1 # put 1 (true) on stack LIT $sp, 0, 1 # now top of stack has truth value CODE FOR BINARY RELOP CONDITIONS // file ast.h typedef struct { file_location *file_loc; AST_type type_tag; expr_t expr1; token_t rel_op; expr_t expr2; } rel_op_condition_t; // file gen_code.c // Generate code to eval. condAST // Modifies when executed: $sp code_seq gen_code_relop_cond( rel_op_condition_t condAST, reg_num_type reg) { } ABSTRACT SYNTAX FOR COMPOUND STATEMENTS S ::= begin S* | if C S1* S2* | while C S* So what is the code structure? Code looks like: begin S1 S2 ... end if C S1 if C S1 S2 while C S SUPPORTING PROCEDURES AND CALLS Main issues: - storing their code Why? - knowing exactly where each starts Why? Another issue: - sending the right static link WHERE TO PUT PROCEDURE CODE? Possible layouts in VM's code array: NESTED PROCEDURES ARE A PROBLEM procedure A; procedure B; begin # B's body code... call A # ... # ... end begin # A's body code call B # ... # ... end If lay out the code as [ code for A ] [ code for B ] How do we know the address of B to compile the call to B? What about the other direction? RECURSIVE PROCEDURES, SIMILAR PROBLEM procedure R; begin # R's body code ... call R # ... end Before storing code for R, how do we know where it starts? MUTUAL RECURSION procedure O; begin # O's body code... call E # ... end procedure E; begin # E's body code ... call O # ... One of these must before the other in the code area of the VM... SOLUTION STRATEGIES FOR CALLS [Multiple passes]: 1. Generate code for each procedure (+ store offsets in symbol table, + layout procedure code in memory) 2. Gather table of addresses (map from names to addresses, using offsets and beginning address) 3. Patch up code addresses for calls (+ output code) [Lazy evaluation, labels]: 1. Generate code for each procedure with calls to labels (+ store or update labels in symbol table) (+ output code) GENERAL SOLUTION: MULTIPLE PASSES Problem: where does each procedure start? Solution idea: 1. Compile all procedure code (now know how big each procedure is) 2. Lay out procedure code in memory (now know where each starts) 3. Change each call instruction GENERAL SOLUTION: LABELS Use "labels" to allow Term "label" is from assembly language ; ... jmp L ; ... L: ; ... APPROACHES TO FIXING LABELS Problem: convert labels to addresses (1) Use multiple passes a. Generate code with labels b. Lay out memory for procedures (determine starting addresses) c. Change labels to addresses advantages: disadvantages: (2) Use shared mutable data (lazy eval.) a. labels are unique placeholders, shared by all uses (calls) b. when address is determined, update the placeholder (and all uses are updated) advantages: disadvantages: LABEL DATA STRUCTURE FOR LAZY EVAL // file label.h // ... #include "machine_types.h" typedef struct { bool is_set; unsigned int word_offset; } label; // Return a fresh label that is not set extern label *label_create(); // Requires: lab != NULL // Set the address in the label extern void label_set(label *lab, unsigned int word_offset); // Is the given label set? extern bool label_is_set(label *lab); // Requires: label_is_set(lab) // Return the word offset in lab extern unsigned int label_read(label *lab); CONTEXT VM changes (from the SSM) // machine_types.h // type of 32 bit integers typedef int int_type; // type of 32 bit floats typedef float float_type; typedef float word_type; Instruction changes: Floating point versions of instructions: FADD, FSUB, FMUL, FDIV BFEQ, BFGEZ, BFGTZ, BFLEZ, BFLTZ, FLW, FSW, PFLT, RFLT FMUL and FDIV are 3 register operations FDIV does not do a modulo calculation Conversion instructions: CVT -- converts int to a float RND =- rounds float to an int FLOAT Language differences from PL/0: - no constant declarations, only vars - no procedures - { ... } is a scope ::= ::= { } ::= float ; | bool ; ::= '{' { } '}' | = ; | if ( ) | read ; | write ; LEXICAL GRAMMAR FOR FLOAT LITERALS DIGIT [0-9] SIGN (-|\+) EXPONENTMARKER [eE] NUMBER {NUMBERPART}{EXPONENTPART}? MANTISSA [.]({DIGIT})* NUMBERPART {SIGN}?{DIGIT}+{MANTISSA}? EXPONENTPART {EXPONENTMARKER}{SIGN}?{DIGIT}+ ENHANCED ASTS AS AN IR // changes in // file ast.h #include "type_exp.h" // (possibly signed) floats typedef struct { file_location *file_loc; const char *text; type_exp_e type; word_type value; } number_t; // expr ::= expr op expr // op ::= == | != | < | <= | + | - | * | / typedef struct { file_location *file_loc; type_exp_e type; struct expr_s *expr1; token_t op; struct expr_s *expr2; } binary_op_expr_t; // ... IDENTIFIER USES IN ASTs // E ::= x typedef struct { // name of a constant or variable const char *name; // set during static analysis, // includes info for lexical addr id_use *idu; } ident_t; // S ::= read x typedef struct { AST *ident; } read_t; // S ::= assign x E typedef struct { AST *ident; AST *exp; } assign_t; ID_USE STRUCTURES typedef struct { id_attrs *attrs; unsigned int levelsOutward; } id_use; ID ATTRIBUTES // attributes of idents typedef struct { file_location file_loc; var_type vt; // type // offset from beginning of scope unsigned int loc_offset; } id_attrs; // where: typedef enum {float_t, bool_t} var_type; void scope_check_beginStmt(AST *stmt) { symtab_enter_scope(); // <******* scope_check_varDecls( stmt->data.begin_stmt.vds); AST_list stmts = stmt->data.begin_stmt.stmts; while (!ast_list_is_empty(stmts)) { scope_check_stmt( ast_list_first(stmts)); stmts = ast_list_rest(stmts); } symtab_leave_scope(); // <******** } GENERATING CODE Done in the file gen_code.c - Functions arranged to walk the ASTs - All return a code_seq Useful files: ast.h id_attrs.h id_use.h code.h STEPS FOR CODE GENERATION 1. start with the base cases 2. Write simplest tests possible 3. Design code sequences for the nonterminals involved 4. Write code for each node of the AST 5. Test it: a. check the output machine code b. check the VM's execution EXPRESSIONS What are the base cases? A very simple test: What code sequence do we want? GENERATING CODE Where does execution start? What is the AST for our program? Where do we generate those code sequences? Let's write it! NESTED SCOPES Example in FLOAT: # $Id$ float x; { float y; { float z; z = 0; y = 1; x = 2; } } What kind of code sequence for this?