Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,374 @@
|
||||
module optimize
|
||||
|
||||
import v3.ssa
|
||||
|
||||
// --- Mem2Reg (promote allocas to SSA values + phi nodes) ---
|
||||
// Flat arrays indexed by value_id / block_id. Phi predecessor operands store
|
||||
// raw block ids, matching the rest of the v3 SSA (and the arm64 backend's
|
||||
// emit_phi_edge_copies, which reads operand[odd] as a block id).
|
||||
struct Mem2RegCtx {
|
||||
mut:
|
||||
defs [][]int // alloc_id -> blocks storing to it
|
||||
uses [][]int // alloc_id -> blocks loading from it
|
||||
phi_placements [][]int // block_id -> alloc_ids needing a phi here
|
||||
phi_vals [][]int // block_id -> parallel phi value ids
|
||||
stacks [][]int // alloc_id -> reaching-definition stack
|
||||
is_promotable []bool
|
||||
phi_blocks []int
|
||||
}
|
||||
|
||||
fn array2d_append(mut arr [][]int, idx int, val int) {
|
||||
mut inner := arr[idx]
|
||||
inner << val
|
||||
arr[idx] = inner
|
||||
}
|
||||
|
||||
// promote_memory_to_register promotes scalar allocas to SSA registers, inserting
|
||||
// phi nodes at dominance frontiers and renaming load/store chains.
|
||||
fn promote_memory_to_register(mut m ssa.Module, dom DomInfo, cfg &CfgData) {
|
||||
n_values := m.values.len
|
||||
n_blocks := m.blocks.len
|
||||
|
||||
mut ctx := Mem2RegCtx{
|
||||
defs: [][]int{len: n_values}
|
||||
uses: [][]int{len: n_values}
|
||||
phi_placements: [][]int{len: n_blocks}
|
||||
phi_vals: [][]int{len: n_blocks}
|
||||
stacks: [][]int{len: n_values}
|
||||
is_promotable: []bool{len: n_values}
|
||||
phi_blocks: []int{}
|
||||
}
|
||||
|
||||
mut df := [][]int{len: n_blocks}
|
||||
mut df_blocks := []int{}
|
||||
mut visited := []bool{len: n_blocks}
|
||||
mut has_phi := []bool{len: n_blocks}
|
||||
|
||||
for fi in 0 .. m.funcs.len {
|
||||
mut promotable := []int{}
|
||||
func := m.funcs[fi]
|
||||
for blk_id in func.blocks {
|
||||
blk := m.blocks[blk_id]
|
||||
for val_id in blk.instrs {
|
||||
val := m.values[val_id]
|
||||
if val.kind != .instruction {
|
||||
continue
|
||||
}
|
||||
instr := m.instrs[val.index]
|
||||
if instr.op == .alloca {
|
||||
if is_promotable(m, val_id) {
|
||||
promotable << val_id
|
||||
ctx.is_promotable[val_id] = true
|
||||
}
|
||||
}
|
||||
if instr.op == .store {
|
||||
ptr := instr.operands[1]
|
||||
if ptr < n_values && blk_id !in ctx.defs[ptr] {
|
||||
array2d_append(mut ctx.defs, ptr, blk_id)
|
||||
}
|
||||
} else if instr.op == .load {
|
||||
ptr := instr.operands[0]
|
||||
if ptr < n_values && blk_id !in ctx.uses[ptr] {
|
||||
array2d_append(mut ctx.uses, ptr, blk_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compute_dominance_frontier_flat(m, fi, &dom, cfg, mut df, mut df_blocks)
|
||||
|
||||
// Insert phis at iterated dominance frontiers of each promotable alloca.
|
||||
for alloc_id in promotable {
|
||||
mut worklist := ctx.defs[alloc_id].clone()
|
||||
mut visited_list := []int{}
|
||||
mut has_phi_list := []int{}
|
||||
for worklist.len > 0 {
|
||||
b := worklist.pop()
|
||||
if b < 0 || b >= n_blocks {
|
||||
continue
|
||||
}
|
||||
for d in df[b] {
|
||||
if !has_phi[d] {
|
||||
array2d_append(mut ctx.phi_placements, d, alloc_id)
|
||||
if d !in ctx.phi_blocks {
|
||||
ctx.phi_blocks << d
|
||||
}
|
||||
has_phi[d] = true
|
||||
has_phi_list << d
|
||||
if !visited[d] {
|
||||
visited[d] = true
|
||||
visited_list << d
|
||||
worklist << d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in visited_list {
|
||||
visited[d] = false
|
||||
}
|
||||
for d in has_phi_list {
|
||||
has_phi[d] = false
|
||||
}
|
||||
}
|
||||
|
||||
// Materialize the phi instructions (empty operands, filled during rename).
|
||||
for blk_id in ctx.phi_blocks {
|
||||
for alloc_id in ctx.phi_placements[blk_id] {
|
||||
alloc_val := m.values[alloc_id]
|
||||
alloc_typ := m.type_store.types[alloc_val.typ]
|
||||
typ := alloc_typ.elem_type
|
||||
phi_val := m.add_instr_front(.phi, blk_id, typ, [])
|
||||
mut pv := m.values[phi_val]
|
||||
pv.name = '${alloc_val.name}.phi_${blk_id}'
|
||||
m.values[phi_val] = pv
|
||||
array2d_append(mut ctx.phi_vals, blk_id, phi_val)
|
||||
}
|
||||
}
|
||||
|
||||
// Rename: walk the dominator tree, threading reaching definitions.
|
||||
if func.blocks.len > 0 {
|
||||
rename_blocks(mut m, func.blocks[0], mut ctx, dom, cfg)
|
||||
}
|
||||
|
||||
// Reset per-function state.
|
||||
for alloc_id in promotable {
|
||||
ctx.defs[alloc_id] = []
|
||||
ctx.uses[alloc_id] = []
|
||||
ctx.stacks[alloc_id] = []
|
||||
ctx.is_promotable[alloc_id] = false
|
||||
}
|
||||
for pb in ctx.phi_blocks {
|
||||
ctx.phi_placements[pb] = []
|
||||
ctx.phi_vals[pb] = []
|
||||
}
|
||||
ctx.phi_blocks = []
|
||||
for d in df_blocks {
|
||||
df[d] = []
|
||||
}
|
||||
df_blocks = []
|
||||
}
|
||||
}
|
||||
|
||||
// is_promotable returns true if every use of the alloca is a direct load/store,
|
||||
// i.e. the pointer never escapes. Array- and pointer-backed slots stay in memory.
|
||||
fn is_promotable(m &ssa.Module, alloc_id int) bool {
|
||||
if alloc_id > 0 && alloc_id < m.values.len {
|
||||
alloc_typ_id := m.values[alloc_id].typ
|
||||
if alloc_typ_id > 0 && alloc_typ_id < m.type_store.types.len {
|
||||
alloc_typ := m.type_store.types[alloc_typ_id]
|
||||
if alloc_typ.kind == .ptr_t {
|
||||
elem_typ_id := alloc_typ.elem_type
|
||||
if elem_typ_id > 0 && elem_typ_id < m.type_store.types.len {
|
||||
elem_typ := m.type_store.types[elem_typ_id]
|
||||
// Keep pointer-, array- and aggregate-backed slots in memory.
|
||||
// Promoting them to SSA values would require the backend to
|
||||
// thread by-value aggregates through phi copies, which the v3
|
||||
// arm64 lowering does not yet handle reliably.
|
||||
if elem_typ.kind in [.ptr_t, .array_t, .struct_t] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for u in m.values[alloc_id].uses {
|
||||
if u >= m.values.len {
|
||||
continue
|
||||
}
|
||||
user := m.values[u]
|
||||
if user.kind != .instruction {
|
||||
return false
|
||||
}
|
||||
instr := m.instrs[user.index]
|
||||
match instr.op {
|
||||
.load {
|
||||
if instr.operands.len == 0 || instr.operands[0] != alloc_id {
|
||||
return false
|
||||
}
|
||||
}
|
||||
.store {
|
||||
if instr.operands.len < 2 || instr.operands[1] != alloc_id {
|
||||
return false
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fn compute_dominance_frontier_flat(m &ssa.Module, func_idx int, dom &DomInfo, cfg &CfgData, mut df [][]int, mut df_blocks []int) {
|
||||
func := m.funcs[func_idx]
|
||||
for blk_id in func.blocks {
|
||||
if blk_id < 0 || blk_id >= m.blocks.len {
|
||||
continue
|
||||
}
|
||||
num_preds := cfg.preds[blk_id].len
|
||||
if num_preds >= 2 {
|
||||
for pi in 0 .. num_preds {
|
||||
mut runner := cfg.preds[blk_id][pi]
|
||||
idom := dom.idom[blk_id]
|
||||
for runner != -1 && runner != idom {
|
||||
if runner < 0 || runner >= m.blocks.len {
|
||||
break
|
||||
}
|
||||
if blk_id !in df[runner] {
|
||||
array2d_append(mut df, runner, blk_id)
|
||||
if runner !in df_blocks {
|
||||
df_blocks << runner
|
||||
}
|
||||
}
|
||||
if runner == dom.idom[runner] {
|
||||
break
|
||||
}
|
||||
runner = dom.idom[runner]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RenameFrame {
|
||||
mut:
|
||||
blk_id int
|
||||
child_idx int
|
||||
pushed_allocs []int
|
||||
processed bool
|
||||
}
|
||||
|
||||
fn rename_blocks(mut m ssa.Module, root_blk int, mut ctx Mem2RegCtx, dom DomInfo, cfg &CfgData) {
|
||||
mut work := []RenameFrame{}
|
||||
work << RenameFrame{
|
||||
blk_id: root_blk
|
||||
}
|
||||
mut visited := []bool{len: m.blocks.len}
|
||||
|
||||
for work.len > 0 {
|
||||
fi := work.len - 1
|
||||
blk_id := work[fi].blk_id
|
||||
|
||||
if !work[fi].processed {
|
||||
if blk_id >= 0 && blk_id < visited.len && visited[blk_id] {
|
||||
work.pop()
|
||||
continue
|
||||
}
|
||||
if blk_id >= 0 && blk_id < visited.len {
|
||||
visited[blk_id] = true
|
||||
}
|
||||
mut frame2 := work[fi]
|
||||
frame2.processed = true
|
||||
work[fi] = frame2
|
||||
|
||||
// 1. Push phis defined in this block.
|
||||
if blk_id < ctx.phi_placements.len && ctx.phi_placements[blk_id].len > 0 {
|
||||
n_phi := ctx.phi_placements[blk_id].len
|
||||
for pai in 0 .. n_phi {
|
||||
alloc_id := ctx.phi_placements[blk_id][pai]
|
||||
if pai < ctx.phi_vals[blk_id].len {
|
||||
phi_val_id := ctx.phi_vals[blk_id][pai]
|
||||
mut new_stack := ctx.stacks[alloc_id].clone()
|
||||
new_stack << phi_val_id
|
||||
ctx.stacks[alloc_id] = new_stack
|
||||
mut wf := work[fi]
|
||||
wf.pushed_allocs << alloc_id
|
||||
work[fi] = wf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Process instructions: rewrite loads, record store values, nop them.
|
||||
mut instrs_to_nop := []int{}
|
||||
blk2 := m.blocks[blk_id]
|
||||
for val_id in blk2.instrs {
|
||||
val := m.values[val_id]
|
||||
if val.kind != .instruction {
|
||||
continue
|
||||
}
|
||||
instr := m.instrs[val.index]
|
||||
if instr.op == .store {
|
||||
ptr := instr.operands[1]
|
||||
if ptr < ctx.is_promotable.len && ctx.is_promotable[ptr] {
|
||||
mut new_stack := ctx.stacks[ptr].clone()
|
||||
new_stack << instr.operands[0]
|
||||
ctx.stacks[ptr] = new_stack
|
||||
mut wf := work[fi]
|
||||
wf.pushed_allocs << ptr
|
||||
work[fi] = wf
|
||||
instrs_to_nop << val_id
|
||||
}
|
||||
} else if instr.op == .load {
|
||||
ptr := instr.operands[0]
|
||||
if ptr < ctx.is_promotable.len && ctx.is_promotable[ptr] {
|
||||
stack := ctx.stacks[ptr]
|
||||
mut repl := 0
|
||||
if stack.len > 0 {
|
||||
repl = stack.last()
|
||||
} else {
|
||||
repl = m.get_or_add_const(val.typ, 'undef')
|
||||
}
|
||||
m.replace_uses(val_id, repl)
|
||||
instrs_to_nop << val_id
|
||||
}
|
||||
} else if instr.op == .alloca {
|
||||
if val_id < ctx.is_promotable.len && ctx.is_promotable[val_id] {
|
||||
instrs_to_nop << val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
for vid in instrs_to_nop {
|
||||
vid_val := m.values[vid]
|
||||
mut nop_instr := m.instrs[vid_val.index]
|
||||
nop_instr.op = .bitcast
|
||||
nop_instr.operands = []
|
||||
m.instrs[vid_val.index] = nop_instr
|
||||
}
|
||||
|
||||
// 3. Fill phi operands in CFG successors (raw block id as predecessor).
|
||||
for succ_id in cfg.succs[blk_id] {
|
||||
if succ_id < ctx.phi_placements.len && ctx.phi_placements[succ_id].len > 0 {
|
||||
n_succ_phi := ctx.phi_placements[succ_id].len
|
||||
for spai in 0 .. n_succ_phi {
|
||||
alloc_id := ctx.phi_placements[succ_id][spai]
|
||||
if spai < ctx.phi_vals[succ_id].len {
|
||||
vid := ctx.phi_vals[succ_id][spai]
|
||||
phi_v := m.values[vid]
|
||||
phi_val := if ctx.stacks[alloc_id].len > 0 {
|
||||
ctx.stacks[alloc_id].last()
|
||||
} else {
|
||||
alloc_v := m.values[alloc_id]
|
||||
alloc_typ := m.type_store.types[alloc_v.typ]
|
||||
m.get_or_add_const(alloc_typ.elem_type, 'undef')
|
||||
}
|
||||
m.append_phi_operands(phi_v.index, phi_val, blk_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Recurse into dominator-tree children.
|
||||
n_children := dom.dom_tree[blk_id].len
|
||||
child_idx := work[fi].child_idx
|
||||
if child_idx < n_children {
|
||||
child := dom.dom_tree[blk_id][child_idx]
|
||||
mut frame := work[fi]
|
||||
frame.child_idx++
|
||||
work[fi] = frame
|
||||
work << RenameFrame{
|
||||
blk_id: child
|
||||
}
|
||||
} else {
|
||||
// 5. Pop reaching definitions pushed in this block.
|
||||
pushed := work[fi].pushed_allocs.clone()
|
||||
work.pop()
|
||||
for i := pushed.len - 1; i >= 0; i-- {
|
||||
mut s := ctx.stacks[pushed[i]].clone()
|
||||
s.pop()
|
||||
ctx.stacks[pushed[i]] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user