CS453 Colorado State University ================================================= PA4, (Types,) Control Flow, and Boolean Expressions ================================================= Thursday Match 10, 2011 ------------------------- Today Generating AVR assembly code for ... - booleans - the if statement - the while statement - the negation operator ! - short-circuited && - the equality operator == - BUTTON_LITERAL - Meggy.checkButton - Meggy.delay using a visitor design pattern. ---------------------------- Big Picture This reviews of what was covered last Tuesday. *** What have you done so far? SYNTAX DIRECTED CODE GENERATION Parser actions gather info, print (AVR) instructions, and return RESULT RESULT associated with a nonterminal E:r1 "+" E:r1, now you can do something with r1 and r2. Expression were evaluated ar compile time, WHY COULD WE DO THAT? Why can we not do this in general? Even without variables, what creates a boolean result at run time? (Check button) What are you now going to do? GENERATE AN AST INSTEAD OF DIRECTLY GENERATING CODE. Why is that a good idea? What can we now do better? We can walk over this AST multiple times and perform different functions. eg Type check, generate code, Create symbol table Walking over the AST is done using the Visitor pattern. In this pattern each node of the AST has an accept (sometimes called apply) method, that calls an appropriate visitor method. In our recitation example the plusExp.accept ( or apply) called visitPlusExp (or casePlusExp). In general an AST XYZ node calls the caseXYZ method of the visitor, that the root node was instantiated with. Why is the inheritance hierarchy in the visitor USEFUL?? Why do we structure it this way? reuse, only override small set of methods. A visitXYZ(node) method in a DepthFirstVisitor usually does the following: inXYZ(node) for child c of node in left to right order accept(c); outXYZ(node) Ie, inXYZ is called when the node is first encountered in the DFLR walk, and outXYZ is called when the node is left behind in the DFLR walk. This is often suffucient for code generation purposes (+,-,*,setPixel), but not always: (if, while, &&). *** CODE STRUCTURE In driver, first call the parser to get an AST: mj_ast_parser parser = new mj_ast_parser(lexer); ast.node.Node ast_root = (ast.node.Node)parser.parse().value; Next create a dot file for the AST for debugging purposes: java.io.PrintStream astout = new java.io.PrintStream( new java.io.FileOutputStream(filename + ".ast.dot")); ast_root.accept(new DotVisitor(new PrintWriter(astout))); System.out.println("Printing AST to " + filename + ".ast.dot"); Finally, create an AVRgenVisitor instance and have it visit the AST. java.io.PrintStream avrsout = new java.io.PrintStream( new java.io.FileOutputStream(filename + ".s")); ast_root.accept(new AVRgenVisitor(new PrintWriter(avrsout))); System.out.println("Printing Atmel assembly to " + filename + ".s"); AVRgenVisitor, or whatever you decide to name it, should inherit from DepthFirstVisitor. The first thing to do is move all of the code generation for the main PROLOGUE and EPILOGUE into the AVRgenVisitor. Ideas for where the prologue and epilogue should be generated? The prologue and epilogue can be generated in the driver before and after the AVRgenVisitor visits the AST or in the inMainClass and outMainClass. Where does the code for Meggy.setPixel() go? The Meggy.setPixel() code generation should occur in outMeggySetPixel(). ---------------------------------------------- List of AVR instructions that MJSIM can handle that are relevant to PA4 pop r24 call _Z6DrawPxhhh call _Z12DisplaySlatev ldi r22, 1 push r22 call _Z16CheckButtonsDownv lds r24, Button_A ldi r24,lo8(0) ldi r25,hi8(0) call _Z8delay_msj jmp MJ_L0 tst r24 The conditional branches can only go so far in the code, and the code generated by the reference compiler exceeds that limit. Therefore we have to use jmp sometimes. Notice how I replace a breq with a brne followed by a jmp to handle this issue. cp r24, r25 #WANT breq MJ_L6 brne MJ_L7 jmp MJ_L6 MJ_L7: ... MJ_L6: -------------------------------------------------------- A visitor over the AST will evaluate constant arithmetic expressions, BUT generates code for EVALUATING BOOLEAN EXPRESSIONS ON THE AVR RUN-TIME STACK The main idea is that the code that evaluates any boolean expression should always leave its result on the top of the run-time stack. Notice that the DFLR walk can use the outXYZ method to generate code for a Boolean operator, because code for the children has just been generated and has left the result on the RTS ( we are still evaluating +, -, * expressions at compile time because we still have only constant arithmetic exprs. ) ----------------- AST node classes: TrueExp, FalseExp In the AVRgenVisitor override the following DepthFirstVisitor methods: outTrueExp ldi r24, 1 push r24 outFalseExp ldi r24, 0 push r24 ------------------------------- IfStatement - The if statement needs to check if the condition is true. The key is that the AVR code generated for the if statement does not do the condition computation, it assumes the result of the condition evaluation has been pushed on the AVR run-time stack. - true = any non-zero value, false = 0 *** However true is forced to 1, so that code for boolean operations is simplified *** *** The DFLR visitor encounters ifStat. Here the DFLR inXYZ, outXYZ visitor methods do not suffice. if / | \ B S1 S2 What kind of code should be generated? Ideas? *** Need to handle the If statement with a visit statement so we can control the order that code is generated for its children. First code needs to be generated for the condition, followed by branching instructions, the then block, the else block, and then the end label. THE DETAILS: In the AVRgenVisitor override the following methods: visitIfStatement 1) apply visitor to expression node 2) create labels for true body, false body, and done label trueLabel = new label.Label(); ... // Will want some kind of Label class that provides // a new label name each time constructor is called. // (e.g., L1, L2, L3, ... ) 3) generate code for branch pop top item off stack into register r24 generate code to compare value in r24 to 0 generate branch If r24 is equal to zero then want to branch over the true body to the false body. Problem is that the conditional branch instructions have a limited range and may not make it over the true body. Therefore generate the following code that branches over the jmp if r24 is not equal to zero and otherwise jumps to the false body. brne trueLabel jmp falseLabel 4) generate statement for trueLabel trueLabel + ":" 5) apply visitor to true body 6) generate statement that goes to done label jmp doneLabel 7) generate label, body for false body, and jump to done ??? necessary ??? ??? don't we just drop into it ??? 8) generate statement for done label visitIfStatement in AVRgenVisitor can generate code like the following: # assume result of test is put on stack pop r24 ldi r25,0 cp r24, r25 brne L42 jmp L43 L42: jmp L44 L43: L44: --------------------- NotExp truth table x ! x 0 1 1 0 There is no not operator in AVR, BUT there is a xor operator truth table for xor x y x xor y 0 0 0 0 1 1 1 0 1 1 1 0 How do we use xor to implement NOT? We can implement NOT x with x XOR 1. Hence: outNotExp in AVRgenVisitor can generate the following code: pop r24 ldi r22, 1 eor r24,r22 push r24 ------------------------- WhileStatement *** while / \ bExpr S What is the wiring logic now? SLbl: eval Bexpr on stack if false jump to endLbL genCode for S jump to Slbl endLbL: *** DETAILS Similar to the IfStatement, code generation for the WhileStatement will need to be implemented in the visitWhileStatement() method. loophead: # assume result of test is put on stack pop r24 ldi r25,0 cp r24, r25 brne loopbody jmp loopend loopbody: jmp loophead loopend: ------------------------ AndExp (short-circuited) *** Similar to the IfStatement and WhileStatement, code generation for the AndExp will need to be implemented in the visitAndExp() method. && / \ bExp1 bExp2 can be implemented like if statement if (bExp1) return bExp2 else return false *** DETAILS # assume left op on stack #### if left operand equals zero push it and goto doneLabel pop r24 ; have to pop off of stack into register ldi r25, 0 ; compare left operand with zero cp r24, r25 brne trueLabel push r24 ; result of left operand is result for && jmp doneLabel # Left operand is true so need to evaluate right operand trueLabel: // push result on stack doneLabel: ------------------------ EqualExp, the equality operator == For PA4, we will only be implementing comparison between types that are a single byte. outEqualExp can generate code like the following: pop r25 pop r24 cp r24, r25 breq MJ_L58 # result is false MJ_L57: ldi r24, 0 jmp MJ_L59 # result is true MJ_L58: ldi r24, 1 MJ_L59: push r24 #### must push result on RTS ------------------------ BUTTON_LITERAL Could push the button literal on the stack, but it is a literal so: Do not do any code generation for the button literal. The outMeggyCheckButton() method will grab the button value and generate the appropriate code. ------------------------ Meggy.checkButton Some of the other boolean operations are implemented assuming that true is represented with 1 and false is represented with 0. In C, true can be represented with any non-zero. The boolean value associated with one of the global button vars ( Button_A, Button_B, etc. ) after a CheckButtons call can be a non-zero other than 1. The below code forces a 1 or a 0 to be pushed on the stack for the result of the Meggy.checkButton call. Example output call _Z16CheckButtonsDownv lds r24, Button_A # if button value is zero, push 0 else push 1 tst r24 breq MJ_L6 MJ_L7: ldi r24, 1 push r24 jmp MJ_L8 MJ_L6: push r24 MJ_L8: Need to grab compile-time known value of button expression to load the correct global label. NOTE: MJSIM only works with CheckButtonsDown. It does not and will not work with GetButtons. public void outMeggyCheckButton(MeggyCheckButton node) final ButtonExp button = (ButtonExp)node.getExp(); final String buttonString = button.getLiteral().getText(); if (buttonString.equals("Meggy.Button.B")) { out.println(" lds r24, Button_B"); } else if (buttonString.equals("Meggy.Button.A")) { out.println(" lds r24, Button_A"); ... ------------------------ Meggy.delay The delay call has one parameter, and it is an integer type, which is a two byte type. For now integer expressions are all being computed by the visitor at compile time. The outMeggyDelay method should look up the mapping of its child node to an Integer value and load that integer value into the r24 and r25 registers to pass that value to the delay call. Integer param = mExpVal.get(node.getExp()); Generated code ldi r24, lo(256) ldi r25, hi(256) call _Z8delay_msj ------------------------ wim@cs.colostate.edu, 3/10/11 mstrout@cs.colostate.edu, 2/28/11