UP | HOME

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 to visit()
  • [[ st* ]] is like iterating over each element and calling visit()
  • [[ 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

  • src/simplec/CodeGen.java
    • You implement this for your project
    • Visitors for each grammar construct

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
  • 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
    • Double-check that all test cases have the expected output

Author: Paul Gazzillo

Created: 2023-04-13 Thu 14:59