| 1 | module ssa |
| 2 | |
| 3 | // Parallel-SSA worker support, ported from v2's module.v and adapted to v3's |
| 4 | // raw-block-id operand model: branch/phi/switch operands that name blocks are |
| 5 | // remapped by the block offset, while ordinary value operands are remapped by |
| 6 | // the value offset. |
| 7 | |
| 8 | // FuncSSAData holds the SSA a worker produced for a single function. |
| 9 | pub struct FuncSSAData { |
| 10 | pub: |
| 11 | func_idx int // index into the main module's funcs[] |
| 12 | blocks []BlockID // worker-local block ids |
| 13 | params []ValueID // worker-local param value ids |
| 14 | } |
| 15 | |
| 16 | // is_block_operand reports whether operand `idx` of this instruction names a |
| 17 | // basic block (a raw block id) rather than an SSA value. |
| 18 | pub fn (i &Instruction) is_block_operand(idx int) bool { |
| 19 | return match i.op { |
| 20 | .jmp { idx == 0 } |
| 21 | .br { idx == 1 || idx == 2 } |
| 22 | // switch_: cond, default_blk, [case_val, blk]... -> blocks at odd indices. |
| 23 | .switch_ { idx % 2 == 1 } |
| 24 | // phi: [val, blk]... -> blocks at odd indices. |
| 25 | .phi { idx % 2 == 1 } |
| 26 | else { false } |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | // new_worker_module creates a lightweight Module for parallel SSA building. The |
| 31 | // worker gets a deep copy of the type store and is seeded with the main module's |
| 32 | // values/instrs/blocks so ids produced in earlier (serial) phases stay valid. |
| 33 | pub fn (mut m Module) new_worker_module() &Module { |
| 34 | mut wf := []Function{cap: m.funcs.len} |
| 35 | for f in m.funcs { |
| 36 | wf << f |
| 37 | } |
| 38 | mut wg := []GlobalVar{cap: m.globals.len} |
| 39 | for g in m.globals { |
| 40 | wg << g |
| 41 | } |
| 42 | mut wts := TypeStore{ |
| 43 | cache: map[string]TypeID{} |
| 44 | } |
| 45 | wts.types = []Type{cap: m.type_store.types.len} |
| 46 | for t in m.type_store.types { |
| 47 | wts.types << t |
| 48 | } |
| 49 | for k, v in m.type_store.cache { |
| 50 | wts.cache[k] = v |
| 51 | } |
| 52 | |
| 53 | mut w := &Module{ |
| 54 | name: m.name |
| 55 | target: m.target |
| 56 | type_store: wts |
| 57 | funcs: wf |
| 58 | globals: wg |
| 59 | c_struct_names: map[int]string{} |
| 60 | c_typedef_structs: map[int]bool{} |
| 61 | const_cache: m.const_cache.clone() |
| 62 | } |
| 63 | w.values = []Value{cap: m.values.len + 1024} |
| 64 | for v in m.values { |
| 65 | w.values << v |
| 66 | } |
| 67 | w.instrs = []Instruction{cap: m.instrs.len + 1024} |
| 68 | for instr in m.instrs { |
| 69 | w.instrs << instr |
| 70 | } |
| 71 | w.blocks = []BasicBlock{cap: m.blocks.len + 256} |
| 72 | for blk in m.blocks { |
| 73 | w.blocks << blk |
| 74 | } |
| 75 | return w |
| 76 | } |
| 77 | |
| 78 | // worker_type_cache_key supports worker type cache key handling for ssa. |
| 79 | fn worker_type_cache_key(t Type, type_remap []TypeID) string { |
| 80 | return match t.kind { |
| 81 | .int_t { |
| 82 | if t.is_unsigned { |
| 83 | 'u${t.width}' |
| 84 | } else { |
| 85 | 'i${t.width}' |
| 86 | } |
| 87 | } |
| 88 | .float_t { |
| 89 | 'f${t.width}' |
| 90 | } |
| 91 | .ptr_t { |
| 92 | remapped := if int(t.elem_type) < type_remap.len { |
| 93 | type_remap[int(t.elem_type)] |
| 94 | } else { |
| 95 | t.elem_type |
| 96 | } |
| 97 | 'p${remapped}' |
| 98 | } |
| 99 | .array_t { |
| 100 | remapped := if int(t.elem_type) < type_remap.len { |
| 101 | type_remap[int(t.elem_type)] |
| 102 | } else { |
| 103 | t.elem_type |
| 104 | } |
| 105 | 'a${remapped}_${t.len}' |
| 106 | } |
| 107 | else { |
| 108 | '' |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | // merge_worker_module folds a worker's freshly-built SSA (everything beyond the |
| 114 | // seed lengths) back into the main module, remapping type/value/instr/block/func |
| 115 | // ids. func_data carries each built function's worker-local blocks/params. |
| 116 | pub fn (mut m Module) merge_worker_module(w &Module, func_data []FuncSSAData, seed_values int, seed_instrs int, seed_blocks int, seed_types int, seed_funcs int) { |
| 117 | // 1. Remap worker types >= seed_types into the main type store. |
| 118 | mut type_remap := []TypeID{len: w.type_store.types.len, init: 0} |
| 119 | for ti := 0; ti < seed_types && ti < type_remap.len; ti++ { |
| 120 | type_remap[ti] = ti |
| 121 | } |
| 122 | for ti := seed_types; ti < w.type_store.types.len; ti++ { |
| 123 | wt := w.type_store.types[ti] |
| 124 | cache_key := worker_type_cache_key(wt, type_remap) |
| 125 | if cache_key.len > 0 { |
| 126 | if existing := m.type_store.cache[cache_key] { |
| 127 | if existing > 0 { |
| 128 | type_remap[ti] = existing |
| 129 | continue |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | remap_t := fn (id TypeID, type_remap []TypeID, seed_types int) TypeID { |
| 134 | if int(id) >= seed_types && int(id) < type_remap.len { |
| 135 | return type_remap[int(id)] |
| 136 | } |
| 137 | return id |
| 138 | } |
| 139 | mut new_fields := []TypeID{cap: wt.fields.len} |
| 140 | for f in wt.fields { |
| 141 | new_fields << remap_t(f, type_remap, seed_types) |
| 142 | } |
| 143 | mut new_params := []TypeID{cap: wt.params.len} |
| 144 | for p in wt.params { |
| 145 | new_params << remap_t(p, type_remap, seed_types) |
| 146 | } |
| 147 | new_type := Type{ |
| 148 | kind: wt.kind |
| 149 | width: wt.width |
| 150 | is_unsigned: wt.is_unsigned |
| 151 | elem_type: remap_t(wt.elem_type, type_remap, seed_types) |
| 152 | len: wt.len |
| 153 | fields: new_fields |
| 154 | field_names: wt.field_names |
| 155 | params: new_params |
| 156 | ret_type: remap_t(wt.ret_type, type_remap, seed_types) |
| 157 | is_c_struct: wt.is_c_struct |
| 158 | is_union: wt.is_union |
| 159 | } |
| 160 | new_id := m.type_store.register(new_type) |
| 161 | if cache_key.len > 0 { |
| 162 | m.type_store.cache[cache_key] = new_id |
| 163 | } |
| 164 | type_remap[ti] = new_id |
| 165 | } |
| 166 | |
| 167 | value_off := m.values.len - seed_values |
| 168 | instr_off := m.instrs.len - seed_instrs |
| 169 | block_off := m.blocks.len - seed_blocks |
| 170 | |
| 171 | remap_val := fn (id ValueID, seed_values int, value_off int) ValueID { |
| 172 | return if id >= seed_values { id + value_off } else { id } |
| 173 | } |
| 174 | remap_blk := fn (id BlockID, seed_blocks int, block_off int) BlockID { |
| 175 | return if id >= seed_blocks { id + block_off } else { id } |
| 176 | } |
| 177 | remap_typ := fn (id TypeID, seed_types int, type_remap []TypeID) TypeID { |
| 178 | return if int(id) >= seed_types && int(id) < type_remap.len { |
| 179 | type_remap[int(id)] |
| 180 | } else { |
| 181 | id |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | // 2. Append worker values beyond the seed. |
| 186 | for wi := seed_values; wi < w.values.len; wi++ { |
| 187 | wv := w.values[wi] |
| 188 | mut new_index := wv.index |
| 189 | if wv.kind == .instruction { |
| 190 | new_index += instr_off |
| 191 | } else if wv.kind == .basic_block { |
| 192 | new_index += block_off |
| 193 | } |
| 194 | mut new_uses := []ValueID{cap: wv.uses.len} |
| 195 | for u in wv.uses { |
| 196 | new_uses << remap_val(u, seed_values, value_off) |
| 197 | } |
| 198 | m.values << Value{ |
| 199 | id: wi + value_off |
| 200 | kind: wv.kind |
| 201 | typ: remap_typ(wv.typ, seed_types, type_remap) |
| 202 | name: wv.name |
| 203 | index: new_index |
| 204 | uses: new_uses |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // 3. Append worker instructions beyond the seed, remapping operands by kind. |
| 209 | for ii := seed_instrs; ii < w.instrs.len; ii++ { |
| 210 | instr := w.instrs[ii] |
| 211 | mut new_ops := []ValueID{cap: instr.operands.len} |
| 212 | for oi in 0 .. instr.operands.len { |
| 213 | op := instr.operands[oi] |
| 214 | if instr.is_block_operand(oi) { |
| 215 | new_ops << ValueID(remap_blk(BlockID(op), seed_blocks, block_off)) |
| 216 | } else { |
| 217 | new_ops << remap_val(op, seed_values, value_off) |
| 218 | } |
| 219 | } |
| 220 | m.instrs << Instruction{ |
| 221 | op: instr.op |
| 222 | operands: new_ops |
| 223 | block: remap_blk(instr.block, seed_blocks, block_off) |
| 224 | typ: remap_typ(instr.typ, seed_types, type_remap) |
| 225 | pos: instr.pos |
| 226 | atomic_ord: instr.atomic_ord |
| 227 | inline: instr.inline |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | // 4. Append worker blocks beyond the seed. |
| 232 | for bi := seed_blocks; bi < w.blocks.len; bi++ { |
| 233 | blk := w.blocks[bi] |
| 234 | mut new_instrs := []ValueID{cap: blk.instrs.len} |
| 235 | for vi in blk.instrs { |
| 236 | new_instrs << remap_val(vi, seed_values, value_off) |
| 237 | } |
| 238 | mut new_preds := []BlockID{cap: blk.preds.len} |
| 239 | for p in blk.preds { |
| 240 | new_preds << remap_blk(p, seed_blocks, block_off) |
| 241 | } |
| 242 | mut new_succs := []BlockID{cap: blk.succs.len} |
| 243 | for s in blk.succs { |
| 244 | new_succs << remap_blk(s, seed_blocks, block_off) |
| 245 | } |
| 246 | m.blocks << BasicBlock{ |
| 247 | id: blk.id + block_off |
| 248 | val_id: remap_val(blk.val_id, seed_values, value_off) |
| 249 | name: blk.name |
| 250 | parent: blk.parent |
| 251 | instrs: new_instrs |
| 252 | preds: new_preds |
| 253 | succs: new_succs |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | // 5. Patch the built functions' blocks/params in the main module. |
| 258 | for fd in func_data { |
| 259 | if fd.func_idx >= m.funcs.len { |
| 260 | continue |
| 261 | } |
| 262 | mut f := m.funcs[fd.func_idx] |
| 263 | if f.blocks.len > 0 { |
| 264 | continue // already merged by an earlier worker |
| 265 | } |
| 266 | mut new_blocks := []BlockID{cap: fd.blocks.len} |
| 267 | for blk_id in fd.blocks { |
| 268 | new_blocks << remap_blk(blk_id, seed_blocks, block_off) |
| 269 | } |
| 270 | f.blocks = new_blocks |
| 271 | mut new_params := []ValueID{cap: fd.params.len} |
| 272 | for p in fd.params { |
| 273 | new_params << remap_val(p, seed_values, value_off) |
| 274 | } |
| 275 | f.params = new_params |
| 276 | m.funcs[fd.func_idx] = f |
| 277 | } |
| 278 | |
| 279 | // 6. Append any brand-new functions a worker created (runtime stubs, etc.). |
| 280 | for fi := seed_funcs; fi < w.funcs.len; fi++ { |
| 281 | wfunc := w.funcs[fi] |
| 282 | mut already := false |
| 283 | for existing in m.funcs { |
| 284 | if existing.name == wfunc.name { |
| 285 | already = true |
| 286 | break |
| 287 | } |
| 288 | } |
| 289 | if already { |
| 290 | continue |
| 291 | } |
| 292 | mut new_blocks := []BlockID{cap: wfunc.blocks.len} |
| 293 | for blk_id in wfunc.blocks { |
| 294 | new_blocks << remap_blk(blk_id, seed_blocks, block_off) |
| 295 | } |
| 296 | mut new_params := []ValueID{cap: wfunc.params.len} |
| 297 | for p in wfunc.params { |
| 298 | new_params << remap_val(p, seed_values, value_off) |
| 299 | } |
| 300 | m.funcs << Function{ |
| 301 | id: m.funcs.len |
| 302 | name: wfunc.name |
| 303 | typ: remap_typ(wfunc.typ, seed_types, type_remap) |
| 304 | blocks: new_blocks |
| 305 | params: new_params |
| 306 | is_c_extern: wfunc.is_c_extern |
| 307 | is_prototype: wfunc.is_prototype |
| 308 | linkage: wfunc.linkage |
| 309 | call_conv: wfunc.call_conv |
| 310 | } |
| 311 | } |
| 312 | } |
| 313 | |