312 lines
8.6 KiB
V
312 lines
8.6 KiB
V
module ssa
|
|
|
|
// Parallel-SSA worker support, ported from v2's module.v and adapted to v3's
|
|
// raw-block-id operand model: branch/phi/switch operands that name blocks are
|
|
// remapped by the block offset, while ordinary value operands are remapped by
|
|
// the value offset.
|
|
|
|
// FuncSSAData holds the SSA a worker produced for a single function.
|
|
pub struct FuncSSAData {
|
|
pub:
|
|
func_idx int // index into the main module's funcs[]
|
|
blocks []BlockID // worker-local block ids
|
|
params []ValueID // worker-local param value ids
|
|
}
|
|
|
|
// is_block_operand reports whether operand `idx` of this instruction names a
|
|
// basic block (a raw block id) rather than an SSA value.
|
|
pub fn (i &Instruction) is_block_operand(idx int) bool {
|
|
return match i.op {
|
|
.jmp { idx == 0 }
|
|
.br { idx == 1 || idx == 2 }
|
|
// switch_: cond, default_blk, [case_val, blk]... -> blocks at odd indices.
|
|
.switch_ { idx % 2 == 1 }
|
|
// phi: [val, blk]... -> blocks at odd indices.
|
|
.phi { idx % 2 == 1 }
|
|
else { false }
|
|
}
|
|
}
|
|
|
|
// new_worker_module creates a lightweight Module for parallel SSA building. The
|
|
// worker gets a deep copy of the type store and is seeded with the main module's
|
|
// values/instrs/blocks so ids produced in earlier (serial) phases stay valid.
|
|
pub fn (mut m Module) new_worker_module() &Module {
|
|
mut wf := []Function{cap: m.funcs.len}
|
|
for f in m.funcs {
|
|
wf << f
|
|
}
|
|
mut wg := []GlobalVar{cap: m.globals.len}
|
|
for g in m.globals {
|
|
wg << g
|
|
}
|
|
mut wts := TypeStore{
|
|
cache: map[string]TypeID{}
|
|
}
|
|
wts.types = []Type{cap: m.type_store.types.len}
|
|
for t in m.type_store.types {
|
|
wts.types << t
|
|
}
|
|
for k, v in m.type_store.cache {
|
|
wts.cache[k] = v
|
|
}
|
|
|
|
mut w := &Module{
|
|
name: m.name
|
|
target: m.target
|
|
type_store: wts
|
|
funcs: wf
|
|
globals: wg
|
|
c_struct_names: map[int]string{}
|
|
c_typedef_structs: map[int]bool{}
|
|
const_cache: m.const_cache.clone()
|
|
}
|
|
w.values = []Value{cap: m.values.len + 1024}
|
|
for v in m.values {
|
|
w.values << v
|
|
}
|
|
w.instrs = []Instruction{cap: m.instrs.len + 1024}
|
|
for instr in m.instrs {
|
|
w.instrs << instr
|
|
}
|
|
w.blocks = []BasicBlock{cap: m.blocks.len + 256}
|
|
for blk in m.blocks {
|
|
w.blocks << blk
|
|
}
|
|
return w
|
|
}
|
|
|
|
fn worker_type_cache_key(t Type, type_remap []TypeID) string {
|
|
return match t.kind {
|
|
.int_t {
|
|
if t.is_unsigned {
|
|
'u${t.width}'
|
|
} else {
|
|
'i${t.width}'
|
|
}
|
|
}
|
|
.float_t {
|
|
'f${t.width}'
|
|
}
|
|
.ptr_t {
|
|
remapped := if int(t.elem_type) < type_remap.len {
|
|
type_remap[int(t.elem_type)]
|
|
} else {
|
|
t.elem_type
|
|
}
|
|
'p${remapped}'
|
|
}
|
|
.array_t {
|
|
remapped := if int(t.elem_type) < type_remap.len {
|
|
type_remap[int(t.elem_type)]
|
|
} else {
|
|
t.elem_type
|
|
}
|
|
'a${remapped}_${t.len}'
|
|
}
|
|
else {
|
|
''
|
|
}
|
|
}
|
|
}
|
|
|
|
// merge_worker_module folds a worker's freshly-built SSA (everything beyond the
|
|
// seed lengths) back into the main module, remapping type/value/instr/block/func
|
|
// ids. func_data carries each built function's worker-local blocks/params.
|
|
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) {
|
|
// 1. Remap worker types >= seed_types into the main type store.
|
|
mut type_remap := []TypeID{len: w.type_store.types.len, init: 0}
|
|
for ti := 0; ti < seed_types && ti < type_remap.len; ti++ {
|
|
type_remap[ti] = ti
|
|
}
|
|
for ti := seed_types; ti < w.type_store.types.len; ti++ {
|
|
wt := w.type_store.types[ti]
|
|
cache_key := worker_type_cache_key(wt, type_remap)
|
|
if cache_key.len > 0 {
|
|
if existing := m.type_store.cache[cache_key] {
|
|
if existing > 0 {
|
|
type_remap[ti] = existing
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
remap_t := fn (id TypeID, type_remap []TypeID, seed_types int) TypeID {
|
|
if int(id) >= seed_types && int(id) < type_remap.len {
|
|
return type_remap[int(id)]
|
|
}
|
|
return id
|
|
}
|
|
mut new_fields := []TypeID{cap: wt.fields.len}
|
|
for f in wt.fields {
|
|
new_fields << remap_t(f, type_remap, seed_types)
|
|
}
|
|
mut new_params := []TypeID{cap: wt.params.len}
|
|
for p in wt.params {
|
|
new_params << remap_t(p, type_remap, seed_types)
|
|
}
|
|
new_type := Type{
|
|
kind: wt.kind
|
|
width: wt.width
|
|
is_unsigned: wt.is_unsigned
|
|
elem_type: remap_t(wt.elem_type, type_remap, seed_types)
|
|
len: wt.len
|
|
fields: new_fields
|
|
field_names: wt.field_names
|
|
params: new_params
|
|
ret_type: remap_t(wt.ret_type, type_remap, seed_types)
|
|
is_c_struct: wt.is_c_struct
|
|
is_union: wt.is_union
|
|
}
|
|
new_id := m.type_store.register(new_type)
|
|
if cache_key.len > 0 {
|
|
m.type_store.cache[cache_key] = new_id
|
|
}
|
|
type_remap[ti] = new_id
|
|
}
|
|
|
|
value_off := m.values.len - seed_values
|
|
instr_off := m.instrs.len - seed_instrs
|
|
block_off := m.blocks.len - seed_blocks
|
|
|
|
remap_val := fn (id ValueID, seed_values int, value_off int) ValueID {
|
|
return if id >= seed_values { id + value_off } else { id }
|
|
}
|
|
remap_blk := fn (id BlockID, seed_blocks int, block_off int) BlockID {
|
|
return if id >= seed_blocks { id + block_off } else { id }
|
|
}
|
|
remap_typ := fn (id TypeID, seed_types int, type_remap []TypeID) TypeID {
|
|
return if int(id) >= seed_types && int(id) < type_remap.len {
|
|
type_remap[int(id)]
|
|
} else {
|
|
id
|
|
}
|
|
}
|
|
|
|
// 2. Append worker values beyond the seed.
|
|
for wi := seed_values; wi < w.values.len; wi++ {
|
|
wv := w.values[wi]
|
|
mut new_index := wv.index
|
|
if wv.kind == .instruction {
|
|
new_index += instr_off
|
|
} else if wv.kind == .basic_block {
|
|
new_index += block_off
|
|
}
|
|
mut new_uses := []ValueID{cap: wv.uses.len}
|
|
for u in wv.uses {
|
|
new_uses << remap_val(u, seed_values, value_off)
|
|
}
|
|
m.values << Value{
|
|
id: wi + value_off
|
|
kind: wv.kind
|
|
typ: remap_typ(wv.typ, seed_types, type_remap)
|
|
name: wv.name
|
|
index: new_index
|
|
uses: new_uses
|
|
}
|
|
}
|
|
|
|
// 3. Append worker instructions beyond the seed, remapping operands by kind.
|
|
for ii := seed_instrs; ii < w.instrs.len; ii++ {
|
|
instr := w.instrs[ii]
|
|
mut new_ops := []ValueID{cap: instr.operands.len}
|
|
for oi in 0 .. instr.operands.len {
|
|
op := instr.operands[oi]
|
|
if instr.is_block_operand(oi) {
|
|
new_ops << ValueID(remap_blk(BlockID(op), seed_blocks, block_off))
|
|
} else {
|
|
new_ops << remap_val(op, seed_values, value_off)
|
|
}
|
|
}
|
|
m.instrs << Instruction{
|
|
op: instr.op
|
|
operands: new_ops
|
|
block: remap_blk(instr.block, seed_blocks, block_off)
|
|
typ: remap_typ(instr.typ, seed_types, type_remap)
|
|
pos: instr.pos
|
|
atomic_ord: instr.atomic_ord
|
|
inline: instr.inline
|
|
}
|
|
}
|
|
|
|
// 4. Append worker blocks beyond the seed.
|
|
for bi := seed_blocks; bi < w.blocks.len; bi++ {
|
|
blk := w.blocks[bi]
|
|
mut new_instrs := []ValueID{cap: blk.instrs.len}
|
|
for vi in blk.instrs {
|
|
new_instrs << remap_val(vi, seed_values, value_off)
|
|
}
|
|
mut new_preds := []BlockID{cap: blk.preds.len}
|
|
for p in blk.preds {
|
|
new_preds << remap_blk(p, seed_blocks, block_off)
|
|
}
|
|
mut new_succs := []BlockID{cap: blk.succs.len}
|
|
for s in blk.succs {
|
|
new_succs << remap_blk(s, seed_blocks, block_off)
|
|
}
|
|
m.blocks << BasicBlock{
|
|
id: blk.id + block_off
|
|
val_id: remap_val(blk.val_id, seed_values, value_off)
|
|
name: blk.name
|
|
parent: blk.parent
|
|
instrs: new_instrs
|
|
preds: new_preds
|
|
succs: new_succs
|
|
}
|
|
}
|
|
|
|
// 5. Patch the built functions' blocks/params in the main module.
|
|
for fd in func_data {
|
|
if fd.func_idx >= m.funcs.len {
|
|
continue
|
|
}
|
|
mut f := m.funcs[fd.func_idx]
|
|
if f.blocks.len > 0 {
|
|
continue // already merged by an earlier worker
|
|
}
|
|
mut new_blocks := []BlockID{cap: fd.blocks.len}
|
|
for blk_id in fd.blocks {
|
|
new_blocks << remap_blk(blk_id, seed_blocks, block_off)
|
|
}
|
|
f.blocks = new_blocks
|
|
mut new_params := []ValueID{cap: fd.params.len}
|
|
for p in fd.params {
|
|
new_params << remap_val(p, seed_values, value_off)
|
|
}
|
|
f.params = new_params
|
|
m.funcs[fd.func_idx] = f
|
|
}
|
|
|
|
// 6. Append any brand-new functions a worker created (runtime stubs, etc.).
|
|
for fi := seed_funcs; fi < w.funcs.len; fi++ {
|
|
wfunc := w.funcs[fi]
|
|
mut already := false
|
|
for existing in m.funcs {
|
|
if existing.name == wfunc.name {
|
|
already = true
|
|
break
|
|
}
|
|
}
|
|
if already {
|
|
continue
|
|
}
|
|
mut new_blocks := []BlockID{cap: wfunc.blocks.len}
|
|
for blk_id in wfunc.blocks {
|
|
new_blocks << remap_blk(blk_id, seed_blocks, block_off)
|
|
}
|
|
mut new_params := []ValueID{cap: wfunc.params.len}
|
|
for p in wfunc.params {
|
|
new_params << remap_val(p, seed_values, value_off)
|
|
}
|
|
m.funcs << Function{
|
|
id: m.funcs.len
|
|
name: wfunc.name
|
|
typ: remap_typ(wfunc.typ, seed_types, type_remap)
|
|
blocks: new_blocks
|
|
params: new_params
|
|
is_c_extern: wfunc.is_c_extern
|
|
is_prototype: wfunc.is_prototype
|
|
linkage: wfunc.linkage
|
|
call_conv: wfunc.call_conv
|
|
}
|
|
}
|
|
}
|