Files
agila_open_tool/tools/v/vlib/v3/ssa/optimize/optimize.v
T

146 lines
4.6 KiB
V

module optimize
import os
import v3.ssa
pub struct OptimizeOptions {
pub:
mem2reg bool // promote scalar allocas to SSA values + phi nodes
eliminate_phis bool // lower phis to `assign` copies (for backends without phi)
verify_each_pass bool // run the structured verifier after every structural pass
strict_verify bool // treat historically-noncritical verifier findings as fatal
}
// optimize runs the default, backend-safe optimization pipeline. Structural SSA
// construction (mem2reg / phi elimination) is opt-in via the environment so the
// proven arm64 lowering path is unchanged unless explicitly requested:
// V3_MEM2REG=1 enable alloca promotion + phi insertion
// V3_PHI_ELIM=1 additionally lower phis to assign copies
// V3_VERIFY=1 structured verify after each pass (V3_VERIFY_STRICT=1 = fatal)
pub fn optimize(mut m ssa.Module) {
mem2reg := os.getenv('V3_MEM2REG') != ''
optimize_with_options(mut m, OptimizeOptions{
mem2reg: mem2reg
// The arm64 backend's native phi resolution is incomplete (no copies on
// conditional edges, no parallel-copy sequencing), so phis introduced by
// mem2reg must be lowered to assign copies for correct codegen.
eliminate_phis: mem2reg || os.getenv('V3_PHI_ELIM') != ''
verify_each_pass: os.getenv('V3_VERIFY') != ''
strict_verify: os.getenv('V3_VERIFY_STRICT') != ''
})
}
pub fn optimize_with_options(mut m ssa.Module, opts OptimizeOptions) {
rebuild_use_lists(mut m)
build_cfg(mut m)
verify_ssa(m, 'initial normalization')
verify_pipeline_checkpoint(m, opts, 'input')
constant_fold(mut m)
rebuild_use_lists(mut m)
branch_fold(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
// Branch folding can drop a phi block's predecessor edge; keep phis consistent.
prune_phi_operands(mut m)
rebuild_use_lists(mut m)
if opts.mem2reg {
// Normalize the CFG *before* SSA construction so that every phi
// predecessor (a reachable CFG predecessor) stays present in the
// function afterwards. Block-removing passes must not run once phis
// exist, or their predecessor operands would dangle.
dead_code_elimination(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
remove_unreachable_blocks(mut m)
merge_blocks(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
// Structural SSA construction: dominators -> mem2reg -> simplify phis.
cfg := cfg_data_from_module(m)
dom := compute_dominators(mut m, &cfg)
verify_pipeline_checkpoint(m, opts, 'compute_dominators')
promote_memory_to_register(mut m, dom, &cfg)
rebuild_use_lists(mut m)
verify_pipeline_checkpoint(m, opts, 'mem2reg')
simplify_phi_nodes(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
verify_pipeline_checkpoint(m, opts, 'simplify_phi')
}
// Phi elimination lowers phis to assign copies for backends that cannot
// resolve phis natively. Runs whenever requested (input phis from the builder
// or a worker merge may exist even without mem2reg).
if opts.eliminate_phis {
eliminate_phi_nodes(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
verify_pipeline_checkpoint(m, opts, 'eliminate_phi')
}
dead_code_elimination(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
remove_unreachable_blocks(mut m)
if !opts.mem2reg {
// Without SSA construction, block-merging is phi-aware and safe to run.
merge_blocks(mut m)
}
rebuild_use_lists(mut m)
build_cfg(mut m)
// Final phi-consistency pass: any phi still present must match the final CFG.
prune_phi_operands(mut m)
rebuild_use_lists(mut m)
build_cfg(mut m)
verify_ssa(m, 'optimization')
verify_pipeline_checkpoint(m, opts, 'final')
}
fn verify_pipeline_checkpoint(m &ssa.Module, opts OptimizeOptions, pass_name string) {
if opts.verify_each_pass || opts.strict_verify {
verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{
allow_noncritical: !opts.strict_verify
})
}
}
fn rebuild_use_lists(mut m ssa.Module) {
for vi in 0 .. m.values.len {
mut val := m.values[vi]
val.uses = []
m.values[vi] = val
}
for fi in 0 .. m.funcs.len {
for blk_id in m.funcs[fi].blocks {
if blk_id < 0 || blk_id >= m.blocks.len {
continue
}
for val_id in m.blocks[blk_id].instrs {
if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction {
continue
}
instr_idx := m.values[val_id].index
if instr_idx < 0 || instr_idx >= m.instrs.len {
continue
}
instr := m.instrs[instr_idx]
for op_id in instr.value_operands() {
if op_id >= 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
mut op_val := m.values[op_id]
op_val.uses << val_id
m.values[op_id] = op_val
}
}
}
}
}
}