Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions optvm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,42 @@ a physical machine. Therefore, all our optimization passes will work on the inst
## Guide

* [Register](src/main/java/com/compilerprogramming/ezlang/compiler/Register.java) - implements a virtual register. Virtual registers
have a name, type, and id - the id is unique, but name is not. Initially the compiler generates unique registers for every local
have a name, type, id, frameSlot - the id is unique, but the name is not. Initially the compiler generates unique registers for every local
and temporary, the number of registers grows as we convert to SSA and back out of SSA. Finally, as we run the Chaitin register allocator
we shrink down the number of virtual registers to the minimum.
we shrink down the number of virtual registers to the minimum. When executing code, the abstract machine uses the frameSlot as the location
of the virtual register in the executing function's stack frame. Registers that do not have overlapping lifetimes can share the same
frameSlot.
* [RegisterPool](src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java) - simple pool to allow us to find a register
by its id, and to allocate new virtual registers.
* [BasicBlock](src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java) - Defines our basic block - which contains instructions
that execute sequentially. A basic block ends with a branch. There are two distinguished basic blocks in every function: entry and exit.
that execute sequentially. A basic block ends with a branch. There are two distinguished basic blocks in every function: entry and exit.
* [BBHelper](src/main/java/com/compilerprogramming/ezlang/compiler/BBHelper.java) - Some utilities that manipulate basic blocks.
* [Operand](src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java) - Operands in instructions. Both definitions and uses are treated
as operands. Instruction can have at most one definition; but an instruction can have multiple use operands. Operands can hold registers or
constants or pointers to basic blocks.
* [Instruction](src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java) - Instructions in basic blocks.
* [Instruction](src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java) - Instructions - sequential instructions reside in
basic blocks. Some instructions define variables (registers) and some use them.
* [DominatorTree](src/main/java/com/compilerprogramming/ezlang/compiler/DominatorTree.java) - Calculates dominator tree and dominance frontiers.
* [LiveSet](src/main/java/com/compilerprogramming/ezlang/compiler/LiveSet.java) - Bitset used to track liveness of registers.
* [Liveness](src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java) - Liveness calculator, works for both SSA and non-SSA forms.
* [LiveSet](src/main/java/com/compilerprogramming/ezlang/compiler/LiveSet.java) - Bitset used to track liveness of registers. We exploit the fact that
each register has a unique integer ID and these ids are allocated in a sequential manner.
* [Liveness](src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java) - Liveness calculator, works for both SSA and non-SSA forms. Computes
liveness data per basic block - mainly live-out. Note that the interference graph builder starts here and computes instruction level liveness as necessary.
* [EnterSSA](src/main/java/com/compilerprogramming/ezlang/compiler/EnterSSA.java) - Transforms into SSA, using algorithm by Preston Briggs.
* [ExitSSA](src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java) - Exits SSA form, using algorithm by Preston Briggs.
* [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops.
* [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting.
* [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops. (Not used yet)
* [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting. (Not used yet)
* [InterferenceGraph](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java) - Representation of an Interference Graph
required by the register allocator.
* [InterferenceGraphBuilder](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraphBuilder.java) - Constructs InteferenceGraph for a set
of basic bocks, using liveness information.
* [InterferenceGraphBuilder](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraphBuilder.java) - Constructs an InterferenceGraph for a set
of basic bocks, using basic block level liveness information as a starting point for calculating instruction level liveness.
* [ChaitinGraphColoringRegisterAllocator](src/main/java/com/compilerprogramming/ezlang/compiler/ChaitinGraphColoringRegisterAllocator.java) - basic
Chaitin Graph Coloring Register Allocator - WIP.
Chaitin Graph Coloring Register Allocator. Since our target machine here is an abstract machine, we do not really needing spilling support
as we can size each function's stack frame to accommodate the number of registers needed such that each register is really a slot in the stack
frame. But we will eventually simulate an abstract machine with a limited set of registers and a separate stack frame.

## Compiler

* [CompiledFunction](src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java) - builds and encapsulates the IR for a single function.
* [Compiler](src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java) - simple orchestrator of compilation tasks.
* [Optimizer](src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java) - simple orchestrator of optimization steps. Currently
does not have optimization passes, but translates to SSA and out and then runs the graph coloring register allocator.
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,17 @@ public class BasicBlock {
// Liveness computation
/**
* VarKill contains all the variables that are defined
* in the block.
* in the block. This is often called defs in literature, but
* we use the name in Engineering a Compiler.
*/
LiveSet varKill;
/**
* UEVar contains upward-exposed variables in the block,
* i.e. those variables that are used in the block prior to
* any redefinition in the block.
*
* This is often called uses in Literature but we use the
* name in Engineering a Compiler.
*/
LiveSet UEVar;
/**
Expand Down Expand Up @@ -168,7 +172,7 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(bid);
return bid;
}

public String label() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ private Map<Integer, Integer> preAssignArgsToColors(CompiledFunction function, S

private void updateInstructions(CompiledFunction function, Map<Integer, Integer> assignments) {
var regPool = function.registerPool;
// First reset the slots of every register to -1
for (int r = 0; r < regPool.numRegisters(); r++)
regPool.getReg(r).updateSlot(-1);
// Now set the slot to the color assigned by the graph coloring algo
for (var entry : assignments.entrySet()) {
int reg = entry.getKey();
int slot = entry.getValue();
regPool.getReg(reg).updateSlot(slot);
int color = entry.getValue();
regPool.getReg(reg).updateSlot(color);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@ public class EnterSSA {
/**
* Non-local names are set of variables that are live
* on entry to _some_ BasicBlock in the program.
*
* Note that a variable in the Literature is represented
* by a Register in our implementation
*/
Register[] nonLocalNames;
BBSet[] blockSets;
/**
* Basic blocks in reverse post order
*/
List<BasicBlock> blocks;
/**
* Used to generate SSA version for each variable (register)
*/
int[] counters;
VersionStack[] stacks;

public EnterSSA(CompiledFunction bytecodeFunction) {
this.function = bytecodeFunction;
setupGlobals();
computeDomTreeAndDominanceFrontiers();
this.blocks = domTree.blocks;
this.blocks = domTree.blocks; // the blocks are ordered reverse post order
findNonLocalNames();
insertPhis();
renameVars();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public RegisterOperand(Register reg) {
if (reg == null)
throw new NullPointerException();
}
public int slot() { return reg.nonSSAId(); }
public int frameSlot() { return reg.nonSSAId(); }

@Override
public void replaceRegister(Register register) {
Expand Down Expand Up @@ -57,15 +57,6 @@ public String toString() {
}
}

/**
* Represents the return register, which is the location where
* the caller will expect to see any return value. The VM must map
* this to appropriate location.
*/
public static class ReturnRegisterOperand extends RegisterOperand {
public ReturnRegisterOperand(Register reg) { super(reg); }
}

/**
* Represents a temp register, maps to a location on the
* virtual stack. Temps start at offset 0, but this is a relative
Expand Down Expand Up @@ -110,17 +101,4 @@ public String toString() {
return structOperand + "." + fieldName;
}
}

public static class NewTypeOperand extends Operand {
public final Type type;
public NewTypeOperand(Type type) {
this.type = type;
}

@Override
public String toString() {
return "New(" + type + ")";
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,63 @@
import com.compilerprogramming.ezlang.types.Type;

/**
* Virtual register represents values that are operands of the
* IR.
* Represents a Virtual Register in the abstract machine.
*
* Virtual registers are used as operands in the instruction set of the abstract machine.
* We try to meet several requirements.
*
* When executing instructions in the Interpreter, we require the notion of a frame slot - this
* is the location of the register within the function's frame. Each executing function maps its slots to
* an ExecutionStack, which is simply an array of Values. When the function executes, the location of a
* register on the function's frame is given by the Register's slot plus the base offset of the function.
* The base offset is the location where the function's first argument appears on the execution stack, and is
* determined at runtime based on the call stack.
*
* In addition to the frame slot - for the purpose of SSA, each register must have a unique name. We use a
* unique ID as a proxy for this name. During SSA transformation we assign new IDs as we rename variables, but
* we also maintain the original ID of the register because the SSA algorithm needs to be able to track variables
* by their original names.
*
* After SSA, during register allocation, we use graph coloring to assign slots to registers. We could rename
* all registers in the instruction set again, but using a slot instead allows us to maintain the previously
* generated name - this is mainly useful as we look for relations to the variables in the source program.
*
* @see RegisterPool
* @see ChaitinGraphColoringRegisterAllocator
* @see com.compilerprogramming.ezlang.compiler.Operand.RegisterOperand
*/
public class Register {
/**
* Unique virtual ID
* Unique virtual ID always unique
*/
public final int id;
/**
* The base name - for local variables and function params this should be the name
* in the source program. For temps this is a made up name.
* Does not include ssa version
* in the source program. For temps this is a made up name. Name is suffixed with ssa version
* during SSA transformation.
*
* Not unique.
*/
private final String name;
/**
* The type of a register
* The type of the register
*/
public final Type type;
private int slot;
/**
* The location of this register relative to the base
* of the executing function. Multiple registers may share the same
* frame slot because of different non-overlapping life times.
*/
private int frameSlot;

public Register(int id, String name, Type type) {
this(id,name,type,id); // Initially frame slot is set to the unique ID
}
protected Register(int id, String name, Type type, int frameSlot) {
this.id = id;
this.name = name;
this.type = type;
this.slot = id;
this.frameSlot = frameSlot;
}
@Override
public boolean equals(Object o) {
Expand All @@ -41,15 +73,20 @@ public boolean equals(Object o) {
public int hashCode() {
return id;
}

public String name() {
return name;
}

/**
* The nonSSAID is valid as a frame slot prior to SSA conversion,
* and following register assignment.
* During SSA form this is not valid for registers that are instances of SSARegister.
*/
public int nonSSAId() {
return slot;
return frameSlot;
}
public void updateSlot(int slot) {
this.slot = slot;
this.frameSlot = slot;
}

/**
Expand All @@ -60,7 +97,7 @@ public static class SSARegister extends Register {
public final int ssaVersion;
public final int originalRegNumber;
public SSARegister(Register original, int id, int version) {
super(id, original.name+"_"+version, original.type);
super(id, original.name+"_"+version, original.type, -1);
this.originalRegNumber = original.id;
this.ssaVersion = version;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public Register newTempReg(String baseName, Type type) {
registers.add(reg);
return reg;
}

public Register.SSARegister ssaReg(Register original, int version) {
var id = registers.size();
var reg = new Register.SSARegister(original, id, version);
Expand All @@ -48,7 +47,6 @@ public Register.SSARegister ssaReg(Register original, int version) {
public int numRegisters() {
return registers.size();
}

public void toStr(StringBuilder sb) {
for (Register reg : registers) {
sb.append("Reg #").append(reg.id).append(" ").append(reg.name()).append("\n");
Expand Down
Loading
Loading