Skip to content

Commit e3ae2f7

Browse files
#57 refactor
1 parent cc1d9bd commit e3ae2f7

File tree

4 files changed

+327
-277
lines changed

4 files changed

+327
-277
lines changed

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

Lines changed: 4 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -4,279 +4,12 @@
44

55
/**
66
* Converts from SSA form to non-SSA form.
7-
* Implementation is based on description in
8-
* 'Practical Improvements to the Construction and Destruction
9-
* of Static Single Assignment Form' by Preston Briggs.
10-
*
11-
* The JikesRVM LeaveSSA implements a version of the
12-
* same algorithm.
137
*/
148
public class ExitSSA {
15-
16-
CompiledFunction function;
17-
NameStack[] stacks;
18-
DominatorTree tree;
19-
209
public ExitSSA(CompiledFunction function, EnumSet<Options> options) {
21-
this.function = function;
22-
if (!function.isSSA) throw new IllegalStateException();
23-
function.livenessAnalysis();
24-
if (options.contains(Options.DUMP_SSA_LIVENESS)) function.dumpIR(true, "SSA Liveness Analysis");
25-
tree = new DominatorTree(function.entry);
26-
if (options.contains(Options.DUMP_SSA_DOMTREE)) {
27-
System.out.println("Pre SSA Dominator Tree");
28-
System.out.println(tree.generateDotOutput());
29-
}
30-
initStack();
31-
insertCopies(function.entry);
32-
removePhis();
33-
function.isSSA = false;
34-
if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA");
35-
}
36-
37-
private void removePhis() {
38-
for (BasicBlock block : tree.blocks) {
39-
block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi);
40-
}
41-
}
42-
43-
/* Algorithm for iterating through blocks to perform phi replacement */
44-
private void insertCopies(BasicBlock block) {
45-
List<Integer> pushed = new ArrayList<>();
46-
for (Instruction i: block.instructions) {
47-
// replace all uses u with stacks[i]
48-
replaceUses(i);
49-
}
50-
scheduleCopies(block, pushed);
51-
for (BasicBlock c: block.dominatedChildren) {
52-
insertCopies(c);
53-
}
54-
for (Integer name: pushed) {
55-
stacks[name].pop();
56-
}
57-
}
58-
59-
/**
60-
* replace all uses u with stacks[i]
61-
*/
62-
private void replaceUses(Instruction i) {
63-
if (i instanceof Instruction.Phi)
64-
// FIXME check this can never be valid
65-
// tests 8/9 in TestInterpreter invoke on Phi but
66-
// replacements are same as existing inputs
67-
return;
68-
var oldUses = i.uses();
69-
Register[] newUses = new Register[oldUses.size()];
70-
for (int u = 0; u < oldUses.size(); u++) {
71-
Register use = oldUses.get(u);
72-
if (!stacks[use.id].isEmpty())
73-
newUses[u] = stacks[use.id].top();
74-
else
75-
newUses[u] = use;
76-
}
77-
i.replaceUses(newUses);
78-
}
79-
80-
static class CopyItem {
81-
/** Phi input can be a register or a constant so we record the operand */
82-
final Operand src;
83-
/** The phi destination */
84-
final Register dest;
85-
/** The basic block where the phi was present */
86-
final BasicBlock destBlock;
87-
boolean removed;
88-
89-
public CopyItem(Operand src, Register dest, BasicBlock destBlock) {
90-
this.src = src;
91-
this.dest = dest;
92-
this.destBlock = destBlock;
93-
this.removed = false;
94-
}
95-
}
96-
97-
private void scheduleCopies(BasicBlock block, List<Integer> pushed) {
98-
/* Pass 1 - Initialize data structures */
99-
/* In this pass we count the number of times a name is used by other phi-nodes */
100-
List<CopyItem> copySet = new ArrayList<>();
101-
Map<Integer, Register> map = new HashMap<>();
102-
BitSet usedByAnother = new BitSet(function.registerPool.numRegisters()*2);
103-
for (BasicBlock s: block.successors) {
104-
int j = s.whichPred(block);
105-
for (Instruction.Phi phi: s.phis()) {
106-
Register dst = phi.value();
107-
Operand srcOperand = phi.input(j); // jth operand of phi node
108-
if (srcOperand instanceof Operand.RegisterOperand srcRegisterOperand) {
109-
Register src = srcRegisterOperand.reg;
110-
map.put(src.id, src);
111-
usedByAnother.set(src.id);
112-
}
113-
copySet.add(new CopyItem(srcOperand, dst, s));
114-
map.put(dst.id, dst);
115-
}
116-
}
117-
118-
/* Pass 2: setup up the worklist of initial copies */
119-
/* In this pass we build a worklist of names that are not used in other phi nodes */
120-
List<CopyItem> workList = new ArrayList<>();
121-
for (CopyItem copyItem: copySet) {
122-
if (usedByAnother.get(copyItem.dest.id) != true) {
123-
copyItem.removed = true;
124-
workList.add(copyItem);
125-
}
126-
}
127-
copySet.removeIf(copyItem -> copyItem.removed);
128-
129-
/* Pass 3: iterate over the worklist, inserting copies */
130-
/* Copy operations whose destinations are not used by other copy operations can be scheduled immediately */
131-
/* Each time we insert a copy operation we add the source of that op to the worklist */
132-
while (!workList.isEmpty() || !copySet.isEmpty()) {
133-
while (!workList.isEmpty()) {
134-
final CopyItem copyItem = workList.remove(0);
135-
final Operand src = copyItem.src;
136-
final Register dest = copyItem.dest;
137-
final BasicBlock destBlock = copyItem.destBlock;
138-
/* Engineering a Compiler: We can avoid the lost copy
139-
problem by checking the liveness of the target name
140-
for each copy that we try to insert. When we discover
141-
a copy target that is live, we must preserve the live
142-
value in a temporary name and rewrite subsequent uses to
143-
refer to the temporary name.
144-
145-
This captures the cases when the result of a phi
146-
in a control successor is live on exit of the current block.
147-
This means that it is incorrect to simply insert a copy
148-
of the destination in the current block. So we rename
149-
the destination to a new temporary, and record the renaming
150-
so that the dominator blocks get the new name. Comment adapted
151-
from JikesRVM LeaveSSA
152-
*/
153-
if (block.liveOut.get(dest.id)) {
154-
/* Insert a copy from dest to a new temp t at phi node defining dest */
155-
final Register t = addMoveToTempAfterPhi(destBlock, dest);
156-
stacks[dest.id].push(t); // record the temp name
157-
pushed.add(dest.id);
158-
}
159-
/* Insert a copy operation from map[src] to dest at end of BB */
160-
if (src instanceof Operand.RegisterOperand srcRegisterOperand) {
161-
addMoveAtBBEnd(block, map.get(srcRegisterOperand.reg.id), dest);
162-
map.put(srcRegisterOperand.reg.id, dest);
163-
/* If src is the name of a dest in copySet add item to worklist */
164-
/* see comment on phi cycles below. */
165-
CopyItem item = isCycle(copySet, srcRegisterOperand.reg);
166-
if (item != null) {
167-
workList.add(item);
168-
}
169-
}
170-
else if (src instanceof Operand.ConstantOperand srcConstantOperand) {
171-
addMoveAtBBEnd(block, srcConstantOperand, dest);
172-
}
173-
}
174-
/* Engineering a Compiler: To solve the swap problem
175-
we can detect cases where phi functions reference the
176-
targets of other phi functions in the same block. For each
177-
cycle of references, it must insert a copy to a temporary
178-
that breaks the cycle. Then we can schedule the copies to
179-
respect the dependencies implied by the phi functions.
180-
181-
An empty work list with work remaining in the copy set
182-
implies a cycle in the dependencies amongst copies. To break
183-
the cycle copy the destination of an arbitrary member of the
184-
copy set to a temporary. This destination has therefore been
185-
saved and can be safely overwritten. So then add the copy to the
186-
work list. Comment adapted from JikesRVM LeaveSSA.
187-
*/
188-
if (!copySet.isEmpty()) {
189-
CopyItem copyItem = copySet.remove(0);
190-
/* Insert a copy from dst to new temp at the end of Block */
191-
Register t = addMoveToTempAtBBEnd(block, copyItem.dest);
192-
map.put(copyItem.dest.id, t);
193-
workList.add(copyItem);
194-
}
195-
}
196-
}
197-
198-
private void insertAtEnd(BasicBlock bb, Instruction i) {
199-
assert bb.instructions.size() > 0;
200-
// Last instruction is a branch - so new instruction will
201-
// go before that
202-
int pos = bb.instructions.size()-1;
203-
bb.add(pos, i);
204-
}
205-
206-
private void insertAfterPhi(BasicBlock bb, Register phiDef, Instruction newInst) {
207-
assert bb.instructions.size() > 0;
208-
int insertionPos = -1;
209-
for (int pos = 0; pos < bb.instructions.size(); pos++) {
210-
Instruction i = bb.instructions.get(pos);
211-
if (i instanceof Instruction.Phi phi) {
212-
if (phi.value().id == phiDef.id) {
213-
insertionPos = pos+1; // After phi
214-
break;
215-
}
216-
}
217-
}
218-
if (insertionPos < 0) {
219-
throw new IllegalStateException();
220-
}
221-
bb.add(insertionPos, newInst);
222-
}
223-
224-
/* Insert a copy from dest to new temp at end of BB, and return temp */
225-
private Register addMoveToTempAtBBEnd(BasicBlock block, Register dest) {
226-
var temp = function.registerPool.newTempReg(dest.name(), dest.type);
227-
var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp));
228-
insertAtEnd(block, inst);
229-
return temp;
230-
}
231-
232-
/* If src is the name of a dest in copySet remove the item */
233-
private CopyItem isCycle(List<CopyItem> copySet, Register src) {
234-
for (int i = 0; i < copySet.size(); i++) {
235-
CopyItem copyItem = copySet.get(i);
236-
if (copyItem.dest.id == src.id) {
237-
copySet.remove(i);
238-
return copyItem;
239-
}
240-
}
241-
return null;
242-
}
243-
244-
/* Insert a copy from src to dst at end of BB */
245-
private void addMoveAtBBEnd(BasicBlock block, Register src, Register dest) {
246-
var inst = new Instruction.Move(new Operand.RegisterOperand(src), new Operand.RegisterOperand(dest));
247-
insertAtEnd(block, inst);
248-
// If the copy instruction is followed by a cbr which uses the old var
249-
// then we need to update the cbr instruction
250-
// This is not specified in the Briggs paper but t
251-
var brInst = block.instructions.getLast();
252-
if (brInst instanceof Instruction.ConditionalBranch cbr) {
253-
cbr.replaceUse(src,dest);
254-
}
255-
}
256-
/* Insert a copy from constant src to dst at end of BB */
257-
private void addMoveAtBBEnd(BasicBlock block, Operand.ConstantOperand src, Register dest) {
258-
var inst = new Instruction.Move(src, new Operand.RegisterOperand(dest));
259-
insertAtEnd(block, inst);
260-
}
261-
/* Insert a copy dest to a new temp at phi node defining dest, return temp */
262-
private Register addMoveToTempAfterPhi(BasicBlock block, Register dest) {
263-
var temp = function.registerPool.newTempReg(dest.name(), dest.type);
264-
var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp));
265-
insertAfterPhi(block, dest, inst);
266-
return temp;
267-
}
268-
269-
private void initStack() {
270-
stacks = new NameStack[function.registerPool.numRegisters()];
271-
for (int i = 0; i < stacks.length; i++)
272-
stacks[i] = new NameStack();
273-
}
274-
275-
static class NameStack {
276-
List<Register> stack = new ArrayList<>();
277-
void push(Register r) { stack.add(r); }
278-
Register top() { return stack.getLast(); }
279-
void pop() { stack.removeLast(); }
280-
boolean isEmpty() { return stack.isEmpty(); }
10+
if (options.contains(Options.SSA_DESTRUCTION_BOISSINOT_NOCOALESCE))
11+
new ExitSSABoissinotNoCoalesce(function,options);
12+
else
13+
new ExitSSABriggs(function,options);
28114
}
28215
}

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA2.java renamed to optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSABoissinotNoCoalesce.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,50 @@
55
/**
66
* Implement method to exit SSA by converting to conventional SSA,
77
* without coalescing. This is the basic form.
8+
*
9+
* This is essentially Method 1 described by
10+
* Translating Out of Static Single Assignment Form
11+
* Vugranam C. Sreedhar, Roy Dz-Ching Ju, David M. Gillies, and Vatsa Santhanam
12+
*
13+
* However, Sreedhar left out details such as using parallel copies
14+
* and sequencing of parallel copies.
15+
*
16+
* Revisiting Out-of-SSA Translation for Correctness, Code Quality, and Efficiency
17+
* Benoit Boissinot, Alain Darte, Fabrice Rastello, Benoît Dupont de Dinechin, Christophe Guillon
18+
*
19+
* The Boissinot paper gives a more correct description, discussing the need for parallel copy
20+
* and sequencing the parallel copy, but the paper describes a
21+
* more complex approach that performs coalescing.
22+
*
23+
* Engineering a Compiler, 3rd edition, describes the simpler form - omitting the coalescing part.
24+
*
25+
* Our implementation is similar to EaC.
26+
*
27+
* We do not use the sequencing algo described in Boissinot paper. Instead, we use:
28+
*
29+
* https://xavierleroy.org/publi/parallel-move.pdf
30+
* Tilting at windmills with Coq: formal verification of a compilation algorithm for parallel moves
31+
* Laurence Rideau, Bernard Paul Serpette, Xavier Leroy
832
*/
9-
public class ExitSSA2 {
33+
public class ExitSSABoissinotNoCoalesce {
1034

1135
CompiledFunction function;
1236
Map<BasicBlock,PCopy> parallelCopies = new HashMap<>();
1337
List<BasicBlock> allBlocks;
1438

15-
public ExitSSA2(CompiledFunction function, EnumSet<Options> options) {
39+
public ExitSSABoissinotNoCoalesce(CompiledFunction function, EnumSet<Options> options) {
1640
this.function = function;
1741
allBlocks = function.getBlocks();
18-
insertPCopiesForEachBlock();
42+
init();
1943
makeConventionalSSA();
2044
removePhis();
2145
sequenceParallelCopies();
2246
function.isSSA = false;
47+
if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA");
2348
}
2449

25-
private void insertPCopiesForEachBlock() {
26-
// We do not actually insert pcopy instruction until needed
50+
private void init() {
51+
// We do not actually insert parallel copy instruction until needed
2752
// but we create an auxiliary data structure to help us track these
2853
for (BasicBlock block: allBlocks) {
2954
parallelCopies.put(block,new PCopy(block));
@@ -73,8 +98,9 @@ private Instruction.ParallelCopyInstruction getParallelCopyAtBegin(BasicBlock bl
7398
}
7499

75100
/**
76-
* Isolate phi nodes to make SSA conventionalS
101+
* Isolate phi nodes to make SSA conventional.
77102
* This is Phase 1 as described in Engineering a Compiler 3rd Edition, p490.
103+
* It is also described as method 1 by Sreedhar, and explained in detail by Boissinot.
78104
*/
79105
private void makeConventionalSSA() {
80106
var blocks = function.getBlocks();
@@ -100,6 +126,9 @@ private void makeConventionalSSA() {
100126
}
101127
}
102128

129+
/**
130+
* Phase 2 in Engineering a Compiler
131+
*/
103132
private void removePhis() {
104133
var blocks = function.getBlocks();
105134
for (BasicBlock block: blocks) {
@@ -120,6 +149,9 @@ private void removePhis() {
120149
}
121150
}
122151

152+
/**
153+
* Phase 3 in Engineering a Compiler.
154+
*/
123155
private void sequenceParallelCopies() {
124156
for (var block: function.getBlocks()) {
125157
var pcopy = parallelCopies.get(block);
@@ -146,6 +178,7 @@ private boolean isEqual(Operand op1, Operand op2) {
146178
// formal verification of a compilation algorithm
147179
// for parallel moves
148180
// Laurence Rideau, Bernard Paul Serpette, Xavier Leroy
181+
149182
enum MoveStatus {
150183
TO_MOVE, BEING_MOVED, MOVED
151184
}

0 commit comments

Comments
 (0)