Homework 4
This commit is contained in:
parent
0afc86ceeb
commit
72cc3206c4
125 changed files with 4200 additions and 1636 deletions
19
src/cd/backend/ExitCode.java
Normal file
19
src/cd/backend/ExitCode.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package cd.backend;
|
||||
|
||||
public enum ExitCode {
|
||||
OK(0),
|
||||
INVALID_DOWNCAST(1),
|
||||
INVALID_ARRAY_STORE(2),
|
||||
INVALID_ARRAY_BOUNDS(3),
|
||||
NULL_POINTER(4),
|
||||
INVALID_ARRAY_SIZE(5),
|
||||
INFINITE_LOOP(6),
|
||||
DIVISION_BY_ZERO(7),
|
||||
INTERNAL_ERROR(22);
|
||||
|
||||
public final int value;
|
||||
|
||||
private ExitCode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
176
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
176
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.Config;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class AssemblyEmitter {
|
||||
public Writer out;
|
||||
public StringBuilder indent = new StringBuilder();
|
||||
public int counter = 0;
|
||||
|
||||
public AssemblyEmitter(Writer out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/** Creates an constant operand. */
|
||||
static String constant(int i) {
|
||||
return "$" + i;
|
||||
}
|
||||
|
||||
/** Creates an constant operand with the address of a label. */
|
||||
static String labelAddress(String lbl) {
|
||||
return "$" + lbl;
|
||||
}
|
||||
|
||||
/** 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 */
|
||||
static String arrayAddress(Register arrReg, Register idxReg) {
|
||||
final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
|
||||
// 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);
|
||||
}
|
||||
|
||||
void increaseIndent(String comment) {
|
||||
indent.append(" ");
|
||||
if (comment != null)
|
||||
emitComment(comment);
|
||||
}
|
||||
|
||||
void decreaseIndent() {
|
||||
indent.setLength(indent.length() - 2);
|
||||
}
|
||||
|
||||
void emitCommentSection(String name) {
|
||||
int indentLen = indent.length();
|
||||
int breakLen = 68 - indentLen - name.length();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(Config.COMMENT_SEP).append(" ");
|
||||
for (int i = 0; i < indentLen; i++)
|
||||
sb.append("_");
|
||||
sb.append(name);
|
||||
for (int i = 0; i < breakLen; i++)
|
||||
sb.append("_");
|
||||
|
||||
try {
|
||||
out.write(sb.toString());
|
||||
out.write("\n");
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
void emitComment(String comment) {
|
||||
emitRaw(Config.COMMENT_SEP + " " + comment);
|
||||
}
|
||||
|
||||
void emit(String op, Register src, String dest) {
|
||||
emit(op, src.repr, dest);
|
||||
}
|
||||
|
||||
void emit(String op, String src, Register dest) {
|
||||
emit(op, src, dest.repr);
|
||||
}
|
||||
|
||||
void emit(String op, Register src, Register dest) {
|
||||
emit(op, src.repr, dest.repr);
|
||||
}
|
||||
|
||||
void emit(String op, String src, String dest) {
|
||||
emitRaw(String.format("%s %s, %s", op, src, dest));
|
||||
}
|
||||
|
||||
void emit(String op, int src, Register dest) {
|
||||
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);
|
||||
}
|
||||
|
||||
void emit(String op, Register reg) {
|
||||
emit(op, reg.repr);
|
||||
}
|
||||
|
||||
void emit(String op, int dest) {
|
||||
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);
|
||||
}
|
||||
|
||||
void emitMove(Register src, Register dest) {
|
||||
emitMove(src.repr, dest.repr);
|
||||
}
|
||||
|
||||
void emitMove(String src, Register dest) {
|
||||
emitMove(src, dest.repr);
|
||||
}
|
||||
|
||||
void emitMove(String src, String dest) {
|
||||
if (!src.equals(dest))
|
||||
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);
|
||||
}
|
||||
|
||||
void emitStore(Register src, int destOffset, Register dest) {
|
||||
emitStore(src.repr, destOffset, dest);
|
||||
}
|
||||
|
||||
void emitStore(String src, int destOffset, Register dest) {
|
||||
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));
|
||||
}
|
||||
|
||||
String uniqueLabel() {
|
||||
String labelName = "label" + counter++;
|
||||
return labelName;
|
||||
}
|
||||
|
||||
void emitLabel(String label) {
|
||||
try {
|
||||
out.write(label + ":" + "\n");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void emitRaw(String op) {
|
||||
try {
|
||||
out.write(indent.toString());
|
||||
out.write(op);
|
||||
out.write("\n");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
14
src/cd/backend/codegen/AssemblyFailedException.java
Normal file
14
src/cd/backend/codegen/AssemblyFailedException.java
Normal file
|
@ -0,0 +1,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;
|
||||
}
|
||||
}
|
172
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
172
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.Config;
|
||||
import cd.Main;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Symbol.*;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
import static cd.Config.MAIN;
|
||||
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 final Main main;
|
||||
|
||||
final AssemblyEmitter emit;
|
||||
final RegisterManager rm = new RegisterManager();
|
||||
|
||||
AstCodeGenerator(Main main, Writer out) {
|
||||
initMethodData();
|
||||
main.allTypeSymbols = new ArrayList<>();
|
||||
|
||||
this.emit = new AssemblyEmitter(out);
|
||||
this.main = main;
|
||||
this.rnv = new RegsNeededVisitor();
|
||||
|
||||
this.eg = new ExprGenerator(this);
|
||||
this.sg = new StmtGenerator(this);
|
||||
}
|
||||
|
||||
protected void debug(String format, Object... args) {
|
||||
this.main.debug(format, args);
|
||||
}
|
||||
|
||||
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
|
||||
return new AstCodeGenerator(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>
|
||||
* <li>Prologue: Generated by {@link #emitPrefix()}. This contains any
|
||||
* introductory declarations and the like.
|
||||
* <li>Body: Generated by {@link ExprGenerator}. This contains the main
|
||||
* method definitions.
|
||||
* </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)
|
||||
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.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 the new Main().main() code to start the program:
|
||||
|
||||
// 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);
|
||||
|
||||
// 2. Create main variable
|
||||
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);
|
||||
}
|
||||
|
||||
void initMethodData() {
|
||||
rm.initRegisters();
|
||||
}
|
||||
|
||||
|
||||
void emitMethodSuffix(boolean returnNull) {
|
||||
if (returnNull)
|
||||
emit.emit("movl", 0, Register.EAX);
|
||||
emit.emitRaw("leave");
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
613
src/cd/backend/codegen/ExprGenerator.java
Normal file
613
src/cd/backend/codegen/ExprGenerator.java
Normal file
|
@ -0,0 +1,613 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.backend.ExitCode;
|
||||
import cd.backend.codegen.AstCodeGenerator.*;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.ExprVisitor;
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
import cd.ir.Symbol.PrimitiveTypeSymbol;
|
||||
import cd.ir.Symbol.VariableSymbol;
|
||||
import cd.util.debug.AstOneLine;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cd.Config.SCANF;
|
||||
import static cd.backend.codegen.AstCodeGenerator.*;
|
||||
import static cd.backend.codegen.RegisterManager.BASE_REG;
|
||||
import static cd.backend.codegen.RegisterManager.CALLER_SAVE;
|
||||
import static cd.backend.codegen.RegisterManager.STACK_REG;
|
||||
|
||||
/**
|
||||
* Generates code to evaluate expressions. After emitting the code, returns a
|
||||
* String which indicates the register where the result can be found.
|
||||
*/
|
||||
class ExprGenerator extends ExprVisitor<Register,Location> {
|
||||
private final AstCodeGenerator cg;
|
||||
|
||||
ExprGenerator(AstCodeGenerator astCodeGenerator) {
|
||||
cg = astCodeGenerator;
|
||||
}
|
||||
|
||||
public Register gen(Expr ast) {
|
||||
return visit(ast, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register visit(Expr ast, Location 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);
|
||||
} finally {
|
||||
cg.emit.decreaseIndent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register binaryOp(BinaryOp ast, Location arg) {
|
||||
// Simplistic HW1 implementation that does
|
||||
// not care if it runs out of registers
|
||||
|
||||
int leftRN = cg.rnv.calc(ast.left());
|
||||
int rightRN = cg.rnv.calc(ast.right());
|
||||
|
||||
Register leftReg, rightReg;
|
||||
if (leftRN > rightRN) {
|
||||
leftReg = visit(ast.left(), arg);
|
||||
rightReg = visit(ast.right(), arg);
|
||||
} else {
|
||||
rightReg = visit(ast.right(), arg);
|
||||
leftReg = visit(ast.left(), arg);
|
||||
}
|
||||
|
||||
cg.debug("Binary Op: %s (%s,%s)", ast, leftReg, rightReg);
|
||||
|
||||
switch (ast.operator) {
|
||||
case B_TIMES:
|
||||
cg.emit.emit("imul", rightReg, leftReg);
|
||||
break;
|
||||
case B_PLUS:
|
||||
cg.emit.emit("add", rightReg, leftReg);
|
||||
break;
|
||||
case B_MINUS:
|
||||
cg.emit.emit("sub", rightReg, leftReg);
|
||||
break;
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
// Check division by 0
|
||||
String beginDivision = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", 0, rightReg);
|
||||
cg.emit.emit("jne", beginDivision);
|
||||
Interrupts.exit(cg, ExitCode.DIVISION_BY_ZERO);
|
||||
cg.emit.emitLabel(beginDivision);
|
||||
|
||||
// Save EAX, EBX, and EDX to the stack if they are not used
|
||||
// in this subtree (but are used elsewhere). We will be
|
||||
// changing them.
|
||||
List<Register> dontBother = Arrays.asList(rightReg, leftReg);
|
||||
Register[] affected = { Register.EAX, Register.EBX, Register.EDX };
|
||||
|
||||
for (Register s : affected)
|
||||
if (!dontBother.contains(s) && cg.rm.isInUse(s))
|
||||
cg.emit.emit("pushl", s);
|
||||
|
||||
// Move the LHS (numerator) into eax
|
||||
// Move the RHS (denominator) into ebx
|
||||
cg.emit.emit("pushl", rightReg);
|
||||
cg.emit.emit("pushl", leftReg);
|
||||
cg.emit.emit("popl", Register.EAX);
|
||||
cg.emit.emit("popl", Register.EBX);
|
||||
cg.emit.emitRaw("cltd"); // sign-extend %eax into %edx
|
||||
cg.emit.emit("idivl", Register.EBX); // division, result into edx:eax
|
||||
|
||||
// Move the result into the LHS, and pop off anything we saved
|
||||
if (ast.operator == BinaryOp.BOp.B_DIV)
|
||||
cg.emit.emitMove(Register.EAX, leftReg);
|
||||
else
|
||||
cg.emit.emitMove(Register.EDX, leftReg);
|
||||
for (int i = affected.length - 1; i >= 0; i--) {
|
||||
Register s = affected[i];
|
||||
if (!dontBother.contains(s) && cg.rm.isInUse(s))
|
||||
cg.emit.emit("popl", s);
|
||||
}
|
||||
break;
|
||||
case B_AND:
|
||||
cg.emit.emit("and", rightReg, leftReg);
|
||||
break;
|
||||
case B_OR:
|
||||
cg.emit.emit("or", rightReg, leftReg);
|
||||
break;
|
||||
case B_EQUAL: // a == b <--> ! (a != b)
|
||||
cg.emit.emit("xor", rightReg, leftReg);
|
||||
// if 'leftReg'==0, set leftReg to '1'
|
||||
String equal = cg.emit.uniqueLabel();
|
||||
String end = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", 0, leftReg);
|
||||
cg.emit.emit("je", equal);
|
||||
cg.emit.emitMove(AssemblyEmitter.constant(0), leftReg);
|
||||
cg.emit.emit("jmp", end);
|
||||
cg.emit.emitLabel(equal);
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
cg.emit.emitLabel(end);
|
||||
break;
|
||||
case B_NOT_EQUAL:
|
||||
String skipTrue = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("xor", rightReg, leftReg);
|
||||
cg.emit.emit("cmp", 0, leftReg);
|
||||
cg.emit.emit("je", skipTrue);
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
cg.emit.emitLabel(skipTrue);
|
||||
break;
|
||||
default: // Comparison operations
|
||||
String endLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", rightReg, leftReg);
|
||||
// leftReg - rightReg
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
switch (ast.operator) {
|
||||
case B_LESS_THAN:
|
||||
cg.emit.emit("jl", endLabel);
|
||||
break;
|
||||
case B_LESS_OR_EQUAL:
|
||||
cg.emit.emit("jle", endLabel);
|
||||
break;
|
||||
case B_GREATER_THAN:
|
||||
cg.emit.emit("jg", endLabel);
|
||||
break;
|
||||
case B_GREATER_OR_EQUAL:
|
||||
cg.emit.emit("jge", endLabel);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("A binary operation wasn't implemented");
|
||||
}
|
||||
cg.emit.emitMove(FALSE, leftReg);
|
||||
cg.emit.emitLabel(endLabel);
|
||||
}
|
||||
|
||||
cg.rm.releaseRegister(rightReg);
|
||||
|
||||
return leftReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register booleanConst(BooleanConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(ast.value ? TRUE : FALSE, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register builtInRead(BuiltInRead ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emit("sub", 16, STACK_REG);
|
||||
cg.emit.emit("leal", AssemblyEmitter.registerOffset(8, STACK_REG), reg);
|
||||
cg.emit.emitStore(reg, 4, STACK_REG);
|
||||
cg.emit.emitStore("$STR_D", 0, STACK_REG);
|
||||
cg.emit.emit("call", SCANF);
|
||||
cg.emit.emitLoad(8, STACK_REG, reg);
|
||||
cg.emit.emit("add", 16, STACK_REG);
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Cast from one type to another: {@code (typeName)arg}
|
||||
*/
|
||||
@Override
|
||||
public Register cast(Cast ast, Location arg) {
|
||||
// 1. Obtain a register with the desired type's address
|
||||
Register desiredType = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(ast.type), desiredType); // lea copies the label's address to the reg
|
||||
|
||||
// 2. Get a reference to the object to be casted, and push a copy for later
|
||||
Register runtimeType = visit(ast.arg(), arg);
|
||||
cg.emit.emit("push", runtimeType);
|
||||
|
||||
// 3. Go to the type's vtable
|
||||
cg.emit.emitLoad(0, runtimeType, runtimeType);
|
||||
|
||||
// 4. Runtime type check: recursively go to superType until
|
||||
String matchLabel = cg.emit.uniqueLabel();
|
||||
String checkSuperTypeLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLabel(checkSuperTypeLabel);
|
||||
cg.emit.emit("cmpl", desiredType, runtimeType);
|
||||
cg.emit.emit("je", matchLabel); // 4.1 It matches: ok
|
||||
cg.emit.emitLoad(0, runtimeType, runtimeType); // Go to superclass
|
||||
|
||||
cg.emit.emit("cmp", 0, runtimeType); // 4.2 null is reached (super type of Object): error
|
||||
cg.emit.emit("jne", checkSuperTypeLabel);
|
||||
Interrupts.exit(cg, ExitCode.INVALID_DOWNCAST);
|
||||
cg.emit.emitLabel(matchLabel);
|
||||
|
||||
cg.rm.releaseRegister(desiredType);
|
||||
|
||||
// 5. Recover pointer to object and return it
|
||||
cg.emit.emit("pop", runtimeType);
|
||||
return runtimeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register index(Index ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
String invalidBoundLabel = cg.emit.uniqueLabel();
|
||||
String validBoundLabel = cg.emit.uniqueLabel();
|
||||
String checkBoundsLabel = cg.emit.uniqueLabel();
|
||||
|
||||
// Obtain pointer to array and index to be accessed
|
||||
Register pointer = visit(ast.left(), arg);
|
||||
Register index = visit(ast.right(), arg);
|
||||
|
||||
// check for null pointer
|
||||
cg.emit.emit("cmp", 0, pointer);
|
||||
cg.emit.emit("jne", checkBoundsLabel); // 0 != array
|
||||
Interrupts.exit(cg, ExitCode.NULL_POINTER);
|
||||
|
||||
// check if index>=0 and index<arraySize
|
||||
Register arraySizeReg = cg.rm.getRegister();
|
||||
cg.emit.emitLabel(checkBoundsLabel);
|
||||
cg.emit.emitLoad(1 * VAR_SIZE, pointer, arraySizeReg);
|
||||
cg.emit.emit("cmp", 0, index);
|
||||
cg.emit.emit("jl", invalidBoundLabel); // 0 > index
|
||||
cg.emit.emit("cmp", index, arraySizeReg);
|
||||
cg.emit.emit("jg", validBoundLabel); // index < array.length
|
||||
cg.emit.emitLabel(invalidBoundLabel);
|
||||
Interrupts.exit(cg, ExitCode.INVALID_ARRAY_BOUNDS);
|
||||
cg.rm.releaseRegister(arraySizeReg);
|
||||
|
||||
// return array element (base + 2 * VAR_SIZE + index * VAR_SIZE)
|
||||
cg.emit.emitLabel(validBoundLabel);
|
||||
cg.emit.emit("imul", VAR_SIZE, index);
|
||||
cg.emit.emit("add", pointer, index);
|
||||
cg.rm.releaseRegister(pointer);
|
||||
if (!obtainReference)
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, index, index);
|
||||
else
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register intConst(IntConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(ast.value, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register field(Field ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
String accessFieldLabel = cg.emit.uniqueLabel();
|
||||
Register pointer = visit(ast.arg(), arg);
|
||||
cg.emit.emit("cmp", 0, pointer);
|
||||
cg.emit.emit("jne", accessFieldLabel);
|
||||
Interrupts.exit(cg, ExitCode.NULL_POINTER);
|
||||
|
||||
cg.emit.emitLabel(accessFieldLabel);
|
||||
String foundFieldLabel = cg.emit.uniqueLabel();
|
||||
String lookForFieldLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
cg.emit.emitLabel(lookForFieldLabel);
|
||||
cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, pointer));
|
||||
cg.emit.emit("je", foundFieldLabel);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, pointer);
|
||||
cg.emit.emit("jmp", lookForFieldLabel);
|
||||
cg.emit.emitLabel(foundFieldLabel);
|
||||
if (obtainReference)
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
else
|
||||
cg.emit.emitLoad(VAR_SIZE, pointer, pointer);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array structure:
|
||||
* <ul>
|
||||
* <li>Type of array (for casts, assignments and equalities)</li>
|
||||
* <li>Size of array (N)</li>
|
||||
* <li>element 0 or pointer to element 0</li>
|
||||
* <li>...</li>
|
||||
* <li>element N or pointer to element N</li>
|
||||
* </ul>
|
||||
* The type of the elements is stored in the vtable for the array (in order to
|
||||
* save space). <br/>
|
||||
*
|
||||
* The pointer that references the array points to the type of the array.
|
||||
* The reason for this is so that all reference types have at pointer + 0
|
||||
* the type, for dynamic type comparisons for assignments, casts and ==/!= operators
|
||||
*/
|
||||
@Override
|
||||
public Register newArray(NewArray ast, Location arg) {
|
||||
String validArraySizeLabel = cg.emit.uniqueLabel();
|
||||
|
||||
// Check size of array is positive
|
||||
Register arraySizeReg = visit(ast.arg(), arg);
|
||||
cg.emit.emit("cmp", 0, arraySizeReg);
|
||||
cg.emit.emit("jns", validArraySizeLabel); // size >= 0
|
||||
Interrupts.exit(cg, ExitCode.INVALID_ARRAY_SIZE);
|
||||
|
||||
// Reserve for length + 2 variables
|
||||
cg.emit.emitLabel(validArraySizeLabel);
|
||||
cg.emit.emit("push", arraySizeReg);
|
||||
cg.emit.emit("add", 2, arraySizeReg);
|
||||
Register arrayPointerReg = calloc(arraySizeReg);
|
||||
|
||||
// Store overhead information
|
||||
// Type reference
|
||||
Register arrayTypeReg = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(ast.type), arrayTypeReg);
|
||||
cg.emit.emitStore(arrayTypeReg, 0 * VAR_SIZE, arrayPointerReg);
|
||||
cg.rm.releaseRegister(arrayTypeReg);
|
||||
// Number of elements
|
||||
Register numElemReg = cg.rm.getRegister();
|
||||
cg.emit.emit("pop", numElemReg);
|
||||
cg.emit.emitStore(numElemReg, 1 * VAR_SIZE, arrayPointerReg);
|
||||
cg.rm.releaseRegister(numElemReg);
|
||||
|
||||
return arrayPointerReg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure of reference type objects (except arrays)
|
||||
* <ul>
|
||||
* <li>Pointer to type vtable</li>
|
||||
* <li>hashCode0, field0</li>
|
||||
* <li>...</li>
|
||||
* <li>hashCodeN, fieldN</li>
|
||||
* </ul>
|
||||
* The object pointer points to the first element, and every element
|
||||
* is of VAR_SIZE
|
||||
*/
|
||||
@Override
|
||||
public Register newObject(NewObject ast, Location arg) {
|
||||
ClassSymbol sym = (ClassSymbol) ast.type;
|
||||
|
||||
// Obtain from type size of allocated object and allocate memory
|
||||
int size = 1;
|
||||
ClassSymbol auxSym = sym;
|
||||
while (auxSym != ClassSymbol.objectType) {
|
||||
size += auxSym.fields.size() * 2;
|
||||
auxSym = auxSym.superClass;
|
||||
}
|
||||
Register sizeReg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(size, sizeReg);
|
||||
|
||||
Register pointer = calloc(sizeReg);
|
||||
|
||||
// Store the pointer to the type vtable
|
||||
Register auxReg = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(sym), auxReg);
|
||||
cg.emit.emitStore(auxReg, 0, pointer);
|
||||
cg.emit.emit("push", pointer); // Save the pointer to the beginning to return later
|
||||
cg.rm.releaseRegister(auxReg);
|
||||
|
||||
// Store the hashes for the fields' variable symbols
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
auxSym = sym;
|
||||
while (auxSym != ClassSymbol.objectType) {
|
||||
for (VariableSymbol field : auxSym.fields.values()) {
|
||||
cg.emit.emitStore(field.hashCode(), 0, pointer);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, pointer);
|
||||
}
|
||||
auxSym = auxSym.superClass;
|
||||
}
|
||||
|
||||
// Recover the initial address and return
|
||||
cg.emit.emit("pop", pointer);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register nullConst(NullConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(0, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Register thisRef(ThisRef ast, Location arg) {
|
||||
Register register = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, BASE_REG, register);
|
||||
return register;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* implementation of x86 calling convention according to:
|
||||
* http://unixwiz.net/techtips/win32-callconv-asm.html
|
||||
* following the __cdecl calling convention
|
||||
*/
|
||||
@Override
|
||||
public Register methodCall(MethodCallExpr ast, Location arg) {
|
||||
// 0. Save registers to stack
|
||||
List<Register> callerSaved = new ArrayList<>();
|
||||
for (Register reg : CALLER_SAVE) {
|
||||
if (cg.rm.isInUse(reg)) {
|
||||
callerSaved.add(0, reg);
|
||||
cg.emit.emit("push", Register.EAX);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Evaluate all the arguments left-to-right (according to Java's spec)
|
||||
// and push them to the stack in right-to-left order. We will push them ltr
|
||||
// and then swap them so that the order is reversed
|
||||
for (Expr allArgument : ast.allArguments()) {
|
||||
Register argumentRegister = visit(allArgument, arg);
|
||||
cg.emit.emit("push", argumentRegister);
|
||||
cg.rm.releaseRegister(argumentRegister);
|
||||
}
|
||||
for (int i = 0; i < ast.allArguments().size() / 2; i++) {
|
||||
int offset1 = i * VAR_SIZE;
|
||||
int offset2 = (ast.allArguments().size() - 1 - i) * VAR_SIZE;
|
||||
Register aux1 = cg.rm.getRegister();
|
||||
Register aux2 = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(offset1, STACK_REG, aux1);
|
||||
cg.emit.emitLoad(offset2, STACK_REG, aux2);
|
||||
cg.emit.emitStore(aux1, offset2, STACK_REG);
|
||||
cg.emit.emitStore(aux2, offset1, STACK_REG);
|
||||
cg.rm.releaseRegister(aux1);
|
||||
cg.rm.releaseRegister(aux2);
|
||||
}
|
||||
|
||||
// 2. Call function
|
||||
// 2.1. Search for the method pointer using the hashCode
|
||||
Register thisRef = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(0, STACK_REG, thisRef);
|
||||
int hashCode = ast.methodName.hashCode();
|
||||
String lookForMethod = cg.emit.uniqueLabel();
|
||||
String methodFound = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLoad(0, thisRef, thisRef); // Go to type vtable
|
||||
cg.emit.emit("add", 1 * VAR_SIZE, thisRef); // Skip the reference to superType
|
||||
cg.emit.emitLabel(lookForMethod);
|
||||
cg.emit.emit("cmpl", hashCode, AssemblyEmitter.registerOffset(0, thisRef));
|
||||
cg.emit.emit("je", methodFound); // hashCode == methodName.hashCode
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, thisRef); // Go to next hashCode
|
||||
cg.emit.emit("jmp", lookForMethod);
|
||||
cg.emit.emitLabel(methodFound);
|
||||
// 2.2. Call the function
|
||||
cg.emit.emit("call", AssemblyEmitter.registerOffset(VAR_SIZE, thisRef));
|
||||
cg.rm.releaseRegister(thisRef);
|
||||
|
||||
// 3. Pop arguments from stack
|
||||
cg.emit.emit("add", VAR_SIZE * ast.allArguments().size(), STACK_REG);
|
||||
|
||||
// 4. Return result to caller
|
||||
Register result = null;
|
||||
if (ast.sym.returnType != PrimitiveTypeSymbol.voidType) {
|
||||
result = cg.rm.getRegister();
|
||||
cg.emit.emitMove(Register.EAX, result);
|
||||
}
|
||||
|
||||
// 5. Restore registers
|
||||
for (Register reg : callerSaved) {
|
||||
if (cg.rm.isInUse(reg))
|
||||
cg.emit.emit("pop", Register.EAX);
|
||||
else
|
||||
cg.emit.emit("add", 4, Register.ESP); // Don't overwrite
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register unaryOp(UnaryOp ast, Location arg) {
|
||||
Register argReg = visit(ast.arg(), arg);
|
||||
switch (ast.operator) {
|
||||
case U_PLUS:
|
||||
break;
|
||||
|
||||
case U_MINUS:
|
||||
cg.emit.emit("negl", argReg);
|
||||
break;
|
||||
|
||||
case U_BOOL_NOT:
|
||||
cg.emit.emit("negl", argReg);
|
||||
cg.emit.emit("incl", argReg);
|
||||
break;
|
||||
}
|
||||
return argReg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the value or reference to a variable described by a Var node
|
||||
* and a VariableSymbol.
|
||||
*/
|
||||
@Override
|
||||
public Register var(Var ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
Register register = cg.rm.getRegister();
|
||||
cg.emit.emitMove(BASE_REG, register);
|
||||
int offset;
|
||||
switch (ast.sym.kind) {
|
||||
case PARAM:
|
||||
offset = 3 + arg.methodSym().parameters.indexOf(ast.sym);
|
||||
break;
|
||||
case LOCAL:
|
||||
offset = -(1 + positionOfLocal(arg.methodSym(), ast.name));
|
||||
break;
|
||||
case FIELD:
|
||||
String foundFieldLabel = cg.emit.uniqueLabel();
|
||||
String lookForFieldLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, register, register);
|
||||
cg.emit.emit("add", VAR_SIZE, register);
|
||||
cg.emit.emitLabel(lookForFieldLabel);
|
||||
cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, register));
|
||||
cg.emit.emit("je", foundFieldLabel);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, register);
|
||||
cg.emit.emit("jmp", lookForFieldLabel);
|
||||
cg.emit.emitLabel(foundFieldLabel);
|
||||
offset = 1; // The next element will be the reference we want
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("VariableSymbol Kind option not implemented");
|
||||
}
|
||||
if (obtainReference)
|
||||
cg.emit.emit("add", offset * VAR_SIZE, register);
|
||||
else
|
||||
cg.emit.emitLoad(offset * VAR_SIZE, register, register);
|
||||
return register;
|
||||
}
|
||||
|
||||
private int positionOfLocal(MethodSymbol sym, String name) {
|
||||
List<String> locals = new ArrayList<>(sym.locals.keySet());
|
||||
locals.sort(Comparator.comparing(o -> o));
|
||||
for (int i = 0; i < locals.size(); i++)
|
||||
if (locals.get(i).equals(name))
|
||||
return i;
|
||||
throw new RuntimeException("The variable could not be found, and should be there");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that calls calloc to obtain memory for reference type variables
|
||||
* @param numberOfElements Size in bytes, stored in a register
|
||||
* @return A register with the first address of the memory allocated
|
||||
*/
|
||||
private Register calloc(Register numberOfElements) {
|
||||
cg.emit.emitComment("calloc begin arg=" + numberOfElements);
|
||||
String callocOk = cg.emit.uniqueLabel();
|
||||
|
||||
boolean eaxInUse = cg.rm.isInUse(Register.EAX);
|
||||
if (eaxInUse && Register.EAX != numberOfElements)
|
||||
cg.emit.emit("push", Register.EAX);
|
||||
|
||||
// push arguments to the stack, then call calloc
|
||||
// the size of allocated elements is always four bytes!
|
||||
cg.emit.emit("push", AssemblyEmitter.constant(VAR_SIZE));
|
||||
cg.emit.emit("push", numberOfElements);
|
||||
cg.rm.releaseRegister(numberOfElements);
|
||||
cg.emit.emit("call", "calloc");
|
||||
cg.emit.emit("add", 8, STACK_REG);
|
||||
|
||||
// Check for null pointer (if calloc fails)
|
||||
cg.emit.emit("cmp", 0, Register.EAX);
|
||||
cg.emit.emit("jne", callocOk);
|
||||
Interrupts.exit(cg, ExitCode.INTERNAL_ERROR);
|
||||
|
||||
cg.emit.emitLabel(callocOk);
|
||||
Register result = cg.rm.getRegister();
|
||||
cg.emit.emitMove(Register.EAX, result);
|
||||
|
||||
// pop EAX if needed
|
||||
if (eaxInUse && Register.EAX != numberOfElements)
|
||||
cg.emit.emit("pop", Register.EAX);
|
||||
|
||||
cg.emit.emitComment("calloc end");
|
||||
return result;
|
||||
}
|
||||
}
|
19
src/cd/backend/codegen/Interrupts.java
Normal file
19
src/cd/backend/codegen/Interrupts.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
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);
|
||||
}
|
||||
}
|
42
src/cd/backend/codegen/Location.java
Normal file
42
src/cd/backend/codegen/Location.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
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;
|
||||
}
|
||||
}
|
130
src/cd/backend/codegen/RegisterManager.java
Normal file
130
src/cd/backend/codegen/RegisterManager.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Simple class that manages the set of currently used
|
||||
* and unused registers
|
||||
*/
|
||||
public class RegisterManager {
|
||||
private List<Register> registers = new ArrayList<Register>();
|
||||
|
||||
// lists of register to save by the callee and the caller
|
||||
public static final Register CALLEE_SAVE[] = new Register[]{Register.ESI,
|
||||
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};
|
||||
|
||||
// special purpose registers
|
||||
public static final Register BASE_REG = Register.EBP;
|
||||
public static final Register STACK_REG = Register.ESP;
|
||||
|
||||
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(
|
||||
"%esi", null), EDI("%edi", null), EBP("%ebp", null), ESP(
|
||||
"%esp", null);
|
||||
|
||||
public final String repr;
|
||||
private final ByteRegister lowByteVersion;
|
||||
|
||||
private Register(String repr, ByteRegister bv) {
|
||||
this.repr = repr;
|
||||
this.lowByteVersion = bv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return repr;
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if this register has an 8bit version
|
||||
*/
|
||||
public boolean hasLowByteVersion() {
|
||||
return lowByteVersion != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a register like {@code %eax} returns {@code %al}, but doesn't
|
||||
* work for {@code %esi} and {@code %edi}!
|
||||
*/
|
||||
public ByteRegister lowByteVersion() {
|
||||
assert hasLowByteVersion();
|
||||
return lowByteVersion;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ByteRegister {
|
||||
EAX("%al"), EBX("%bl"), ECX("%cl"), EDX("%dl");
|
||||
|
||||
public final String repr;
|
||||
|
||||
private ByteRegister(String repr) {
|
||||
this.repr = repr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return repr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all general purpose registers to free
|
||||
*/
|
||||
public void initRegisters() {
|
||||
registers.clear();
|
||||
registers.addAll(Arrays.asList(GPR));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a free register and marks it as used
|
||||
*/
|
||||
public Register getRegister() {
|
||||
int last = registers.size() - 1;
|
||||
if (last < 0)
|
||||
throw new AssemblyFailedException(
|
||||
"Program requires too many registers");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the register is currently non-free
|
||||
*/
|
||||
public boolean isInUse(Register reg) {
|
||||
return !registers.contains(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the number of free registers
|
||||
*/
|
||||
public int availableRegisters() {
|
||||
return registers.size();
|
||||
}
|
||||
}
|
138
src/cd/backend/codegen/RegsNeededVisitor.java
Normal file
138
src/cd/backend/codegen/RegsNeededVisitor.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
|
||||
import java.util.HashMap;
|
||||
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. */
|
||||
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>();
|
||||
|
||||
/**
|
||||
* Override visit() so as to memorize the results and avoid
|
||||
* unnecessary computation
|
||||
*/
|
||||
@Override
|
||||
public Integer visit(Ast ast, Void arg) {
|
||||
if (memo.containsKey(ast))
|
||||
return memo.get(ast);
|
||||
Integer res = ast.accept(this, null);
|
||||
memo.put(ast, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer dflt(Ast ast, Void arg) {
|
||||
// For a non-expression, it suffices to find the
|
||||
// maximum registers used by any individual expression.
|
||||
int maxReg = 0;
|
||||
for (Ast a : ast.children()) {
|
||||
maxReg = Math.max(calc(a), maxReg);
|
||||
}
|
||||
return maxReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer dfltExpr(Expr ast, Void arg) {
|
||||
throw new RuntimeException("Should never be used");
|
||||
}
|
||||
|
||||
@Override
|
||||
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 overall = min(ifLeftFirst, ifRightFirst);
|
||||
return overall;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer builtInRead(BuiltInRead ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer cast(Cast ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer intConst(IntConst ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer newArray(NewArray ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer newObject(NewObject ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer nullConst(NullConst ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer thisRef(ThisRef ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer methodCall(MethodCallExpr ast, Void arg) {
|
||||
int max = 1;
|
||||
for (Expr argex : ast.allArguments()) {
|
||||
int needed = calc(argex);
|
||||
if (needed > max)
|
||||
max = needed;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer unaryOp(UnaryOp ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer var(Var ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
}
|
288
src/cd/backend/codegen/StmtGenerator.java
Normal file
288
src/cd/backend/codegen/StmtGenerator.java
Normal file
|
@ -0,0 +1,288 @@
|
|||
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.Symbol.MethodSymbol;
|
||||
import cd.util.debug.AstOneLine;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cd.backend.codegen.AstCodeGenerator.VAR_SIZE;
|
||||
import static cd.backend.codegen.AstCodeGenerator.TRUE;
|
||||
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> {
|
||||
protected final AstCodeGenerator cg;
|
||||
|
||||
StmtGenerator(AstCodeGenerator astCodeGenerator) {
|
||||
cg = astCodeGenerator;
|
||||
|
||||
}
|
||||
|
||||
public void gen(Ast ast) {
|
||||
visit(ast, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register visit(Ast ast, Location 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);
|
||||
} 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
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register methodDecl(MethodDecl ast, Location arg) {
|
||||
// Bookkeeping for framework
|
||||
arg.enterMethod(ast.sym);
|
||||
cg.initMethodData();
|
||||
|
||||
// Begin method
|
||||
cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym));
|
||||
|
||||
// 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);
|
||||
|
||||
// 2. Save CPU registers
|
||||
Register[] regs = RegisterManager.CALLEE_SAVE;
|
||||
for (int i = 0; i < regs.length; i++)
|
||||
cg.emit.emit("push", regs[i]);
|
||||
|
||||
// 3. Run the code contained in the function
|
||||
Register returnReg = visit(ast.body(), arg);
|
||||
cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym));
|
||||
|
||||
// 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]);
|
||||
|
||||
// 5 and 6: Restore the old base pointer and return from the function
|
||||
cg.emitMethodSuffix(returnReg == null);
|
||||
|
||||
// Framework bookkeeping
|
||||
arg.leaveMethod();
|
||||
return null;
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue