1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
|
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
import java.util.BitSet;
import java.util.IdentityHashMap;
/** Control Flow Graph Nodes
* <p>
* CFG nodes have a immediate dominator depth (idepth) and a loop nesting
* depth(loop_depth).
* <p>
* idepth is computed lazily upon first request, and is valid even in the
* Parser, and is used by peepholes during parsing and afterward.
* <p>
* loop_depth is computed after optimization as part of scheduling.
*
* Start - Block head; has constants and trailing Funs
* Region - Block head; has Phis. Includes Fun and Parms.
* CallEnd - Block head; followed by CProj and Proj; also linked RPC Parms
* CProj - Block head
*
* If - Block tail; followed by CProj same block
* Call - Block tail; followed by CallEnd and linked Funs
* New/intrinsic - If followed by CProj: block tail else mid-block; followed by Proj
*
*/
public abstract class CFGNode extends Node {
public CFGNode(Node... nodes) { super(nodes); }
public CFGNode(CFGNode cfg) {
super(cfg);
if( cfg != null ) {
_idepth = cfg._idepth;
_idepthVersion = cfg._idepthVersion;
_ltree = cfg._ltree;
_pre = cfg._pre;
}
}
public CFGNode cfg(int idx) { return (CFGNode)in(idx); }
// Block head is Start, Region, CProj, but not e.g. If, Return, Stop
public boolean blockHead() { return false; }
// Get the one control following; error to call with more than one e.g. an
// IfNode or other multi-way branch.
public CFGNode uctrl() {
CFGNode c = null;
for( Node n : _outputs )
if( n instanceof CFGNode cfg )
{ assert c==null; c = cfg; }
return c;
}
// Used by the encoding / final BB layout
public CFGNode uctrlSkipEmpty() {
CFGNode x = this, y;
while( x.nOuts() == 1 && (y=x.uctrl())!=null && !(y instanceof CallNode) ) // Skip empty blocks
x = y;
return x;
}
// ------------------------------------------------------------------------
/**
* Immediate dominator tree depth, used to approximate a real IDOM during
* parsing where we do not have the whole program, and also peepholes
* change the CFG incrementally.
* <p>
* See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
*/
public char _idepth;
public char _idepthVersion;
public int idepth() { return validIDepth() ? _idepth : cacheIDepth(idom().idepth()+1); }
// Zero depth means uncached. Check before narrowing so overflow cannot wrap.
final int cacheIDepth(int depth) {
assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535";
_idepthVersion = CodeGen.CODE.iDepthVersion();
return _idepth = (char)depth;
}
final boolean validIDepth() { return _idepth!=0 && _idepthVersion==CodeGen.CODE.iDepthVersion(); }
// Return the immediate dominator of this Node and compute dom tree depth.
public CFGNode idom(Node dep) { return cfg(0); }
public final CFGNode idom() { return idom(null); }
// Return the LCA of two idoms
public CFGNode domLCA(CFGNode rhs, Node dep) {
if( rhs==null ) return this;
CFGNode lhs = this;
while( lhs != rhs ) {
if( lhs==null || rhs==null ) return null;
int comp = lhs.idepth() - rhs.idepth();
if( comp >= 0 ) lhs = (dep==null ? lhs : dep.addDep(lhs)).idom();
if( comp <= 0 ) rhs = (dep==null ? rhs : dep.addDep(rhs)).idom();
}
return lhs;
}
// Safe read of the control input of a data node.
public static CFGNode safeCFG(Node n) {
if( n==null ) return null;
if( !(n.in(0) instanceof CFGNode cfg) ) return null;
return cfg._safeCFG();
}
private CFGNode _safeCFG() {
return switch(this) {
case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG();
default -> this;
};
}
// True when this node dominates `sub` in the current idom tree.
public boolean dominates(CFGNode sub, Node dep) {
for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() )
if( cfg == this )
return true;
else if( dep != null ) dep.addDep(cfg);
return false;
}
public static CFGNode deepest(CFGNode a, CFGNode b) {
if( a == null ) return b;
if( b == null ) return a;
return b.idepth() > a.idepth() ? b : a;
}
public static CFGNode earlyCFG(Node n) {
return earlyCFG(n,new IdentityHashMap<>(),new BitSet());
}
public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
if( n == null ) return null;
CFGNode cached = cache.get(n);
if( cached != null ) return cached;
if( active.get(n._nid) )
return safeCFG(n);
active.set(n._nid);
CFGNode early = null;
for( Node x : n._inputs )
early = deepest(early,defCFG(x,cache,active));
active.clear(n._nid);
cache.put(n,early);
return early;
}
private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
if( n == null || n.isConst() ) return null; // no impact on early cfg
return switch( n ) {
case PhiNode phi -> phi.region();
case CFGNode cfg -> cfg;
case ProjNode projNode -> safeCFG( n );
default -> earlyCFG( n, cache, active );
};
}
public boolean sameFun(CFGNode that) {
FunNode fun = fun();
return fun != null && fun == that.fun();
}
// Find nearest enclosing FunNode
public FunNode fun() {
CFGNode cfg = this;
while( !(cfg instanceof FunNode fun) ) {
if( cfg==null ) return null;
cfg = cfg.idom();
}
return fun;
}
// ------------------------------------------------------------------------
// Loop nesting
public LoopNode loop() { return _ltree._head; }
public int loopDepth() { return _ltree==null ? 0 : _ltree.depth(); }
public LoopTree _ltree;
public int _pre; // Pre-order numbers for loop tree finding
public static class LoopTree {
public LoopTree _par;
public final LoopNode _head;
public int _depth;
LoopTree(LoopNode head) { _head = head; }
@Override public String toString() { return label(_head); }
public int depth() {
return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
}
}
// ------------------------------------------------------------------------
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start contains the function bodies at depth 0; real loops add depth.
public void buildLoopTree( Ary<FunNode> funs, StopNode stop) {
_ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
Ary<FunNode> work = new Ary<>(FunNode.class);
BitSet post = new BitSet();
int pre = 1;
_pre = pre++;
for( FunNode fun : funs ) {
if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) {
work.push(fun);
if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) {
start._pre = pre++;
start._ltree = start.cfg(1)._ltree = _ltree;
post.set(start._nid);
}
};
}
for( int i=0; i<work._len; i++ )
pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
}
int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
// Pre-walked?
if( _pre!=0 ) return pre;
_pre = pre++;
// Seed the function's outer tree before walking its body. Synthetic
// exits can be added before the post-order walk reaches the entry.
if( this instanceof FunNode ) _ltree = stop._ltree;
if( this instanceof ReturnNode ) {
_ltree = fun._ltree;
post.set(_nid);
return pre; // Stop/Start links describe the outside world, not a loop.
}
// Pre-walk
for( Node use : _outputs )
if( use instanceof CFGNode usecfg ) {
if( skip( usecfg ) ) {
if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 );
} else
pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
}
// Post-order work: find innermost loop
LoopTree inner = null, ltree;
for( Node use : _outputs ) {
if( !(use instanceof CFGNode usecfg) ) continue;
if( skip(usecfg) ) continue;
if( usecfg._type == Type.XCONTROL || // Do not walk dead control
usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
continue;
// Child visited but not post-visited?
if( !post.get(usecfg._nid) ) {
// Must be a backedge to a LoopNode then
ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
} else {
// Take child's loop choice, which must exist
ltree = usecfg._ltree;
// If falling into a loop, use the target loop's parent instead
if( ltree._head == usecfg ) {
if( ltree._par == null )
// This loop never had an If test choose to take its
// exit, i.e. it is a no-exit infinite loop.
ltree._par = ltree._head.forceExit(fun,stop)._ltree;
ltree = ltree._par;
}
}
// Sort inner loops. The decision point is some branch far removed
// from either loop head OR either backedge so requires pre-order
// numbers to figure out innermost.
if( inner == null ) { inner = ltree; continue; }
if( inner == ltree ) continue; // No change
LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
inner = ltree._head._pre > inner._head._pre ? ltree : inner;
inner._par = outer;
}
// Set selected loop
if( inner!=null )
_ltree = inner;
// Tag as post-walked
post.set(_nid);
return pre;
}
public boolean skip(CFGNode usecfg) {
// Only walk control users that are alive.
// Do not walk from a Call to linked Fun's.
return usecfg instanceof XCtrlNode ||
(this instanceof CallNode && usecfg instanceof FunNode) ||
(this instanceof ReturnNode && usecfg instanceof CallEndNode);
}
public static String label( CFGNode target ) {
return switch(target) {
case StartNode start -> start.label();
case FunNode fun -> fun.label();
case LoopNode loop -> "LOOP"+target._nid;
default -> "L"+target._nid;
};
}
}
|