v4 / vlib / v3 / ssa / optimize / optimize.v
149 lines · 133 sloc · 4.88 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
1module optimize
2
3import os
4import v3.ssa
5
6// OptimizeOptions controls optimize options behavior used by optimize.
7pub struct OptimizeOptions {
8pub:
9 mem2reg bool // promote scalar allocas to SSA values + phi nodes
10 eliminate_phis bool // lower phis to `assign` copies (for backends without phi)
11 verify_each_pass bool // run the structured verifier after every structural pass
12 strict_verify bool // treat historically-noncritical verifier findings as fatal
13}
14
15// optimize runs the default, backend-safe optimization pipeline. Structural SSA
16// construction (mem2reg / phi elimination) is opt-in via the environment so the
17// proven arm64 lowering path is unchanged unless explicitly requested:
18// V3_MEM2REG=1 enable alloca promotion + phi insertion
19// V3_PHI_ELIM=1 additionally lower phis to assign copies
20// V3_VERIFY=1 structured verify after each pass (V3_VERIFY_STRICT=1 = fatal)
21pub fn optimize(mut m ssa.Module) {
22 mem2reg := os.getenv('V3_MEM2REG') != ''
23 optimize_with_options(mut m, OptimizeOptions{
24 mem2reg: mem2reg
25 // The arm64 backend's native phi resolution is incomplete (no copies on
26 // conditional edges, no parallel-copy sequencing), so phis introduced by
27 // mem2reg must be lowered to assign copies for correct codegen.
28 eliminate_phis: mem2reg || os.getenv('V3_PHI_ELIM') != ''
29 verify_each_pass: os.getenv('V3_VERIFY') != ''
30 strict_verify: os.getenv('V3_VERIFY_STRICT') != ''
31 })
32}
33
34// optimize_with_options supports optimize with options handling for optimize.
35pub fn optimize_with_options(mut m ssa.Module, opts OptimizeOptions) {
36 rebuild_use_lists(mut m)
37 build_cfg(mut m)
38 verify_ssa(m, 'initial normalization')
39 verify_pipeline_checkpoint(m, opts, 'input')
40
41 constant_fold(mut m)
42 rebuild_use_lists(mut m)
43
44 branch_fold(mut m)
45 rebuild_use_lists(mut m)
46 build_cfg(mut m)
47 // Branch folding can drop a phi block's predecessor edge; keep phis consistent.
48 prune_phi_operands(mut m)
49 rebuild_use_lists(mut m)
50
51 if opts.mem2reg {
52 // Normalize the CFG *before* SSA construction so that every phi
53 // predecessor (a reachable CFG predecessor) stays present in the
54 // function afterwards. Block-removing passes must not run once phis
55 // exist, or their predecessor operands would dangle.
56 dead_code_elimination(mut m)
57 rebuild_use_lists(mut m)
58 build_cfg(mut m)
59 remove_unreachable_blocks(mut m)
60 merge_blocks(mut m)
61 rebuild_use_lists(mut m)
62 build_cfg(mut m)
63
64 // Structural SSA construction: dominators -> mem2reg -> simplify phis.
65 cfg := cfg_data_from_module(m)
66 dom := compute_dominators(mut m, &cfg)
67 verify_pipeline_checkpoint(m, opts, 'compute_dominators')
68 promote_memory_to_register(mut m, dom, &cfg)
69 rebuild_use_lists(mut m)
70 verify_pipeline_checkpoint(m, opts, 'mem2reg')
71 simplify_phi_nodes(mut m)
72 rebuild_use_lists(mut m)
73 build_cfg(mut m)
74 verify_pipeline_checkpoint(m, opts, 'simplify_phi')
75 }
76
77 // Phi elimination lowers phis to assign copies for backends that cannot
78 // resolve phis natively. Runs whenever requested (input phis from the builder
79 // or a worker merge may exist even without mem2reg).
80 if opts.eliminate_phis {
81 eliminate_phi_nodes(mut m)
82 rebuild_use_lists(mut m)
83 build_cfg(mut m)
84 verify_pipeline_checkpoint(m, opts, 'eliminate_phi')
85 }
86
87 dead_code_elimination(mut m)
88 rebuild_use_lists(mut m)
89 build_cfg(mut m)
90
91 remove_unreachable_blocks(mut m)
92 if !opts.mem2reg {
93 // Without SSA construction, block-merging is phi-aware and safe to run.
94 merge_blocks(mut m)
95 }
96 rebuild_use_lists(mut m)
97 build_cfg(mut m)
98
99 // Final phi-consistency pass: any phi still present must match the final CFG.
100 prune_phi_operands(mut m)
101 rebuild_use_lists(mut m)
102 build_cfg(mut m)
103
104 verify_ssa(m, 'optimization')
105 verify_pipeline_checkpoint(m, opts, 'final')
106}
107
108// verify_pipeline_checkpoint validates verify pipeline checkpoint state for optimize.
109fn verify_pipeline_checkpoint(m &ssa.Module, opts OptimizeOptions, pass_name string) {
110 if opts.verify_each_pass || opts.strict_verify {
111 verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{
112 allow_noncritical: !opts.strict_verify
113 })
114 }
115}
116
117// rebuild_use_lists supports rebuild use lists handling for optimize.
118fn rebuild_use_lists(mut m ssa.Module) {
119 for vi in 0 .. m.values.len {
120 mut val := m.values[vi]
121 val.uses = []
122 m.values[vi] = val
123 }
124 for fi in 0 .. m.funcs.len {
125 for blk_id in m.funcs[fi].blocks {
126 if blk_id < 0 || blk_id >= m.blocks.len {
127 continue
128 }
129 for val_id in m.blocks[blk_id].instrs {
130 if val_id <= 0 || val_id >= m.values.len || m.values[val_id].kind != .instruction {
131 continue
132 }
133 instr_idx := m.values[val_id].index
134 if instr_idx < 0 || instr_idx >= m.instrs.len {
135 continue
136 }
137 instr := m.instrs[instr_idx]
138
139 for op_id in instr.value_operands() {
140 if op_id >= 0 && op_id < m.values.len && val_id !in m.values[op_id].uses {
141 mut op_val := m.values[op_id]
142 op_val.uses << val_id
143 m.values[op_id] = op_val
144 }
145 }
146 }
147 }
148 }
149}
150