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
303
304
305
|
package com.seaofnodes.simple;
import com.seaofnodes.graph.GraphObserver;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.node.cpus.x86_64_v2.MemOpX86;
import com.seaofnodes.simple.type.*;
import java.util.BitSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
public class CodeGen {
public GraphObserver<Node> _obs;
// Inlining can insert CFG edges and invalidate cached depth ordering.
private char _iDepthVersion;
public char iDepthVersion() { return _iDepthVersion; }
public void invalidateIDepthCaches() {
assert _iDepthVersion < Character.MAX_VALUE : "Dominator cache version exceeds 65535";
++_iDepthVersion;
}
// Last created CodeGen as a global; used all over to avoid passing about a
// "context".
public static CodeGen CODE;
public enum Phase {
Parse, // Parse ASCII text into Sea-of-Nodes IR
Opto, // Run ideal optimizations
TypeCheck, // Last check for bad programs
InstructionSelection, // Convert to target hardware nodes
Schedule, // Global schedule (code motion) nodes
LocalSched, // Local schedule
RegAlloc; // Register allocation
}
public enum CallingConv {
Win64,
SystemV
}
public Phase _phase;
// ---------------------------
// Compilation source code
public final String _src;
// Compile-time known initial argument type
public final TypeInteger _arg;
public CallingConv _callingConv;
// ---------------------------
public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L ); }
public CodeGen( String src, TypeInteger arg, long workListSeed ) {
CODE = this;
_phase = null;
_callingConv = null;
_start = new StartNode(arg);
_stop = new StopNode(src);
_src = src;
_arg = arg;
_iter = new IterPeeps(workListSeed);
P = new Parser(this,arg);
}
// ---------------------------
/**
* A counter, for unique node id generation. Starting with value 1, to
* avoid bugs confusing node ID 0 with uninitialized values.
*/
private int _uid = 1;
public int UID() { return _uid; }
public int getUID() { return _uid++; }
// Next available memory alias number
private int _alias = 2; // 0 is for control, 1 for memory
public int getALIAS() { return _alias++; }
// Popular visit bitset, declared here, so it gets reused all over
public final BitSet _visit = new BitSet();
public BitSet visit() { assert _visit.isEmpty(); return _visit; }
// Start and stop; end points of the generated IR
public StartNode _start;
public StopNode _stop;
// Global Value Numbering. Hash over opcode and inputs; hits in this table
// are structurally equal.
public final HashMap<Node,Node> _gvn = new HashMap<>();
// Compute "function indices": FIDX.
// Each new request at the same signature gets a new FIDX.
private final HashMap<TypeTuple,Integer> FIDXS = new HashMap<>();
public TypeFunPtr makeFun( TypeTuple sig, Type ret ) {
Integer i = FIDXS.get(sig);
int fidx = i==null ? 0 : i;
FIDXS.put(sig,fidx+1); // Track count per sig
assert fidx<64; // TODO: need a larger FIDX space
return TypeFunPtr.make((byte)2,sig,ret, 1L<<fidx );
}
// Signature for MAIN
public TypeFunPtr _main = makeFun(TypeTuple.MAIN,Type.BOTTOM);
// Reverse from a constant function pointer to the IR function being called
public FunNode link( TypeFunPtr tfp ) {
assert tfp.isConstant();
return _linker.get(tfp.makeFrom(Type.BOTTOM));
}
// Insert linker mapping from constant function signature to the function
// being called.
public void link(FunNode fun) {
_linker.put(fun.sig().makeFrom(Type.BOTTOM),fun);
}
// "Linker" mapping from constant TypeFunPtrs to heads of function. These
// TFPs all have exact single fidxs and their return is wiped to BOTTOM (so
// the return is not part of the match).
private final HashMap<TypeFunPtr,FunNode> _linker = new HashMap<>();
// Parser object
public final Parser P;
// Parse ASCII text into Sea-of-Nodes IR
public CodeGen parse() {
assert _phase == null;
_phase = Phase.Parse;
P.parse();
if( _obs != null ) _obs.phase("Parse");
return this;
}
// ---------------------------
// Iterator peepholes.
public final IterPeeps _iter;
// Stop tracking deps while assertin
public boolean _midAssert;
// Statistics on peepholes
public int _iter_cnt, _iter_nop_cnt;
// Stat gathering
public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
// Run ideal optimizations
public CodeGen opto() {
assert _phase == Phase.Parse;
_phase = Phase.Opto;
// Pessimistic peephole optimization on a worklist
_iter.iterate(this);
// TODO:
// Optimistic
// TODO:
// loop unroll, peel, RCE, etc
if( _obs != null ) _obs.phase("Opto");
return this;
}
public <N extends Node> N add( N n ) { return (N)_iter.add(n); }
public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
// ---------------------------
// Last check for bad programs
public CodeGen typeCheck() {
// Demand phase Opto for cleaning up dead control flow at least,
// required for the following GCM.
assert _phase.ordinal() <= Phase.Opto.ordinal();
_phase = Phase.TypeCheck;
Parser.ParseException err = _stop.walk(n -> {
Parser.ParseException msg = n.err();
if( msg != null && _obs != null ) _obs.error(n, msg.getMessage());
return msg;
});
if( err != null )
throw err;
if( _obs != null ) _obs.phase("TypeCheck");
return this;
}
// ---------------------------
// Code generation CPU target
public Machine _mach;
// Convert to target hardware nodes
public CodeGen instSelect( String cpu, String callingConv ) {
assert _phase.ordinal() <= Phase.TypeCheck.ordinal();
_phase = Phase.InstructionSelection;
_callingConv = switch(callingConv) {
case "SystemV" -> CallingConv.SystemV;
case "Win64" -> CallingConv.Win64;
default -> throw Utils.TODO(); // Unknown calling convention
};
// Look for CPU in fixed named place:
// com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
String clzFile = "com.seaofnodes.simple.node.cpus."+cpu+"."+cpu;
try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor().newInstance(); }
catch( Exception e ) { throw new RuntimeException(e); }
// Convert to machine ops
_uid = 1; // All new machine nodes reset numbering
var map = new IdentityHashMap<Node,Node>();
_instSelect( _stop, map );
_stop = ( StopNode)map.get(_stop );
_start = (StartNode)map.get(_start);
_instOuts(_stop,visit());
_visit.clear();
return this;
}
// Walk all ideal nodes, recursively mapping ideal to machine nodes, then
// make a machine node for "this".
private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
if( n==null ) return null;
Node x = map.get(n);
if( x !=null ) return x; // Been there, done that
// If n is a MachConcrete, then its part of a multi-node expansion.
// It does not need instruction selection (already selected!)
// but it does need its inputs walked.
if( n instanceof MachConcreteNode || n instanceof MemOpX86) {
for( int i=0; i < n.nIns(); i++ )
n._inputs.set(i, _instSelect(n.in(i),map) );
return n;
}
// Produce a machine node from n; map it to flag as done so stops cycles.
map.put(n, x=_mach.instSelect(n) );
// Walk machine op and replace inputs with mapped inputs
for( int i=0; i < x.nIns(); i++ )
x._inputs.set(i, _instSelect(x.in(i),map) );
if( x instanceof MachNode mach )
mach.postSelect(); // Post selection action
// Updates forward edges only.
n._outputs.clear();
return x;
}
// Walk all machine Nodes, and set their output edges
private void _instOuts( Node n, BitSet visit ) {
if( visit.get(n._nid) ) return;
visit.set(n._nid);
for( Node in : n._inputs )
if( in!=null ) {
in._outputs.push(n);
_instOuts(in,visit);
}
}
// ---------------------------
// Control Flow Graph in RPO order.
Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
// Global schedule (code motion) nodes
public CodeGen GCM() {
assert _phase.ordinal() <= Phase.InstructionSelection.ordinal();
_phase = Phase.Schedule;
// Build the loop tree, fix never-exit loops
_start.buildLoopTree(_stop);
GlobalCodeMotion.buildCFG(this);
return this;
}
// ---------------------------
// Local (basic block) scheduler phase, a classic list scheduler
public CodeGen localSched() {
assert _phase == Phase.Schedule;
_phase = Phase.LocalSched;
ListScheduler.sched(this);
return this;
}
// ---------------------------
public String reg(Node n) {
if( _phase == Phase.RegAlloc ) throw Utils.TODO();
return "N"+ n._nid;
}
// ---------------------------
SB asm(SB sb) { return ASMPrinter.print(sb,this); }
String asm() { return asm(new SB()).toString(); }
// Testing shortcuts
Node ctrl() { return _stop.ret().ctrl(); }
Node expr() { return _stop.ret().expr(); }
String print() { return _stop.print(); }
// Debugging helper
@Override public String toString() { return IRPrinter.prettyPrint(this); }
// Debugging helper
public Node f(int idx) { return _stop.find(idx); }
}
|