vxx2 / vlib / v3 / ssa / optimize / verify.v
807 lines · 780 sloc · 21.56 KB · 6c4d26f1f98c5464b012a375667ab345e462f7e0
Raw
1module optimize
2
3import v3.ssa
4
5// VerifyError is a structured SSA verification failure. Unlike verify_ssa()
6// (which panics on the first problem), the structured API collects every error so
7// callers can decide which are fatal.
8pub struct VerifyError {
9pub:
10 msg string
11 func_id int = -1
12 func_name string
13 block_id int = -1
14 val_id int = -1
15}
16
17// str returns the string form for VerifyError.
18pub fn (e VerifyError) str() string {
19 mut s := 'SSA verify error'
20 if e.func_id >= 0 {
21 s += ' in func ${e.func_id}'
22 if e.func_name != '' {
23 s += ' (${e.func_name})'
24 }
25 }
26 if e.block_id >= 0 {
27 s += ' block ${e.block_id}'
28 }
29 if e.val_id >= 0 {
30 s += ' value ${e.val_id}'
31 }
32 return '${s}: ${e.msg}'
33}
34
35// VerifyPanicOptions controls verify panic options behavior used by optimize.
36pub struct VerifyPanicOptions {
37pub:
38 allow_noncritical bool
39}
40
41// verify performs comprehensive validation of SSA invariants and returns every
42// error found (empty slice == valid). v3 stores branch/phi/switch targets as raw
43// block ids, so block operands are validated as block ids, not block values.
44pub fn verify(m &ssa.Module) []VerifyError {
45 mut errors := []VerifyError{}
46 for fi in 0 .. m.funcs.len {
47 errors << verify_function(m, m.funcs[fi])
48 }
49 errors << verify_use_def_chains(m)
50 return errors
51}
52
53// verify_and_panic runs verification and panics on critical errors only.
54pub fn verify_and_panic(m &ssa.Module, pass_name string) {
55 verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{
56 allow_noncritical: true
57 })
58}
59
60// verify_and_panic_with_options runs verification with an explicit warning policy.
61// Declaration-only functions (C externs / prototypes) are always accepted.
62pub fn verify_and_panic_with_options(m &ssa.Module, pass_name string, opts VerifyPanicOptions) {
63 errors := verify(m)
64 if errors.len == 0 {
65 return
66 }
67 mut msg := 'SSA verification failed after ${pass_name}:\n'
68 mut critical := []VerifyError{}
69 mut warnings := 0
70 for err in errors {
71 if is_accepted_declaration_verify_error(m, err) {
72 continue
73 }
74 if opts.allow_noncritical && is_legacy_noncritical_verify_error(err) {
75 warnings++
76 } else {
77 critical << err
78 }
79 }
80 for err in critical {
81 msg += ' ${err.msg}\n'
82 }
83 if warnings > 0 {
84 msg += ' (${warnings} non-critical warnings suppressed)\n'
85 }
86 if critical.len > 0 {
87 panic(msg)
88 }
89}
90
91// is_accepted_declaration_verify_error supports is_accepted_declaration_verify_error handling.
92fn is_accepted_declaration_verify_error(m &ssa.Module, err VerifyError) bool {
93 if err.msg == 'function has no blocks' {
94 if err.func_id < 0 || err.func_id >= m.funcs.len {
95 return false
96 }
97 func := m.funcs[err.func_id]
98 return func.is_c_extern || func.is_prototype
99 }
100 return false
101}
102
103// is_legacy_noncritical_verify_error reports is_legacy_noncritical_verify_error logic in optimize.
104fn is_legacy_noncritical_verify_error(err VerifyError) bool {
105 // Dominance / use-def / phi mismatches are commonly transient between passes.
106 if err.msg.contains('does not dominate') || err.msg.contains('uses list')
107 || err.msg.contains('phi') || err.msg.contains('block mismatch') {
108 return true
109 }
110 // Structural-normalization artifacts emitted by the builder before the
111 // optimizer cleans them up (empty/unreachable blocks, dead code that follows
112 // a terminator within the same block). These are tolerated by the lenient
113 // verify_ssa() and removed by remove_unreachable_blocks / DCE.
114 if err.msg.contains('has no instructions') || err.msg.contains('not at end of block')
115 || err.msg.contains('multiple terminators')
116 || err.msg.contains('does not end with terminator') {
117 return true
118 }
119 // Type-precision findings. The v3 builder does not maintain strict SSA typing
120 // on every load/store/binary operand (e.g. aggregates loaded by value), and
121 // the backends tolerate it, so these are advisory rather than fatal.
122 return err.msg.contains('should be pointer type') || err.msg.contains('mismatched types')
123 || err.msg.contains('result type should be pointer')
124}
125
126// verify_function validates verify function state for optimize.
127fn verify_function(m &ssa.Module, func ssa.Function) []VerifyError {
128 mut errors := []VerifyError{}
129 if func.blocks.len == 0 {
130 errors << VerifyError{
131 msg: 'function has no blocks'
132 func_id: func.id
133 }
134 return errors
135 }
136 for blk_id in func.blocks {
137 if blk_id < 0 || blk_id >= m.blocks.len {
138 errors << VerifyError{
139 msg: 'invalid block id ${blk_id}'
140 func_id: func.id
141 }
142 continue
143 }
144 errors << verify_block(m, func, blk_id)
145 }
146 errors << verify_dominance(m, func)
147 return errors
148}
149
150// verify_block validates verify block state for optimize.
151fn verify_block(m &ssa.Module, func ssa.Function, blk_id int) []VerifyError {
152 mut errors := []VerifyError{}
153 blk := m.blocks[blk_id]
154 if blk.parent != func.id {
155 errors << VerifyError{
156 msg: 'block parent mismatch: expected ${func.id}, got ${blk.parent}'
157 func_id: func.id
158 block_id: blk_id
159 }
160 }
161 if blk.instrs.len == 0 {
162 errors << VerifyError{
163 msg: 'block has no instructions (missing terminator)'
164 func_id: func.id
165 block_id: blk_id
166 }
167 return errors
168 }
169
170 mut seen_terminator := false
171 mut seen_non_phi := false
172 for i, val_id in blk.instrs {
173 if val_id < 0 || val_id >= m.values.len {
174 errors << VerifyError{
175 msg: 'invalid value id ${val_id} in instruction list'
176 func_id: func.id
177 block_id: blk_id
178 }
179 continue
180 }
181 val := m.values[val_id]
182 if val.kind != .instruction {
183 continue
184 }
185 if val.index < 0 || val.index >= m.instrs.len {
186 errors << VerifyError{
187 msg: 'value ${val_id} has invalid instruction index ${val.index}'
188 func_id: func.id
189 block_id: blk_id
190 val_id: val_id
191 }
192 continue
193 }
194 instr := m.instrs[val.index]
195 if instr.block != blk_id {
196 errors << VerifyError{
197 msg: 'instruction block mismatch: expected ${blk_id}, got ${instr.block}'
198 func_id: func.id
199 block_id: blk_id
200 val_id: val_id
201 }
202 }
203 if instr.op == .phi {
204 if seen_non_phi {
205 errors << VerifyError{
206 msg: 'phi node after non-phi instruction'
207 func_id: func.id
208 block_id: blk_id
209 val_id: val_id
210 }
211 }
212 } else {
213 seen_non_phi = true
214 }
215 is_terminator := instr.op in [.ret, .br, .jmp, .switch_, .unreachable]
216 if is_terminator {
217 if seen_terminator {
218 errors << VerifyError{
219 msg: 'multiple terminators in block'
220 func_id: func.id
221 func_name: func.name
222 block_id: blk_id
223 val_id: val_id
224 }
225 }
226 if i != blk.instrs.len - 1 {
227 errors << VerifyError{
228 msg: 'terminator not at end of block'
229 func_id: func.id
230 func_name: func.name
231 block_id: blk_id
232 val_id: val_id
233 }
234 }
235 seen_terminator = true
236 }
237 errors << verify_instruction(m, func.id, blk_id, val_id, instr)
238 }
239 if !seen_terminator {
240 errors << VerifyError{
241 msg: 'block does not end with terminator'
242 func_id: func.id
243 block_id: blk_id
244 }
245 }
246 errors << verify_cfg_consistency(m, func.id, blk_id)
247 return errors
248}
249
250// verify_instruction validates verify instruction state for optimize.
251fn verify_instruction(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
252 mut errors := []VerifyError{}
253 // Only value operands are validated as value ids; block ids (branch/phi/switch
254 // targets) are validated by the op-specific checks below.
255 for i, op_id in instr.value_operands() {
256 if op_id < 0 || op_id >= m.values.len {
257 errors << VerifyError{
258 msg: 'operand ${i} has invalid value id ${op_id}'
259 func_id: func_id
260 block_id: blk_id
261 val_id: val_id
262 }
263 }
264 }
265 match instr.op {
266 .add, .sub, .mul, .sdiv, .udiv, .srem, .urem {
267 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
268 }
269 .fadd, .fsub, .fmul, .fdiv, .frem {
270 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
271 }
272 .shl, .lshr, .ashr, .and_, .or_, .xor {
273 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
274 }
275 .lt, .gt, .le, .ge, .eq, .ne, .ult, .ugt, .ule, .uge {
276 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
277 }
278 .load {
279 errors << verify_load(m, func_id, blk_id, val_id, instr)
280 }
281 .store {
282 errors << verify_store(m, func_id, blk_id, val_id, instr)
283 }
284 .alloca, .heap_alloc {
285 if instr.typ > 0 && instr.typ < m.type_store.types.len {
286 typ := m.type_store.types[instr.typ]
287 if typ.kind != .ptr_t {
288 errors << VerifyError{
289 msg: '${instr.op} result type should be pointer, got ${typ.kind}'
290 func_id: func_id
291 block_id: blk_id
292 val_id: val_id
293 }
294 }
295 }
296 }
297 .phi {
298 errors << verify_phi(m, func_id, blk_id, val_id, instr)
299 }
300 .br {
301 errors << verify_branch(m, func_id, blk_id, val_id, instr)
302 }
303 .jmp {
304 errors << verify_jump(m, func_id, blk_id, val_id, instr)
305 }
306 .switch_ {
307 errors << verify_switch(m, func_id, blk_id, val_id, instr)
308 }
309 .ret {
310 if instr.operands.len > 1 {
311 errors << VerifyError{
312 msg: 'ret has ${instr.operands.len} operands, expected 0 or 1'
313 func_id: func_id
314 block_id: blk_id
315 val_id: val_id
316 }
317 }
318 }
319 .call, .call_indirect, .call_sret, .go_call, .spawn_call {
320 if instr.operands.len == 0 {
321 errors << VerifyError{
322 msg: '${instr.op} has no operands'
323 func_id: func_id
324 block_id: blk_id
325 val_id: val_id
326 }
327 }
328 }
329 .select {
330 if instr.operands.len != 3 {
331 errors << VerifyError{
332 msg: 'select has ${instr.operands.len} operands, expected 3'
333 func_id: func_id
334 block_id: blk_id
335 val_id: val_id
336 }
337 }
338 }
339 .assign {
340 if instr.operands.len != 2 {
341 errors << VerifyError{
342 msg: 'assign has ${instr.operands.len} operands, expected 2'
343 func_id: func_id
344 block_id: blk_id
345 val_id: val_id
346 }
347 }
348 }
349 else {}
350 }
351
352 return errors
353}
354
355// verify_binary_op validates verify binary op state for optimize.
356fn verify_binary_op(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
357 mut errors := []VerifyError{}
358 if instr.operands.len != 2 {
359 errors << VerifyError{
360 msg: 'binary op has ${instr.operands.len} operands, expected 2'
361 func_id: func_id
362 block_id: blk_id
363 val_id: val_id
364 }
365 return errors
366 }
367 op0 := instr.operands[0]
368 op1 := instr.operands[1]
369 if op0 < m.values.len && op1 < m.values.len {
370 t0 := m.values[op0].typ
371 t1 := m.values[op1].typ
372 if t0 != t1 && t0 != 0 && t1 != 0 {
373 if t0 < m.type_store.types.len && t1 < m.type_store.types.len {
374 k0 := m.type_store.types[t0].kind
375 k1 := m.type_store.types[t1].kind
376 // Allow common mixed-kind binary patterns (different widths, ptr
377 // arithmetic, struct/nil comparisons, int/float coercions).
378 if k0 == .int_t && k1 == .int_t {
379 return errors
380 }
381 if k0 == .float_t && k1 == .float_t {
382 return errors
383 }
384 if (k0 == .int_t && k1 == .float_t) || (k0 == .float_t && k1 == .int_t) {
385 return errors
386 }
387 if (k0 == .ptr_t && k1 == .int_t) || (k0 == .int_t && k1 == .ptr_t) {
388 return errors
389 }
390 if k0 == .ptr_t && k1 == .ptr_t {
391 return errors
392 }
393 if (k0 == .ptr_t && k1 == .struct_t) || (k0 == .struct_t && k1 == .ptr_t) {
394 return errors
395 }
396 if k0 == .struct_t && k1 == .struct_t {
397 return errors
398 }
399 if (k0 == .struct_t && k1 == .int_t) || (k0 == .int_t && k1 == .struct_t) {
400 return errors
401 }
402 errors << VerifyError{
403 msg: 'binary op operands have mismatched types: ${t0} (${k0}) vs ${t1} (${k1})'
404 func_id: func_id
405 block_id: blk_id
406 val_id: val_id
407 }
408 }
409 }
410 }
411 return errors
412}
413
414// verify_load validates verify load state for optimize.
415fn verify_load(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
416 mut errors := []VerifyError{}
417 if instr.operands.len != 1 {
418 errors << VerifyError{
419 msg: 'load has ${instr.operands.len} operands, expected 1 (pointer)'
420 func_id: func_id
421 block_id: blk_id
422 val_id: val_id
423 }
424 return errors
425 }
426 ptr_id := instr.operands[0]
427 if ptr_id < m.values.len {
428 ptr_typ_id := m.values[ptr_id].typ
429 if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
430 if m.type_store.types[ptr_typ_id].kind != .ptr_t {
431 errors << VerifyError{
432 msg: 'load operand should be pointer type, got ${m.type_store.types[ptr_typ_id].kind}'
433 func_id: func_id
434 block_id: blk_id
435 val_id: val_id
436 }
437 }
438 }
439 }
440 return errors
441}
442
443// verify_store validates verify store state for optimize.
444fn verify_store(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
445 mut errors := []VerifyError{}
446 if instr.operands.len != 2 {
447 errors << VerifyError{
448 msg: 'store has ${instr.operands.len} operands, expected 2 (value, pointer)'
449 func_id: func_id
450 block_id: blk_id
451 val_id: val_id
452 }
453 return errors
454 }
455 ptr_id := instr.operands[1]
456 if ptr_id < m.values.len {
457 ptr_typ_id := m.values[ptr_id].typ
458 if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
459 if m.type_store.types[ptr_typ_id].kind != .ptr_t {
460 errors << VerifyError{
461 msg: 'store destination should be pointer type, got ${m.type_store.types[ptr_typ_id].kind}'
462 func_id: func_id
463 block_id: blk_id
464 val_id: val_id
465 }
466 }
467 }
468 }
469 return errors
470}
471
472// verify_phi validates verify phi state for optimize.
473fn verify_phi(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
474 mut errors := []VerifyError{}
475 if instr.operands.len % 2 != 0 {
476 errors << VerifyError{
477 msg: 'phi has odd number of operands (${instr.operands.len}), expected (value, block) pairs'
478 func_id: func_id
479 block_id: blk_id
480 val_id: val_id
481 }
482 return errors
483 }
484 if instr.operands.len == 0 {
485 errors << VerifyError{
486 msg: 'phi has no operands'
487 func_id: func_id
488 block_id: blk_id
489 val_id: val_id
490 }
491 return errors
492 }
493 blk := m.blocks[blk_id]
494 for i := 0; i < instr.operands.len; i += 2 {
495 val_in := instr.operands[i]
496 pred_blk_id := int(instr.operands[i + 1])
497 if val_in < 0 || val_in >= m.values.len {
498 errors << VerifyError{
499 msg: 'phi operand ${i} has invalid value id ${val_in}'
500 func_id: func_id
501 block_id: blk_id
502 val_id: val_id
503 }
504 }
505 if pred_blk_id < 0 || pred_blk_id >= m.blocks.len {
506 errors << VerifyError{
507 msg: 'phi operand ${i + 1} has invalid block id ${pred_blk_id}'
508 func_id: func_id
509 block_id: blk_id
510 val_id: val_id
511 }
512 } else if blk.preds.len > 0 && pred_blk_id !in blk.preds {
513 errors << VerifyError{
514 msg: 'phi references block ${pred_blk_id} which is not a predecessor'
515 func_id: func_id
516 block_id: blk_id
517 val_id: val_id
518 }
519 }
520 }
521 if blk.preds.len > 0 {
522 actual_pairs := instr.operands.len / 2
523 if actual_pairs != blk.preds.len {
524 errors << VerifyError{
525 msg: 'phi has ${actual_pairs} operand pairs but block has ${blk.preds.len} predecessors'
526 func_id: func_id
527 block_id: blk_id
528 val_id: val_id
529 }
530 }
531 }
532 return errors
533}
534
535// verify_branch validates verify branch state for optimize.
536fn verify_branch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
537 mut errors := []VerifyError{}
538 if instr.operands.len != 3 {
539 errors << VerifyError{
540 msg: 'br has ${instr.operands.len} operands, expected 3 (cond, true_blk, false_blk)'
541 func_id: func_id
542 block_id: blk_id
543 val_id: val_id
544 }
545 return errors
546 }
547 for i in [1, 2] {
548 b := int(instr.operands[i])
549 if b < 0 || b >= m.blocks.len {
550 errors << VerifyError{
551 msg: 'br operand ${i} has invalid block id ${b}'
552 func_id: func_id
553 block_id: blk_id
554 val_id: val_id
555 }
556 }
557 }
558 return errors
559}
560
561// verify_jump validates verify jump state for optimize.
562fn verify_jump(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
563 mut errors := []VerifyError{}
564 if instr.operands.len != 1 {
565 errors << VerifyError{
566 msg: 'jmp has ${instr.operands.len} operands, expected 1 (target_blk)'
567 func_id: func_id
568 block_id: blk_id
569 val_id: val_id
570 }
571 return errors
572 }
573 b := int(instr.operands[0])
574 if b < 0 || b >= m.blocks.len {
575 errors << VerifyError{
576 msg: 'jmp operand has invalid block id ${b}'
577 func_id: func_id
578 block_id: blk_id
579 val_id: val_id
580 }
581 }
582 return errors
583}
584
585// verify_switch validates verify switch state for optimize.
586fn verify_switch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
587 mut errors := []VerifyError{}
588 if instr.operands.len < 2 {
589 errors << VerifyError{
590 msg: 'switch has ${instr.operands.len} operands, expected at least 2 (cond, default)'
591 func_id: func_id
592 block_id: blk_id
593 val_id: val_id
594 }
595 return errors
596 }
597 def_blk := int(instr.operands[1])
598 if def_blk < 0 || def_blk >= m.blocks.len {
599 errors << VerifyError{
600 msg: 'switch default has invalid block id ${def_blk}'
601 func_id: func_id
602 block_id: blk_id
603 val_id: val_id
604 }
605 }
606 remaining := instr.operands.len - 2
607 if remaining % 2 != 0 {
608 errors << VerifyError{
609 msg: 'switch has odd number of case operands (${remaining})'
610 func_id: func_id
611 block_id: blk_id
612 val_id: val_id
613 }
614 }
615 for i := 3; i < instr.operands.len; i += 2 {
616 b := int(instr.operands[i])
617 if b < 0 || b >= m.blocks.len {
618 errors << VerifyError{
619 msg: 'switch case has invalid block id ${b}'
620 func_id: func_id
621 block_id: blk_id
622 val_id: val_id
623 }
624 }
625 }
626 return errors
627}
628
629// verify_cfg_consistency validates verify cfg consistency state for optimize.
630fn verify_cfg_consistency(m &ssa.Module, func_id int, blk_id int) []VerifyError {
631 mut errors := []VerifyError{}
632 blk := m.blocks[blk_id]
633 if blk.succs.len == 0 && blk.preds.len == 0 {
634 return errors
635 }
636 for succ_id in blk.succs {
637 if succ_id < 0 || succ_id >= m.blocks.len {
638 errors << VerifyError{
639 msg: 'invalid successor block id ${succ_id}'
640 func_id: func_id
641 block_id: blk_id
642 }
643 continue
644 }
645 if blk_id !in m.blocks[succ_id].preds {
646 errors << VerifyError{
647 msg: 'block ${blk_id} has successor ${succ_id} but is not in its predecessors'
648 func_id: func_id
649 block_id: blk_id
650 }
651 }
652 }
653 for pred_id in blk.preds {
654 if pred_id < 0 || pred_id >= m.blocks.len {
655 errors << VerifyError{
656 msg: 'invalid predecessor block id ${pred_id}'
657 func_id: func_id
658 block_id: blk_id
659 }
660 continue
661 }
662 if blk_id !in m.blocks[pred_id].succs {
663 errors << VerifyError{
664 msg: 'block ${blk_id} has predecessor ${pred_id} but is not in its successors'
665 func_id: func_id
666 block_id: blk_id
667 }
668 }
669 }
670 return errors
671}
672
673// verify_dominance validates verify dominance state for optimize.
674fn verify_dominance(m &ssa.Module, func ssa.Function) []VerifyError {
675 mut errors := []VerifyError{}
676 mut def_block := map[int]int{}
677 for param_id in func.params {
678 if func.blocks.len > 0 {
679 def_block[param_id] = func.blocks[0]
680 }
681 }
682 for blk_id in func.blocks {
683 if blk_id >= m.blocks.len {
684 continue
685 }
686 for val_id in m.blocks[blk_id].instrs {
687 def_block[val_id] = blk_id
688 }
689 }
690
691 entry_block := func.blocks[0]
692 dominators_computed := func.blocks.len == 1 || m.blocks[entry_block].dom_tree.len > 0
693 if !dominators_computed {
694 return errors
695 }
696
697 for blk_id in func.blocks {
698 if blk_id >= m.blocks.len {
699 continue
700 }
701 for val_id in m.blocks[blk_id].instrs {
702 if val_id >= m.values.len || m.values[val_id].kind != .instruction {
703 continue
704 }
705 if m.values[val_id].index >= m.instrs.len {
706 continue
707 }
708 instr := m.instrs[m.values[val_id].index]
709 // Phi operands legitimately come from predecessors, not dominators.
710 if instr.op == .phi {
711 continue
712 }
713 for i, op_id in instr.value_operands() {
714 if op_id >= m.values.len {
715 continue
716 }
717 op_val := m.values[op_id]
718 if op_val.kind in [.basic_block, .constant, .global, .string_literal,
719 .c_string_literal, .func_ref, .argument] {
720 continue
721 }
722 if def_blk := def_block[op_id] {
723 if !dominates(m, func, def_blk, blk_id) {
724 errors << VerifyError{
725 msg: 'operand ${i} (value ${op_id}) defined in block ${def_blk} does not dominate use in block ${blk_id}'
726 func_id: func.id
727 func_name: func.name
728 block_id: blk_id
729 val_id: val_id
730 }
731 }
732 }
733 }
734 }
735 }
736 return errors
737}
738
739// dominates supports dominates handling for optimize.
740fn dominates(m &ssa.Module, _func ssa.Function, a int, b int) bool {
741 if a == b {
742 return true
743 }
744 mut curr := b
745 for {
746 if curr < 0 || curr >= m.blocks.len {
747 return false
748 }
749 idom := m.blocks[curr].idom
750 if idom < 0 {
751 return false
752 }
753 if idom == curr {
754 return a == curr
755 }
756 if idom == a {
757 return true
758 }
759 curr = idom
760 }
761 return false
762}
763
764// verify_use_def_chains validates verify use def chains state for optimize.
765fn verify_use_def_chains(m &ssa.Module) []VerifyError {
766 mut errors := []VerifyError{}
767 mut expected_uses := map[int]map[int]bool{}
768 for fi in 0 .. m.funcs.len {
769 for blk_id in m.funcs[fi].blocks {
770 if blk_id >= m.blocks.len {
771 continue
772 }
773 for val_id in m.blocks[blk_id].instrs {
774 if val_id >= m.values.len || m.values[val_id].kind != .instruction {
775 continue
776 }
777 if m.values[val_id].index >= m.instrs.len {
778 continue
779 }
780 instr := m.instrs[m.values[val_id].index]
781 for op_id in instr.value_operands() {
782 if op_id >= 0 && op_id < m.values.len {
783 if op_id !in expected_uses {
784 expected_uses[op_id] = map[int]bool{}
785 }
786 expected_uses[op_id][val_id] = true
787 }
788 }
789 }
790 }
791 }
792 for val_id, users in expected_uses {
793 if val_id >= m.values.len {
794 continue
795 }
796 actual := m.values[val_id].uses
797 for user_id, _ in users {
798 if user_id !in actual {
799 errors << VerifyError{
800 msg: 'value ${val_id} is used by ${user_id} but ${user_id} is not in uses list'
801 val_id: val_id
802 }
803 }
804 }
805 }
806 return errors
807}
808