code wiki / (root) / dom_fn.nx

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}