Skip to content

Commit 41aa91c

Browse files
Small cleanups and doc updates
Small cleanups and doc updates
1 parent 99e6a9d commit 41aa91c

File tree

8 files changed

+120
-80
lines changed

8 files changed

+120
-80
lines changed

optvm/README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,42 @@ a physical machine. Therefore, all our optimization passes will work on the inst
1313
## Guide
1414

1515
* [Register](src/main/java/com/compilerprogramming/ezlang/compiler/Register.java) - implements a virtual register. Virtual registers
16-
have a name, type, and id - the id is unique, but name is not. Initially the compiler generates unique registers for every local
16+
have a name, type, id, frameSlot - the id is unique, but the name is not. Initially the compiler generates unique registers for every local
1717
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
18-
we shrink down the number of virtual registers to the minimum.
18+
we shrink down the number of virtual registers to the minimum. When executing code, the abstract machine uses the frameSlot as the location
19+
of the virtual register in the executing function's stack frame. Registers that do not have overlapping lifetimes can share the same
20+
frameSlot.
1921
* [RegisterPool](src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java) - simple pool to allow us to find a register
2022
by its id, and to allocate new virtual registers.
2123
* [BasicBlock](src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java) - Defines our basic block - which contains instructions
22-
that execute sequentially. A basic block ends with a branch. There are two distinguished basic blocks in every function: entry and exit.
24+
that execute sequentially. A basic block ends with a branch. There are two distinguished basic blocks in every function: entry and exit.
2325
* [BBHelper](src/main/java/com/compilerprogramming/ezlang/compiler/BBHelper.java) - Some utilities that manipulate basic blocks.
2426
* [Operand](src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java) - Operands in instructions. Both definitions and uses are treated
2527
as operands. Instruction can have at most one definition; but an instruction can have multiple use operands. Operands can hold registers or
2628
constants or pointers to basic blocks.
27-
* [Instruction](src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java) - Instructions in basic blocks.
29+
* [Instruction](src/main/java/com/compilerprogramming/ezlang/compiler/Instruction.java) - Instructions - sequential instructions reside in
30+
basic blocks. Some instructions define variables (registers) and some use them.
2831
* [DominatorTree](src/main/java/com/compilerprogramming/ezlang/compiler/DominatorTree.java) - Calculates dominator tree and dominance frontiers.
29-
* [LiveSet](src/main/java/com/compilerprogramming/ezlang/compiler/LiveSet.java) - Bitset used to track liveness of registers.
30-
* [Liveness](src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java) - Liveness calculator, works for both SSA and non-SSA forms.
32+
* [LiveSet](src/main/java/com/compilerprogramming/ezlang/compiler/LiveSet.java) - Bitset used to track liveness of registers. We exploit the fact that
33+
each register has a unique integer ID and these ids are allocated in a sequential manner.
34+
* [Liveness](src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java) - Liveness calculator, works for both SSA and non-SSA forms. Computes
35+
liveness data per basic block - mainly live-out. Note that the interference graph builder starts here and computes instruction level liveness as necessary.
3136
* [EnterSSA](src/main/java/com/compilerprogramming/ezlang/compiler/EnterSSA.java) - Transforms into SSA, using algorithm by Preston Briggs.
3237
* [ExitSSA](src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java) - Exits SSA form, using algorithm by Preston Briggs.
33-
* [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops.
34-
* [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting.
38+
* [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops. (Not used yet)
39+
* [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting. (Not used yet)
3540
* [InterferenceGraph](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java) - Representation of an Interference Graph
3641
required by the register allocator.
37-
* [InterferenceGraphBuilder](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraphBuilder.java) - Constructs InteferenceGraph for a set
38-
of basic bocks, using liveness information.
42+
* [InterferenceGraphBuilder](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraphBuilder.java) - Constructs an InterferenceGraph for a set
43+
of basic bocks, using basic block level liveness information as a starting point for calculating instruction level liveness.
3944
* [ChaitinGraphColoringRegisterAllocator](src/main/java/com/compilerprogramming/ezlang/compiler/ChaitinGraphColoringRegisterAllocator.java) - basic
40-
Chaitin Graph Coloring Register Allocator - WIP.
45+
Chaitin Graph Coloring Register Allocator. Since our target machine here is an abstract machine, we do not really needing spilling support
46+
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
47+
frame. But we will eventually simulate an abstract machine with a limited set of registers and a separate stack frame.
4148

4249
## Compiler
4350

4451
* [CompiledFunction](src/main/java/com/compilerprogramming/ezlang/compiler/CompiledFunction.java) - builds and encapsulates the IR for a single function.
52+
* [Compiler](src/main/java/com/compilerprogramming/ezlang/compiler/Compiler.java) - simple orchestrator of compilation tasks.
53+
* [Optimizer](src/main/java/com/compilerprogramming/ezlang/compiler/Optimizer.java) - simple orchestrator of optimization steps. Currently
54+
does not have optimization passes, but translates to SSA and out and then runs the graph coloring register allocator.

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,17 @@ public class BasicBlock {
4747
// Liveness computation
4848
/**
4949
* VarKill contains all the variables that are defined
50-
* in the block.
50+
* in the block. This is often called defs in literature, but
51+
* we use the name in Engineering a Compiler.
5152
*/
5253
LiveSet varKill;
5354
/**
5455
* UEVar contains upward-exposed variables in the block,
5556
* i.e. those variables that are used in the block prior to
5657
* any redefinition in the block.
58+
*
59+
* This is often called uses in Literature but we use the
60+
* name in Engineering a Compiler.
5761
*/
5862
LiveSet UEVar;
5963
/**
@@ -168,7 +172,7 @@ public boolean equals(Object o) {
168172

169173
@Override
170174
public int hashCode() {
171-
return Objects.hash(bid);
175+
return bid;
172176
}
173177

174178
public String label() {

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ChaitinGraphColoringRegisterAllocator.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,14 @@ private Map<Integer, Integer> preAssignArgsToColors(CompiledFunction function, S
6969

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

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/EnterSSA.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,27 @@ public class EnterSSA {
1717
/**
1818
* Non-local names are set of variables that are live
1919
* on entry to _some_ BasicBlock in the program.
20+
*
21+
* Note that a variable in the Literature is represented
22+
* by a Register in our implementation
2023
*/
2124
Register[] nonLocalNames;
2225
BBSet[] blockSets;
26+
/**
27+
* Basic blocks in reverse post order
28+
*/
2329
List<BasicBlock> blocks;
30+
/**
31+
* Used to generate SSA version for each variable (register)
32+
*/
2433
int[] counters;
2534
VersionStack[] stacks;
2635

2736
public EnterSSA(CompiledFunction bytecodeFunction) {
2837
this.function = bytecodeFunction;
2938
setupGlobals();
3039
computeDomTreeAndDominanceFrontiers();
31-
this.blocks = domTree.blocks;
40+
this.blocks = domTree.blocks; // the blocks are ordered reverse post order
3241
findNonLocalNames();
3342
insertPhis();
3443
renameVars();

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Operand.java

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public RegisterOperand(Register reg) {
2727
if (reg == null)
2828
throw new NullPointerException();
2929
}
30-
public int slot() { return reg.nonSSAId(); }
30+
public int frameSlot() { return reg.nonSSAId(); }
3131

3232
@Override
3333
public void replaceRegister(Register register) {
@@ -57,15 +57,6 @@ public String toString() {
5757
}
5858
}
5959

60-
/**
61-
* Represents the return register, which is the location where
62-
* the caller will expect to see any return value. The VM must map
63-
* this to appropriate location.
64-
*/
65-
public static class ReturnRegisterOperand extends RegisterOperand {
66-
public ReturnRegisterOperand(Register reg) { super(reg); }
67-
}
68-
6960
/**
7061
* Represents a temp register, maps to a location on the
7162
* virtual stack. Temps start at offset 0, but this is a relative
@@ -110,17 +101,4 @@ public String toString() {
110101
return structOperand + "." + fieldName;
111102
}
112103
}
113-
114-
public static class NewTypeOperand extends Operand {
115-
public final Type type;
116-
public NewTypeOperand(Type type) {
117-
this.type = type;
118-
}
119-
120-
@Override
121-
public String toString() {
122-
return "New(" + type + ")";
123-
}
124-
}
125-
126104
}

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Register.java

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,63 @@
33
import com.compilerprogramming.ezlang.types.Type;
44

55
/**
6-
* Virtual register represents values that are operands of the
7-
* IR.
6+
* Represents a Virtual Register in the abstract machine.
7+
*
8+
* Virtual registers are used as operands in the instruction set of the abstract machine.
9+
* We try to meet several requirements.
10+
*
11+
* When executing instructions in the Interpreter, we require the notion of a frame slot - this
12+
* is the location of the register within the function's frame. Each executing function maps its slots to
13+
* an ExecutionStack, which is simply an array of Values. When the function executes, the location of a
14+
* register on the function's frame is given by the Register's slot plus the base offset of the function.
15+
* The base offset is the location where the function's first argument appears on the execution stack, and is
16+
* determined at runtime based on the call stack.
17+
*
18+
* In addition to the frame slot - for the purpose of SSA, each register must have a unique name. We use a
19+
* unique ID as a proxy for this name. During SSA transformation we assign new IDs as we rename variables, but
20+
* we also maintain the original ID of the register because the SSA algorithm needs to be able to track variables
21+
* by their original names.
22+
*
23+
* After SSA, during register allocation, we use graph coloring to assign slots to registers. We could rename
24+
* all registers in the instruction set again, but using a slot instead allows us to maintain the previously
25+
* generated name - this is mainly useful as we look for relations to the variables in the source program.
26+
*
27+
* @see RegisterPool
28+
* @see ChaitinGraphColoringRegisterAllocator
29+
* @see com.compilerprogramming.ezlang.compiler.Operand.RegisterOperand
830
*/
931
public class Register {
1032
/**
11-
* Unique virtual ID
33+
* Unique virtual ID always unique
1234
*/
1335
public final int id;
1436
/**
1537
* The base name - for local variables and function params this should be the name
16-
* in the source program. For temps this is a made up name.
17-
* Does not include ssa version
38+
* in the source program. For temps this is a made up name. Name is suffixed with ssa version
39+
* during SSA transformation.
40+
*
41+
* Not unique.
1842
*/
1943
private final String name;
2044
/**
21-
* The type of a register
45+
* The type of the register
2246
*/
2347
public final Type type;
24-
private int slot;
48+
/**
49+
* The location of this register relative to the base
50+
* of the executing function. Multiple registers may share the same
51+
* frame slot because of different non-overlapping life times.
52+
*/
53+
private int frameSlot;
2554

2655
public Register(int id, String name, Type type) {
56+
this(id,name,type,id); // Initially frame slot is set to the unique ID
57+
}
58+
protected Register(int id, String name, Type type, int frameSlot) {
2759
this.id = id;
2860
this.name = name;
2961
this.type = type;
30-
this.slot = id;
62+
this.frameSlot = frameSlot;
3163
}
3264
@Override
3365
public boolean equals(Object o) {
@@ -41,15 +73,20 @@ public boolean equals(Object o) {
4173
public int hashCode() {
4274
return id;
4375
}
44-
4576
public String name() {
4677
return name;
4778
}
79+
80+
/**
81+
* The nonSSAID is valid as a frame slot prior to SSA conversion,
82+
* and following register assignment.
83+
* During SSA form this is not valid for registers that are instances of SSARegister.
84+
*/
4885
public int nonSSAId() {
49-
return slot;
86+
return frameSlot;
5087
}
5188
public void updateSlot(int slot) {
52-
this.slot = slot;
89+
this.frameSlot = slot;
5390
}
5491

5592
/**
@@ -60,7 +97,7 @@ public static class SSARegister extends Register {
6097
public final int ssaVersion;
6198
public final int originalRegNumber;
6299
public SSARegister(Register original, int id, int version) {
63-
super(id, original.name+"_"+version, original.type);
100+
super(id, original.name+"_"+version, original.type, -1);
64101
this.originalRegNumber = original.id;
65102
this.ssaVersion = version;
66103
}

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/RegisterPool.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ public Register newTempReg(String baseName, Type type) {
3838
registers.add(reg);
3939
return reg;
4040
}
41-
4241
public Register.SSARegister ssaReg(Register original, int version) {
4342
var id = registers.size();
4443
var reg = new Register.SSARegister(original, id, version);
@@ -48,7 +47,6 @@ public Register.SSARegister ssaReg(Register original, int version) {
4847
public int numRegisters() {
4948
return registers.size();
5049
}
51-
5250
public void toStr(StringBuilder sb) {
5351
for (Register reg : registers) {
5452
sb.append("Reg #").append(reg.id).append(" ").append(reg.name()).append("\n");

0 commit comments

Comments
 (0)