dom_fn.nx source
↩ module page · 255 lines · 9448 B
1// dom_fn.nx -- dominator tree on *Function (opt.nx consumer).
2//
3// Separate from dom.nx (which operates on a local Block type
4// declared in that file). dom_fn computes immediate dominators
5// directly on the runtime's shared BasicBlock struct so opt.nx
6// passes (LICM next) can consume it without type translation.
7//
8// Algorithm: Cooper-Harvey-Kennedy "A Simple, Fast Dominance
9// Algorithm" (2001). O(N * E * alpha) in practice, trivial
10// implementation compared to Lengauer-Tarjan.
11//
12// Invariants:
13// DF1 Entry block is its own idom by convention.
14// DF2 Unreachable blocks have idom == NULL. Callers should
15// run opt_sweep_unreachable_function first if they want
16// every block reachable.
17// DF3 idom[i] is indexed by ARRAY POSITION (block_index), not
18// by block.id. Caller uses block_index() to look up an
19// idom from a BasicBlock pointer.
20// DF4 RPO (reverse-postorder) guarantees every dominator is
21// seen before its dominatees during the fixpoint pass;
22// this is the invariant that makes Cooper-Harvey-Kennedy's
23// iterate-to-fixpoint converge in O(d) where d is max
24// dominator-tree depth.
25
26import "syscalls.nx"
27import "types.nx"
28import "ir.nx"
29
30// Result bundle: idom pointers indexed by array position, plus
31// RPO ordering so callers can iterate dominatees-before-dominator
32// (or reverse) without recomputing.
33struct DomInfoFn {
34 f: *Function,
35 n: i64, // == f.n_blocks
36 idom: *BasicBlock, // array of n pointers (*BasicBlock-valued)
37 rpo: *BasicBlock, // array of n pointers, RPO order (reachable first)
38 rpo_n: i64, // count of reachable blocks
39 rpo_num: *i64, // block_index -> RPO position, or -1 if unreachable
40}
41
42// Look up a block's array index in f.blocks[]. Linear scan,
43// O(n); fine for small functions. Returns -1 if not found.
44func df_block_index(f: *Function, bb: *BasicBlock) -> i64 {
45 var i: i64 = 0
46 while i < f.n_blocks {
47 if block_at(f, i) == bb { return i }
48 i = i + 1
49 }
50 return -1
51}
52
53// DFS from `b`, visiting successors in inline-slot order. On
54// exit from each recursion level, push onto rpo array. The
55// resulting rpo array, reversed at the end, is the reverse-
56// postorder traversal.
57func df_dfs(f: *Function, b: *BasicBlock,
58 visited: *u8, rpo: *BasicBlock, rpo_n: *i64) -> i64 {
59 let idx: i64 = df_block_index(f, b)
60 if idx < 0 { return 0 }
61 if visited[idx] == 1 { return 0 }
62 visited[idx] = 1
63 if b.succ0 != (0 as *BasicBlock) {
64 df_dfs(f, b.succ0, visited, rpo, rpo_n)
65 }
66 if b.succ1 != (0 as *BasicBlock) {
67 df_dfs(f, b.succ1, visited, rpo, rpo_n)
68 }
69 let n: i64 = *rpo_n
70 // rpo is an array of BasicBlock* stored as 8-byte slots; we
71 // write them via pointer arithmetic since NishiLang lacks a
72 // *BasicBlock -> i64 slot array primitive.
73 let rpo_base: i64 = rpo as i64
74 let slot: *i64 = (rpo_base + n * 8) as *i64
75 *slot = b as i64
76 *rpo_n = n + 1
77 return 0
78}
79
80// Walk up the idom chain of two blocks until they meet.
81// Parameters: idom_by_rpo is the current idom array keyed by
82// RPO position; rpo_num maps block_index to RPO position. Both
83// b1 and b2 are array indices of blocks (not RPO positions).
84func df_intersect(idom_arr: *i64, rpo_num: *i64,
85 b1: i64, b2: i64) -> i64 {
86 var a: i64 = b1
87 var b: i64 = b2
88 while a != b {
89 while rpo_num[a] > rpo_num[b] {
90 a = idom_arr[a]
91 if a < 0 { return b }
92 }
93 while rpo_num[b] > rpo_num[a] {
94 b = idom_arr[b]
95 if b < 0 { return a }
96 }
97 }
98 return a
99}
100
101// Main entry: compute immediate dominators for every block in f.
102// Returns 0 on success, negative on allocation failure. Caller
103// passes in pre-allocated DomInfoFn struct; helper fills arrays.
104func dom_compute_fn(f: *Function, info: *DomInfoFn) -> i64 {
105 let n: i64 = f.n_blocks
106 info.f = f
107 info.n = n
108 if n == 0 { return 0 }
109
110 // RPO via DFS from entry, then reverse.
111 let visited_raw: *u8 = sys_mmap(n + 16)
112 let visited: *u8 = visited_raw
113 var i: i64 = 0
114 while i < n { visited[i] = 0; i = i + 1 }
115 let rpo_fwd_raw: *u8 = sys_mmap(n * 8 + 16)
116 let rpo_fwd: *BasicBlock = rpo_fwd_raw as *BasicBlock
117 let rpo_n_raw: *u8 = sys_mmap(16)
118 let rpo_n_p: *i64 = rpo_n_raw as *i64
119 *rpo_n_p = 0
120 df_dfs(f, f.entry, visited, rpo_fwd, rpo_n_p)
121 let rpo_count: i64 = *rpo_n_p
122
123 // Reverse the postorder array in place.
124 let rpo_raw: *u8 = sys_mmap(n * 8 + 16)
125 let rpo: *BasicBlock = rpo_raw as *BasicBlock
126 let rpo_base: i64 = rpo as i64
127 let rpo_fwd_base: i64 = rpo_fwd as i64
128 i = 0
129 while i < rpo_count {
130 let src_slot: *i64 = (rpo_fwd_base + (rpo_count - 1 - i) * 8) as *i64
131 let dst_slot: *i64 = (rpo_base + i * 8) as *i64
132 *dst_slot = *src_slot
133 i = i + 1
134 }
135 info.rpo = rpo
136 info.rpo_n = rpo_count
137
138 // rpo_num[array_index] = RPO position, or -1.
139 let rpo_num_raw: *u8 = sys_mmap(n * 8 + 16)
140 let rpo_num: *i64 = rpo_num_raw as *i64
141 i = 0
142 while i < n { rpo_num[i] = -1; i = i + 1 }
143 i = 0
144 while i < rpo_count {
145 let slot: *i64 = (rpo_base + i * 8) as *i64
146 let bb: *BasicBlock = *slot as *BasicBlock
147 let idx: i64 = df_block_index(f, bb)
148 if idx >= 0 { rpo_num[idx] = i }
149 i = i + 1
150 }
151 info.rpo_num = rpo_num
152
153 // idom[array_index] = array_index of immediate dominator, or -1
154 // for unreachable / entry. Start: all -1 except entry which
155 // points at itself.
156 let idom_arr_raw: *u8 = sys_mmap(n * 8 + 16)
157 let idom_arr: *i64 = idom_arr_raw as *i64
158 i = 0
159 while i < n { idom_arr[i] = -1; i = i + 1 }
160 let entry_idx: i64 = df_block_index(f, f.entry)
161 idom_arr[entry_idx] = entry_idx
162
163 // Iterate to fixpoint over non-entry reachable blocks in RPO
164 // order. For each block, idom = intersection of idom(preds).
165 var changed: i64 = 1
166 while changed == 1 {
167 changed = 0
168 var ri: i64 = 1 // skip entry (index 0 in RPO)
169 while ri < rpo_count {
170 let slot: *i64 = (rpo_base + ri * 8) as *i64
171 let bb: *BasicBlock = *slot as *BasicBlock
172 let bi: i64 = df_block_index(f, bb)
173 // Find first processed predecessor.
174 var new_idom: i64 = -1
175 var p_idx: i64 = 0
176 // Three pred slots to check.
177 let pred_list: *i64 = sys_mmap(32) as *i64
178 pred_list[0] = bb.pred0 as i64
179 pred_list[1] = bb.pred1 as i64
180 pred_list[2] = bb.pred2 as i64
181 while p_idx < 3 {
182 let p: *BasicBlock = pred_list[p_idx] as *BasicBlock
183 if p != (0 as *BasicBlock) {
184 let p_arr: i64 = df_block_index(f, p)
185 if p_arr >= 0 {
186 if idom_arr[p_arr] >= 0 {
187 if new_idom < 0 { new_idom = p_arr }
188 else {
189 new_idom = df_intersect(idom_arr, rpo_num,
190 new_idom, p_arr)
191 }
192 }
193 }
194 }
195 p_idx = p_idx + 1
196 }
197 if new_idom >= 0 {
198 if idom_arr[bi] != new_idom {
199 idom_arr[bi] = new_idom
200 changed = 1
201 }
202 }
203 ri = ri + 1
204 }
205 }
206
207 // Pack idom_arr into info.idom as BasicBlock pointers (rather
208 // than indices) for caller convenience.
209 let idom_bb_raw: *u8 = sys_mmap(n * 8 + 16)
210 let idom_bb: *BasicBlock = idom_bb_raw as *BasicBlock
211 let idom_bb_base: i64 = idom_bb as i64
212 i = 0
213 while i < n {
214 let dst: *i64 = (idom_bb_base + i * 8) as *i64
215 if idom_arr[i] >= 0 {
216 *dst = block_at(f, idom_arr[i]) as i64
217 } else {
218 *dst = 0
219 }
220 i = i + 1
221 }
222 info.idom = idom_bb
223 return 0
224}
225
226// Returns 1 iff `a` dominates `b`. Walks b's idom chain; reaches
227// a => yes, or reaches entry first => no.
228func dom_dominates(info: *DomInfoFn, a: *BasicBlock, b: *BasicBlock) -> i64 {
229 if a == b { return 1 }
230 let idom_base: i64 = info.idom as i64
231 var cur_idx: i64 = df_block_index(info.f, b)
232 if cur_idx < 0 { return 0 }
233 var hops: i64 = 0
234 while hops < info.n {
235 let slot: *i64 = (idom_base + cur_idx * 8) as *i64
236 let idom_bb: *BasicBlock = *slot as *BasicBlock
237 if idom_bb == a { return 1 }
238 if idom_bb == (0 as *BasicBlock) { return 0 }
239 let next_idx: i64 = df_block_index(info.f, idom_bb)
240 if next_idx == cur_idx { return 0 } // entry's self-loop
241 cur_idx = next_idx
242 hops = hops + 1
243 }
244 return 0
245}
246
247// Compile-only smoke: dom_compute on a nominal function with
248// entry only.
249func main() -> i64 {
250 let info_raw: *u8 = sys_mmap(128)
251 let info: *DomInfoFn = info_raw as *DomInfoFn
252 info.f = 0 as *Function
253 info.n = 0
254 return 0
255}