Homework B (benchmarks)

This commit is contained in:
Carlos Galindo 2020-01-15 22:38:07 +01:00
parent 72cc3206c4
commit 76fbabdf53
Signed by: kauron
GPG key ID: 83E68706DEE119A3
141 changed files with 7540 additions and 2032 deletions

View file

@ -3,137 +3,160 @@ package cd;
import java.io.File;
public class Config {
public static enum SystemKind {
LINUX,
WINDOWS,
MACOSX
}
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
*/
public static final String ASMEXT = ".s";
/** Defines the extension used for binary files on this platform. */
public static final String BINARYEXT;
/** Defines the name of the main function to be used in .s file */
public static final String MAIN;
/** Defines the name of the printf function to be used in .s file */
public static final String PRINTF;
/** Defines the name of the scanf function to be used in .s file */
public static final String SCANF;
/** Defines the name of the calloc function to be used in .s file */
public static final String CALLOC;
/** Defines the name of the exit function to be used in .s file */
public static final String EXIT;
/** The assembler directive used to define a constant string */
public static final String DOT_STRING;
/** The assembler directive used to define a constant int */
public static final String DOT_INT;
/** The assembler directive used to start the text section */
public static final String TEXT_SECTION;
/** The assembler directive used to start the section for integer data */
public static final String DATA_INT_SECTION;
/**
* The assembler directive used to start the section for string data (if
* different from {@link #DATA_INT_SECTION}
*/
public static final String DATA_STR_SECTION;
/** Comment separator used in assembly files */
public static final String COMMENT_SEP;
/**
* Defines the assembler command to use. Should be a string array where each
* entry is one argument. Use the special string "$0" to refer to the output
* file, and $1 to refer to the ".s" file.
*/
public static final String[] ASM;
/**
* The directory from which to run the assembler. In a CYGWIN installation,
* this can make a big difference!
*/
public static final File ASM_DIR;
/**
* sizeof a pointer in bytes in the target platform.
*/
public static final int SIZEOF_PTR = 4;
/**
* Name of java executable in JRE path
*/
public static enum SystemKind {
LINUX,
WINDOWS,
MACOSX
}
public static final int TRUE = 1;
public static final int FALSE = 0;
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
*/
public static final String ASMEXT = ".s";
/**
* Defines the extension used for binary files on this platform.
*/
public static final String BINARYEXT;
/**
* Defines the name of the main function to be used in .s file
*/
public static final String MAIN;
/**
* Defines the name of the printf function to be used in .s file
*/
public static final String PRINTF;
/**
* Defines the name of the scanf function to be used in .s file
*/
public static final String SCANF;
/**
* Defines the name of the calloc function to be used in .s file
*/
public static final String CALLOC;
/**
* Defines the name of the exit function to be used in .s file
*/
public static final String EXIT;
/**
* The assembler directive used to define a constant string
*/
public static final String DOT_STRING;
/**
* The assembler directive used to define a constant int
*/
public static final String DOT_INT;
/**
* The assembler directive used to start the text section
*/
public static final String TEXT_SECTION;
/**
* The assembler directive used to start the section for integer data
*/
public static final String DATA_INT_SECTION;
/**
* The assembler directive used to start the section for string data (if
* different from {@link #DATA_INT_SECTION}
*/
public static final String DATA_STR_SECTION;
/**
* Comment separator used in assembly files
*/
public static final String COMMENT_SEP;
/**
* Defines the assembler command to use. Should be a string array where each
* entry is one argument. Use the special string "$0" to refer to the output
* file, and $1 to refer to the ".s" file.
*/
public static final String[] ASM;
/**
* The directory from which to run the assembler. In a CYGWIN installation,
* this can make a big difference!
*/
public static final File ASM_DIR;
/**
* sizeof a pointer in bytes in the target platform.
*/
public static final int SIZEOF_PTR = 4;
/**
* Name of java executable in JRE path
*/
public static final String JAVA_EXE;
static {
final String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows") || os.contains("nt")) {
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
// These are set up for a Cygwin installation on C:,
// you can change as needed.
ASM = new String[]{"gcc", "-o", "$0", "$1"};
ASM_DIR = new File("C:\\CYGWIN\\BIN");
JAVA_EXE = "javaw.exe";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
else if(os.contains("mac os x") || os.contains("darwin")) {
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".asciz";
DOT_INT = ".long";
TEXT_SECTION = ".text";
DATA_INT_SECTION = ".data";
DATA_STR_SECTION = ".cstring";
COMMENT_SEP = "#";
}
else {
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";
SCANF = "scanf";
CALLOC = "calloc";
EXIT = "exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
}
static {
final String os = System.getProperty("os.name").toLowerCase();
if (os.contains("windows") || os.contains("nt")) {
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
// These are set up for a Cygwin installation on C:,
// you can change as needed.
ASM = new String[]{"gcc", "-o", "$0", "$1"};
ASM_DIR = new File("C:\\CYGWIN\\BIN");
JAVA_EXE = "javaw.exe";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
} else if (os.contains("mac os x") || os.contains("darwin")) {
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".asciz";
DOT_INT = ".long";
TEXT_SECTION = ".text";
DATA_INT_SECTION = ".data";
DATA_STR_SECTION = ".cstring";
COMMENT_SEP = "#";
} else {
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";
SCANF = "scanf";
CALLOC = "calloc";
EXIT = "exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
}
}

View file

@ -1,6 +1,6 @@
package cd;
import cd.backend.codegen.AstCodeGenerator;
import cd.backend.codegen.CfgCodeGenerator;
import cd.frontend.parser.JavaliAstVisitor;
import cd.frontend.parser.JavaliLexer;
import cd.frontend.parser.JavaliParser;
@ -8,9 +8,13 @@ import cd.frontend.parser.JavaliParser.UnitContext;
import cd.frontend.parser.ParseFailure;
import cd.frontend.semantic.SemanticAnalyzer;
import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.AstOptimizer;
import cd.transform.CfgBuilder;
import cd.util.debug.AstDump;
import cd.util.debug.CfgDump;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
@ -20,26 +24,29 @@ import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* The main entrypoint for the compiler. Consists of a series
* of routines which must be invoked in order. The main()
* routine here invokes these routines, as does the unit testing
* code. This is not the <b>best</b> programming practice, as the
* series of calls to be invoked is duplicated in two places in the
* code, but it will do for now. */
/**
* The main entrypoint for the compiler. Consists of a series of routines which must be invoked in
* order. The main() routine here invokes these routines, as does the unit testing code. This is not
* the <b>best</b> programming practice, as the series of calls to be invoked is duplicated in two
* places in the code, but it will do for now.
*/
public class Main {
// Set to non-null to write debug info out
public Writer debug = null;
// Set to non-null to write dump of control flow graph
public File cfgdumpbase;
/** Symbol for the Main type */
public Symbol.ClassSymbol mainType;
/** List of all type symbols, used by code generator. */
public List<TypeSymbol> allTypeSymbols;
/**
* Symbol for the Main type
*/
public ClassSymbol mainType;
/**
* List of all type symbols, used by code generator.
*/
public List<TypeSymbol> allTypeSymbols;
public void debug(String format, Object... args) {
if (debug != null) {
@ -53,23 +60,28 @@ public class Main {
}
}
}
/** Parse command line, invoke compile() routine */
/**
* Parse command line, invoke compile() routine
*/
public static void main(String args[]) throws IOException {
Main m = new Main();
for (String arg : args) {
if (arg.equals("-d"))
m.debug = new OutputStreamWriter(System.err);
else {
if (m.debug != null)
m.cfgdumpbase = new File(arg);
FileReader fin = new FileReader(arg);
// Parse:
List<ClassDecl> astRoots = m.parse(fin);
// Run the semantic check:
m.semanticCheck(astRoots);
// Generate code:
String sFile = arg + Config.ASMEXT;
try (FileWriter fout = new FileWriter(sFile)) {
@ -78,51 +90,63 @@ public class Main {
}
}
}
/** Parses an input stream into an AST
* @throws IOException */
/**
* Parses an input stream into an AST
*
* @throws IOException
*/
public List<ClassDecl> parse(Reader reader) throws IOException {
List<ClassDecl> result = new ArrayList<ClassDecl>();
try {
JavaliLexer lexer = new JavaliLexer(new ANTLRInputStream(reader));
JavaliParser parser = new JavaliParser(new CommonTokenStream(lexer));
parser.setErrorHandler(new BailErrorStrategy());
UnitContext unit = parser.unit();
JavaliAstVisitor visitor = new JavaliAstVisitor();
visitor.visit(unit);
result = visitor.classDecls;
result = visitor.classDecls;
} catch (ParseCancellationException e) {
ParseFailure pf = new ParseFailure(0, "?");
pf.initCause(e);
throw pf;
}
debug("AST Resulting From Parsing Stage:");
dumpAst(result);
return result;
}
// TODO: decide how to do/call optimization
public void semanticCheck(List<ClassDecl> astRoots) {
{
new SemanticAnalyzer(this).check(astRoots);
}
}
public void generateCode(List<ClassDecl> astRoots, Writer out) {
{
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
cg.go(astRoots);
}
new SemanticAnalyzer(this).check(astRoots);
// Build control flow graph:
for (ClassDecl cd : astRoots)
for (MethodDecl md : cd.methods()) {
new CfgBuilder().build(md);
}
CfgDump.toString(astRoots, ".cfg", cfgdumpbase, false);
// Optimize
new AstOptimizer().go(astRoots, allTypeSymbols);
CfgDump.toString(astRoots, ".cfgOPT", cfgdumpbase, false);
}
/** Dumps the AST to the debug stream */
public void generateCode(List<ClassDecl> astRoots, Writer out) {
CfgCodeGenerator cg = new CfgCodeGenerator(this, out);
cg.go(astRoots);
}
/**
* Dumps the AST to the debug stream
*/
private void dumpAst(List<ClassDecl> astRoots) throws IOException {
if (this.debug == null) return;
if (this.debug == null)
return;
this.debug.write(AstDump.toString(astRoots));
}
}

View file

@ -1,14 +0,0 @@
package cd;
/** TAs insert this to mark code that students need to write */
public class ToDoException extends RuntimeException {
private static final long serialVersionUID = 4054810321239901944L;
public ToDoException() {
}
public ToDoException(String message) {
super(message);
}
}

View file

@ -10,10 +10,10 @@ public enum ExitCode {
INFINITE_LOOP(6),
DIVISION_BY_ZERO(7),
INTERNAL_ERROR(22);
public final int value;
private ExitCode(int value) {
this.value = value;
private ExitCode(int value) {
this.value = value;
}
}

View file

@ -15,25 +15,33 @@ public class AssemblyEmitter {
this.out = out;
}
/** Creates an constant operand. */
/**
* Creates an constant operand.
*/
static String constant(int i) {
return "$" + i;
}
/** Creates an constant operand with the address of a label. */
/**
* Creates an constant operand with the address of a label.
*/
static String labelAddress(String lbl) {
return "$" + lbl;
}
/** Creates an operand relative to another operand. */
/**
* Creates an operand relative to another operand.
*/
static String registerOffset(int offset, Register reg) {
return String.format("%d(%s)", offset, reg);
}
/** Creates an operand addressing an item in an array */
/**
* Creates an operand addressing an item in an array
*/
static String arrayAddress(Register arrReg, Register idxReg) {
final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
// vptr and length
// vptr and length
final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem
return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul);
}
@ -90,10 +98,6 @@ public class AssemblyEmitter {
emit(op, constant(src), dest);
}
void emit(String op, int src, String dest) {
emit(op, constant(src), dest);
}
void emit(String op, String dest) {
emitRaw(op + " " + dest);
}
@ -106,10 +110,6 @@ public class AssemblyEmitter {
emit(op, constant(dest));
}
void emit(String op, int src, int dest) {
emit(op, constant(src), constant(dest));
}
void emitMove(Register src, String dest) {
emitMove(src.repr, dest);
}
@ -127,10 +127,6 @@ public class AssemblyEmitter {
emit("movl", src, dest);
}
void emitMove(int src, Register dest) {
emitMove(constant(src), dest);
}
void emitLoad(int srcOffset, Register src, Register dest) {
emitMove(registerOffset(srcOffset, src), dest.repr);
}
@ -143,10 +139,6 @@ public class AssemblyEmitter {
emitMove(src, registerOffset(destOffset, dest));
}
void emitStore(int src, int destOffset, Register dest) {
emitStore(constant(src), destOffset, dest);
}
void emitConstantData(String data) {
emitRaw(String.format("%s %s", Config.DOT_INT, data));
}

View file

@ -2,13 +2,14 @@ package cd.backend.codegen;
public class AssemblyFailedException extends RuntimeException {
private static final long serialVersionUID = -5658502514441032016L;
public final String assemblerOutput;
public AssemblyFailedException(
String assemblerOutput) {
super("Executing assembler failed.\n"
+ "Output:\n"
+ assemblerOutput);
this.assemblerOutput = assemblerOutput;
}
}
}

View file

@ -2,43 +2,42 @@ package cd.backend.codegen;
import cd.Config;
import cd.Main;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.*;
import java.io.Writer;
import java.util.*;
import java.util.Collections;
import java.util.List;
import static cd.Config.MAIN;
import static cd.Config.*;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.AssemblyEmitter.registerOffset;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;
public class AstCodeGenerator {
/** Constant representing the boolean TRUE as integer */
static final int TRUE = 1;
/** Constant representing the boolean FALSE as integer */
static final int FALSE = 0;
/** Size of any variable in assembly
* Primitive variables take up 4 bytes (booleans are integers)
* Reference variables are a 4 byte pointer
*/
static final int VAR_SIZE = 4;
RegsNeededVisitor rnv;
ExprGenerator eg;
StmtGenerator sg;
protected RegsNeededVisitor rnv;
protected ExprGenerator eg;
protected StmtGenerator sg;
protected final Main main;
final AssemblyEmitter emit;
final RegisterManager rm = new RegisterManager();
protected final AssemblyEmitter emit;
protected final RegisterManager rm = new RegisterManager();
protected ExprGeneratorRef egRef;
protected StmtGeneratorRef sgRef;
AstCodeGenerator(Main main, Writer out) {
initMethodData();
main.allTypeSymbols = new ArrayList<>();
this.emit = new AssemblyEmitter(out);
this.main = main;
this.rnv = new RegsNeededVisitor();
@ -52,14 +51,15 @@ public class AstCodeGenerator {
}
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
return new AstCodeGenerator(main, out);
return new AstCodeGeneratorRef(main, out);
}
/**
* Main method. Causes us to emit x86 assembly corresponding to {@code ast}
* into {@code file}. Throws a {@link RuntimeException} should any I/O error
* occur.
*
*
* <p>
* The generated file will be divided into two sections:
* <ol>
@ -70,103 +70,693 @@ public class AstCodeGenerator {
* </ol>
*/
public void go(List<? extends ClassDecl> astRoots) {
// Find main type and assign to main.mainType
// Add array types to the types list to lookup later
for (ClassDecl decl : astRoots) {
if (decl.name.equals("Main")) {
main.mainType = decl.sym;
}
main.allTypeSymbols.add(new ArrayTypeSymbol(decl.sym));
}
main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.intType));
main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.booleanType));
main.allTypeSymbols.add(new ArrayTypeSymbol(ClassSymbol.objectType));
emitPrefix();
for (ClassDecl ast : astRoots) {
sg.gen(ast);
}
}
private void emitPrefix() {
// Emit some useful string constants (copied from old HW1 method declaration)
protected void initMethodData() {
rm.initRegisters();
}
protected void emitMethodSuffix(boolean returnNull) {
if (returnNull)
emit.emit("movl", "$0", Register.EAX);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
}
class AstCodeGeneratorRef extends AstCodeGenerator {
/**
* The address of the this ptr relative to the BP. Note that the this ptr is
* always the first argument. Default offset value is 8 but this can change
* depending on the number of parameters pushed on the stack.
*/
protected int THIS_OFFSET = 8;
/**
* Name of the internal Javali$CheckCast() helper function we generate.
*/
static final String CHECK_CAST = "Javali$CheckCast";
/**
* Name of the internal Javali$CheckNull() helper function we generate.
*/
static final String CHECK_NULL = "Javali$CheckNull";
/**
* Name of the internal Javali$CheckNonZero() helper function we generate.
*/
static final String CHECK_NON_ZERO = "Javali$CheckNonZero";
/**
* Name of the internal Javali$CheckArraySize() helper function we generate.
*/
static final String CHECK_ARRAY_SIZE = "Javali$CheckArraySize";
/**
* Name of the internal Javali$CheckArrayBounds() helper function we
* generate.
*/
static final String CHECK_ARRAY_BOUNDS = "Javali$CheckArrayBounds";
/**
* Name of the internal Javali$Alloc() helper function we generate.
*/
static final String ALLOC = "Javali$Alloc";
/**
* Name of the internal Javali$PrintNewLine() helper function we generate.
*/
static final String PRINT_NEW_LINE = "Javali$PrintNewLine";
/**
* Name of the internal Javali$PrintInteger() helper function we generate.
*/
static final String PRINT_INTEGER = "Javali$PrintInteger";
/**
* Name of the internal Javali$ReadInteger() helper function we generate.
*/
static final String READ_INTEGER = "Javali$ReadInteger";
public AstCodeGeneratorRef(Main main, Writer out) {
super(main, out);
this.egRef = new ExprGeneratorRef(this);
this.eg = this.egRef;
this.sgRef = new StmtGeneratorRef(this);
this.sg = this.sgRef;
}
protected void emitPrefix(List<? extends ClassDecl> astRoots) {
// compute method and field offsets
for (ClassDecl ast : astRoots) {
computeFieldOffsets(ast.sym);
computeVtableOffsets(ast.sym);
}
// emit vtables
for (TypeSymbol ts : main.allTypeSymbols)
emitVtable(ts);
// Emit some useful string constants and static data:
emit.emitRaw(Config.DATA_STR_SECTION);
emit.emitLabel("STR_NL");
emit.emitRaw(Config.DOT_STRING + " \"\\n\"");
emit.emitLabel("STR_D");
emit.emitRaw(Config.DOT_STRING + " \"%d\"");
// Define Object, Object[], int, int[], boolean and boolean[]
List<TypeSymbol> elementTypes = new ArrayList<>();
elementTypes.add(ClassSymbol.objectType);
elementTypes.add(PrimitiveTypeSymbol.intType);
elementTypes.add(PrimitiveTypeSymbol.booleanType);
emit.emitLabel("STR_F");
emit.emitRaw(Config.DOT_STRING + " \"%.5f\"");
emit.emitLabel("SCANF_STR_F");
emit.emitRaw(Config.DOT_STRING + " \"%f\"");
emit.emitRaw(Config.DATA_INT_SECTION);
for (TypeSymbol type : elementTypes) {
// type vtable
emit.emitLabel(Label.type(type));
emit.emitConstantData("0"); // Supertype (null)
// array vtable
emit.emitLabel(Label.type(new ArrayTypeSymbol(type)));
emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
emit.emitConstantData(Label.type(type)); // Element type
emit.emitRaw(Config.TEXT_SECTION);
// Generate a helper method for checking casts:
// It takes first a vtable and second an object ptr.
{
Register obj = RegisterManager.CALLER_SAVE[0];
Register cls = RegisterManager.CALLER_SAVE[1];
String looplbl = emit.uniqueLabel();
String donelbl = emit.uniqueLabel();
String faillbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_CAST + " function");
emit.emitLabel(CHECK_CAST);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, cls);
emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, obj);
emit.emit("cmpl", constant(0), obj);
emit.emit("je", donelbl); // allow null objects to pass
emit.emitLoad(0, obj, obj); // load vtbl of object
emit.emitLabel(looplbl);
emit.emit("cmpl", obj, cls);
emit.emit("je", donelbl);
emit.emit("cmpl", constant(0), obj);
emit.emit("je", faillbl);
emit.emitLoad(0, obj, obj); // load parent vtable
emit.emit("jmp", looplbl);
emit.emitLabel(faillbl);
emit.emitStore(constant(ExitCode.INVALID_DOWNCAST.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(donelbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Emit the new Main().main() code to start the program:
// Generate a helper method for checking for null ptrs:
{
String oknulllbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_NULL + " function");
emit.emitLabel(CHECK_NULL);
// 1. Enter TEXT and start the program
emit.emitRaw(Config.TEXT_SECTION);
emit.emit(".globl", MAIN);
emit.emitLabel(MAIN);
// 1.1. Prepare first frame
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
// 2. Create main variable
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jne", oknulllbl);
emit.emitStore(constant(ExitCode.NULL_POINTER.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(oknulllbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking that we don't divide by zero:
{
String oknzlbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_NON_ZERO + " function");
emit.emitLabel(CHECK_NON_ZERO);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jne", oknzlbl);
emit.emitStore(constant(ExitCode.DIVISION_BY_ZERO.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(oknzlbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking array size:
{
String okunqlbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_ARRAY_SIZE + " function");
emit.emitLabel(CHECK_ARRAY_SIZE);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("cmpl", constant(0), registerOffset(SIZEOF_PTR * 2, BASE_REG));
emit.emit("jge", okunqlbl);
emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
emit.emitLabel(okunqlbl);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for checking array bounds:
{
Register arr = RegisterManager.CALLER_SAVE[0];
Register idx = RegisterManager.CALLER_SAVE[1];
String faillbl = emit.uniqueLabel();
emit.emitCommentSection(CHECK_ARRAY_BOUNDS + " function");
emit.emitLabel(CHECK_ARRAY_BOUNDS);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(SIZEOF_PTR * 3, BASE_REG, idx);
emit.emitLoad(SIZEOF_PTR * 2, BASE_REG, arr);
emit.emit("cmpl", constant(0), idx); // idx < 0
emit.emit("jl", faillbl);
emit.emit("cmpl", registerOffset(Config.SIZEOF_PTR, arr), idx); // idx >= len
emit.emit("jge", faillbl);
// done
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
// fail
emit.emitLabel(faillbl);
emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
emit.emit("call", Config.EXIT);
}
// Generate a helper method for allocating objects/arrays
{
Register size = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(ALLOC + " function");
emit.emitLabel(ALLOC);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(8, BASE_REG, size);
emit.emitStore(size, 0, STACK_REG);
emit.emitStore(constant(1), 4, STACK_REG);
emit.emit("call", Config.CALLOC);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for printing a new line
{
emit.emitCommentSection(PRINT_NEW_LINE + " function");
emit.emitLabel(PRINT_NEW_LINE);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitStore("$STR_NL", 0, STACK_REG);
emit.emit("call", Config.PRINTF);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for printing an integer
{
Register temp = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(PRINT_INTEGER + " function");
emit.emitLabel(PRINT_INTEGER);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emitLoad(8, BASE_REG, temp);
emit.emitStore(temp, 4, STACK_REG);
emit.emitStore("$STR_D", 0, STACK_REG);
emit.emit("call", Config.PRINTF);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate a helper method for reading an integer
{
Register number = RegisterManager.CALLER_SAVE[0];
emit.emitCommentSection(READ_INTEGER + " function");
emit.emitLabel(READ_INTEGER);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", constant(-16), STACK_REG);
emit.emit("sub", constant(16), STACK_REG);
emit.emit("leal", registerOffset(8, STACK_REG), number);
emit.emitStore(number, 4, STACK_REG);
emit.emitStore("$STR_D", 0, STACK_REG);
emit.emit("call", SCANF);
emit.emitLoad(8, STACK_REG, Register.EAX);
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
// Generate AST for main() method:
// new Main().main()
Ast.NewObject newMain = new Ast.NewObject("Main");
newMain.type = main.mainType;
Register mainLocation = eg.visit(newMain, null);
// 3. Call main()
emit.emit("push", mainLocation);
for (ClassSymbol sym = main.mainType; sym != ClassSymbol.objectType; sym = sym.superClass)
if (sym.methods.getOrDefault("main", null) != null) {
emit.emit("call", Label.method(sym, sym.methods.get("main")));
break;
}
emitMethodSuffix(true);
Ast.MethodCallExpr mce = new Ast.MethodCallExpr(newMain, "main", Collections.<Expr>emptyList());
Ast.MethodCall callMain = new Ast.MethodCall(mce);
mce.sym = main.mainType.getMethod("main");
// Emit the main() method:
// new Main().main();
emit.emitCommentSection("main() function");
emit.emitRaw(".globl " + MAIN);
emit.emitLabel(MAIN);
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(8), STACK_REG);
emit.emit("and", -16, STACK_REG);
sg.gen(callMain);
emit.emit("movl", constant(ExitCode.OK.value), Register.EAX); // normal termination:
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
void initMethodData() {
rm.initRegisters();
@Override
public void go(List<? extends ClassDecl> astRoots) {
emitPrefix(astRoots);
super.go(astRoots);
}
/**
* Computes the vtable offset for each method defined in the class
* {@code sym}.
*/
protected int computeVtableOffsets(ClassSymbol sym) {
if (sym == null)
return 0;
if (sym.totalMethods != -1)
return sym.totalMethods;
int index = computeVtableOffsets(sym.superClass);
for (MethodSymbol ms : sym.methods.values()) {
assert ms.vtableIndex == -1;
if (ms.overrides != null)
ms.vtableIndex = ms.overrides.vtableIndex;
else
ms.vtableIndex = index++;
}
sym.totalMethods = index;
return index;
}
/**
* Computes the offset for each field.
*/
protected int computeFieldOffsets(ClassSymbol sym) {
if (sym == null)
return 0;
if (sym.totalFields != -1)
return sym.totalFields;
int index = computeFieldOffsets(sym.superClass);
for (VariableSymbol fs : sym.fields.values()) {
assert fs.offset == -1;
// compute offset in bytes; note that 0 is the vtable
fs.offset = (index * SIZEOF_PTR) + SIZEOF_PTR;
index++;
}
sym.totalFields = index;
sym.sizeof = (sym.totalFields + 1) * Config.SIZEOF_PTR;
return index;
}
private void collectVtable(MethodSymbol[] vtable, ClassSymbol sym) {
if (sym.superClass != null)
collectVtable(vtable, sym.superClass);
for (MethodSymbol ms : sym.methods.values())
vtable[ms.vtableIndex] = ms;
}
protected void emitVtable(TypeSymbol ts) {
if (ts instanceof ClassSymbol) {
ClassSymbol cs = (ClassSymbol) ts;
// Collect the vtable:
MethodSymbol[] vtable = new MethodSymbol[cs.totalMethods];
collectVtable(vtable, cs);
// Emit vtable for this class:
emit.emitLabel(vtable(cs));
if (cs.superClass != null)
emit.emitConstantData(vtable(cs.superClass));
else
emit.emitConstantData("0");
for (int i = 0; i < cs.totalMethods; i++)
emit.emitConstantData(methodLabel(vtable[i]));
} else if (ts instanceof ArrayTypeSymbol) {
ArrayTypeSymbol as = (ArrayTypeSymbol) ts;
emit.emitLabel(vtable(as));
emit.emitConstantData(vtable(ClassSymbol.objectType));
}
}
protected String vtable(TypeSymbol ts) {
if (ts instanceof ClassSymbol) {
return "vtable_" + ((ClassSymbol) ts).name;
} else if (ts instanceof ArrayTypeSymbol) {
return "vtablearr_" + ((ArrayTypeSymbol) ts).elementType.name;
} else {
throw new RuntimeException("No vtable for " + ts.name);
}
}
void emitMethodSuffix(boolean returnNull) {
@Override
protected void initMethodData() {
THIS_OFFSET = 8;
bytes = 0;
super.initMethodData();
}
protected int padding(int numberOfParameters) {
int padding = (bytes + numberOfParameters * Config.SIZEOF_PTR + 15) & 0xFFFFFFF0;
return padding - bytes - numberOfParameters * Config.SIZEOF_PTR;
}
protected void push(int padding) {
if (padding > 0) {
emit.emit("sub", padding, STACK_REG);
bytes += padding;
}
}
protected void pop(int padding) {
if (padding > 0) {
emit.emit("add", padding, STACK_REG);
bytes -= padding;
}
assert bytes >= 0;
}
protected void push(String reg) {
emit.emit("push", reg);
bytes += Config.SIZEOF_PTR;
}
protected void pop(String reg) {
emit.emit("pop", reg);
bytes -= Config.SIZEOF_PTR;
assert bytes >= 0;
}
protected void restoreCalleeSaveRegs() {
for (int reg = RegisterManager.CALLEE_SAVE.length - 1; reg >= 0; reg--) {
emit.emit("pop", RegisterManager.CALLEE_SAVE[reg]);
}
}
protected void storeCalleeSaveRegs() {
bytes = 0;
for (int reg = 0; reg < RegisterManager.CALLEE_SAVE.length; reg++) {
emit.emit("push", RegisterManager.CALLEE_SAVE[reg]);
bytes += Config.SIZEOF_PTR;
}
}
protected void restoreCallerSaveRegs(Register res) {
for (int reg = RegisterManager.CALLER_SAVE.length - 1; reg >= 0; reg--) {
if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
continue; // not in use
if (RegisterManager.CALLER_SAVE[reg].equals(res))
continue; // contains our result
pop(RegisterManager.CALLER_SAVE[reg].repr);
}
}
protected void storeCallerSaveRegs(Register res) {
for (int reg = 0; reg < RegisterManager.CALLER_SAVE.length; reg++) {
if (!rm.isInUse(RegisterManager.CALLER_SAVE[reg]))
continue; // not in use
if (RegisterManager.CALLER_SAVE[reg].equals(res))
continue; // will contain our result
push(RegisterManager.CALLER_SAVE[reg].repr);
}
}
protected int emitCallPrefix(Register res, int numberOfParameters) {
storeCallerSaveRegs(res);
int padding = padding(numberOfParameters);
push(padding);
return padding;
}
protected void emitCallSuffix(Register res, int numberOfParameters,
int padding) {
pop(numberOfParameters * Config.SIZEOF_PTR + padding);
if (res != null) {
emit.emitMove(Register.EAX, res);
}
restoreCallerSaveRegs(res);
}
/**
* Generates code which evaluates {@code ast} and branches to {@code lbl} if
* the value generated for {@code ast} is false.
*/
protected void genJumpIfFalse(Expr ast, String lbl) {
// A better way to implement this would be with a separate
// visitor.
Register reg = eg.gen(ast);
emit.emit("cmpl", "$0", reg);
emit.emit("je", lbl);
rm.releaseRegister(reg);
}
/**
* Used to store the temporaries. We grow our stack dynamically, we allocate
* "temporary" values on this stack during method execution. Values can be
* stored and retrieved using {@link #push(String)} and {@link #pop(String)}
* , which use the program stack.
*/
protected int bytes = 0;
protected String methodLabel(MethodSymbol msym) {
return "meth_" + msym.owner.name + "_" + msym.name;
}
protected void emitMethodPrefix(MethodDecl ast) {
// Emit the label for the method:
emit.emitRaw(Config.TEXT_SECTION);
emit.emitCommentSection(String.format("Method %s.%s", ast.sym.owner.name,
ast.name));
emit.emitRaw(".globl " + methodLabel(ast.sym));
emit.emitLabel(methodLabel(ast.sym));
// Compute the size and layout of the stack frame. Our
// frame looks like (the numbers are relative to our ebp):
//
// (caller's locals)
// (padding)
// arg 0 (this ptr)
// ...
// 12 arg N - 1
// 8 arg N
// 4 linkage ptr (return address)
// 0 saved ebp
// -4 locals
// (callee's arguments + temporaries)
//
// We allocate on the stack during the course of
// a function call using push(...) and pop(...) instructions.
//
// Stack slots fall into several
// categories:
// - "Linkage": overhead for function calls.
// This includes the return address and saved ebp.
// - locals: these store the value of user-declared local
// variables.
// - temporaries: these are stack slots used to store
// values during expression evaluation when we run out
// of registers, saving caller-saved registers, and
// other miscellaneous purposes.
// - padding: only there to ensure the stack size is a multiple
// of 16.
// - arguments: values we will pass to functions being
// invoked.
//
// We calculate all address relative to the base pointer.
// Initialize method-specific data
initMethodData();
// Assign parameter offsets:
// As shown above, these start from 8.
// Being able to evaluate parameters like in Java
// with left-to-right evaluation order they result
// on the stack in reversed order.
// The "this" parameter is the first pushed on the stack
// thus receiving higher offset.
int paramOffset = Config.SIZEOF_PTR * 2;
for (int i = ast.sym.parameters.size() - 1; i >= 0; i--) {
final VariableSymbol param = ast.sym.parameters.get(i);
assert param.offset == -1;
param.offset = paramOffset;
paramOffset += Config.SIZEOF_PTR;
}
THIS_OFFSET = paramOffset;
paramOffset += Config.SIZEOF_PTR;
// First few slots are reserved for caller save regs:
int localSlot = RegisterManager.CALLER_SAVE.length * RegisterManager.SIZEOF_REG;
// Assign local variable offsets:
emit.emitComment(String.format("%-10s Offset", "Variable"));
for (VariableSymbol local : ast.sym.locals.values()) {
assert local.offset == -1;
local.offset = -localSlot;
localSlot += Config.SIZEOF_PTR;
emit.emitComment(String.format("%-10s %d", local, local.offset));
}
// Round up stack size to make it a multiple of 16.
// The actual amount passed to the enter instruction is 8
// less, however, because it wants to know the amount
// in addition to the linkage ptr and saved ebp.
int implicit = Config.SIZEOF_PTR * 2;
int stackSize = (implicit + localSlot + 15) & 0xFFFFFFF0;
stackSize -= implicit;
emit.emitComment(String.format("implicit=%d localSlot=%d sum=%d", implicit,
localSlot, implicit + localSlot));
// emit.emitRaw(String.format("enter $%d, $0", stackSize));
emit.emit("push", BASE_REG);
emit.emitMove(STACK_REG, BASE_REG);
emit.emit("sub", constant(stackSize), STACK_REG);
emit.emit("and", -16, STACK_REG);
storeCalleeSaveRegs();
// zero-initialize locals
for (VariableSymbol local : ast.sym.locals.values()) {
emit.emitMove(constant(0), registerOffset(local.offset, BASE_REG));
}
}
@Override
protected void emitMethodSuffix(boolean returnNull) {
if (returnNull)
emit.emit("movl", 0, Register.EAX);
emit.emitRaw("leave");
emit.emit("movl", "$0", Register.EAX);
restoreCalleeSaveRegs();
emit.emitMove(BASE_REG, STACK_REG);
emit.emit("pop", BASE_REG);
emit.emitRaw("ret");
}
static class Label {
static String type(TypeSymbol symbol) {
if (symbol instanceof ClassSymbol)
return String.format("type_%s", symbol);
else if (symbol instanceof ArrayTypeSymbol)
return String.format("array_%s", ((ArrayTypeSymbol) symbol).elementType);
else if (symbol instanceof PrimitiveTypeSymbol)
return String.format("primive_%s", symbol);
throw new RuntimeException("Unimplemented type symbol");
}
static String method(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
return String.format("method_%s_%s", classSymbol.name, methodSymbol.name);
}
static String returnMethod(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
return String.format("return_%s", method(classSymbol, methodSymbol));
}
}
}
class AstCodeGeneratorNop90 extends AstCodeGeneratorRef {
public AstCodeGeneratorNop90(Main main, Writer out, CfgCodeGenerator cfgCg) {
super(main, out);
this.egRef = new ExprGeneratorNop90(this, cfgCg);
this.eg = this.egRef;
this.sgRef = new StmtGeneratorNop90(this, cfgCg);
this.sg = this.sgRef;
}
}

View file

@ -0,0 +1,130 @@
package cd.backend.codegen;
import cd.Main;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.Stmt;
import cd.ir.Ast;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.ArraySizeAnalysis;
import cd.transform.analysis.AssignmentAnalysis;
import cd.transform.analysis.DynamicTypeAnalysis;
import cd.transform.analysis.MaybeC;
import cd.transform.analysis.NullAnalysis;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CfgCodeGenerator {
public final Main main;
private final AstCodeGeneratorRef cg;
public final CompileTimeChecks check;
protected Map<VariableSymbol, MaybeC<Integer>> arraySizeState;
protected Map<VariableSymbol, MaybeC<Boolean>> nullState;
protected Map<VariableSymbol, MaybeC<TypeSymbol>> dynamicTypeState;
//protected Map<VariableSymbol, MaybeC<List<Ast.Assign>>> unusedAssignmentsState;
public CfgCodeGenerator(Main main, Writer out) {
this.main = main;
cg = new AstCodeGeneratorNop90(main, out, this);
check = new CompileTimeChecks(cg, this);
}
public void go(List<? extends ClassDecl> astRoots) {
cg.emitPrefix(astRoots);
for (ClassDecl cdecl : astRoots)
new CfgStmtVisitor().visit(cdecl, null);
}
private class CfgStmtVisitor extends AstVisitor<Void, Void> {
@Override
public Void classDecl(ClassDecl ast, Void arg) {
cg.emit.emitCommentSection("Class " + ast.name);
cg.emit.increaseIndent("");
super.classDecl(ast, arg);
cg.emit.decreaseIndent();
return null;
}
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
cg.emitMethodPrefix(ast);
ControlFlowGraph cfg = ast.cfg;
assert cfg != null;
Map<BasicBlock, String> labels = new HashMap<BasicBlock, String>();
for (BasicBlock blk : cfg.allBlocks)
labels.put(blk, cg.emit.uniqueLabel());
String exitLabel = cg.emit.uniqueLabel();
cg.emit.emit("jmp", labels.get(cfg.start));
// Run analysis here to have the relevant results to this cfg object
ArraySizeAnalysis arraySizeAnalysis = new ArraySizeAnalysis(cfg);
NullAnalysis nullAnalysis = new NullAnalysis(cfg);
DynamicTypeAnalysis dynamicTypeAnalysis = new DynamicTypeAnalysis(cfg);
//AssignmentAnalysis assignmentAnalysis = new AssignmentAnalysis(cfg);
for (BasicBlock blk : cfg.allBlocks) {
arraySizeState = arraySizeAnalysis.inStateOf(blk);
nullState = nullAnalysis.inStateOf(blk);
dynamicTypeState = dynamicTypeAnalysis.inStateOf(blk);
//unusedAssignmentsState = assignmentAnalysis.inStateOf(blk);
assert nullState != null;
assert arraySizeState != null;
assert dynamicTypeState != null;
//assert unusedAssignmentsState != null;
cg.emit.emitCommentSection("Basic block " + blk.index);
cg.emit.emitLabel(labels.get(blk));
for (Stmt stmt : blk.stmts) {
cg.sg.gen(stmt);
//assignmentAnalysis.transferStmt(stmt, unusedAssignmentsState);
arraySizeAnalysis.transferStmt(stmt, arraySizeState);
nullAnalysis.transferStmt(stmt, nullState);
dynamicTypeAnalysis.transferStmt(stmt, dynamicTypeState);
}
if (blk == cfg.end) {
cg.emit.emitComment(String.format("Return"));
assert blk.successors.size() == 0;
cg.emit.emit("jmp", exitLabel);
} else if (blk.condition != null) {
assert blk.successors.size() == 2;
cg.emit.emitComment(String.format(
"Exit to block %d if true, block %d if false",
blk.trueSuccessor().index, blk.falseSuccessor().index));
cg.genJumpIfFalse(blk.condition, labels.get(blk.falseSuccessor()));
cg.emit.emit("jmp", labels.get(blk.trueSuccessor()));
} else {
cg.emit.emitComment(String.format(
"Exit to block %d", blk.successors.get(0).index));
assert blk.successors.size() == 1;
cg.emit.emit("jmp", labels.get(blk.successors.get(0)));
}
}
cg.emit.emitLabel(exitLabel);
if (ast.sym.returnType.equals(PrimitiveTypeSymbol.voidType))
cg.emitMethodSuffix(true);
else
cg.emitMethodSuffix(true);
return null;
}
}
}

View file

@ -0,0 +1,106 @@
package cd.backend.codegen;
import cd.Config;
import cd.backend.ExitCode;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.CompileTimeEvaluator;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.MaybeC;
import java.util.Optional;
import static cd.backend.codegen.AssemblyEmitter.constant;
import static cd.backend.codegen.RegisterManager.STACK_REG;
/**
* Class with simple static checks mimicking the runtime system with the functions to check
* possible errors at runtime. The purpose is to skip unnecessary calls to those functions
*/
public class CompileTimeChecks {
protected final CfgCodeGenerator cfgCg;
protected final AstCodeGeneratorRef cgRef;
protected CompileTimeEvaluator cte;
public CompileTimeChecks(AstCodeGeneratorRef cg, CfgCodeGenerator cfgCodeGenerator) {
this.cfgCg = cfgCodeGenerator;
this.cgRef = cg;
cte = new CompileTimeEvaluator();
}
/**
* Convenience method that extracts the VariableSymbol from a Var or Field node.
* <br/> If the node is not one of those, the node must be a Index
*/
private VariableSymbol symbolOf(Expr expr) {
if (expr instanceof Var)
return ((Var) expr).sym;
if (expr instanceof Field)
return ((Field) expr).sym;
// assert expr instanceof Index // only for local checks
return null;
}
/** Returns whether a NewArray expression needs to check dynamically for the
* validity of the index (>=0). In case that it is known but invalid, a call
* to exit is done with the appropriate error code.
*/
public boolean checkArraySize(Ast.NewArray expr) {
Optional<Integer> arraySize = cte.calc(expr.arg());
if (!arraySize.isPresent()) {
return true;
}
int arraySizeValue = arraySize.get();
if (arraySizeValue < 0) {
cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_SIZE.value), 0, STACK_REG);
cgRef.emit.emit("call", Config.EXIT);
return false;
}
return false;
}
/** Returns whether a run-time check for the bounds of an array index is necessary.
* <br/> A check is only necessary if the index expression cannot be evaluated in
* compile time or the size of the array is unknown.
* <br/> If the value is known and it is invalid, an unconditional call to exit is
* performed with the appropriate exit code
*/
public boolean checkArrayBound(Index expr) {
Optional<Integer> index = cte.calc(expr.right());
if (!index.isPresent()) {
return true;
}
VariableSymbol symbol = symbolOf(expr.left());
if (!cfgCg.arraySizeState.containsKey(symbol)) {
return true;
}
int indexValue = index.get();
MaybeC<Integer> bound = cfgCg.arraySizeState.get(symbol);
if (bound.isNotConstant()) {
return true;
}
int boundValue = bound.getValue();
if (indexValue < 0 || indexValue > (boundValue - 1)) {
cgRef.emit.emitStore(constant(ExitCode.INVALID_ARRAY_BOUNDS.value), 0, STACK_REG);
cgRef.emit.emit("call", Config.EXIT);
}
return false;
}
/**
* Returns true only when it is impossible for the expression (Field or Var) to be null.
* <br/> Currently doesn't check indexes.
*/
public boolean isNotNull(Expr expr) {
if (expr instanceof ThisRef) {
return false;
} else {
VariableSymbol symbol = symbolOf(expr);
MaybeC<Boolean> isNull = cfgCg.nullState.get(symbol);
return isNull != null && isNull.isConstant() && !isNull.getValue();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,19 +0,0 @@
package cd.backend.codegen;
import cd.backend.ExitCode;
import cd.backend.codegen.RegisterManager.Register;
public class Interrupts {
protected static final int INTERRUPT_EXIT = 1;
/**
* Generates a exit interrupt with the code provided
* @param cg AstCodeGenerator to print instructions
* @param exitCode Number to use as exit code (can use constants in this class)
*/
protected static void exit(AstCodeGenerator cg, ExitCode exitCode) {
cg.emit.emitMove(AssemblyEmitter.constant(INTERRUPT_EXIT), Register.EAX);
cg.emit.emitMove(AssemblyEmitter.constant(exitCode.value), Register.EBX);
cg.emit.emit("int", 0x80);
}
}

View file

@ -1,42 +0,0 @@
package cd.backend.codegen;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
public class Location {
private ClassSymbol classSymbol;
private MethodSymbol methodSymbol = null;
private boolean obtainReference = false;
public Location (ClassSymbol sym) {
classSymbol = sym;
}
public ClassSymbol classSym() {
return classSymbol;
}
public MethodSymbol methodSym() {
assert methodSymbol != null;
return methodSymbol;
}
public void enterMethod(MethodSymbol sym) {
methodSymbol = sym;
}
public void leaveMethod() {
methodSymbol = null;
}
public boolean isObtainReference() {
boolean aux = obtainReference;
obtainReference = false;
return aux;
}
public Location obtainReference() {
obtainReference = true;
return this;
}
}

View file

@ -16,10 +16,10 @@ public class RegisterManager {
Register.EDI, Register.EBX};
public static final Register CALLER_SAVE[] = new Register[]{Register.EAX,
Register.ECX, Register.EDX};
// list of general purpose registers
public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX,
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
// special purpose registers
public static final Register BASE_REG = Register.EBP;
@ -27,7 +27,7 @@ public class RegisterManager {
public static final int SIZEOF_REG = 4;
public enum Register {
EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX(
"%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI(
@ -99,18 +99,11 @@ public class RegisterManager {
return registers.remove(last);
}
public void useRegister(Register reg) {
assert registers.contains(reg);
assert reg != null;
registers.remove(reg);
}
/**
* marks a currently used register as free
*/
public void releaseRegister(Register reg) {
assert !registers.contains(reg);
assert reg != null;
registers.add(reg);
}

View file

@ -10,18 +10,19 @@ import java.util.Map;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* Determines the maximum number of registers
* required to execute one subtree. */
/**
* Determines the maximum number of registers
* required to execute one subtree.
*/
public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public int calc(Ast ast) {
return visit(ast, null);
}
private Map<Ast,Integer> memo = new HashMap<Ast, Integer>();
/**
private Map<Ast, Integer> memo = new HashMap<Ast, Integer>();
/**
* Override visit() so as to memorize the results and avoid
* unnecessary computation
*/
@ -31,9 +32,9 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
return memo.get(ast);
Integer res = ast.accept(this, null);
memo.put(ast, res);
return res;
return res;
}
@Override
protected Integer dflt(Ast ast, Void arg) {
// For a non-expression, it suffices to find the
@ -54,8 +55,8 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer binaryOp(BinaryOp ast, Void arg) {
int left = calc(ast.left());
int right = calc(ast.right());
int ifLeftFirst = max(left, right+1);
int ifRightFirst = max(left+1, right);
int ifLeftFirst = max(left, right + 1);
int ifRightFirst = max(left + 1, right);
int overall = min(ifLeftFirst, ifRightFirst);
return overall;
}
@ -64,7 +65,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer assign(Assign ast, Void arg) {
return max(calc(ast.left()) + 1, calc(ast.right()));
}
@Override
public Integer booleanConst(BooleanConst ast, Void arg) {
return 1;
@ -74,7 +75,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer builtInRead(BuiltInRead ast, Void arg) {
return 1;
}
@Override
public Integer cast(Cast ast, Void arg) {
return calc(ast.arg());
@ -84,7 +85,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer index(Index ast, Void arg) {
return max(calc(ast.left()), calc(ast.right()) + 1);
}
@Override
public Integer field(Field ast, Void arg) {
return calc(ast.arg());
@ -114,7 +115,7 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
public Integer thisRef(ThisRef ast, Void arg) {
return 1;
}
@Override
public Integer methodCall(MethodCallExpr ast, Void arg) {
int max = 1;
@ -134,5 +135,5 @@ public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
@Override
public Integer var(Var ast, Void arg) {
return 1;
}
}
}

View file

@ -1,34 +1,31 @@
package cd.backend.codegen;
import cd.Config;
import cd.backend.ExitCode;
import cd.backend.codegen.AstCodeGenerator.Label;
import cd.backend.codegen.RegisterManager.Register;
import cd.frontend.semantic.ReturnCheckerVisitor;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.MethodSymbol;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.util.Pair;
import cd.util.debug.AstOneLine;
import java.util.*;
import java.util.List;
import static cd.backend.codegen.AstCodeGenerator.VAR_SIZE;
import static cd.backend.codegen.AstCodeGenerator.TRUE;
import static cd.backend.codegen.AssemblyEmitter.arrayAddress;
import static cd.backend.codegen.RegisterManager.BASE_REG;
import static cd.backend.codegen.RegisterManager.STACK_REG;
/**
* Generates code to process statements and declarations.
*/
class StmtGenerator extends AstVisitor<Register,Location> {
class StmtGenerator extends AstVisitor<Register, Void> {
protected final AstCodeGenerator cg;
StmtGenerator(AstCodeGenerator astCodeGenerator) {
cg = astCodeGenerator;
}
public void gen(Ast ast) {
@ -36,253 +33,337 @@ class StmtGenerator extends AstVisitor<Register,Location> {
}
@Override
public Register visit(Ast ast, Location arg) {
public Register visit(Ast ast, Void arg) {
try {
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) {
Deque<Register> pushed = new ArrayDeque<>();
for (Register r : RegisterManager.GPR) {
if (cg.rm.isInUse(r)) {
cg.emit.emit("push", r);
pushed.push(r);
cg.rm.releaseRegister(r);
}
}
Register result = super.visit(ast, arg);
for (Register r : pushed)
cg.rm.useRegister(r);
Register finalResult = cg.rm.getRegister();
cg.emit.emitMove(result, finalResult);
for (Register r = pushed.pop(); !pushed.isEmpty(); r = pushed.pop())
cg.emit.emit("pop", r);
return finalResult;
} else return super.visit(ast, arg);
return super.visit(ast, arg);
} finally {
cg.emit.decreaseIndent();
}
}
@Override
public Register methodCall(MethodCall ast, Location arg) {
Register result = cg.eg.visit(ast.getMethodCallExpr(), arg);
if (result != null)
cg.rm.releaseRegister(result);
return null;
}
public Register methodCall(MethodSymbol sym, List<Expr> allArguments) {
throw new RuntimeException("Not required");
}
/**
* vtable structure for a class type: pointer to superclass, and methods, each with 2 entries, name's hashCode and
* pointer to method execution
*/
}
/*
* StmtGenerator with the reference solution
*/
class StmtGeneratorRef extends StmtGenerator {
/* cg and cgRef are the same instance. cgRef simply
* provides a wider interface */
protected final AstCodeGeneratorRef cgRef;
StmtGeneratorRef(AstCodeGeneratorRef astCodeGenerator) {
super(astCodeGenerator);
this.cgRef = astCodeGenerator;
}
@Override
public Register classDecl(ClassDecl ast, Location arg) {
cg.emit.emitRaw(Config.DATA_INT_SECTION);
// Emit vtable for class
cg.emit.emitLabel(Label.type(ast.sym)); // Label
cg.emit.emitConstantData(Label.type(ast.sym.superClass)); // Superclass
// Methods (don't write those that are overridden twice)
Set<String> generated = new HashSet<>();
ClassSymbol currSym = ast.sym;
while (currSym != ClassSymbol.objectType) {
ClassSymbol finalCurrSym = currSym;
currSym.methods.values().forEach(o -> {
if (!generated.add(o.name)) return;
cg.emit.emitConstantData(String.valueOf(o.name.hashCode()));
cg.emit.emitConstantData(Label.method(finalCurrSym, o));
});
currSym = currSym.superClass;
public Register methodCall(MethodSymbol mthSymbol, List<Expr> allArgs) {
// Push the arguments and the method prefix (caller save register,
// and padding) onto the stack.
// Note that the space for the arguments is not already reserved,
// so we just push them in the Java left-to-right order.
//
// After each iteration of the following loop, reg holds the
// register used for the previous argument.
int padding = cgRef.emitCallPrefix(null, allArgs.size());
Register reg = null;
for (int i = 0; i < allArgs.size(); i++) {
if (reg != null) {
cgRef.rm.releaseRegister(reg);
}
reg = cgRef.eg.gen(allArgs.get(i));
cgRef.push(reg.repr);
}
// End of class vtable
// Array vtable
boolean found = false;
for (Symbol.TypeSymbol type : cg.main.allTypeSymbols) {
if (type instanceof Symbol.ArrayTypeSymbol) {
Symbol.ArrayTypeSymbol aType = (Symbol.ArrayTypeSymbol) type;
if (aType.elementType == ast.sym) {
cg.emit.emitLabel(Label.type(aType)); // Label
found = true;
break;
}
// Since "this" is the first parameter that push
// we have to get it back to resolve the method call
cgRef.emit.emitComment("Load \"this\" pointer");
cgRef.emit.emitLoad((allArgs.size() - 1) * Config.SIZEOF_PTR, STACK_REG, reg);
// Check for a null receiver
int cnPadding = cgRef.emitCallPrefix(null, 1);
cgRef.push(reg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, cnPadding);
// Load the address of the method to call into "reg"
// and call it indirectly.
cgRef.emit.emitLoad(0, reg, reg);
int mthdoffset = 4 + mthSymbol.vtableIndex * Config.SIZEOF_PTR;
cgRef.emit.emitLoad(mthdoffset, reg, reg);
cgRef.emit.emit("call", "*" + reg);
cgRef.emitCallSuffix(reg, allArgs.size(), padding);
if (mthSymbol.returnType == PrimitiveTypeSymbol.voidType) {
cgRef.rm.releaseRegister(reg);
return null;
}
return reg;
}
@Override
public Register methodCall(MethodCall ast, Void dummy) {
Register reg = cgRef.eg.gen(ast.getMethodCallExpr());
if (reg != null)
cgRef.rm.releaseRegister(reg);
return reg;
}
@Override
public Register classDecl(ClassDecl ast, Void arg) {
// Emit each method:
cgRef.emit.emitCommentSection("Class " + ast.name);
return visitChildren(ast, arg);
}
@Override
public Register methodDecl(MethodDecl ast, Void arg) {
cgRef.emitMethodPrefix(ast);
gen(ast.body());
cgRef.emitMethodSuffix(false);
return null;
}
@Override
public Register ifElse(IfElse ast, Void arg) {
String falseLbl = cgRef.emit.uniqueLabel();
String doneLbl = cgRef.emit.uniqueLabel();
cgRef.genJumpIfFalse(ast.condition(), falseLbl);
gen(ast.then());
cgRef.emit.emit("jmp", doneLbl);
cgRef.emit.emitLabel(falseLbl);
gen(ast.otherwise());
cgRef.emit.emitLabel(doneLbl);
return null;
}
@Override
public Register whileLoop(WhileLoop ast, Void arg) {
String nextLbl = cgRef.emit.uniqueLabel();
String doneLbl = cgRef.emit.uniqueLabel();
cgRef.emit.emitLabel(nextLbl);
cgRef.genJumpIfFalse(ast.condition(), doneLbl);
gen(ast.body());
cgRef.emit.emit("jmp", nextLbl);
cgRef.emit.emitLabel(doneLbl);
return null;
}
@Override
public Register assign(Assign ast, Void arg) {
class AssignVisitor extends ExprVisitor<Void, Expr> {
@Override
public Void var(Var ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
@Override
public Void field(Field ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.arg());
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(regs.b.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b);
cgRef.rm.releaseRegister(regs.b);
cgRef.rm.releaseRegister(regs.a);
return null;
}
@Override
public Void index(Index ast, Expr right) {
Register rhsReg = cgRef.egRef.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.left());
rhsReg = regs.a;
Register arrReg = regs.b;
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
regs = cgRef.egRef.genPushing(arrReg, ast.right());
arrReg = regs.a;
Register idxReg = regs.b;
// Check array bounds
padding = cgRef.emitCallPrefix(null, 2);
cgRef.push(idxReg.repr);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS);
cgRef.emitCallSuffix(null, 2, padding);
cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg));
cgRef.rm.releaseRegister(arrReg);
cgRef.rm.releaseRegister(idxReg);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
@Override
protected Void dfltExpr(Expr ast, Expr arg) {
throw new RuntimeException("Store to unexpected lvalue " + ast);
}
}
new AssignVisitor().visit(ast.left(), ast.right());
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Void arg) {
Register reg = cgRef.eg.gen(ast.arg());
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(reg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_INTEGER);
cgRef.emitCallSuffix(null, 1, padding);
cgRef.rm.releaseRegister(reg);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Void arg) {
int padding = cgRef.emitCallPrefix(null, 0);
cgRef.emit.emit("call", AstCodeGeneratorRef.PRINT_NEW_LINE);
cgRef.emitCallSuffix(null, 0, padding);
return null;
}
@Override
public Register returnStmt(ReturnStmt ast, Void arg) {
if (ast.arg() != null) {
Register reg = cgRef.eg.gen(ast.arg());
cgRef.emit.emitMove(reg, "%eax");
cgRef.emitMethodSuffix(false);
cgRef.rm.releaseRegister(reg);
} else {
cgRef.emitMethodSuffix(true); // no return value -- return NULL as
// a default (required for main())
}
return null;
}
}
class StmtGeneratorNop90 extends StmtGeneratorRef {
protected final CfgCodeGenerator cfgCg;
protected CompileTimeEvaluator cte;
StmtGeneratorNop90(AstCodeGeneratorNop90 astCodeGenerator, CfgCodeGenerator cfgCodeGenerator) {
super(astCodeGenerator);
this.cfgCg = cfgCodeGenerator;
cte = new CompileTimeEvaluator();
}
@Override
public Register assign(Assign ast, Void arg) {
/*
if (ast.left() instanceof Ast.Var) {
Ast.Var var = (Ast.Var) ast.left();
VariableSymbol sym = var.sym;
MaybeC<List<Assign>> maybeStateList = cfgCg.unusedAssignmentsState.get(sym);
List<Ast.Assign> stateList = maybeStateList.getValue();
if (stateList.contains(ast)) {
return null;
}
}
if (!found)
throw new RuntimeException("The array type could not be found");
cg.emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
cg.emit.emitConstantData(Label.type(ast.sym)); // Type of elements
// End of array vtable
cg.emit.emitRaw(Config.TEXT_SECTION);
// Method bodies
for (Ast method : ast.methods())
visit(method, new Location(ast.sym));
return null;
}
*/
class AssignVisitor extends ExprVisitor<Void, Expr> {
@Override
public Register methodDecl(MethodDecl ast, Location arg) {
// Bookkeeping for framework
arg.enterMethod(ast.sym);
cg.initMethodData();
@Override
public Void var(Var ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
cgRef.emit.emitStore(rhsReg, ast.sym.offset, BASE_REG);
cgRef.rm.releaseRegister(rhsReg);
return null;
}
// Begin method
cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym));
@Override
public Void field(Field ast, Expr right) {
final Register rhsReg = cgRef.eg.gen(right);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.arg());
if (!cfgCg.check.isNotNull(ast.arg())) {
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(regs.b.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
}
cgRef.emit.emitStore(regs.a, ast.sym.offset, regs.b);
cgRef.rm.releaseRegister(regs.b);
cgRef.rm.releaseRegister(regs.a);
return null;
}
// 1. Save and update the base register, reserve space for local variables
cg.emit.emit("enter", ast.sym.locals.size() * VAR_SIZE, 0);
for (int i = 0; i < ast.sym.locals.size(); i++)
cg.emit.emitStore(0, -(i + 1) * VAR_SIZE, BASE_REG);
@Override
public Void index(Index ast, Expr right) {
boolean isNotNull = cfgCg.check.isNotNull(ast.left());
boolean emitBoundCheck = cfgCg.check.checkArrayBound(ast);
Register rhsReg = cgRef.egRef.gen(right);
// 2. Save CPU registers
Register[] regs = RegisterManager.CALLEE_SAVE;
for (int i = 0; i < regs.length; i++)
cg.emit.emit("push", regs[i]);
Pair<Register> regs = cgRef.egRef.genPushing(rhsReg, ast.left());
rhsReg = regs.a;
Register arrReg = regs.b;
if (!isNotNull) {
int padding = cgRef.emitCallPrefix(null, 1);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_NULL);
cgRef.emitCallSuffix(null, 1, padding);
}
// 3. Run the code contained in the function
Register returnReg = visit(ast.body(), arg);
cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym));
regs = cgRef.egRef.genPushing(arrReg, ast.right());
arrReg = regs.a;
Register idxReg = regs.b;
// 4. Restore saved registers (if there is a mismatch, the stack will be corrupted)
for (int i = regs.length - 1; i >= 0; i--)
cg.emit.emit("pop", regs[i]);
// Check array bounds
if (emitBoundCheck) {
int padding = cgRef.emitCallPrefix(null, 2);
cgRef.push(idxReg.repr);
cgRef.push(arrReg.repr);
cgRef.emit.emit("call", AstCodeGeneratorRef.CHECK_ARRAY_BOUNDS);
cgRef.emitCallSuffix(null, 2, padding);
}
// 5 and 6: Restore the old base pointer and return from the function
cg.emitMethodSuffix(returnReg == null);
cgRef.emit.emitMove(rhsReg, arrayAddress(arrReg, idxReg));
cgRef.rm.releaseRegister(arrReg);
cgRef.rm.releaseRegister(idxReg);
cgRef.rm.releaseRegister(rhsReg);
// Framework bookkeeping
arg.leaveMethod();
return null;
}
return null;
}
@Override
protected Void dfltExpr(Expr ast, Expr arg) {
throw new RuntimeException("Store to unexpected lvalue " + ast);
}
@Override
public Register ifElse(IfElse ast, Location arg) {
// Emit condition check
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
// If both blocks are empty, no more code need be generated, and the condition can be ignored
if (ast.then().children().isEmpty() && ast.otherwise().children().isEmpty()) {
// Generate the condition and ignore the result
cg.rm.releaseRegister(conditionRegister);
return null;
}
String notIfLabel = cg.emit.uniqueLabel();
String endLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (!ast.then().children().isEmpty()) {
cg.emit.emit("jne", notIfLabel);
visit(ast.then(), arg);
// If there is no otherwise, the jump instruction makes no sense
// as the next code executed will be the next instruction
if (!ast.otherwise().children().isEmpty())
cg.emit.emit("jmp", endLabel);
} else {
// No if, therefore the else follows the condition immediately
cg.emit.emit("je", endLabel);
}
cg.emit.emitLabel(notIfLabel);
// Emit otherwise
visit(ast.otherwise(), arg);
cg.emit.emitLabel(endLabel);
// Check if the ifElse ast node contains a return statement
ReturnCheckerVisitor rc = new ReturnCheckerVisitor();
if (rc.ifElse(ast, null)) {
return Register.EAX;
} else {
return null;
}
}
@Override
public Register whileLoop(WhileLoop ast, Location arg) {
String conditionLabel = cg.emit.uniqueLabel();
cg.emit.emitLabel(conditionLabel);
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
cg.emit.emit("cmp", TRUE, conditionRegister);
cg.rm.releaseRegister(conditionRegister);
if (ast.body().children().isEmpty()) {
// Jump structure can be easier if there is no body
cg.emit.emit("je", conditionLabel);
} else {
String endLabel = cg.emit.uniqueLabel();
// Emit jumps, labels and body
cg.emit.emit("jne", endLabel);
visit(ast.body(), arg);
cg.emit.emit("jmp", conditionLabel);
cg.emit.emitLabel(endLabel);
}
return null;
}
@Override
public Register assign(Assign ast, Location arg) {
Register value = cg.eg.visit(ast.right(), arg);
// If the type is a reference, visiting the lhs will yield its address
// else, the lhs will yield the current value. We need a visitor that
// returns a pointer to the position of the variable/field in memory
// for primitive types.
Register pointer = cg.eg.visit(ast.left(), arg.obtainReference());
if (ast.left().type.isReferenceType()) {
// Check null pointer
String validPointerLabel = cg.emit.uniqueLabel();
cg.emit.emit("cmp", 0, pointer);
cg.emit.emit("jne", validPointerLabel);
Interrupts.exit(cg, ExitCode.NULL_POINTER);
cg.emit.emitLabel(validPointerLabel);
}
cg.emit.emitStore(value, 0, pointer);
cg.rm.releaseRegister(pointer);
cg.rm.releaseRegister(value);
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Location arg) {
Register reg = cg.eg.visit(ast.arg(), arg);
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore(reg, 4, STACK_REG);
cg.emit.emitStore("$STR_D", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
cg.rm.releaseRegister(reg);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Location arg) {
cg.emit.emit("sub", 16, STACK_REG);
cg.emit.emitStore("$STR_NL", 0, STACK_REG);
cg.emit.emit("call", Config.PRINTF);
cg.emit.emit("add", 16, STACK_REG);
return null;
}
@Override
public Register returnStmt(ReturnStmt ast, Location arg) {
if (ast.arg() != null) {
Register retReg = cg.eg.visit(ast.arg(), arg);
cg.emit.emitMove(retReg, Register.EAX);
cg.rm.releaseRegister(retReg);
}
cg.emit.emit("jmp", Label.returnMethod(arg.classSym(), arg.methodSym()));
return Register.EAX;
}
@Override
public Register seq(Seq ast, Location arg) {
for (Ast instruction : ast.children()) {
Register res = visit(instruction, arg);
if (res != null)
return res; // don't generate instructions after a return
}
new AssignVisitor().visit(ast.left(), ast.right());
return null;
}
}

View file

@ -0,0 +1,40 @@
package cd.frontend.semantic;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Var;
import cd.ir.AstRewriteVisitor;
import cd.ir.Symbol.ClassSymbol;
/**
* Runs after the semantic check and rewrites expressions to be more normalized.
* References to a field {@code foo} are rewritten to always use
* {@link Ast.Field} objects (i.e., {@code this.foo}.
*/
public class FieldQualifier {
public void rewrite(ClassDecl cd) {
AstRewriteVisitor<ClassSymbol> rewriter = new AstRewriteVisitor<ClassSymbol>() {
@Override
public Ast var(Var ast, ClassSymbol cs) {
switch (ast.sym.kind) {
case PARAM:
case LOCAL:
// Leave params or local variables alone
return ast;
case FIELD:
// Convert an implicit field reference to "this.foo"
Ast.Field f = new Ast.Field(new Ast.ThisRef(), ast.name);
f.arg().type = cs;
f.sym = ast.sym;
f.type = ast.type;
return f;
}
throw new RuntimeException("Unknown kind of var");
}
};
cd.accept(rewriter, cd.sym);
}
}

View file

@ -13,13 +13,13 @@ import java.util.HashSet;
import java.util.Set;
public class InheritanceChecker extends AstVisitor<Void, Void> {
ClassSymbol classSym;
@Override
public Void classDecl(ClassDecl ast, Void arg) {
classSym = ast.sym;
// check for cycles in the inheritance hierarchy:
Set<ClassSymbol> supers = new HashSet<ClassSymbol>();
ClassSymbol sc = classSym.superClass;
@ -28,30 +28,31 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
if (supers.contains(sc))
throw new SemanticFailure(
Cause.CIRCULAR_INHERITANCE,
"Class %s has %s as a superclass twice",
"Class %s has %s as a superclass twice",
ast.name, sc.name);
supers.add(sc);
sc = sc.superClass;
}
this.visitChildren(ast, null);
return null;
}
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
// check that methods overridden from a parent class agree
// on number/type of parameters
MethodSymbol sym = ast.sym;
MethodSymbol superSym = classSym.superClass.getMethod(ast.name);
sym.overrides = superSym;
if (superSym != null) {
if (superSym.parameters.size() != sym.parameters.size())
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Overridden method %s has %d parameters, " +
"but original has %d",
"but original has %d",
ast.name, sym.parameters.size(),
superSym.parameters.size());
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
@ -59,17 +60,17 @@ public class InheritanceChecker extends AstVisitor<Void, Void> {
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Method parameter %s has type %s, but " +
"corresponding base class parameter %s has type %s",
"corresponding base class parameter %s has type %s",
pair.a.name, pair.a.type, pair.b.name, pair.b.type);
if (superSym.returnType != sym.returnType)
throw new SemanticFailure(
Cause.INVALID_OVERRIDE,
"Overridden method %s has return type %s," +
"but its superclass has %s",
"but its superclass has %s",
ast.name, sym.returnType, superSym.returnType);
}
return null;
}
}
}

View file

@ -5,22 +5,21 @@ import cd.ir.Ast.*;
import cd.ir.AstVisitor;
/**
* Visitor that checks if all paths of a given sequence have a
* return statement.
*
* <p>
* This visitor only needs to be used if are not using the Control Flow Graph.
*
*
* @author Leo Buttiker
*/
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
protected Boolean dfltStmt(Stmt ast, Void arg) {
return false;
}
@Override
public Boolean returnStmt(ReturnStmt ast, Void arg) {
return true;
@ -28,7 +27,7 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
public Boolean ifElse(IfElse ast, Void arg) {
boolean allPathHaveAReturnStmt = true;
boolean allPathHaveAReturnStmt = true;
allPathHaveAReturnStmt &= visit(ast.then(), null);
allPathHaveAReturnStmt &= visit(ast.otherwise(), null);
return allPathHaveAReturnStmt;
@ -36,17 +35,17 @@ public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
@Override
public Boolean seq(Seq ast, Void arg) {
boolean allPathHaveAReturnStmt = false;
boolean allPathHaveAReturnStmt = false;
for (Ast child : ast.children()) {
allPathHaveAReturnStmt |= this.visit(child, null);
}
return allPathHaveAReturnStmt;
}
@Override
public Boolean whileLoop(WhileLoop ast, Void arg) {
return false;
return false;
}
}

View file

@ -11,70 +11,76 @@ import java.util.ArrayList;
import java.util.List;
public class SemanticAnalyzer {
public final Main main;
public SemanticAnalyzer(Main main) {
this.main = main;
}
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
public void check(List<ClassDecl> classDecls)
throws SemanticFailure {
{
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
checkInheritance(classDecls);
checkStartPoint(typeSymbols);
checkMethodBodies(typeSymbols, classDecls);
{
rewriteMethodBodies(classDecls);
main.allTypeSymbols = typeSymbols.allSymbols();
}
}
}
/**
* Creates a symbol table with symbols for all built-in types,
* Creates a symbol table with symbols for all built-in types,
* as well as all classes and their fields and methods. Also
* creates a corresponding array symbol for every type
* creates a corresponding array symbol for every type
* (named {@code type[]}).
*
* @see SymbolCreator
*/
private SymTable<TypeSymbol> createSymbols(List<ClassDecl> classDecls) {
// Start by creating a symbol for all built-in types.
SymTable<TypeSymbol> typeSymbols = new SymTable<TypeSymbol>(null);
typeSymbols.add(PrimitiveTypeSymbol.intType);
typeSymbols.add(PrimitiveTypeSymbol.booleanType);
typeSymbols.add(PrimitiveTypeSymbol.voidType);
typeSymbols.add(ClassSymbol.objectType);
// Add symbols for all declared classes.
for (ClassDecl ast : classDecls) {
// Check for classes named Object
if (ast.name.equals(ClassSymbol.objectType.name))
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED);
ast.sym = new ClassSymbol(ast);
typeSymbols.add(ast.sym);
typeSymbols.add(ast.sym);
}
// Create symbols for arrays of each type.
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
Symbol.ArrayTypeSymbol array =
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
Symbol.ArrayTypeSymbol array =
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
typeSymbols.add(array);
}
// For each class, create symbols for each method and field
SymbolCreator sc = new SymbolCreator(main, typeSymbols);
for (ClassDecl ast : classDecls)
sc.createSymbols(ast);
return typeSymbols;
}
/**
* Check for errors related to inheritance:
* Check for errors related to inheritance:
* circular inheritance, invalid super
* classes, methods with different types, etc.
* Note that this must be run early because other code assumes
* that the inheritance is correct, for type checking etc.
*
* @see InheritanceChecker
*/
private void checkInheritance(List<ClassDecl> classDecls) {
@ -93,25 +99,26 @@ public class SemanticAnalyzer {
MethodSymbol mainMethod = cs.getMethod("main");
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
main.mainType = cs;
return; // found the main() method!
}
}
throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found");
}
/**
* Check the bodies of methods for errors, particularly type errors
* but also undefined identifiers and the like.
*
* @see TypeChecker
*/
private void checkMethodBodies(
SymTable<TypeSymbol> typeSymbols,
List<ClassDecl> classDecls)
{
List<ClassDecl> classDecls) {
TypeChecker tc = new TypeChecker(typeSymbols);
for (ClassDecl classd : classDecls) {
SymTable<VariableSymbol> fldTable = new SymTable<VariableSymbol>(null);
// add all fields of this class, or any of its super classes
@ -119,37 +126,42 @@ public class SemanticAnalyzer {
for (VariableSymbol s : p.fields.values())
if (!fldTable.contains(s.name))
fldTable.add(s);
// type check any method bodies and final locals
for (MethodDecl md : classd.methods()) {
boolean hasReturn = new ReturnCheckerVisitor().visit(md.body(), null);
if (!md.returnType.equals("void") && !hasReturn) {
throw new SemanticFailure(Cause.MISSING_RETURN,
"Method %s.%s is missing a return statement",
classd.name,
throw new SemanticFailure(Cause.MISSING_RETURN,
"Method %s.%s is missing a return statement",
classd.name,
md.name);
}
SymTable<VariableSymbol> mthdTable = new SymTable<VariableSymbol>(fldTable);
mthdTable.add(classd.sym.thisSymbol);
for (VariableSymbol p : md.sym.parameters) {
mthdTable.add(p);
}
for (VariableSymbol l : md.sym.locals.values()) {
mthdTable.add(l);
}
tc.checkMethodDecl(md, mthdTable);
}
}
}
private void rewriteMethodBodies(List<ClassDecl> classDecls) {
for (ClassDecl cd : classDecls)
new FieldQualifier().rewrite(cd);
}
}

View file

@ -1,40 +1,50 @@
package cd.frontend.semantic;
/**
/**
* Thrown by the semantic checker when a semantic error is detected
* in the user's program. */
* in the user's program.
*/
public class SemanticFailure extends RuntimeException {
private static final long serialVersionUID = 5375946759285719123L;
public enum Cause {
/**
/**
* Caused by an assignment to either a final field, {@code this},
* or some other kind of expression which cannot be assigned to.
* <b>Not</b> used for type errors in assignments, which fall
* under {@link #TYPE_ERROR}. */
* under {@link #TYPE_ERROR}.
*/
NOT_ASSIGNABLE,
/** Two variables, fields, methods, or classes with the same name
* were declared in the same scope */
/**
* Two variables, fields, methods, or classes with the same name
* were declared in the same scope
*/
DOUBLE_DECLARATION,
/** A field was accessed that does not exist */
/**
* A field was accessed that does not exist
*/
NO_SUCH_FIELD,
/** A method was called that does not exist */
/**
* A method was called that does not exist
*/
NO_SUCH_METHOD,
/**
/**
* A variable or other identifier was used in a method
* body which has no corresponding declaration */
* body which has no corresponding declaration
*/
NO_SUCH_VARIABLE,
/**
* A method with a return type is missing a return statement among one of its paths */
/**
* A method with a return type is missing a return statement among one of its paths
*/
MISSING_RETURN,
/**
/**
* Can occur in many contents:
* <ul>
* <li> Assignment to a variable from an expression of wrong type
@ -53,12 +63,12 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
TYPE_ERROR,
/**
* A class is its own super class
*/
CIRCULAR_INHERITANCE,
/**
* One of the following:
* <ul>
@ -69,13 +79,13 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
INVALID_START_POINT,
/**
* A class {@code Object} was defined. This class is implicitly
* defined and cannot be defined explicitly.
*/
OBJECT_CLASS_DEFINED,
/**
* A type name was found for which no class declaration exists.
* This can occur in many contexts:
@ -86,34 +96,36 @@ public class SemanticFailure extends RuntimeException {
* </ul>
*/
NO_SUCH_TYPE,
/**
* The parameters of an overridden method have different types
* from the base method, there is a different
* from the base method, there is a different
* number of parameters, or the return value is different.
*/
INVALID_OVERRIDE,
/** A method was called with the wrong number of arguments */
/**
* A method was called with the wrong number of arguments
*/
WRONG_NUMBER_OF_ARGUMENTS,
/**
/**
* Indicates the use of a local variable that may not have been
* initialized (ACD only).
*/
POSSIBLY_UNINITIALIZED,
}
public final Cause cause;
public SemanticFailure(Cause cause) {
super(cause.name());
this.cause = cause;
}
public SemanticFailure(Cause cause, String format, Object... args) {
super(String.format(format, args));
this.cause = cause;
}
}

View file

@ -3,24 +3,23 @@ package cd.frontend.semantic;
import cd.frontend.semantic.SemanticFailure.Cause;
import cd.ir.Symbol;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
/**
* A simple symbol table, with a pointer to the enclosing scope.
/**
* A simple symbol table, with a pointer to the enclosing scope.
* Used by {@link TypeChecker} to store the various scopes for
* local, parameter, and field lookup. */
* local, parameter, and field lookup.
*/
public class SymTable<S extends Symbol> {
private final Map<String, S> map = new HashMap<String, S>();
private final SymTable<S> parent;
public SymTable(SymTable<S> parent) {
this.parent = parent;
}
public void add(S sym) {
// check that the symbol is not already declared *at this level*
if (containsLocally(sym.name))
@ -28,10 +27,21 @@ public class SymTable<S extends Symbol> {
map.put(sym.name, sym);
}
public List<S> allSymbols() {
List<S> result = new ArrayList<S>();
SymTable<S> st = this;
while (st != null) {
for (S sym : st.map.values())
result.add(sym);
st = st.parent;
}
return result;
}
public Collection<S> localSymbols() {
return this.map.values();
}
/**
* True if there is a declaration with the given name at any
* level in the symbol table
@ -40,7 +50,7 @@ public class SymTable<S extends Symbol> {
return get(name) != null;
}
/**
/**
* True if there is a declaration at THIS level in the symbol
* table; may return {@code false} even if a declaration exists
* in some enclosing scope
@ -49,9 +59,10 @@ public class SymTable<S extends Symbol> {
return this.map.containsKey(name);
}
/**
/**
* Base method: returns {@code null} if no symbol by that
* name can be found, in this table or in its parents */
* name can be found, in this table or in its parents
*/
public S get(String name) {
S res = map.get(name);
if (res != null)
@ -60,11 +71,12 @@ public class SymTable<S extends Symbol> {
return null;
return parent.get(name);
}
/**
* Finds the symbol with the given name, or fails with a
* Finds the symbol with the given name, or fails with a
* NO_SUCH_TYPE error. Only really makes sense to use this
* if S == TypeSymbol, but we don't strictly forbid it... */
* if S == TypeSymbol, but we don't strictly forbid it...
*/
public S getType(String name) {
S res = get(name);
if (res == null)

View file

@ -21,10 +21,10 @@ import java.util.Set;
* and local variables.
*/
public class SymbolCreator extends Object {
final Main main;
final SymTable<TypeSymbol> typesTable;
public SymbolCreator(Main main, SymTable<TypeSymbol> typesTable) {
this.main = main;
this.typesTable = typesTable;
@ -34,33 +34,33 @@ public class SymbolCreator extends Object {
// lookup the super class. the grammar guarantees that this
// will refer to a class, if the lookup succeeds.
cd.sym.superClass = (ClassSymbol) typesTable.getType(cd.superClass);
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
}
/**
* Useful method which adds a symbol to a map, checking to see
* Useful method which adds a symbol to a map, checking to see
* that there is not already an entry with the same name.
* If a symbol with the same name exists, throws an exception.
*/
public <S extends Symbol> void add(Map<String,S> map, S sym) {
public <S extends Symbol> void add(Map<String, S> map, S sym) {
if (map.containsKey(sym.name))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
"Symbol '%s' was declared twice in the same scope",
"Symbol '%s' was declared twice in the same scope",
sym.name);
map.put(sym.name, sym);
}
/**
* Creates symbols for all fields/constants/methods in a class.
* Uses {@link MethodSymbolCreator} to create symbols for all
* Uses {@link MethodSymbolCreator} to create symbols for all
* parameters and local variables to each method as well.
* Checks for duplicate members.
*/
public class ClassSymbolCreator extends AstVisitor<Void, Void> {
final ClassSymbol classSym;
final ClassSymbol classSym;
public ClassSymbolCreator(ClassSymbol classSym) {
this.classSym = classSym;
}
@ -75,9 +75,11 @@ public class SymbolCreator extends Object {
@Override
public Void methodDecl(MethodDecl ast, Void arg) {
ast.sym = new MethodSymbol(ast);
ast.sym.owner = classSym;
add(classSym.methods, ast.sym);
// create return type symbol
@ -86,13 +88,13 @@ public class SymbolCreator extends Object {
} else {
ast.sym.returnType = typesTable.getType(ast.returnType);
}
// create symbols for each parameter
Set<String> pnames = new HashSet<String>();
for (int i = 0; i < ast.argumentNames.size(); i++) {
String argumentName = ast.argumentNames.get(i);
String argumentType = ast.argumentTypes.get(i);
if (pnames.contains(argumentName))
if (pnames.contains(argumentName))
throw new SemanticFailure(
Cause.DOUBLE_DECLARATION,
"Method '%s' has two parameters named '%s'",
@ -102,23 +104,23 @@ public class SymbolCreator extends Object {
argumentName, typesTable.getType(argumentType));
ast.sym.parameters.add(vs);
}
// create symbols for the local variables
new MethodSymbolCreator(ast.sym).visitChildren(ast.decls(), null);
return null;
}
}
public class MethodSymbolCreator extends AstVisitor<Void, Void> {
final MethodSymbol methodSym;
public MethodSymbolCreator(MethodSymbol methodSym) {
this.methodSym = methodSym;
}
@Override
public Void varDecl(VarDecl ast, Void arg) {
ast.sym = new VariableSymbol(

View file

@ -11,7 +11,7 @@ import cd.util.debug.AstOneLine;
public class TypeChecker {
final private SymTable<TypeSymbol> typeSymbols;
public TypeChecker(SymTable<TypeSymbol> typeSymbols) {
this.typeSymbols = typeSymbols;
}
@ -19,11 +19,12 @@ public class TypeChecker {
public void checkMethodDecl(MethodDecl method, SymTable<VariableSymbol> locals) {
new MethodDeclVisitor(method, locals).visit(method.body(), null);
}
/**
* Checks whether two expressions have the same type
* and throws an exception otherwise.
*
* @return The common type of the two expression.
*/
private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable<VariableSymbol> locals) {
@ -32,30 +33,30 @@ public class TypeChecker {
if (leftType != rightType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
Cause.TYPE_ERROR,
"Expected operand types to be equal but found %s, %s",
leftType,
rightType);
}
return leftType;
}
private void checkTypeIsInt(TypeSymbol type) {
if (type != PrimitiveTypeSymbol.intType) {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Expected %s for operands but found type %s",
PrimitiveTypeSymbol.intType,
type);
Cause.TYPE_ERROR,
"Expected %s for operands but found type %s",
PrimitiveTypeSymbol.intType,
type);
}
}
private ClassSymbol asClassSymbol(TypeSymbol type) {
if (type instanceof ClassSymbol)
return (ClassSymbol) type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
Cause.TYPE_ERROR,
"A class type was required, but %s was found", type);
}
@ -63,10 +64,10 @@ public class TypeChecker {
if (type instanceof ArrayTypeSymbol)
return (ArrayTypeSymbol) type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
Cause.TYPE_ERROR,
"An array type was required, but %s was found", type);
}
private TypeSymbol typeExpr(Expr expr, SymTable<VariableSymbol> locals) {
return new TypingVisitor().visit(expr, locals);
}
@ -80,9 +81,9 @@ public class TypeChecker {
expected,
actual);
}
private class MethodDeclVisitor extends AstVisitor<Void, Void> {
private MethodDecl method;
private SymTable<VariableSymbol> locals;
@ -95,7 +96,7 @@ public class TypeChecker {
protected Void dfltExpr(Expr ast, Void arg) {
throw new RuntimeException("Should not get here");
}
@Override
public Void assign(Assign ast, Void arg) {
TypeSymbol lhs = typeLhs(ast.left(), locals);
@ -113,15 +114,15 @@ public class TypeChecker {
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
return null;
}
@Override
public Void builtInWriteln(BuiltInWriteln ast, Void arg) {
return null;
}
@Override
public Void ifElse(IfElse ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
visit(ast.then(), arg);
if (ast.otherwise() != null)
visit(ast.otherwise(), arg);
@ -131,17 +132,17 @@ public class TypeChecker {
@Override
public Void methodCall(MethodCall ast, Void arg) {
typeExpr(ast.getMethodCallExpr(), locals);
return null;
}
@Override
public Void whileLoop(WhileLoop ast, Void arg) {
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
checkType((Expr) ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
return visit(ast.body(), arg);
}
@Override
public Void returnStmt(ReturnStmt ast, Void arg) {
boolean hasArg = ast.arg() != null;
@ -150,11 +151,11 @@ public class TypeChecker {
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement of method with void return type should "
+ "not have arguments.");
+ "not have arguments.");
} else if (!hasArg) {
// X m() { return; }
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
throw new SemanticFailure(
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Return statement has no arguments. Expected %s but type was %s",
method.sym.returnType,
@ -164,58 +165,58 @@ public class TypeChecker {
// X m() { return y; }
checkType(ast.arg(), method.sym.returnType, locals);
}
return null;
}
}
private class TypingVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
@Override
public TypeSymbol visit(Expr ast, SymTable<VariableSymbol> arg) {
ast.type = super.visit(ast, arg);
return ast.type;
}
@Override
public TypeSymbol binaryOp(BinaryOp ast, SymTable<VariableSymbol> locals) {
switch (ast.operator) {
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
checkTypeIsInt(type);
return type;
case B_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
checkTypeIsInt(type);
return type;
case B_AND:
case B_OR:
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
case B_EQUAL:
case B_NOT_EQUAL:
TypeSymbol left = typeExpr(ast.left(), locals);
TypeSymbol right = typeExpr(ast.right(), locals);
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
return PrimitiveTypeSymbol.booleanType;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s could never be equal",
left, right);
case B_LESS_THAN:
case B_LESS_OR_EQUAL:
case B_GREATER_THAN:
case B_GREATER_OR_EQUAL:
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unhandled operator "+ast.operator);
throw new RuntimeException("Unhandled operator " + ast.operator);
}
@Override
@ -232,10 +233,10 @@ public class TypeChecker {
public TypeSymbol cast(Cast ast, SymTable<VariableSymbol> locals) {
TypeSymbol argType = typeExpr(ast.arg(), locals);
ast.type = typeSymbols.getType(ast.typeName);
if (argType.isSuperTypeOf(ast.type) || ast.type.isSuperTypeOf(argType))
return ast.type;
throw new SemanticFailure(
Cause.TYPE_ERROR,
"Types %s and %s in cast are completely unrelated.",
@ -295,21 +296,20 @@ public class TypeChecker {
@Override
public TypeSymbol unaryOp(UnaryOp ast, SymTable<VariableSymbol> locals) {
switch (ast.operator) {
case U_PLUS:
case U_MINUS:
{
TypeSymbol type = typeExpr(ast.arg(), locals);
checkTypeIsInt(type);
return type;
case U_PLUS:
case U_MINUS: {
TypeSymbol type = typeExpr(ast.arg(), locals);
checkTypeIsInt(type);
return type;
}
case U_BOOL_NOT:
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
}
case U_BOOL_NOT:
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
return PrimitiveTypeSymbol.booleanType;
}
throw new RuntimeException("Unknown unary op "+ast.operator);
throw new RuntimeException("Unknown unary op " + ast.operator);
}
@Override
@ -322,20 +322,20 @@ public class TypeChecker {
ast.setSymbol(locals.get(ast.name));
return ast.sym.type;
}
@Override
public TypeSymbol methodCall(MethodCallExpr ast, SymTable<VariableSymbol> locals) {
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
MethodSymbol mthd = rcvrType.getMethod(ast.methodName);
if (mthd == null)
throw new SemanticFailure(
Cause.NO_SUCH_METHOD,
"Class %s has no method %s()",
rcvrType.name, ast.methodName);
rcvrType.name, ast.methodName);
ast.sym = mthd;
// Check that the number of arguments is correct.
if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size())
throw new SemanticFailure(
@ -344,33 +344,36 @@ public class TypeChecker {
ast.methodName,
mthd.parameters.size(),
ast.argumentsWithoutReceiver().size());
// Check that the arguments are of correct type.
int i = 0;
for (Ast argAst : ast.argumentsWithoutReceiver())
checkType((Expr)argAst, mthd.parameters.get(i++).type, locals);
checkType((Expr) argAst, mthd.parameters.get(i++).type, locals);
return ast.sym.returnType;
}
}
/**
* Checks an expr as the left-hand-side of an assignment,
* returning the type of value that may be assigned there.
* May fail if the expression is not a valid LHS (for example,
* a "final" field). */
* a "final" field).
*/
private TypeSymbol typeLhs(Expr expr, SymTable<VariableSymbol> locals) {
return new LValueVisitor().visit(expr, locals);
}
/**
* @see TypeChecker#typeLhs(Expr, SymTable)
*/
private class LValueVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
/** Fields, array-indexing, and vars can be on the LHS: */
/**
* Fields, array-indexing, and vars can be on the LHS:
*/
@Override
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
@ -385,13 +388,15 @@ public class TypeChecker {
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
return typeExpr(ast, locals);
}
/** Any other kind of expression is not a value lvalue */
/**
* Any other kind of expression is not a value lvalue
*/
@Override
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> locals) {
throw new SemanticFailure(
Cause.NOT_ASSIGNABLE,
"'%s' is not a valid lvalue",
"'%s' is not a valid lvalue",
AstOneLine.toString(ast));
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
package cd.ir;
import cd.ir.Ast.Expr;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import java.util.Map;
/**
* A visitor that replaces AST nodes in toBeReplaced
* used in ConstantAnalysis, deletes all the variables that have a constant value and replace them with a constant
*/
public class AstReplaceVisitor extends AstRewriteVisitor<Ast> {
// use this information to replace each Var node with it's correct constant value
public Map<Ast.Expr, Ast.LeafExpr> toBeReplaced;
public Map<Expr, Integer> initializePositions;
public int currentPosition;
public Ast.LeafExpr getReplacement(Ast.Var arg) {
// Var was declared constant but initialization happens later
// replace it with IntConst or BooleanConst and their default value
if (toBeReplaced.containsKey(arg) && currentPosition <= initializePositions.get(arg)) {
//if (toBeReplaced.containsKey(arg)) {
Ast.LeafExpr leafEpr = (Ast.LeafExpr) arg;
if (leafEpr.type == PrimitiveTypeSymbol.intType) {
return new Ast.IntConst(0);
} else {
return new Ast.BooleanConst(false);
}
}
// Var was declared constant and initialization already happened
else if (toBeReplaced.containsKey(arg) && currentPosition > initializePositions.get(arg)) {
return toBeReplaced.get(arg);
}
// Var was not declared to be a constant
else {
return arg;
}
}
public Ast var(Ast.Var ast, Void arg) {
return getReplacement(ast);
}
}

View file

@ -19,13 +19,14 @@ public class AstRewriteVisitor<A> extends AstVisitor<Ast, A> {
}
return ast;
}
/**
* This method is called when a node is replaced. Subclasses can override it to do some
* bookkeeping.
* <p>
* The default implementation does nothing.
*/
protected void nodeReplaced(Ast oldNode, Ast newNode) {}
protected void nodeReplaced(Ast oldNode, Ast newNode) {
}
}

View file

@ -4,18 +4,21 @@ import cd.ir.Ast.Decl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
/** A visitor that visits any kind of node */
public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
/**
* A visitor that visits any kind of node
*/
public class AstVisitor<R, A> extends ExprVisitor<R, A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
* it can be overloaded to introduce memoization,
* for example.
*/
public R visit(Ast ast, A arg) {
return ast.accept(this, arg);
}
/**
* Overrides {@link ExprVisitor#visitChildren(Expr, Object)} and
* delegates to the more general {@link #visitChildren(Ast, Object)}
@ -26,11 +29,11 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public final R visitChildren(Expr ast, A arg) {
return visitChildren((Ast) ast, arg);
}
/**
* A handy function which visits the children of {@code ast},
* providing "arg" to each of them. It returns the result of
* the last child in the list. It is invoked by the method
* the last child in the list. It is invoked by the method
* {@link #dflt(Ast, Object)} by default.
*/
public R visitChildren(Ast ast, A arg) {
@ -39,35 +42,39 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
lastValue = visit(child, arg);
return lastValue;
}
/**
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
* by seq() by default.
*/
protected R dflt(Ast ast, A arg) {
return visitChildren(ast, arg);
}
/**
* The default action for statements is to call this */
/**
* The default action for statements is to call this
*/
protected R dfltStmt(Stmt ast, A arg) {
return dflt(ast, arg);
}
/**
* The default action for expressions is to call this */
/**
* The default action for expressions is to call this
*/
@Override
protected R dfltExpr(Expr ast, A arg) {
return dflt(ast, arg);
}
/**
/**
* The default action for AST nodes representing declarations
* is to call this function */
* is to call this function
*/
protected R dfltDecl(Decl ast, A arg) {
return dflt(ast, arg);
}
public R assign(Ast.Assign ast, A arg) {
return dfltStmt(ast, arg);
}
@ -79,23 +86,23 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) {
return dfltStmt(ast, arg);
}
public R classDecl(Ast.ClassDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R methodDecl(Ast.MethodDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R varDecl(Ast.VarDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R ifElse(Ast.IfElse ast, A arg) {
return dfltStmt(ast, arg);
}
public R returnStmt(Ast.ReturnStmt ast, A arg) {
return dfltStmt(ast, arg);
}
@ -107,11 +114,11 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
public R nop(Ast.Nop ast, A arg) {
return dfltStmt(ast, arg);
}
public R seq(Ast.Seq ast, A arg) {
return dflt(ast, arg);
}
public R whileLoop(Ast.WhileLoop ast, A arg) {
return dfltStmt(ast, arg);
}

113
src/cd/ir/BasicBlock.java Normal file
View file

@ -0,0 +1,113 @@
package cd.ir;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Node in a control flow graph. New instances should be created
* via the methods in {@link ControlFlowGraph}.
* Basic blocks consist of a list of statements ({@link #stmts}) which are
* executed at runtime. When the basic block ends, control flows into its
* {@link #successors}. If the block has more than one successor, it must also
* have a non-{@code null} value for {@link #condition}, which describes an expression
* that will determine which successor to take. Basic blocks also have fields
* for storing the parent and children in the dominator tree. These are generally computed
* in a second pass once the graph is fully built.
* <p>
* Your team will have to write code that builds the control flow graph and computes the
* relevant dominator information.
*/
public class BasicBlock {
/**
* Unique numerical index assigned by CFG builder between 0 and the total number of
* basic blocks. Useful for indexing into arrays and the like.
*/
public final int index;
/**
* List of predecessor blocks in the flow graph (i.e., blocks for
* which {@code this} is a successor).
*/
public final List<BasicBlock> predecessors = new ArrayList<BasicBlock>();
/**
* List of successor blocks in the flow graph (those that come after the
* current block). This list is always either of size 0, 1 or 2: 1 indicates
* that control flow continues directly into the next block, and 2 indicates
* that control flow goes in one of two directions, depending on the
* value that results when {@link #condition} is evaluated at runtime.
* If there are two successors, then the 0th entry is taken when {@code condition}
* evaluates to {@code true}.
*
* @see #trueSuccessor()
* @see #falseSuccessor()
*/
public final List<BasicBlock> successors = new ArrayList<BasicBlock>();
/**
* List of statements in this basic block.
*/
public final List<Stmt> stmts = new ArrayList<>();
/**
* If non-null, indicates that this basic block should have
* two successors. Control flows to the first successor if
* this condition evaluates at runtime to true, otherwise to
* the second successor. If null, the basic block should have
* only one successor.
*/
public Expr condition;
/**
* Parent of this basic block in the dominator tree (initially null until computed).
* Otherwise known as the immediate dominator.
*/
public BasicBlock dominatorTreeParent = null;
/**
* Children of this basic block in the dominator tree (initially empty until
* computed).
*/
public final List<BasicBlock> dominatorTreeChildren = new ArrayList<BasicBlock>();
/**
* Contains the dominance frontier of this block. A block b is in the dominance
* frontier of another block c if c does not dominate b, but c DOES dominate a
* predecessor of b.
*/
public final Set<BasicBlock> dominanceFrontier = new HashSet<BasicBlock>();
public BasicBlock(int index) {
this.index = index;
}
public BasicBlock trueSuccessor() {
assert this.condition != null;
return this.successors.get(0);
}
public BasicBlock falseSuccessor() {
assert this.condition != null;
return this.successors.get(1);
}
public <A, B> A accept(AstVisitor<A, B> visitor, B arg) {
A lastA = null;
for (Stmt stmt : stmts)
lastA = visitor.visit(stmt, arg);
if (condition != null)
lastA = visitor.visit(condition, arg);
return lastA;
}
@Override
public String toString() {
return "BB" + index;
}
}

View file

@ -0,0 +1,135 @@
package cd.ir;
import cd.ir.Ast.BinaryOp;
import cd.ir.Ast.BooleanConst;
import cd.ir.Ast.Expr;
import cd.ir.Ast.UnaryOp;
import cd.ir.Symbol.VariableSymbol;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static cd.Config.FALSE;
import static cd.Config.TRUE;
/**
* A visitor that only visits {@link Expr} nodes.
* calculates the compile time value of an rhs in an assignment statement or the boolean value of a condition
*/
public class CompileTimeEvaluator extends ExprVisitor<Integer, Map<VariableSymbol, Integer>> {
// if the expression contains non constant variables, field or index; or if the expression is a read()
// no compile Time value can be computed
private boolean failedToEvaluate;
public Optional<Integer> calc(Ast.Expr ast) {
return calc(ast, new HashMap<>());
}
public Optional<Integer> calc(Ast.Expr ast, Map<VariableSymbol, Integer> constantMap) {
try {
failedToEvaluate = false;
Integer result = visit(ast, constantMap);
if (failedToEvaluate)
return Optional.empty();
else
return Optional.of(result);
} catch (ArithmeticException e) {
return Optional.empty();
}
}
@Override
protected Integer dfltExpr(Expr ast, Map<VariableSymbol, Integer> arg) {
failedToEvaluate = true;
return Integer.MIN_VALUE;
}
@Override
public Integer binaryOp(BinaryOp ast, Map<VariableSymbol, Integer> arg) {
boolean tempResult;
int left = visit(ast.left(), arg);
int right = visit(ast.right(), arg);
switch (ast.operator) {
case B_TIMES:
return left * right;
case B_PLUS:
return left + right;
case B_MINUS:
return left - right;
case B_DIV:
return left / right;
case B_MOD:
return left % right;
case B_AND:
tempResult = (left == TRUE) && (right == TRUE);
break;
case B_OR:
tempResult = (left == TRUE) || (right == TRUE);
break;
case B_EQUAL:
tempResult = left == right;
break;
case B_NOT_EQUAL:
tempResult = left != right;
break;
case B_LESS_THAN:
tempResult = left < right;
break;
case B_LESS_OR_EQUAL:
tempResult = left <= right;
break;
case B_GREATER_THAN:
tempResult = left > right;
break;
case B_GREATER_OR_EQUAL:
tempResult = left >= right;
break;
default:
throw new RuntimeException("Invalid binary operator");
}
return tempResult ? TRUE : FALSE;
}
@Override
public Integer unaryOp(UnaryOp ast, Map<VariableSymbol, Integer> arg) {
int result = visit(ast.arg(), arg);
switch (ast.operator) {
case U_PLUS:
return result;
case U_MINUS:
return -result;
case U_BOOL_NOT:
return result == FALSE ? TRUE : FALSE;
default:
throw new RuntimeException("Invalid unary operator");
}
}
@Override
public Integer booleanConst(BooleanConst ast, Map<VariableSymbol, Integer> arg) {
return ast.value ? TRUE : FALSE;
}
@Override
public Integer intConst(Ast.IntConst ast, Map<VariableSymbol, Integer> arg) {
return ast.value;
}
// check if a given Variable has a constant value
@Override
public Integer var(Ast.Var ast, Map<VariableSymbol, Integer> arg) {
if (arg.containsKey(ast.sym)) {
return arg.get(ast.sym);
} else {
failedToEvaluate = true;
return Integer.MIN_VALUE;
}
}
}

View file

@ -0,0 +1,61 @@
package cd.ir;
import cd.ir.Ast.Expr;
import java.util.ArrayList;
import java.util.List;
/**
* Represents the control flow graph of a single method.
*/
public class ControlFlowGraph {
public BasicBlock start, end;
public final List<BasicBlock> allBlocks = new ArrayList<BasicBlock>();
public int count() {
return allBlocks.size();
}
public BasicBlock newBlock() {
BasicBlock blk = new BasicBlock(count());
allBlocks.add(blk);
return blk;
}
/**
* Given a list of basic blocks that do not yet have successors,
* merges their control flows into a single successor and returns
* the new successor.
*/
public BasicBlock join(BasicBlock... pred) {
BasicBlock result = newBlock();
for (BasicBlock p : pred) {
assert p.condition == null;
assert p.successors.size() == 0;
p.successors.add(result);
result.predecessors.add(p);
}
return result;
}
/**
* Terminates {@code blk} so that it evaluates {@code cond},
* and creates two new basic blocks, one for the case where
* the result is true, and one for the case where the result is
* false.
*/
public void terminateInCondition(BasicBlock blk, Expr cond) {
assert blk.condition == null;
assert blk.successors.size() == 0;
blk.condition = cond;
blk.successors.add(newBlock());
blk.successors.add(newBlock());
blk.trueSuccessor().predecessors.add(blk);
blk.falseSuccessor().predecessors.add(blk);
}
public void connect(BasicBlock from, BasicBlock to) {
to.predecessors.add(from);
from.successors.add(to);
}
}

View file

@ -5,16 +5,17 @@ import cd.ir.Ast.Expr;
/**
* A visitor that only visits {@link Expr} nodes.
*/
public class ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
public class ExprVisitor<R, A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
* it can be overloaded to introduce memoization,
* for example.
*/
public R visit(Expr ast, A arg) {
return ast.accept(this, arg);
}
/**
* Visits all children of the expression. Relies on the fact
* that {@link Expr} nodes only contain other {@link Expr} nodes.
@ -22,18 +23,19 @@ public class ExprVisitor<R,A> {
public R visitChildren(Expr ast, A arg) {
R lastValue = null;
for (Ast child : ast.children())
lastValue = visit((Expr)child, arg);
lastValue = visit((Expr) child, arg);
return lastValue;
}
/**
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
* by seq() by default.
*/
protected R dfltExpr(Expr ast, A arg) {
return visitChildren(ast, arg);
}
public R binaryOp(Ast.BinaryOp ast, A arg) {
return dfltExpr(ast, arg);
}
@ -41,15 +43,15 @@ public class ExprVisitor<R,A> {
public R booleanConst(Ast.BooleanConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R builtInRead(Ast.BuiltInRead ast, A arg) {
return dfltExpr(ast, arg);
}
public R cast(Ast.Cast ast, A arg) {
return dfltExpr(ast, arg);
}
public R field(Ast.Field ast, A arg) {
return dfltExpr(ast, arg);
}
@ -61,7 +63,7 @@ public class ExprVisitor<R,A> {
public R intConst(Ast.IntConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R methodCall(Ast.MethodCallExpr ast, A arg) {
return dfltExpr(ast, arg);
}
@ -73,7 +75,7 @@ public class ExprVisitor<R,A> {
public R newArray(Ast.NewArray ast, A arg) {
return dfltExpr(ast, arg);
}
public R nullConst(Ast.NullConst ast, A arg) {
return dfltExpr(ast, arg);
}

View file

@ -1,43 +1,45 @@
package cd.ir;
import cd.backend.codegen.AstCodeGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class Symbol {
public final String name;
public static abstract class TypeSymbol extends Symbol {
public TypeSymbol(String name) {
super(name);
}
public abstract boolean isReferenceType();
@Override
public String toString() {
public String toString() {
return name;
}
public abstract TypeSymbol getSuperType();
public boolean isSuperTypeOf(TypeSymbol sub) {
// "void" is not a subtype of any type not even itself
if(this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType)
return false;
// "void" is not a subtype of any type not even itself
if (this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType)
return false;
if (sub == this)
return true;
if (this instanceof PrimitiveTypeSymbol || sub instanceof PrimitiveTypeSymbol)
return false; // no hierarchy with primitive types
if (sub == ClassSymbol.nullType && this.isReferenceType())
return true;
TypeSymbol curr = sub;
while (curr != null) {
if (curr == this)
@ -46,94 +48,106 @@ public abstract class Symbol {
}
return false;
}
}
public static class PrimitiveTypeSymbol extends TypeSymbol {
/** Symbols for the built-in primitive types */
/**
* Symbols for the built-in primitive types
*/
public static final PrimitiveTypeSymbol intType = new PrimitiveTypeSymbol("int");
public static final PrimitiveTypeSymbol voidType = new PrimitiveTypeSymbol("void");
public static final PrimitiveTypeSymbol booleanType = new PrimitiveTypeSymbol("boolean");
public PrimitiveTypeSymbol(String name) {
super(name);
}
}
@Override
public boolean isReferenceType() {
public boolean isReferenceType() {
return false;
}
@Override
public TypeSymbol getSuperType() {
public TypeSymbol getSuperType() {
throw new RuntimeException("should not call this on PrimitiveTypeSymbol");
}
}
public static class ArrayTypeSymbol extends TypeSymbol {
public final TypeSymbol elementType;
public ArrayTypeSymbol(TypeSymbol elementType) {
super(elementType.name+"[]");
super(elementType.name + "[]");
this.elementType = elementType;
}
@Override
public boolean isReferenceType() {
public boolean isReferenceType() {
return true;
}
@Override
public TypeSymbol getSuperType() {
public TypeSymbol getSuperType() {
return ClassSymbol.objectType;
}
}
public static class ClassSymbol extends TypeSymbol {
public final Ast.ClassDecl ast;
public ClassSymbol superClass;
public final VariableSymbol thisSymbol =
new VariableSymbol("this", this);
public final Map<String, VariableSymbol> fields =
new HashMap<String, VariableSymbol>();
new VariableSymbol("this", this);
public final Map<String, VariableSymbol> fields =
new HashMap<String, VariableSymbol>();
public final Map<String, MethodSymbol> methods =
new HashMap<String, MethodSymbol>();
new HashMap<String, MethodSymbol>();
/** Symbols for the built-in Object and null types */
public int totalMethods = -1;
public int totalFields = -1;
public int sizeof = -1;
/**
* Symbols for the built-in Object and null types
*/
public static final ClassSymbol nullType = new ClassSymbol("<null>");
public static final ClassSymbol objectType = new ClassSymbol("Object");
public static final ClassSymbol objectType = new ClassSymbol("Object");
public ClassSymbol(Ast.ClassDecl ast) {
super(ast.name);
this.ast = ast;
}
/** Used to create the default {@code Object}
* and {@code <null>} types */
/**
* Used to create the default {@code Object}
* and {@code <null>} types
*/
public ClassSymbol(String name) {
super(name);
this.ast = null;
}
@Override
public boolean isReferenceType() {
public boolean isReferenceType() {
return true;
}
@Override
public TypeSymbol getSuperType() {
public TypeSymbol getSuperType() {
return superClass;
}
public VariableSymbol getField(String name) {
VariableSymbol fsym = fields.get(name);
if (fsym == null && superClass != null)
return superClass.getField(name);
return fsym;
}
public MethodSymbol getMethod(String name) {
MethodSymbol msym = methods.get(name);
if (msym == null && superClass != null)
@ -143,32 +157,53 @@ public abstract class Symbol {
}
public static class MethodSymbol extends Symbol {
public final Ast.MethodDecl ast;
public final Map<String, VariableSymbol> locals =
new HashMap<String, VariableSymbol>();
new HashMap<String, VariableSymbol>();
public final List<VariableSymbol> parameters =
new ArrayList<VariableSymbol>();
new ArrayList<VariableSymbol>();
public TypeSymbol returnType;
public ClassSymbol owner;
public int vtableIndex = -1;
public MethodSymbol overrides;
public MethodSymbol(Ast.MethodDecl ast) {
super(ast.name);
this.ast = ast;
}
@Override
public String toString() {
public String toString() {
return name + "(...)";
}
}
public static class VariableSymbol extends Symbol {
public static enum Kind { PARAM, LOCAL, FIELD };
public static enum Kind {PARAM, LOCAL, FIELD}
;
public final TypeSymbol type;
public final Kind kind;
/**
* Meaning depends on the kind of variable, but generally refers
* to the offset in bytes from some base ptr to where the variable
* is found.
* <ul>
* <li>{@code PARAM}, {@code LOCAL}: Offset from BP
* <li>{@code FIELD}: Offset from object
* <li>{@code CONSTANT}: N/A
* </ul>
* Computed in {@link AstCodeGenerator}.
*/
public int offset = -1;
public VariableSymbol(String name, TypeSymbol type) {
this(name, type, Kind.PARAM);
}
@ -176,11 +211,11 @@ public abstract class Symbol {
public VariableSymbol(String name, TypeSymbol type, Kind kind) {
super(name);
this.type = type;
this.kind = kind;
this.kind = kind;
}
@Override
public String toString() {
public String toString() {
return name;
}
}

View file

@ -0,0 +1,46 @@
package cd.transform;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.optimizers.*;
import java.util.List;
public class AstOptimizer {
private static final IOptimizer[] optimizers = {
new TrivialOpRemoval(), // 100% accuracy
new BranchElimination(), // 100% accuracy
new BlockRemoval(), // 100% accuracy
new ConstantPropagator(),// 98% accuracy on its own
// new WriteOnlyVarRemoval(),
};
private static final IGlobalOptimizer[] globalOptimizers = {
// new UnusedClassRemoval(),// 98% accuracy on its own
// new UnusedMethodRemoval(),
};
public void go(List<ClassDecl> classDeclList, List<TypeSymbol> allTypeSymbols) {
// Local optimizations inside methods/blocks always comes first
for (ClassDecl classDecl : classDeclList) {
for (MethodDecl methodDecl : classDecl.methods()) {
boolean changed = true;
while (changed) {
changed = false;
for (IOptimizer optimizer : optimizers) {
changed |= optimizer.go(methodDecl.cfg);
}
}
}
}
boolean globalChange = true;
while (globalChange) {
globalChange = false;
for (IGlobalOptimizer globalOptimizer : globalOptimizers) {
globalChange |= globalOptimizer.go(classDeclList, allTypeSymbols);
}
}
}
}

View file

@ -0,0 +1,85 @@
package cd.transform;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
public class CfgBuilder {
ControlFlowGraph cfg;
public void build(MethodDecl mdecl) {
cfg = mdecl.cfg = new ControlFlowGraph();
cfg.start = cfg.newBlock(); // Note: Use newBlock() to create new basic blocks
cfg.end = cfg.newBlock(); // unique exit block to which all blocks that end with a return stmt. lead
{
BasicBlock lastInBody = new Visitor().visit(mdecl.body(), cfg.start);
if (lastInBody != null) cfg.connect(lastInBody, cfg.end);
}
// CFG and AST are not synchronized, only use CFG from now on
mdecl.setBody(null);
}
protected class Visitor extends AstVisitor<BasicBlock, BasicBlock> {
@Override
protected BasicBlock dfltStmt(Stmt ast, BasicBlock arg) {
if (arg == null) return null; // dead code, no need to generate anything
arg.stmts.add(ast);
return arg;
}
@Override
public BasicBlock ifElse(IfElse ast, BasicBlock arg) {
if (arg == null) return null; // dead code, no need to generate anything
cfg.terminateInCondition(arg, ast.condition());
BasicBlock then = visit(ast.then(), arg.trueSuccessor());
BasicBlock otherwise = visit(ast.otherwise(), arg.falseSuccessor());
if (then != null && otherwise != null) {
return cfg.join(then, otherwise);
} else if (then != null) {
BasicBlock newBlock = cfg.newBlock();
cfg.connect(then, newBlock);
return newBlock;
} else if (otherwise != null) {
BasicBlock newBlock = cfg.newBlock();
cfg.connect(otherwise, newBlock);
return newBlock;
} else {
return null;
}
}
@Override
public BasicBlock seq(Seq ast, BasicBlock arg_) {
BasicBlock arg = arg_;
for (Ast child : ast.children())
arg = this.visit(child, arg);
return arg;
}
@Override
public BasicBlock whileLoop(WhileLoop ast, BasicBlock arg) {
if (arg == null) return null; // dead code, no need to generate anything
BasicBlock cond = cfg.join(arg);
cfg.terminateInCondition(cond, ast.condition());
BasicBlock body = visit(ast.body(), cond.trueSuccessor());
if (body != null) cfg.connect(body, cond);
return cond.falseSuccessor();
}
@Override
public BasicBlock returnStmt(Ast.ReturnStmt ast, BasicBlock arg) {
if (arg == null) return null; // dead code, no need to generate anything
arg.stmts.add(ast);
cfg.connect(arg, cfg.end);
return null; // null means that this block leads nowhere else
}
}
}

View file

@ -0,0 +1,51 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.VariableSymbol;
import java.util.Map;
import java.util.Optional;
public class ArraySizeAnalysis extends MaybeFlowAnalysis<Integer> {
public ArraySizeAnalysis(ControlFlowGraph cfg) {
super(cfg, false, true);
}
@Override
protected MaybeC<Integer> defaultInit(VariableSymbol varSym) {
return MaybeC.notConstant();
}
@Override
public void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<Integer>> state) {
CompileTimeEvaluator cte = new CompileTimeEvaluator();
if (stmt instanceof Assign) {
Assign assign = (Assign) stmt;
if (assign.right() instanceof Ast.NewArray) {
Ast.NewArray newArray = (Ast.NewArray) assign.right();
VariableSymbol sym = getVarSym(assign.left());
if (sym == null) return;
Optional<Integer> arraySize = cte.calc(newArray.arg());
if (arraySize.isPresent()) {
state.put(sym, MaybeC.value(arraySize.get()));
} else {
state.get(sym).setNotConstant();
}
}
}
}
private VariableSymbol getVarSym(Ast.Expr expr) {
if (expr instanceof Ast.Var) {
return ((Ast.Var) expr).sym;
} else if (expr instanceof Ast.Field) {
return ((Ast.Field) expr).sym;
}
return null;
}
}

View file

@ -0,0 +1,101 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.NewArray;
import cd.ir.Ast.NewObject;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.NullAnalysis.NullVisitor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class AssignmentAnalysis extends MaybeFlowAnalysis<List<Ast.Assign>> {
public AssignmentAnalysis(ControlFlowGraph cfg) {
super(cfg, false, true);
}
@Override
protected MaybeC<List<Ast.Assign>> defaultInit(VariableSymbol varSym) {
List<Ast.Assign> value = new ArrayList<Ast.Assign>();
return MaybeC.value(value);
}
/*
* the state contains all assignments that are unused and can be removed
* the last assign stmt in the list represents the current assignment of a variable
*
* if assignment: check lhs:
* if lhs is variable, put assign stmt in state of the variable
* meaning that this assign stmt was not used yet
* visit rhs to check for used variables
*
* if not assignment, visit ast node for all var symbols and remove the last assign stmt if var was used
*/
@Override
public void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<List<Ast.Assign>>> state) {
if (stmt instanceof Assign) {
Assign assign = (Assign) stmt;
if (assign.right() instanceof Ast.Var) {
Ast.Var var = (Ast.Var) assign.left();
VariableSymbol sym = var.sym;
if (sym == null) return;
MaybeC<List<Assign>> maybeStateList = state.get(sym);
List<Ast.Assign> stateList = maybeStateList.getValue();
stateList.add(assign);
MaybeC<List<Assign>> newMaybeStateList = MaybeC.value(stateList);
state.put(sym, newMaybeStateList);
}
removeAssignStmt(assign.right(), state);
return;
}
// TODO: if not assign stmt visit node and check for used variables
if (stmt instanceof Ast.BuiltInWrite) {
Ast.BuiltInWrite write = (Ast.BuiltInWrite) stmt;
removeAssignStmt(write.arg(), state);
}
}
// iterates over all var symbols and checks if one was used
// if so, remove last assignment
public void removeAssignStmt(Ast.Expr ast, Map<VariableSymbol, MaybeC<List<Ast.Assign>>> state) {
for (VariableSymbol varSym: state.keySet()) {
Boolean isused = new varVisitor().visit(ast, varSym);
if (isused) {
MaybeC<List<Assign>> maybeStateList = state.get(varSym);
List<Ast.Assign> stateList = maybeStateList.getValue();
if (!stateList.isEmpty()) {
int end = stateList.size() - 1;
stateList.remove(end);
}
MaybeC<List<Assign>> newMaybeStateList = MaybeC.value(stateList);
state.put(varSym, newMaybeStateList);
}
}
return;
}
// returns true if a given variable is used inside an Ast node
static class varVisitor extends ExprVisitor<Boolean, VariableSymbol> {
@Override
public Boolean var(Ast.Var ast, VariableSymbol var) {
return ast.sym == var;
}
}
}

View file

@ -0,0 +1,76 @@
package cd.transform.analysis;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.ir.Symbol.ArrayTypeSymbol;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.TypeSymbol;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ClassUsage extends AstVisitor<Void, Set<ClassSymbol>> {
public Set<ClassDecl> go(List<ClassDecl> classDecls) {
Set<ClassDecl> used = new HashSet<>();
Set<ClassSymbol> todo = new HashSet<>();
for (ClassDecl classDecl : classDecls)
if (classDecl.name.equals("Main"))
todo.add(classDecl.sym);
while (!todo.isEmpty()) {
ClassSymbol sym = todo.iterator().next();
todo.remove(sym);
if (!used.contains(sym.ast) && sym != ClassSymbol.objectType) {
visit(sym.ast, todo);
used.add(sym.ast);
}
}
return used;
}
@Override
public Void classDecl(ClassDecl ast, Set<ClassSymbol> arg) {
arg.add(ast.sym.superClass);
return super.classDecl(ast, arg);
}
@Override
protected Void dfltExpr(Expr ast, Set<ClassSymbol> arg) {
queueIfClassSymbol(ast.type, arg);
return super.dfltExpr(ast, arg);
}
@Override
public Void newObject(NewObject ast, Set<ClassSymbol> arg) {
arg.add((ClassSymbol) ast.type);
return super.newObject(ast, arg);
}
@Override
public Void newArray(NewArray ast, Set<ClassSymbol> arg) {
queueIfClassSymbol(ast.type, arg);
return super.newArray(ast, arg);
}
@Override
public Void varDecl(VarDecl ast, Set<ClassSymbol> arg) {
queueIfClassSymbol(ast.sym.type, arg);
return super.varDecl(ast, arg);
}
@Override
public Void methodDecl(MethodDecl ast, Set<ClassSymbol> arg) {
queueIfClassSymbol(ast.sym.returnType, arg);
return super.methodDecl(ast, arg);
}
private void queueIfClassSymbol(TypeSymbol type, Set<ClassSymbol> arg) {
if (type instanceof ArrayTypeSymbol)
type = ((ArrayTypeSymbol) type).elementType;
if (type instanceof ClassSymbol)
arg.add((ClassSymbol) type);
}
}

View file

@ -0,0 +1,50 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.Var;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.VariableSymbol;
import java.util.Map;
import java.util.Optional;
/**
* This class implements constant propagation:
* remember variables that have constant values
* no need evaluate them at runtime, save movl instruction
*/
public class ConstantAnalysis extends MaybeFlowAnalysis<Integer> {
public ConstantAnalysis(ControlFlowGraph cfg) {
super(cfg, true, false);
}
@Override
protected MaybeC<Integer> defaultInit(VariableSymbol varSym) {
if (varSym.kind == VariableSymbol.Kind.LOCAL)
return MaybeC.value(0);
else
return MaybeC.notConstant();
}
@Override
public void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<Integer>> state) {
if (stmt instanceof Assign) {
Assign ast = (Assign) stmt;
if (ast.left() instanceof Var) {
VariableSymbol symbol = ((Var) ast.left()).sym;
if (!state.containsKey(symbol)) return;
// Obtain value and previous MaybeConstant
Optional<Integer> valueOpt = cte.calc(ast.right());
MaybeC<Integer> mc = state.get(symbol);
if (valueOpt.isPresent()) {
state.put(symbol, MaybeC.value(valueOpt.get()));
} else {
mc.setNotConstant();
}
}
}
}
}

View file

@ -0,0 +1,189 @@
package cd.transform.analysis;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.unmodifiableMap;
/**
* The abstract superclass of all data-flow analyses. This class provides a framework to
* implement concrete analyses by providing {@link #initialState()},
* {@link #startState()}, {@link #transferFunction(BasicBlock, Object)}, and
* {@link #join(Set)} methods.
*
* @param <State> The type of states the analysis computes, specified by a concrete subclass.
* Typically, this is a set or map type.
*/
public abstract class DataFlowAnalysis<State> {
protected final ControlFlowGraph cfg;
Map<BasicBlock, State> inStates;
Map<BasicBlock, State> outStates;
public DataFlowAnalysis(ControlFlowGraph cfg) {
this.cfg = cfg;
}
/**
* Returns the in-state of basic block <code>block</code>.
*/
public State inStateOf(BasicBlock block) {
return inStates.getOrDefault(block, startState());
}
/**
* Returns the out-state of basic block <code>block</code>.
*/
public State outStateOf(BasicBlock block) {
return outStates.get(block);
}
protected void emptyStates() {
inStates = new HashMap<>();
outStates = new HashMap<>();
}
/**
* Do forward flow fixed-point iteration until out-states do not change anymore.
* Subclasses should call this method in their constructor after the required
* initialization.
*/
protected void iterate() {
inStates = new HashMap<>();
outStates = new HashMap<>();
for (BasicBlock block : cfg.allBlocks)
outStates.put(block, initialState());
Set<BasicBlock> todo = new HashSet<>();
todo.addAll(cfg.allBlocks);
while (!todo.isEmpty()) {
BasicBlock block = todo.iterator().next();
todo.remove(block);
/* calculate in-state */
State inState;
if (block == cfg.start)
inState = startState();
else {
Set<State> predOutStates = new HashSet<>();
for (BasicBlock pred : block.predecessors)
predOutStates.add(outStates.get(pred));
inState = join(predOutStates);
}
inStates.put(block, inState);
State newOutState = transferFunction(block, inState);
/* if out-state changed, recalculate successors */
if (!newOutState.equals(outStates.get(block))) {
outStates.put(block, newOutState);
todo.addAll(block.successors);
}
}
outStates = unmodifiableMap(outStates);
}
/**
* Returns the initial state for all blocks except the {@link ControlFlowGraph#start start}
* block.
*/
protected abstract State initialState();
/**
* Returns the initial state for the {@link ControlFlowGraph#start start} block.
*/
protected abstract State startState();
/**
* Calculates the out-state for a basic block <code>block</code> and an in-state
* <code>inState</code>
*/
protected abstract State transferFunction(BasicBlock block, State inState);
/**
* Merges together several out-states and returns the in-state for the transfer function.
*/
protected abstract State join(Set<State> states);
/**
* DataFlowAnalysis implementation with reversed direction. The methods are overridden in
* order to reflect the change in the parameters' names and in the javadoc.
*/
public static abstract class Reverse<A> extends DataFlowAnalysis<A> {
public Reverse(ControlFlowGraph cfg) {
super(cfg);
}
/**
* Do backward flow fixed-point iteration until in-states do not change anymore.
* Subclasses should call this method in their constructor after the required
* initialization.
*/
@Override
protected void iterate() {
inStates = new HashMap<>();
outStates = new HashMap<>();
for (BasicBlock block : cfg.allBlocks)
inStates.put(block, initialState());
Set<BasicBlock> todo = new HashSet<>();
todo.addAll(cfg.allBlocks);
while (!todo.isEmpty()) {
BasicBlock block = todo.iterator().next();
todo.remove(block);
/* calculate out-state */
A outState;
if (block == cfg.end)
outState = startState();
else {
Set<A> predInStates = new HashSet<>();
for (BasicBlock pred : block.successors)
predInStates.add(inStates.get(pred));
outState = join(predInStates);
}
outStates.put(block, outState);
A newInState = transferFunction(block, outState);
/* if in-state changed, recalculate successors */
if (!newInState.equals(inStates.get(block))) {
inStates.put(block, newInState);
todo.addAll(block.predecessors);
}
}
inStates = unmodifiableMap(inStates);
}
/**
* Returns the initial state for all blocks except the {@link ControlFlowGraph#end end}
* block.
*/
@Override
protected abstract A initialState();
/**
* Returns the initial state for the {@link ControlFlowGraph#end end} block.
*/
@Override
protected abstract A startState();
/**
* Calculates the in-state for a basic block <code>block</code> and an out-state
* <code>outState</code>
*/
@Override
protected abstract A transferFunction(BasicBlock block, A outState);
/**
* Merges together several in-states and returns the out-state for the transfer function.
*/
@Override
protected abstract A join(Set<A> states);
}
}

View file

@ -0,0 +1,50 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.Var;
import cd.ir.ControlFlowGraph;
import cd.ir.ExprVisitor;
import cd.ir.Symbol;
import cd.ir.Symbol.TypeSymbol;
import java.util.Map;
public class DynamicTypeAnalysis extends MaybeFlowAnalysis<TypeSymbol> {
public DynamicTypeAnalysis(ControlFlowGraph cfg) {
super(cfg, false, true);
}
@Override
public void transferStmt(Ast.Stmt stmt, Map<Symbol.VariableSymbol, MaybeC<TypeSymbol>> state) {
if (stmt instanceof Assign) {
Assign assign = (Assign) stmt;
if (!(assign.left() instanceof Var)) return;
Symbol.VariableSymbol sym = ((Var) assign.left()).sym;
if (!state.containsKey(sym)) return;
TypeSymbol type = new TypeVisitor().visit(assign.right(), null);
if (type != null) {
state.put(sym, MaybeC.value(type));
} else {
state.get(sym).setNotConstant();
}
}
}
@Override
protected MaybeC<TypeSymbol> defaultInit(Symbol.VariableSymbol varSym) {
return MaybeC.notConstant();
}
static class TypeVisitor extends ExprVisitor<TypeSymbol, Void> {
@Override
public TypeSymbol newObject(Ast.NewObject ast, Void arg) {
return ast.type;
}
@Override
public TypeSymbol newArray(Ast.NewArray ast, Void arg) {
return ast.type;
}
}
}

View file

@ -0,0 +1,92 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.VariableSymbol;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class LiveVarAnalysis extends DataFlowAnalysis.Reverse<Set<VariableSymbol>> {
private Map<BasicBlock, Set<VariableSymbol>> killSets = new HashMap<>();
private Map<BasicBlock, Set<VariableSymbol>> genSets = new HashMap<>();
public LiveVarAnalysis(ControlFlowGraph cfg) {
super(cfg);
// For faster iteration, the transfer function will just use kill and gen sets
for (BasicBlock block : cfg.allBlocks)
killGenSetOf(block);
iterate();
}
private void killGenSetOf(BasicBlock block) {
Set<VariableSymbol> kill = new HashSet<>();
Set<VariableSymbol> gen = new HashSet<>();
if (block.condition != null)
new Visitor().visit(block.condition, gen);
for (int i = block.stmts.size() - 1; i >= 0; i--) {
VariableSymbol var = new Visitor().visit(block.stmts.get(i), gen);
if (var != null) {
// TODO: review this code
kill.add(var);
gen.remove(var);
}
}
killSets.put(block, kill);
genSets.put(block, gen);
}
@Override
protected Set<VariableSymbol> initialState() {
return new HashSet<>();
}
@Override
protected Set<VariableSymbol> startState() {
return new HashSet<>();
}
@Override
protected Set<VariableSymbol> transferFunction(BasicBlock block, Set<VariableSymbol> inState) {
Set<VariableSymbol> result = new HashSet<>(inState);
result.removeAll(killSets.get(block));
result.addAll(genSets.get(block));
return result;
}
@Override
protected Set<VariableSymbol> join(Set<Set<VariableSymbol>> states) {
Set<VariableSymbol> result = new HashSet<>();
states.forEach(result::addAll);
return result;
}
public static class Visitor extends AstVisitor<VariableSymbol, Set<VariableSymbol>> {
@Override
public VariableSymbol assign(Assign ast, Set<VariableSymbol> arg) {
visit(ast.right(), arg);
if (ast.left() instanceof Ast.Var)
return visit(ast.left(), arg);
else
return null;
}
@Override
public VariableSymbol var(Ast.Var ast, Set<VariableSymbol> arg) {
if (!ast.sym.type.isReferenceType()) {
arg.add(ast.sym);
return ast.sym;
}
return null;
}
}
}

View file

@ -0,0 +1,112 @@
package cd.transform.analysis;
public class MaybeC<E> {
private E value;
private boolean anyValue;
static <T> MaybeC<T> anyValue() {
return new MaybeC<>(null, true);
}
static <T> MaybeC<T> notConstant() {
return new MaybeC<>(null, false);
}
static <T> MaybeC<T> value(T value) {
return new MaybeC<>(value, false);
}
private MaybeC(E value, boolean anyValue) {
this.value = value;
this.anyValue = anyValue;
}
MaybeC(MaybeC<E> base) {
this.value = base.value;
this.anyValue = base.anyValue;
}
public void setValue(E value) {
assert isAnyValue() || isConstant();
assert value != null;
anyValue = false;
this.value = value;
}
public E getValue() {
assert isConstant();
return value;
}
public boolean isAnyValue() {
return anyValue && value == null;
}
public boolean isNotConstant() {
return !anyValue && value == null;
}
public boolean isConstant() {
return !anyValue && value != null;
}
public void setNotConstant() {
anyValue = false;
value = null;
}
public void setAnyValue() {
assert isAnyValue();
}
/**
* Updates the current object, following the table: <br/>
* <table border="1">
* <tr><td> </td><td>notConstant</td><td>v1</td><td>anyValue</td></tr>
* <tr><td>notConstant</td><td>notConstant</td><td>notConstant</td><td>notConstant</td></tr>
* <tr><td>v2</td><td>notConstant</td><td>v1==v2?v1:notConstant</td><td>v2</td></tr>
* <tr><td>anyValue</td><td>notConstant</td><td>v1</td><td>anyValue</td></tr>
* </table>
*/
public void joinWith(MaybeC<E> other) {
if (this.isNotConstant() || other.isNotConstant()) {
setNotConstant();
} else if (!this.isAnyValue() || !other.isAnyValue()) {
if (this.isAnyValue())
setValue(other.getValue()); // Copy the other's value
else if (!other.isAnyValue() && this.getValue() != other.getValue())
setNotConstant(); // Values don't match
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MaybeC) {
MaybeC other = (MaybeC) obj;
if (isNotConstant() && other.isNotConstant())
return true;
if (isAnyValue() && other.isAnyValue())
return true;
if (isConstant() && other.isConstant()) {
if (value == null || other.value == null) {
return value == other.value;
} else {
return value.equals(other.value);
}
}
}
return false;
}
@Override
public String toString() {
if (isAnyValue()) return "any";
if (isNotConstant()) return "none";
if (isConstant()) return getValue().toString();
throw new RuntimeException("MaybeC in erroneous state");
}
}

View file

@ -0,0 +1,115 @@
package cd.transform.analysis;
import cd.ir.*;
import cd.ir.Ast.Var;
import cd.ir.Symbol.VariableSymbol;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public abstract class MaybeFlowAnalysis<E> extends DataFlowAnalysis<Map<VariableSymbol, MaybeC<E>>> {
protected final CompileTimeEvaluator cte = new CompileTimeEvaluator();
private final Set<VariableSymbol> allVars = new HashSet<>();
public MaybeFlowAnalysis(ControlFlowGraph cfg, boolean primitiveVars, boolean referenceVars) {
super(cfg);
// Obtain all variables
for (BasicBlock block : cfg.allBlocks) {
VarVisitor visitor = new VarVisitor(primitiveVars, referenceVars);
block.accept(visitor, allVars);
}
if (!allVars.isEmpty())
iterate();
else
emptyStates();
}
public Map<VariableSymbol, E> inStateConstantsOf(BasicBlock block) {
return getConstants(inStateOf(block));
}
public Map<VariableSymbol, E> outStateConstantsOf(BasicBlock block) {
return getConstants(outStateOf(block));
}
private Map<VariableSymbol, E> getConstants(Map<VariableSymbol, MaybeC<E>> map) {
Map<VariableSymbol, E> result = new HashMap<>();
if (map == null) return result;
map.forEach((key, value) -> {
if (value.isConstant())
result.put(key, value.getValue());
});
return result;
}
/**
* Reads a statement and updates the current state accordingly,
*/
public abstract void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<E>> state);
/**
* Default value for each constant at the beginning of the start block
*/
protected abstract MaybeC<E> defaultInit(VariableSymbol varSym);
@Override
protected Map<VariableSymbol, MaybeC<E>> initialState() {
Map<VariableSymbol, MaybeC<E>> map = new HashMap<>();
allVars.forEach(v -> map.put(v, MaybeC.anyValue()));
return map;
}
@Override
protected Map<VariableSymbol, MaybeC<E>> startState() {
Map<VariableSymbol, MaybeC<E>> map = new HashMap<>();
allVars.forEach(v -> map.put(v, defaultInit(v)));
return map;
}
@Override
protected Map<VariableSymbol, MaybeC<E>> transferFunction(BasicBlock block, Map<VariableSymbol, MaybeC<E>> inState) {
Map<VariableSymbol, MaybeC<E>> outState = new HashMap<>();
inState.forEach((key, value) -> outState.put(key, new MaybeC<>(value)));
for (Ast.Stmt stmt : block.stmts)
transferStmt(stmt, outState);
return outState;
}
@Override
protected Map<VariableSymbol, MaybeC<E>> join(Set<Map<VariableSymbol, MaybeC<E>>> states) {
// The basic condition is that the value must be the same in all maps for the constant to be included
Map<VariableSymbol, MaybeC<E>> result = new HashMap<>();
for (VariableSymbol sym : allVars) {
MaybeC<E> value = MaybeC.anyValue();
for (Map<VariableSymbol, MaybeC<E>> map : states) {
value.joinWith(map.get(sym));
if (value.isNotConstant())
break; // It cannot change once it is notConstant
}
result.put(sym, value);
}
return result;
}
class VarVisitor extends AstVisitor<Void, Set<VariableSymbol>> {
private final boolean primitive, reference;
VarVisitor(boolean primitive, boolean reference) {
this.primitive = primitive;
this.reference = reference;
}
@Override
public Void var(Var ast, Set<VariableSymbol> arg) {
if (ast.sym.type.isReferenceType() && reference)
arg.add(ast.sym);
else if (!ast.sym.type.isReferenceType() && primitive)
arg.add(ast.sym);
return null;
}
}
}

View file

@ -0,0 +1,59 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.NewArray;
import cd.ir.Ast.NewObject;
import cd.ir.ControlFlowGraph;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.VariableSymbol;
import java.util.Map;
public class NullAnalysis extends MaybeFlowAnalysis<Boolean> {
public NullAnalysis(ControlFlowGraph cfg) {
super(cfg, false, true);
}
@Override
protected MaybeC<Boolean> defaultInit(VariableSymbol varSym) {
if (varSym.kind == VariableSymbol.Kind.LOCAL)
return MaybeC.value(true);
else
return MaybeC.notConstant();
}
@Override
public void transferStmt(Ast.Stmt stmt, Map<VariableSymbol, MaybeC<Boolean>> state) {
if (stmt instanceof Assign) {
Assign assign = (Assign) stmt;
if (assign.left() instanceof Ast.Var &&
state.containsKey(((Ast.Var) assign.left()).sym)) {
VariableSymbol sym = ((Ast.Var) assign.left()).sym;
if (!state.containsKey(sym)) return;
Boolean rightHandSide = new NullVisitor().visit(assign.right(), null);
if (rightHandSide != null)
state.put(sym, MaybeC.value(rightHandSide));
else
state.get(sym).setNotConstant();
}
}
}
static class NullVisitor extends ExprVisitor<Boolean, Void> {
@Override
public Boolean newObject(NewObject ast, Void arg) {
return false;
}
@Override
public Boolean newArray(NewArray ast, Void arg) {
return false;
}
@Override
public Boolean nullConst(Ast.NullConst ast, Void arg) {
return true;
}
}
}

View file

@ -0,0 +1,39 @@
package cd.transform.analysis;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.AstVisitor;
import cd.ir.BasicBlock;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class StaticMethodUsage extends AstVisitor<Void, Set<MethodSymbol>> {
public Set<MethodSymbol> go(List<ClassDecl> classDecls) {
Set<MethodSymbol> usedMethods = new HashSet<>();
for (ClassDecl classDecl : classDecls) {
if (classDecl.name.equals("Main")) {
Ast.MethodDecl mainMethod = classDecl.sym.getMethod("main").ast;
for (BasicBlock block : mainMethod.cfg.allBlocks) {
block.accept(this, usedMethods);
}
}
}
return usedMethods;
}
@Override
public Void methodCall(Ast.MethodCall ast, Set<MethodSymbol> arg) {
ClassSymbol classSym = (ClassSymbol) ast.getMethodCallExpr().receiver().type;
MethodSymbol method = classSym.getMethod(ast.getMethodCallExpr().methodName);
arg.add(method);
visit(method.ast, arg);
return null;
}
}

View file

@ -0,0 +1,55 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import java.util.ListIterator;
public class BlockRemoval implements IOptimizer {
@Override
public boolean go(ControlFlowGraph cfg) {
boolean changed = false;
ListIterator<BasicBlock> iterator = cfg.allBlocks.listIterator();
while (iterator.hasNext()) {
BasicBlock block = iterator.next();
if (block == cfg.start || block == cfg.end)
continue;
if (block.condition == null
&& (block.stmts.isEmpty() || (block.stmts.size() == 1
&& block.stmts.get(0) instanceof Ast.Nop))) {
// The block is empty or has a Nop, there is no branch
// Therefore the predecessors can be directly linked to the successor
BasicBlock successor = block.successors.get(0);
boolean cancelDeletion = false;
for (BasicBlock pred : block.predecessors) {
if (pred.condition != null) {
cancelDeletion = true;
break;
}
int posInPred = pred.successors.indexOf(block);
pred.successors.set(posInPred, successor);
}
if (cancelDeletion) continue;
for (BasicBlock succ : block.successors) {
succ.predecessors.remove(block);
succ.predecessors.addAll(block.predecessors);
}
iterator.remove();
changed = true;
} else if (block.condition != null
&& block.trueSuccessor() == block.falseSuccessor()) {
// There is a condition and both successors are equal, therefore we can remove it
BasicBlock successor = block.trueSuccessor();
block.successors.clear();
block.successors.add(successor);
block.condition = null;
changed = true;
}
}
return changed;
}
}

View file

@ -0,0 +1,50 @@
package cd.transform.optimizers;
import cd.ir.BasicBlock;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import static cd.Config.TRUE;
/**
* this class eliminates branches if a block condition evaluates to false at compile time
*/
public class BranchElimination implements IOptimizer {
@Override
public boolean go(ControlFlowGraph cfg) {
CompileTimeEvaluator cte = new CompileTimeEvaluator();
Set<BasicBlock> removedSet = new HashSet<>();
boolean changed = false;
for (BasicBlock block : cfg.allBlocks) {
// Removed blocks and blocks without condition don't matter
if (block.condition == null || removedSet.contains(block))
continue;
// If the condition cannot be computed, skip block
Optional<Integer> condition = cte.calc(block.condition);
if (!condition.isPresent()) continue;
changed = true;
// Remove all connections to unreachable block
BasicBlock eliminated;
if (condition.get() == TRUE)
eliminated = block.falseSuccessor();
else
eliminated = block.trueSuccessor();
block.successors.remove(eliminated);
block.condition = null;
for (BasicBlock b : eliminated.successors)
b.predecessors.remove(eliminated);
removedSet.add(eliminated);
}
// Can't edit the list while we are iterating over it
cfg.allBlocks.removeAll(removedSet);
return changed;
}
}

View file

@ -0,0 +1,70 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.Ast.BooleanConst;
import cd.ir.Ast.Expr;
import cd.ir.Ast.IntConst;
import cd.ir.Ast.Var;
import cd.ir.BasicBlock;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import cd.ir.Symbol.PrimitiveTypeSymbol;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.ConstantAnalysis;
import java.util.Map;
import java.util.Optional;
import static cd.Config.TRUE;
/**
* Class that propagates constant values to avoid accessing variables at runtime,
* and also includes simplifications to arithmetic operations.
*/
public class ConstantPropagator extends OptimizerVisitor<Map<VariableSymbol, Integer>> {
private CompileTimeEvaluator cte = new CompileTimeEvaluator();
@Override
public boolean go(ControlFlowGraph cfg) {
setChanged(false);
ConstantAnalysis constantAnalysis = new ConstantAnalysis(cfg);
for (BasicBlock block : cfg.allBlocks) {
block.accept(this, constantAnalysis.inStateConstantsOf(block));
}
return hasChanged();
}
@Override
public Ast assign(Ast.Assign ast, Map<VariableSymbol, Integer> arg) {
// Visit and replace only the RHS of the statement
Expr lhs = ast.left();
ast.setLeft(null);
visitChildren(ast, arg);
ast.setLeft(lhs);
// Update the map (only with constant values)
if (ast.left() instanceof Var && !ast.left().type.isReferenceType()) {
Optional<Integer> rightValue = cte.calc(ast.right(), arg);
if (rightValue.isPresent())
arg.put(((Var) ast.left()).sym, rightValue.get());
else
arg.remove(((Var) ast.left()).sym);
}
return ast;
}
@Override
public Ast var(Var ast, Map<VariableSymbol, Integer> arg) {
Integer value = arg.get(ast.sym);
if (value == null)
return ast;
if (ast.type == PrimitiveTypeSymbol.intType)
return new IntConst(value);
assert ast.type == PrimitiveTypeSymbol.booleanType;
return new BooleanConst(value == TRUE);
}
}

View file

@ -0,0 +1,23 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.AstRewriteVisitor;
public abstract class GlobalOptimizerVisitor<A>
extends AstRewriteVisitor<A>
implements IGlobalOptimizer {
private boolean changed;
@Override
protected void nodeReplaced(Ast oldNode, Ast newNode) {
changed = true;
}
boolean hasChanged() {
return changed;
}
final void setChanged(boolean changed) {
this.changed = changed;
}
}

View file

@ -0,0 +1,10 @@
package cd.transform.optimizers;
import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol.TypeSymbol;
import java.util.List;
public interface IGlobalOptimizer {
boolean go(List<ClassDecl> classDecls, List<TypeSymbol> typeList);
}

View file

@ -0,0 +1,7 @@
package cd.transform.optimizers;
import cd.ir.ControlFlowGraph;
public interface IOptimizer {
boolean go(ControlFlowGraph cfg);
}

View file

@ -0,0 +1,23 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.AstRewriteVisitor;
public abstract class OptimizerVisitor<E>
extends AstRewriteVisitor<E> implements IOptimizer {
private boolean changed;
@Override
protected void nodeReplaced(Ast oldNode, Ast newNode) {
changed = true;
}
boolean hasChanged() {
return changed;
}
final void setChanged(boolean changed) {
this.changed = changed;
}
}

View file

@ -0,0 +1,143 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.BasicBlock;
import cd.ir.CompileTimeEvaluator;
import cd.ir.ControlFlowGraph;
import java.util.Optional;
import static cd.Config.TRUE;
import static cd.ir.Ast.UnaryOp.UOp.U_MINUS;
public class TrivialOpRemoval extends OptimizerVisitor<Void> {
private CompileTimeEvaluator cte = new CompileTimeEvaluator();
@Override
public boolean go(ControlFlowGraph cfg) {
setChanged(false);
for (BasicBlock block : cfg.allBlocks)
block.accept(this, null);
return hasChanged();
}
@Override
public Ast binaryOp(BinaryOp ast, Void arg) {
visitChildren(ast, arg);
Optional<Integer> rightRes = cte.calc(ast.right());
Optional<Integer> leftRes = cte.calc(ast.left());
if (leftRes.isPresent() && rightRes.isPresent()) {
// Skip the operation if both arguments' values are known
Optional<Integer> resOpt = cte.calc(ast);
if (!resOpt.isPresent())
return ast;
switch (ast.operator) {
case B_TIMES:
case B_DIV:
case B_MOD:
case B_PLUS:
case B_MINUS:
return new IntConst(resOpt.get());
default:
return new BooleanConst(resOpt.get() == TRUE);
}
} else if (!leftRes.isPresent() && !rightRes.isPresent()) {
// Nothing can be done if no argument is known
return ast;
}
switch (ast.operator) {
case B_PLUS: // a + 0 = a (commutative)
if (leftRes.isPresent() && leftRes.get() == 0)
return ast.right();
else if (rightRes.isPresent() && rightRes.get() == 0)
return ast.left();
break;
case B_MINUS: // a - 0 = a | 0 - a = -a
if (leftRes.isPresent() && leftRes.get() == 0)
return new UnaryOp(U_MINUS, ast.right());
else if (rightRes.isPresent() && rightRes.get() == 0)
return ast.left();
break;
case B_TIMES: // a * 0 = 0 | a * 1 = a | a * -1 = -a (commutative)
if (rightRes.isPresent()) {
if (rightRes.get() == 1)
return ast.left();
else if (rightRes.get() == -1)
return new UnaryOp(U_MINUS, ast.left());
// else if (rightRes.get() == 0)
// return new IntConst(0);
} else {
if (leftRes.get() == 1)
return ast.right();
else if (leftRes.get() == -1)
return new UnaryOp(U_MINUS, ast.right());
// else if (leftRes.get() == 0)
// return new IntConst(0);
}
break;
case B_DIV: // 0 / a = 0 | a / 1 = a | a / -1 = -a
if (rightRes.isPresent()) {
if (rightRes.get() == 1)
return ast.left();
else if (rightRes.get() == -1)
return new UnaryOp(U_MINUS, ast.left());
// } else if (leftRes.get() == 0)
// return new IntConst(0);
}
break;
case B_MOD: // 0 % a = 0 | a % 1 = 0
// if (rightValid && rightValue == 1)
// return new IntConst(0);
// else if (leftValid && leftValue == 0)
// return new IntConst(0);
break;
}
return ast;
}
@Override
public Ast unaryOp(UnaryOp ast, Void arg) {
visitChildren(ast, arg);
Optional<Integer> valueOpt = cte.calc(ast);
if (valueOpt.isPresent()) {
// Skip the operation if the argument's value is known
switch (ast.operator) {
case U_BOOL_NOT:
return new BooleanConst(valueOpt.get() == TRUE);
default:
return new IntConst(valueOpt.get());
}
} else {
switch (ast.operator) {
case U_PLUS:
// The plus operator is meaningless
return ast.arg();
default:
// Nested unary operators with the same operator are meaningless
if (ast.arg() instanceof UnaryOp) {
UnaryOp inner = (UnaryOp) ast.arg();
if (inner.operator == ast.operator)
return inner.arg();
else return ast;
} else {
return ast;
}
}
}
}
@Override
public Ast cast(Cast ast, Void arg) {
visitChildren(ast, arg);
if (ast.type == ast.arg().type)
return ast.arg();
return ast;
}
}

View file

@ -0,0 +1,38 @@
package cd.transform.optimizers;
import cd.ir.Ast.ClassDecl;
import cd.ir.Symbol.ArrayTypeSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.analysis.ClassUsage;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UnusedClassRemoval extends GlobalOptimizerVisitor<Void> {
public boolean go(List<ClassDecl> classDecls, List<TypeSymbol> typeList) {
Set<ClassDecl> used = new ClassUsage().go(classDecls);
Set<ClassDecl> toRemove = new HashSet<>(classDecls);
toRemove.removeAll(used);
// Remove classes from list of declarations
classDecls.clear();
classDecls.addAll(used);
// Remove corresponding types and array types
for (ClassDecl classDecl : toRemove) {
typeList.remove(classDecl.sym);
TypeSymbol arrayType = null;
for (TypeSymbol type : typeList) {
if (type instanceof ArrayTypeSymbol && ((ArrayTypeSymbol) type).elementType == classDecl.sym) {
arrayType = type;
break;
}
}
assert arrayType != null;
typeList.remove(arrayType);
}
return !toRemove.isEmpty();
}
}

View file

@ -0,0 +1,56 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Symbol.ClassSymbol;
import cd.ir.Symbol.MethodSymbol;
import cd.ir.Symbol.TypeSymbol;
import cd.transform.analysis.StaticMethodUsage;
import java.util.List;
import java.util.Set;
public class UnusedMethodRemoval extends GlobalOptimizerVisitor<Set<MethodSymbol>> {
public boolean go(List<ClassDecl> classDecls, List<TypeSymbol> typeList) {
Set<MethodSymbol> used = new StaticMethodUsage().go(classDecls);
for (ClassDecl classDecl : classDecls)
visit(classDecl, used);
return hasChanged();
}
@Override
public Ast methodDecl(MethodDecl ast, Set<MethodSymbol> arg) {
// To remove a method, it mustn't be in the Set
boolean keepMethod = arg.contains(ast.sym);
if (keepMethod) return ast;
// But also needs not to be overriding a method in the Set
ClassSymbol classSym = ast.sym.owner;
while (classSym != ClassSymbol.objectType) {
keepMethod = arg.contains(classSym.getMethod(ast.name));
classSym = classSym.superClass;
}
if (keepMethod) return ast;
// Remove method from class (only Symbol, Visitor takes care of Ast)
ast.sym.owner.methods.remove(ast.name);
// Remove all methods overridden by the deleted method (both Ast and Symbol, as we are not currently
// looping over it
classSym = ast.sym.owner;
while (classSym != ClassSymbol.objectType) {
assert classSym.ast != null;
classSym.ast.rwChildren.removeIf(child -> child instanceof MethodDecl &&
((MethodDecl) child).name.equals(ast.name));
classSym.methods.remove(ast.name);
classSym = classSym.superClass;
}
// Return from this classes' Ast node
return null;
}
}

View file

@ -0,0 +1,58 @@
package cd.transform.optimizers;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import cd.ir.ExprVisitor;
import cd.ir.Symbol.VariableSymbol;
import cd.transform.analysis.LiveVarAnalysis;
import java.util.*;
public class WriteOnlyVarRemoval extends OptimizerVisitor<Set<VariableSymbol>> {
@Override
public boolean go(ControlFlowGraph cfg) {
LiveVarAnalysis analysis = new LiveVarAnalysis(cfg);
for (BasicBlock block : cfg.allBlocks) {
Set<VariableSymbol> state = new HashSet<>(analysis.outStateOf(block));
if (block.condition != null)
new LiveVarAnalysis.Visitor().visit(block.condition, state);
for (ListIterator<Stmt> iterator = block.stmts.listIterator(block.stmts.size()); iterator.hasPrevious(); ) {
Stmt stmt = iterator.previous();
Ast res = visit(stmt, state);
// TODO: this code should work? even if we iterate through the newly created Stmt nodes
if (stmt != res) {
iterator.remove();
assert res instanceof Seq;
Seq seq = (Seq) res;
for (Ast ast : seq.rwChildren) {
assert ast instanceof Stmt;
iterator.add((Stmt) ast);
}
nodeReplaced(stmt, res);
}
}
}
return hasChanged();
}
@Override
public Ast assign(Assign ast, Set<VariableSymbol> arg) {
if (ast.left() instanceof Var && !arg.contains(((Var) ast.left()).sym)) {
List<Ast> methodCalls = new ArrayList<>();
new MethodCallFinder().visit(ast.right(), methodCalls);
return new Seq(methodCalls);
} else {
return ast;
}
}
private class MethodCallFinder extends ExprVisitor<Void, List<Ast>> {
@Override
public Void methodCall(Ast.MethodCallExpr ast, List<Ast> arg) {
arg.add(new MethodCall(ast));
return null;
}
}
}

View file

@ -0,0 +1,60 @@
package cd.util;
import cd.ir.BasicBlock;
import cd.ir.ControlFlowGraph;
import java.util.*;
/**
* A potentially handy iterator which yields the blocks in a control-flow
* graph. The order is pre-order, depth-first. Pre-order means that a
* node is visited before its successors.
*/
public class DepthFirstSearchPreOrder implements Iterable<BasicBlock> {
public final ControlFlowGraph cfg;
public DepthFirstSearchPreOrder(ControlFlowGraph cfg) {
this.cfg = cfg;
}
public Iterator<BasicBlock> iterator() {
return new Iterator<BasicBlock>() {
/** Blocks we still need to visit */
private final Stack<BasicBlock> stack = new Stack<BasicBlock>();
/** Blocks we pushed thus far */
private final Set<BasicBlock> pushed = new HashSet<BasicBlock>();
{
stack.add(cfg.start);
pushed.add(cfg.start);
}
public boolean hasNext() {
return !stack.isEmpty();
}
public BasicBlock next() {
if (stack.isEmpty())
throw new NoSuchElementException();
BasicBlock res = stack.pop();
for (BasicBlock s : res.successors)
if (!pushed.contains(s)) {
pushed.add(s);
stack.add(s);
}
return res;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

View file

@ -9,12 +9,12 @@ public class FileUtil {
public static String readAll(Reader ubReader) throws IOException {
try (BufferedReader bReader = new BufferedReader(ubReader)) {
StringBuilder sb = new StringBuilder();
while (true) {
int ch = bReader.read();
if (ch == -1)
break;
sb.append((char) ch);
}
return sb.toString();
@ -32,7 +32,7 @@ public class FileUtil {
}
public static String runCommand(File dir, String[] command,
String[] substs, String input, boolean detectError)
String[] substs, String input, boolean detectError)
throws IOException {
// Substitute the substitution strings $0, $1, etc
String newCommand[] = new String[command.length];
@ -53,12 +53,12 @@ public class FileUtil {
osw.write(input);
}
}
try {
final StringBuffer result = new StringBuffer();
// thread to read stdout from child so that p.waitFor() is interruptible
// by JUnit's timeout mechanism. Otherwise it would block in readAll()
Thread t = new Thread () {
Thread t = new Thread() {
public void run() {
try {
result.append(readAll(new InputStreamReader(p.getInputStream())));
@ -67,7 +67,7 @@ public class FileUtil {
}
};
t.start();
if (detectError) {
int err = p.waitFor();
@ -76,7 +76,7 @@ public class FileUtil {
if (err != 0)
return "Error: " + err + "\n";
}
t.join();
return result.toString();
} catch (InterruptedException e) {
@ -94,13 +94,15 @@ public class FileUtil {
public static void findJavaliFiles(File testDir, List<Object[]> result) {
for (File testFile : testDir.listFiles()) {
if (testFile.getName().endsWith(".javali"))
result.add(new Object[] { testFile });
result.add(new Object[]{testFile});
else if (testFile.isDirectory())
findJavaliFiles(testFile, result);
}
}
/** Finds all .javali under directory {@code testDir} and returns them. */
/**
* Finds all .javali under directory {@code testDir} and returns them.
*/
public static List<File> findJavaliFiles(File testDir) {
List<File> result = new ArrayList<File>();
for (File testFile : testDir.listFiles()) {

View file

@ -3,16 +3,18 @@ package cd.util;
import java.util.ArrayList;
import java.util.List;
/** Simple class for joining two objects of the same type */
/**
* Simple class for joining two objects of the same type
*/
public class Pair<T> {
public T a;
public T b;
public Pair(T a, T b) {
this.a = a;
this.b = b;
}
public static <T> List<Pair<T>> zip(List<T> listA, List<T> listB) {
List<Pair<T>> res = new ArrayList<Pair<T>>();
for (int i = 0; i < Math.min(listA.size(), listB.size()); i++) {
@ -20,24 +22,24 @@ public class Pair<T> {
}
return res;
}
public static <T> List<T> unzipA(List<Pair<T>> list) {
List<T> res = new ArrayList<T>();
for (Pair<T> p : list)
res.add(p.a);
return res;
}
public static <T> List<T> unzipB(List<Pair<T>> list) {
List<T> res = new ArrayList<T>();
for (Pair<T> p : list)
res.add(p.b);
return res;
}
public static String join(
List<Pair<?>> pairs,
String itemSep,
List<Pair<?>> pairs,
String itemSep,
String pairSep) {
StringBuilder sb = new StringBuilder();
boolean first = true;

View file

@ -1,15 +1,17 @@
package cd.util;
/** Simple class for joining two objects of different type */
public class Tuple<A,B> {
/**
* Simple class for joining two objects of different type
*/
public class Tuple<A, B> {
public A a;
public B b;
public Tuple(A a, B b) {
this.a = a;
this.b = b;
}
public String toString() {
return "(" + a.toString() + ", " + b.toString() + ")";
}

View file

@ -8,11 +8,11 @@ import java.lang.reflect.Field;
import java.util.*;
public class AstDump {
public static String toString(Ast ast) {
return toString(ast, "");
}
public static String toString(List<? extends Ast> astRoots) {
StringBuilder sb = new StringBuilder();
for (Ast a : astRoots) {
@ -26,22 +26,22 @@ public class AstDump {
ad.dump(ast, indent);
return ad.sb.toString();
}
public static String toStringFlat(Ast ast) {
AstDump ad = new AstDump();
ad.dumpFlat(ast);
return ad.sb.toString();
}
private StringBuilder sb = new StringBuilder();
private Visitor vis = new Visitor();
protected void dump(Ast ast, String indent) {
// print out the overall class structure
sb.append(indent);
String nodeName = ast.getClass().getSimpleName();
List<Pair<?>> flds = vis.visit(ast, null);
sb.append(String.format("%s (%s)\n",
sb.append(String.format("%s (%s)\n",
nodeName,
Pair.join(flds, ": ", ", ")));
@ -51,11 +51,11 @@ public class AstDump {
dump(child, newIndent);
}
}
protected void dumpFlat(Ast ast) {
String nodeName = ast.getClass().getSimpleName();
List<Pair<?>> flds = vis.visit(ast, null);
sb.append(String.format("%s(%s)[",
sb.append(String.format("%s(%s)[",
nodeName,
Pair.join(flds, ":", ",")));
@ -68,39 +68,39 @@ public class AstDump {
sb.append("]");
}
protected class Visitor extends AstVisitor<List<Pair<?>>, Void> {
@Override
protected List<Pair<?>> dflt(Ast ast, Void arg) {
ArrayList<Pair<?>> res = new ArrayList<Pair<?>>();
// Get the list of fields and sort them by name:
java.lang.Class<? extends Ast> rclass = ast.getClass();
List<java.lang.reflect.Field> rflds =
Arrays.asList(rclass.getFields());
Collections.sort(rflds, new Comparator<java.lang.reflect.Field> () {
List<java.lang.reflect.Field> rflds =
Arrays.asList(rclass.getFields());
Collections.sort(rflds, new Comparator<java.lang.reflect.Field>() {
public int compare(Field o1, Field o2) {
return o1.getName().compareTo(o2.getName());
}
});
// Create pairs for each one that is not of type Ast:
for (java.lang.reflect.Field rfld : rflds) {
rfld.setAccessible(true);
// ignore various weird fields that show up from
// time to time:
if (rfld.getName().startsWith("$"))
continue;
// ignore fields of AST type, and rwChildren (they should be
// uncovered using the normal tree walk)
if (rfld.getType().isAssignableFrom(Ast.class))
continue;
if (rfld.getName().equals("rwChildren"))
continue;
// ignore NULL fields, but add others to our list of pairs
try {
Object value = rfld.get(ast);
@ -110,12 +110,12 @@ public class AstDump {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
return res;
}
}
}

View file

@ -5,13 +5,13 @@ import cd.ir.Ast.*;
import cd.ir.AstVisitor;
public class AstOneLine {
public static String toString(Ast ast) {
return new Visitor().visit(ast, null);
}
protected static class Visitor extends AstVisitor<String, Void> {
public String str(Ast ast) {
return ast.accept(this, null);
}
@ -23,7 +23,7 @@ public class AstOneLine {
@Override
public String binaryOp(BinaryOp ast, Void arg) {
return String.format("(%s %s %s)",
return String.format("(%s %s %s)",
str(ast.left()), ast.operator.repr, str(ast.right()));
}
@ -36,12 +36,12 @@ public class AstOneLine {
public String builtInRead(BuiltInRead ast, Void arg) {
return String.format("read()");
}
@Override
public String builtInWrite(BuiltInWrite ast, Void arg) {
return String.format("write(%s)", str(ast.arg()));
}
@Override
public String builtInWriteln(BuiltInWriteln ast, Void arg) {
return String.format("writeln()");
@ -81,12 +81,12 @@ public class AstOneLine {
public String methodCall(MethodCall ast, Void arg) {
return str(ast.getMethodCallExpr());
}
@Override
public String methodCall(MethodCallExpr ast, Void arg) {
return String.format("%s.%s(...)", str(ast.receiver()), ast.methodName);
}
@Override
public String methodDecl(MethodDecl ast, Void arg) {
return String.format("%s %s(...) {...}", ast.returnType, ast.name);
@ -121,7 +121,7 @@ public class AstOneLine {
public String thisRef(ThisRef ast, Void arg) {
return "this";
}
@Override
public String returnStmt(ReturnStmt ast, Void arg) {
return ast.arg() != null ? String.format("return %s", str(ast.arg())) : "return";
@ -136,10 +136,10 @@ public class AstOneLine {
public String var(Var ast, Void arg) {
{
if (ast.sym != null) {
String symName = ast.sym.toString();
String symName = ast.sym.toString();
if (ast.name == null || ast.name.equals(symName))
return symName;
// Return something strange to warn about the mismatch here:
return String.format("(%s!=%s)", symName, ast.name);
} else
@ -156,6 +156,6 @@ public class AstOneLine {
public String whileLoop(WhileLoop ast, Void arg) {
return String.format("while (%s) {...}", str(ast.condition()));
}
}
}

View file

@ -0,0 +1,156 @@
package cd.util.debug;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.Stmt;
import cd.ir.BasicBlock;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
public class CfgDump {
public static void toString(
MethodDecl mdecl,
String phase,
File filename,
boolean dumpDominators) {
if (filename == null) return;
FileWriter fw;
try {
fw = new FileWriter(new File(filename.getAbsolutePath() + phase + ".dot"));
fw.write(toString(mdecl, dumpDominators));
fw.close();
} catch (IOException e) {
}
}
public static void toString(
List<? extends ClassDecl> astRoots,
String phase,
File filename,
boolean dumpDominators) {
if (filename == null) return;
FileWriter fw;
try {
fw = new FileWriter(new File(filename.getAbsolutePath() + phase + ".dot"));
fw.write(toString(astRoots, dumpDominators));
fw.close();
} catch (IOException e) {
}
}
public static String toString(
List<? extends ClassDecl> astRoots,
boolean dumpDominators) {
return new CfgDump().dump(astRoots, dumpDominators);
}
public static String toString(
MethodDecl mdecl,
boolean dumpDominators) {
return new CfgDump().dump(mdecl, dumpDominators);
}
private int indent = 0;
private final StringBuilder sb = new StringBuilder();
private void append(String format, Object... args) {
String out = String.format(format, args);
if (out.startsWith("}") || out.startsWith("]")) indent -= 2;
for (int i = 0; i < indent; i++) sb.append(" ");
if (out.endsWith("{") || out.endsWith("[")) indent += 2;
sb.append(String.format(format, args));
sb.append("\n");
}
private String dump(
MethodDecl mdecl,
boolean dumpDominators) {
sb.setLength(0);
append("digraph G {");
append("graph [ rankdir = \"LR\" ];");
dumpBlocks(dumpDominators, mdecl, "");
append("}");
return sb.toString();
}
private String dump(
List<? extends ClassDecl> astRoots,
boolean dumpDominators) {
sb.setLength(0);
append("digraph G {");
append("graph [ rankdir = \"LR\" ];");
int sgcntr = 0, mcntr = 0;
for (ClassDecl cdecl : astRoots) {
//append("subgraph cluster_%d {", sgcntr++);
//append("label = \"%s\";", cdecl.name);
for (MethodDecl mdecl : cdecl.methods()) {
append("subgraph cluster_%d {", sgcntr++);
append("label = \"%s.%s\"", cdecl.name, mdecl.name);
String m = String.format("M%d_", mcntr++);
dumpBlocks(dumpDominators, mdecl, m);
append("}");
}
//append("}");
}
append("}");
return sb.toString();
}
private void dumpBlocks(boolean dumpDominators, MethodDecl mdecl, String m) {
for (BasicBlock blk : mdecl.cfg.allBlocks) {
append("%sBB%d [", m, blk.index);
append("shape=\"record\"");
// If we are not just dumping dominators, then build up
// a label with all the instructions in the block.
StringBuilder blklbl = new StringBuilder();
blklbl.append(String.format("BB%d", blk.index));
if (!dumpDominators || true) {
for (Stmt stmt : blk.stmts)
blklbl.append("|").append(AstOneLine.toString(stmt));
if (blk.condition != null)
blklbl.append("|If: " + AstOneLine.toString(blk.condition));
}
String[] replacements = new String[]{
"<", "\\<",
">", "\\>",
"@", "\\@",
"||", "\\|\\|",
};
String blklbls = blklbl.toString();
for (int i = 0; i < replacements.length; i += 2)
blklbls = blklbls.replace(replacements[i], replacements[i + 1]);
append("label=\"%s\"", blklbls);
append("];");
for (int idx = 0; idx < blk.successors.size(); idx++) {
BasicBlock sblk = blk.successors.get(idx);
String edgelbl = (idx == 0 ? "" : " [label=\"False\"]");
append("%sBB%d -> %sBB%d%s;",
m, blk.index, m, sblk.index, edgelbl);
}
{
if (dumpDominators && blk.dominatorTreeParent != null) {
append("%sBB%d -> %sBB%d [color=\"red\" label=\"dom\"];",
m, blk.index, m, blk.dominatorTreeParent.index);
}
}
}
}
}

View file

@ -11,13 +11,13 @@ import java.util.Set;
import static java.util.Collections.sort;
class DumpUtils {
static final Comparator<ClassDecl> classComparator = new Comparator<ClassDecl>() {
public int compare(ClassDecl left, ClassDecl right) {
return left.name.compareTo(right.name);
}
};
static final Comparator<MethodDecl> methodComparator = new Comparator<MethodDecl>() {
public int compare(MethodDecl left, MethodDecl right) {
return left.name.compareTo(right.name);
@ -26,7 +26,7 @@ class DumpUtils {
static List<String> sortedStrings(Set<?> set) {
List<String> strings = new ArrayList<String>();
for(Object element : set)
for (Object element : set)
strings.add(element.toString());
sort(strings);
return strings;