Code Generation II
Lecture 12
Table of Contents
Review
Questions about the last class?
Quiz
Write the following SimpleC program as an equivalent C program.
def iseven(x : int) -> bool { if ((x) == ((x / 2) * 2)) { return true; } else { return false; } } def main() -> int { in : int; b : bool; printString("Enter an integer: "); in = readInt(); printString("\n"); printBool(iseven(in)); printString("\n"); return 0; }
Assume that printString, printBool, and readInt are already declared and defined the resulting C program and that "stdbool.h" is already included.
Quiz Discussion
- What is program equivalence?
- How can we demonstrate equivalence?
For turing machines, a general algorithm for checking equivalence would be impossible, since it could solve the halting problem.
But we could find a proof of equivalence for a particular program, e.g., by using the formal semantics to prove that the set of instructions always yields the same output.
Functional equivalence: the same inputs yield the same outputs for all inputs, regardless of how those outputs are computed.
What observable behaviors may not be equivalent even for functional equivalent programs?
Examples
- How the store works
- The trick with conditionals and while
- How this works with while as well
How to specify languages with pointers
- Separate the environment (maps symbols to location) from the store (maps locations to values)
- Variable assignment
S, E |- <e> => v l = E.lookup(x) S' = {l: v}S ---------------------- S, E |- <x = e;> => S' l = E.lookup(x) v = S.get(l) ----------------- S, E |- <x> => v l = E.lookup(x) ----------------- S, E |- <&x> => l l = E.lookup(x) p = S.get(l) v = S.get(p) ----------------- S, E |- <*x> => v
Denotational semantics
[[ simplec syntax ]] ::= c syntax // literals [[ n ]] ::= n [[ b ]] ::= b [[ s ]] ::= s // expressions [[ op e ]] ::= op [[e]] [[ e1 op e2 ]] ::= [[e1]] op [[e2]] [[ ( e ) ]] ::= ( [[ e ]] ) // variables [[ x: t; ]] ::= [[ t ]] x ; [[ x ]] ::= x [[ x = e; ]] ::= x = [[ e ]] ; // control-flow and statements [[ while e s ]] ::= while [[ e ]] [[ s ]] [[ if e s1 else s2 ]] ::= if [[ e ]] [[ s1 ]] else [[ s2 ]] [[ if e s ]] ::= [[ if e s else { } ]] [[ e ; ]] ::= [[ e ]] ; [[ { st* } ]] ::= { [[ st* ]] } // functions [[ def f(fs) -> t { d* st* } ]] ::= [[ t ]] f ( [[ fs ]] ) { [[ d* ]] [[ st* ]] } [[ return e; ]] ::= return [[ e ]] ; [[ f(actuals) ]] ::= f ( [[ actuals ]] )
Extending the language
[[ for (e1; e2; e3) s ]] ::= [[ e1; while e2 { s e3; } ]]
Implementing semantics as a visitor
[[ e ]]
is like the call tovisit()
[[ st* ]]
is like iterating over each element and callingvisit()
[[ t ]]
depends on where it is used- Visitors construct strings
In the given Type.java API, these are already defined for =[[ t ]]=: - getDeclarationType for declarations - getAnonymousType for definitions - getReferenceType for function arguments
Example program
def iseven(x : int) -> bool { b : bool; if ((x) == ((x / 2) * 2)) { b = true; } else { b = false; } return b; }
Looking at the parse tree, each node is constructing a string that represents the translation of the construct. Constructs that have child nodes are like a template where the child node's string fills in the placeholders. For example, return true;
[[ return e ; ]] ::= return [[ e ]] ; [[ true ]] ::= true ;
Prologue
@Override public String visitProgram(GrammarParser.ProgramContext ctx) { // TODO: also support adding a prologue to the output and/or make simpleh headers for them (probably needed in order to get type checker to work) // emit the stdbool header for the bool type StringBuilder sb = new StringBuilder(); sb.append("#include \"stdbool.h\"\n"); sb.append("#include \"malloc.h\"\n"); sb.append("int printInt(int);\n"); sb.append("int printBool(bool);\n"); sb.append("int printString(char *);\n"); sb.append("int readInt();\n"); sb.append("bool readBool();\n"); sb.append("char * readString();\n"); for (GrammarParser.ToplevelContext tctx : ctx.toplevel()) { sb.append(visit(tctx)); } return sb.toString(); }
Getting type names
@Override public String visitDef(GrammarParser.DefContext ctx) { StringBuilder sb = new StringBuilder(); String name = ctx.ID().getText(); assert scope.hasSymbol(name); // guaranteed by typechecker Type type = scope.getSymbol(name); assert type instanceof Type.FunctionType; // guaranteed by typechecker Type.FunctionType funType = (Type.FunctionType) type; sb.append("\n"); sb.append(funType.returnType.getAnonymousType()); sb.append(" "); sb.append(name); sb.append("("); assert scope.hasScope(name); // guaranteed by typechecker scope = scope.getScope(name); if (null != ctx.formalParams()) { String delim = ""; for (GrammarParser.FormalParamContext formalParam : ctx.formalParams().formalParam()) { String paramName = formalParam.ID().getText(); assert scope.hasSymbol(paramName); // guaranteed by typechecker Type paramType = scope.getSymbol(paramName); sb.append(delim); sb.append(paramType.getReferenceType(paramName)); delim = ", "; } } sb.append(")"); sb.append(" "); sb.append("{"); sb.append("\n"); for (GrammarParser.DeclContext dctx : ctx.decl()) sb.append(visit(dctx)); for (GrammarParser.StmtContext sctx : ctx.stmt()) sb.append(visit(sctx)); scope = scope.getParent(); sb.append("}"); sb.append("\n"); return sb.toString(); } @Override public String visitDecl(GrammarParser.DeclContext ctx) { StringBuilder sb = new StringBuilder(); String name = ctx.ID().getText(); assert scope.hasSymbol(name); // guaranteed by typechecker Type type = scope.getSymbol(name); sb.append(type.getDeclarationType(name)); sb.append(";"); sb.append("\n"); return sb.toString(); }
The implementation
Relevant skeleton files
Building and running your compiler
# from root of your repository source configure.sh cd src/simplec make java Compiler ../../tests/example.simplec | tee ../../tests/example.c gcc -o ../../tests/example.bin ../../tests/example.c ../../runtime/io.c echo "432" | ../../tests/example.bin
If "make" fails, be sure you have ANTLR in your CLASSPATH and PATH (which source configure.sh
will do for you), check that you have no compilation errors in your *.java
files, and be sure you are in the =src/simplec` directory.
Compiler will emit C code. Well compiling the C code, be sure to link it with io.c to provide definitions for the print and read functions. Execute the resulting binary as usual.
Project
(2.5 weeks)
Implement your code generator
- References to keep on hand
- Keep open the following while implementing the type checker
- The grammar (Grammar.g4)
- The semantic specifications
- The ANTLR visitor implementation tutorial
- Keep open the following while implementing the type checker
- Write test programs as you go
- Start from leaves of the grammar and simpler constructs in the grammar
- Build up to complex ones, once the child nodes are well-tested and working
- Assume the child nodes' are correct, and implement parent visitor on its own
- Submission instructions
- Submit your completed CodeGen.java with your git repo
- Submit your tests cases in your git repo
- Double-check that the compiler is buildable and runnable
- Run
make clean
and/or reclone your repo in another directory to build from scratch
- Run
- Double-check that all test cases have the expected output