|
4 | 4 |
|
5 | 5 | /** |
6 | 6 | * 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. |
13 | 7 | */ |
14 | 8 | public class ExitSSA { |
15 | | - |
16 | | - CompiledFunction function; |
17 | | - NameStack[] stacks; |
18 | | - DominatorTree tree; |
19 | | - |
20 | 9 | 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); |
281 | 14 | } |
282 | 15 | } |
0 commit comments