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 cleanand/or reclone your repo in another directory to build from scratch
- Run
- Double-check that all test cases have the expected output