v / vlib / v2 / ssa / optimize / verify.v
1053 lines · 949 sloc · 27.05 KB · 81a5657604ec6da99c25e26546870c6888d6fdde
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module optimize
6
7import v2.ssa
8
9// VerifyError represents an SSA verification failure
10struct VerifyError {
11 msg string
12 func_id int
13 func_name string
14 block_id int
15 val_id int
16}
17
18fn (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
35pub struct VerifyPanicOptions {
36pub:
37 allow_noncritical bool
38}
39
40// verify performs comprehensive validation of SSA invariants.
41// Returns a list of errors found (empty if valid).
42// Call this after optimization passes to catch bugs.
43pub fn verify(m &ssa.Module) []VerifyError {
44 mut errors := []VerifyError{}
45
46 // 1. Verify each function
47 for fi in 0 .. m.funcs.len {
48 errors << verify_function(m, &m.funcs[fi])
49 }
50
51 // 2. Verify global use-def chains
52 errors << verify_use_def_chains(m)
53
54 return errors
55}
56
57// verify_and_panic runs verification and panics on critical errors.
58// Historical non-critical verifier warnings remain suppressed by default.
59pub fn verify_and_panic(m &ssa.Module, pass_name string) {
60 verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{
61 allow_noncritical: true
62 })
63}
64
65// verify_and_panic_with_options runs verification with explicit warning policy.
66// Set allow_noncritical to false for strict verifier checkpoints.
67pub fn verify_and_panic_with_options(m &ssa.Module, pass_name string, opts VerifyPanicOptions) {
68 errors := verify(m)
69 if errors.len > 0 {
70 mut msg := 'SSA verification failed after ${pass_name}:\n'
71 // Filter out accepted declarations and optionally keep legacy warnings non-fatal:
72 // - Explicit prototypes with no blocks are declarations kept in the module
73 // - Dominance errors are often false positives in nested control flow
74 // - Use-def chain errors happen during optimization when uses aren't cleaned up
75 // - Phi errors are from mem2reg and don't affect codegen
76 // - Block mismatch errors happen during block merging/optimization
77 mut critical_errors := []VerifyError{}
78 mut warning_count := 0
79 for err in errors {
80 if is_accepted_declaration_verify_error(m, err) {
81 continue
82 }
83 if opts.allow_noncritical && is_legacy_noncritical_verify_error(err) {
84 warning_count++
85 } else {
86 critical_errors << err
87 }
88 }
89 for err in critical_errors {
90 msg += ' ${err.msg}\n'
91 }
92 if warning_count > 0 {
93 msg += ' (${warning_count} non-critical warnings suppressed)\n'
94 }
95 if critical_errors.len > 0 {
96 panic(msg)
97 }
98 }
99}
100
101fn is_accepted_declaration_verify_error(m &ssa.Module, err VerifyError) bool {
102 if err.msg == 'function has no blocks' {
103 if err.func_id < 0 || err.func_id >= m.funcs.len {
104 return false
105 }
106 func := m.funcs[err.func_id]
107 return func.is_c_extern || func.is_prototype
108 }
109 return false
110}
111
112fn is_legacy_noncritical_verify_error(err VerifyError) bool {
113 return err.msg.contains('does not dominate') || err.msg.contains('uses list')
114 || err.msg.contains('phi') || err.msg.contains('block mismatch')
115}
116
117fn verify_function(m &ssa.Module, func &ssa.Function) []VerifyError {
118 mut errors := []VerifyError{}
119
120 // Function must have at least one block (entry)
121 if func.blocks.len == 0 {
122 errors << VerifyError{
123 msg: 'function has no blocks'
124 func_id: func.id
125 }
126 return errors
127 }
128
129 // Verify each block
130 for blk_id in func.blocks {
131 if blk_id < 0 || blk_id >= m.blocks.len {
132 errors << VerifyError{
133 msg: 'invalid block id ${blk_id}'
134 func_id: func.id
135 block_id: -1
136 }
137 continue
138 }
139 errors << verify_block(m, func, blk_id)
140 }
141
142 // Verify dominance (values must dominate their uses)
143 errors << verify_dominance(m, func)
144
145 return errors
146}
147
148fn verify_block(m &ssa.Module, func &ssa.Function, blk_id int) []VerifyError {
149 mut errors := []VerifyError{}
150 blk := m.blocks[blk_id]
151
152 // Block must belong to this function
153 if blk.parent != func.id {
154 errors << VerifyError{
155 msg: 'block parent mismatch: expected ${func.id}, got ${blk.parent}'
156 func_id: func.id
157 block_id: blk_id
158 }
159 }
160
161 // Block must have instructions
162 if blk.instrs.len == 0 {
163 errors << VerifyError{
164 msg: 'block has no instructions (missing terminator)'
165 func_id: func.id
166 block_id: blk_id
167 }
168 return errors
169 }
170
171 // Verify instruction structure
172 mut seen_terminator := false
173 mut seen_non_phi := false
174
175 for i, val_id in blk.instrs {
176 if val_id < 0 || val_id >= m.values.len {
177 errors << VerifyError{
178 msg: 'invalid value id ${val_id} in instruction list'
179 func_id: func.id
180 block_id: blk_id
181 }
182 continue
183 }
184
185 val := m.values[val_id]
186 if val.kind != .instruction {
187 // Skip non-instruction values (shouldn't be in instrs list normally)
188 continue
189 }
190
191 if val.index < 0 || val.index >= m.instrs.len {
192 errors << VerifyError{
193 msg: 'value ${val_id} has invalid instruction index ${val.index}'
194 func_id: func.id
195 block_id: blk_id
196 val_id: val_id
197 }
198 continue
199 }
200
201 instr := m.instrs[val.index]
202
203 // Instruction must belong to this block
204 if instr.block != blk_id {
205 errors << VerifyError{
206 msg: 'instruction block mismatch: expected ${blk_id}, got ${instr.block}'
207 func_id: func.id
208 block_id: blk_id
209 val_id: val_id
210 }
211 }
212
213 // Phi nodes must come before all other instructions
214 if instr.op == .phi {
215 if seen_non_phi {
216 errors << VerifyError{
217 msg: 'phi node after non-phi instruction'
218 func_id: func.id
219 block_id: blk_id
220 val_id: val_id
221 }
222 }
223 } else {
224 seen_non_phi = true
225 }
226
227 // Terminator must be last
228 is_terminator := instr.op in [.ret, .br, .jmp, .switch_, .unreachable]
229 if is_terminator {
230 if seen_terminator {
231 errors << VerifyError{
232 msg: 'multiple terminators in block'
233 func_id: func.id
234 func_name: func.name
235 block_id: blk_id
236 val_id: val_id
237 }
238 }
239 if i != blk.instrs.len - 1 {
240 errors << VerifyError{
241 msg: 'terminator not at end of block'
242 func_id: func.id
243 func_name: func.name
244 block_id: blk_id
245 val_id: val_id
246 }
247 }
248 seen_terminator = true
249 }
250
251 // Verify instruction operands and types
252 errors << verify_instruction(m, func.id, blk_id, val_id, instr)
253 }
254
255 // Block must end with a terminator
256 if !seen_terminator {
257 errors << VerifyError{
258 msg: 'block does not end with terminator'
259 func_id: func.id
260 block_id: blk_id
261 }
262 }
263
264 // Verify CFG consistency (if CFG is built)
265 errors << verify_cfg_consistency(m, func.id, blk_id)
266
267 return errors
268}
269
270fn verify_instruction(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
271 mut errors := []VerifyError{}
272
273 // Verify all operands are valid
274 for i, op_id in instr.operands {
275 if op_id < 0 || op_id >= m.values.len {
276 errors << VerifyError{
277 msg: 'operand ${i} has invalid value id ${op_id}'
278 func_id: func_id
279 block_id: blk_id
280 val_id: val_id
281 }
282 }
283 }
284
285 // Type-specific verification
286 match instr.op {
287 // Binary arithmetic - operands should have compatible types
288 .add, .sub, .mul, .sdiv, .udiv, .srem, .urem {
289 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
290 }
291 // Binary float
292 .fadd, .fsub, .fmul, .fdiv, .frem {
293 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
294 }
295 // Bitwise - operands should be integers
296 .shl, .lshr, .ashr, .and_, .or_, .xor {
297 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
298 }
299 // Comparisons - operands should have same type
300 .lt, .gt, .le, .ge, .eq, .ne, .ult, .ugt, .ule, .uge {
301 errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
302 }
303 // Memory operations
304 .load {
305 errors << verify_load(m, func_id, blk_id, val_id, instr)
306 }
307 .store {
308 errors << verify_store(m, func_id, blk_id, val_id, instr)
309 }
310 .alloca, .heap_alloc {
311 // alloca/heap_alloc result should be a pointer type
312 if instr.typ > 0 && instr.typ < m.type_store.types.len {
313 typ := m.type_store.types[instr.typ]
314 if typ.kind != .ptr_t {
315 errors << VerifyError{
316 msg: '${instr.op} result type should be pointer, got ${typ.kind}'
317 func_id: func_id
318 block_id: blk_id
319 val_id: val_id
320 }
321 }
322 }
323 }
324 // Control flow
325 .phi {
326 errors << verify_phi(m, func_id, blk_id, val_id, instr)
327 }
328 .br {
329 errors << verify_branch(m, func_id, blk_id, val_id, instr)
330 }
331 .jmp {
332 errors << verify_jump(m, func_id, blk_id, val_id, instr)
333 }
334 .switch_ {
335 errors << verify_switch(m, func_id, blk_id, val_id, instr)
336 }
337 .ret {
338 // ret can have 0 or 1 operands
339 if instr.operands.len > 1 {
340 errors << VerifyError{
341 msg: 'ret has ${instr.operands.len} operands, expected 0 or 1'
342 func_id: func_id
343 block_id: blk_id
344 val_id: val_id
345 }
346 }
347 }
348 .call, .call_indirect, .call_sret, .go_call, .spawn_call {
349 // call should have at least a target (function pointer or name)
350 if instr.operands.len == 0 {
351 errors << VerifyError{
352 msg: '${instr.op} has no operands'
353 func_id: func_id
354 block_id: blk_id
355 val_id: val_id
356 }
357 }
358 }
359 .select {
360 // select should have 3 operands: condition, true_val, false_val
361 if instr.operands.len != 3 {
362 errors << VerifyError{
363 msg: 'select has ${instr.operands.len} operands, expected 3'
364 func_id: func_id
365 block_id: blk_id
366 val_id: val_id
367 }
368 }
369 }
370 .assign {
371 // assign should have 2 operands: dest, src
372 if instr.operands.len != 2 {
373 errors << VerifyError{
374 msg: 'assign has ${instr.operands.len} operands, expected 2'
375 func_id: func_id
376 block_id: blk_id
377 val_id: val_id
378 }
379 }
380 }
381 else {}
382 }
383
384 return errors
385}
386
387fn verify_binary_op(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
388 mut errors := []VerifyError{}
389
390 if instr.operands.len != 2 {
391 errors << VerifyError{
392 msg: 'binary op has ${instr.operands.len} operands, expected 2'
393 func_id: func_id
394 block_id: blk_id
395 val_id: val_id
396 }
397 return errors
398 }
399
400 // Check operand types match (for most binary ops)
401 op0 := instr.operands[0]
402 op1 := instr.operands[1]
403
404 if op0 < m.values.len && op1 < m.values.len {
405 t0 := m.values[op0].typ
406 t1 := m.values[op1].typ
407
408 // Types should be compatible (same type or one is 0/void for constants)
409 if t0 != t1 && t0 != 0 && t1 != 0 {
410 // Check if both are integers of potentially different widths
411 if t0 < m.type_store.types.len && t1 < m.type_store.types.len {
412 typ0 := m.type_store.types[t0]
413 typ1 := m.type_store.types[t1]
414 // Allow int-to-int operations with different widths (common pattern)
415 if typ0.kind == .int_t && typ1.kind == .int_t {
416 return errors
417 }
418 // Allow float-to-float operations with different widths (f32 vs f64)
419 if typ0.kind == .float_t && typ1.kind == .float_t {
420 return errors
421 }
422 // Allow int-float operations (common in numeric code, needs proper cast in builder)
423 if (typ0.kind == .int_t && typ1.kind == .float_t)
424 || (typ0.kind == .float_t && typ1.kind == .int_t) {
425 return errors
426 }
427 // Allow pointer arithmetic (ptr +/- int)
428 if (typ0.kind == .ptr_t && typ1.kind == .int_t)
429 || (typ0.kind == .int_t && typ1.kind == .ptr_t) {
430 return errors
431 }
432 // Allow pointer comparison/subtraction (ptr - ptr)
433 if typ0.kind == .ptr_t && typ1.kind == .ptr_t {
434 return errors
435 }
436 // Allow ptr vs struct (common in V when passing structs by value/reference)
437 if (typ0.kind == .ptr_t && typ1.kind == .struct_t)
438 || (typ0.kind == .struct_t && typ1.kind == .ptr_t) {
439 return errors
440 }
441 // Allow struct comparisons (V allows == on structs)
442 if typ0.kind == .struct_t && typ1.kind == .struct_t {
443 return errors
444 }
445 // Allow struct vs int (for comparisons with nil/0)
446 if (typ0.kind == .struct_t && typ1.kind == .int_t)
447 || (typ0.kind == .int_t && typ1.kind == .struct_t) {
448 return errors
449 }
450 errors << VerifyError{
451 msg: 'binary op operands have mismatched types: ${t0} (${typ0.kind}) vs ${t1} (${typ1.kind})'
452 func_id: func_id
453 block_id: blk_id
454 val_id: val_id
455 }
456 }
457 }
458 }
459
460 return errors
461}
462
463fn verify_load(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
464 mut errors := []VerifyError{}
465
466 if instr.operands.len != 1 {
467 errors << VerifyError{
468 msg: 'load has ${instr.operands.len} operands, expected 1 (pointer)'
469 func_id: func_id
470 block_id: blk_id
471 val_id: val_id
472 }
473 return errors
474 }
475
476 ptr_id := instr.operands[0]
477 if ptr_id < m.values.len {
478 ptr_typ_id := m.values[ptr_id].typ
479 if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
480 ptr_typ := m.type_store.types[ptr_typ_id]
481 if ptr_typ.kind != .ptr_t {
482 errors << VerifyError{
483 msg: 'load operand should be pointer type, got ${ptr_typ.kind}'
484 func_id: func_id
485 block_id: blk_id
486 val_id: val_id
487 }
488 }
489 }
490 }
491
492 return errors
493}
494
495fn verify_store(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
496 mut errors := []VerifyError{}
497
498 if instr.operands.len != 2 {
499 errors << VerifyError{
500 msg: 'store has ${instr.operands.len} operands, expected 2 (value, pointer)'
501 func_id: func_id
502 block_id: blk_id
503 val_id: val_id
504 }
505 return errors
506 }
507
508 ptr_id := instr.operands[1]
509 if ptr_id < m.values.len {
510 ptr_typ_id := m.values[ptr_id].typ
511 if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
512 ptr_typ := m.type_store.types[ptr_typ_id]
513 if ptr_typ.kind != .ptr_t {
514 errors << VerifyError{
515 msg: 'store destination should be pointer type, got ${ptr_typ.kind}'
516 func_id: func_id
517 block_id: blk_id
518 val_id: val_id
519 }
520 }
521 }
522 }
523
524 return errors
525}
526
527fn verify_phi(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
528 mut errors := []VerifyError{}
529
530 // Phi operands come in pairs: [val0, block0, val1, block1, ...]
531 if instr.operands.len % 2 != 0 {
532 errors << VerifyError{
533 msg: 'phi has odd number of operands (${instr.operands.len}), expected pairs of (value, block)'
534 func_id: func_id
535 block_id: blk_id
536 val_id: val_id
537 }
538 return errors
539 }
540
541 if instr.operands.len == 0 {
542 errors << VerifyError{
543 msg: 'phi has no operands'
544 func_id: func_id
545 block_id: blk_id
546 val_id: val_id
547 }
548 return errors
549 }
550
551 // Verify each operand pair
552 for i := 0; i < instr.operands.len; i += 2 {
553 val_in := instr.operands[i]
554 blk_val := instr.operands[i + 1]
555
556 // Value operand should exist
557 if val_in >= m.values.len {
558 errors << VerifyError{
559 msg: 'phi operand ${i} has invalid value id ${val_in}'
560 func_id: func_id
561 block_id: blk_id
562 val_id: val_id
563 }
564 }
565
566 // Block operand should be a basic_block
567 if blk_val < m.values.len {
568 if m.values[blk_val].kind != .basic_block {
569 errors << VerifyError{
570 msg: 'phi operand ${i + 1} should be basic_block, got ${m.values[blk_val].kind}'
571 func_id: func_id
572 block_id: blk_id
573 val_id: val_id
574 }
575 }
576 }
577
578 // Check type consistency: phi operand type should match phi result type
579 if val_in < m.values.len {
580 op_typ := m.values[val_in].typ
581 phi_typ := instr.typ
582 // Allow type mismatch with void (constants may have type 0)
583 if op_typ != phi_typ && op_typ != 0 && phi_typ != 0 {
584 // Only warn if both are real types
585 if op_typ < m.type_store.types.len && phi_typ < m.type_store.types.len {
586 op_kind := m.type_store.types[op_typ].kind
587 phi_kind := m.type_store.types[phi_typ].kind
588 // Allow int-to-int (common pattern)
589 if op_kind != .int_t || phi_kind != .int_t {
590 errors << VerifyError{
591 msg: 'phi operand ${i} type ${op_typ} does not match phi type ${phi_typ}'
592 func_id: func_id
593 block_id: blk_id
594 val_id: val_id
595 }
596 }
597 }
598 }
599 }
600 }
601
602 // If CFG is built, verify phi operand count matches predecessor count
603 blk := m.blocks[blk_id]
604 if blk.preds.len > 0 {
605 expected_pairs := blk.preds.len
606 actual_pairs := instr.operands.len / 2
607 if actual_pairs != expected_pairs {
608 errors << VerifyError{
609 msg: 'phi has ${actual_pairs} operand pairs but block has ${expected_pairs} predecessors'
610 func_id: func_id
611 block_id: blk_id
612 val_id: val_id
613 }
614 }
615
616 // Verify each phi block operand is actually a predecessor
617 for i := 1; i < instr.operands.len; i += 2 {
618 blk_val := instr.operands[i]
619 if blk_val < m.values.len && m.values[blk_val].kind == .basic_block {
620 pred_blk_id := m.values[blk_val].index
621 if pred_blk_id !in blk.preds {
622 errors << VerifyError{
623 msg: 'phi references block ${pred_blk_id} which is not a predecessor'
624 func_id: func_id
625 block_id: blk_id
626 val_id: val_id
627 }
628 }
629 }
630 }
631 }
632
633 return errors
634}
635
636fn verify_branch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
637 mut errors := []VerifyError{}
638
639 // br should have 3 operands: condition, true_block, false_block
640 if instr.operands.len != 3 {
641 errors << VerifyError{
642 msg: 'br has ${instr.operands.len} operands, expected 3 (cond, true_blk, false_blk)'
643 func_id: func_id
644 block_id: blk_id
645 val_id: val_id
646 }
647 return errors
648 }
649
650 // Verify block operands are basic blocks
651 for i in [1, 2] {
652 blk_val := instr.operands[i]
653 if blk_val < m.values.len {
654 if m.values[blk_val].kind != .basic_block {
655 errors << VerifyError{
656 msg: 'br operand ${i} should be basic_block, got ${m.values[blk_val].kind}'
657 func_id: func_id
658 block_id: blk_id
659 val_id: val_id
660 }
661 }
662 }
663 }
664
665 return errors
666}
667
668fn verify_jump(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
669 mut errors := []VerifyError{}
670
671 // jmp should have 1 operand: target block
672 if instr.operands.len != 1 {
673 errors << VerifyError{
674 msg: 'jmp has ${instr.operands.len} operands, expected 1 (target_blk)'
675 func_id: func_id
676 block_id: blk_id
677 val_id: val_id
678 }
679 return errors
680 }
681
682 blk_val := instr.operands[0]
683 if blk_val < m.values.len {
684 if m.values[blk_val].kind != .basic_block {
685 errors << VerifyError{
686 msg: 'jmp operand should be basic_block, got ${m.values[blk_val].kind}'
687 func_id: func_id
688 block_id: blk_id
689 val_id: val_id
690 }
691 }
692 }
693
694 return errors
695}
696
697fn verify_switch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
698 mut errors := []VerifyError{}
699
700 // switch should have: condition, default_block, then pairs of (value, block)
701 if instr.operands.len < 2 {
702 errors << VerifyError{
703 msg: 'switch has ${instr.operands.len} operands, expected at least 2 (cond, default)'
704 func_id: func_id
705 block_id: blk_id
706 val_id: val_id
707 }
708 return errors
709 }
710
711 // Verify default block is a basic block
712 default_blk := instr.operands[1]
713 if default_blk < m.values.len {
714 if m.values[default_blk].kind != .basic_block {
715 errors << VerifyError{
716 msg: 'switch default should be basic_block, got ${m.values[default_blk].kind}'
717 func_id: func_id
718 block_id: blk_id
719 val_id: val_id
720 }
721 }
722 }
723
724 // Remaining operands should be pairs of (value, block)
725 remaining := instr.operands.len - 2
726 if remaining % 2 != 0 {
727 errors << VerifyError{
728 msg: 'switch has odd number of case operands (${remaining})'
729 func_id: func_id
730 block_id: blk_id
731 val_id: val_id
732 }
733 }
734
735 return errors
736}
737
738fn verify_cfg_consistency(m &ssa.Module, func_id int, blk_id int) []VerifyError {
739 mut errors := []VerifyError{}
740 blk := m.blocks[blk_id]
741
742 // Skip if CFG not built yet
743 if blk.succs.len == 0 && blk.preds.len == 0 {
744 return errors
745 }
746
747 // Verify successor symmetry: if B is successor of A, then A should be predecessor of B
748 for succ_id in blk.succs {
749 if succ_id < 0 || succ_id >= m.blocks.len {
750 errors << VerifyError{
751 msg: 'invalid successor block id ${succ_id}'
752 func_id: func_id
753 block_id: blk_id
754 }
755 continue
756 }
757 if blk_id !in m.blocks[succ_id].preds {
758 errors << VerifyError{
759 msg: 'block ${blk_id} has successor ${succ_id} but is not in its predecessors'
760 func_id: func_id
761 block_id: blk_id
762 }
763 }
764 }
765
766 // Verify predecessor symmetry: if A is predecessor of B, then B should be successor of A
767 for pred_id in blk.preds {
768 if pred_id < 0 || pred_id >= m.blocks.len {
769 errors << VerifyError{
770 msg: 'invalid predecessor block id ${pred_id}'
771 func_id: func_id
772 block_id: blk_id
773 }
774 continue
775 }
776 if blk_id !in m.blocks[pred_id].succs {
777 errors << VerifyError{
778 msg: 'block ${blk_id} has predecessor ${pred_id} but is not in its successors'
779 func_id: func_id
780 block_id: blk_id
781 }
782 }
783 }
784
785 // Verify terminator matches successors
786 if blk.instrs.len > 0 {
787 term_val_id := blk.instrs[blk.instrs.len - 1]
788 if term_val_id < m.values.len && m.values[term_val_id].kind == .instruction {
789 term := m.instrs[m.values[term_val_id].index]
790 mut expected_succs := []int{}
791
792 match term.op {
793 .jmp {
794 if term.operands.len >= 1 {
795 target_val := term.operands[0]
796 if target_val < m.values.len && m.values[target_val].kind == .basic_block {
797 expected_succs << m.values[target_val].index
798 }
799 }
800 }
801 .br {
802 if term.operands.len >= 3 {
803 true_val := term.operands[1]
804 false_val := term.operands[2]
805 if true_val < m.values.len && m.values[true_val].kind == .basic_block {
806 expected_succs << m.values[true_val].index
807 }
808 if false_val < m.values.len && m.values[false_val].kind == .basic_block {
809 if m.values[false_val].index !in expected_succs {
810 expected_succs << m.values[false_val].index
811 }
812 }
813 }
814 }
815 .switch_ {
816 // Default block
817 if term.operands.len >= 2 {
818 default_val := term.operands[1]
819 if default_val < m.values.len && m.values[default_val].kind == .basic_block {
820 expected_succs << m.values[default_val].index
821 }
822 }
823 // Case blocks
824 for i := 3; i < term.operands.len; i += 2 {
825 blk_val := term.operands[i]
826 if blk_val < m.values.len && m.values[blk_val].kind == .basic_block {
827 idx := m.values[blk_val].index
828 if idx !in expected_succs {
829 expected_succs << idx
830 }
831 }
832 }
833 }
834 .ret, .unreachable {
835 // No successors expected
836 }
837 else {}
838 }
839
840 // Check for missing successors
841 for succ in expected_succs {
842 if succ !in blk.succs {
843 errors << VerifyError{
844 msg: 'terminator targets block ${succ} but it is not in successors'
845 func_id: func_id
846 block_id: blk_id
847 }
848 }
849 }
850 }
851 }
852
853 return errors
854}
855
856fn verify_dominance(m &ssa.Module, func &ssa.Function) []VerifyError {
857 mut errors := []VerifyError{}
858
859 // Build map of value -> defining block
860 mut def_block := map[int]int{}
861
862 // Function parameters are defined in entry block
863 for param_id in func.params {
864 if func.blocks.len > 0 {
865 def_block[param_id] = func.blocks[0]
866 }
867 }
868
869 // Build definition map for all instructions
870 for blk_id in func.blocks {
871 if blk_id >= m.blocks.len {
872 continue
873 }
874 for val_id in m.blocks[blk_id].instrs {
875 def_block[val_id] = blk_id
876 }
877 }
878
879 // Skip dominance check if dominators not computed
880 // After compute_dominators, dom_tree is populated for the entry block (if function has multiple blocks)
881 // Before computation, dom_tree is empty. For single-block functions, there's nothing to verify.
882 entry_block := func.blocks[0]
883 dominators_computed := func.blocks.len == 1 || m.blocks[entry_block].dom_tree.len > 0
884
885 if !dominators_computed {
886 return errors
887 }
888
889 // For each use, verify definition dominates use
890 for blk_id in func.blocks {
891 if blk_id >= m.blocks.len {
892 continue
893 }
894 for val_id in m.blocks[blk_id].instrs {
895 if val_id >= m.values.len || m.values[val_id].kind != .instruction {
896 continue
897 }
898 if m.values[val_id].index >= m.instrs.len {
899 continue
900 }
901 instr := m.instrs[m.values[val_id].index]
902
903 // Check each operand
904 for i, op_id in instr.operands {
905 if op_id >= m.values.len {
906 continue
907 }
908
909 // Skip block references and constants
910 op_val := m.values[op_id]
911 if op_val.kind in [.basic_block, .constant, .global, .string_literal,
912 .c_string_literal] {
913 continue
914 }
915
916 // Phi nodes are special: operands come from predecessors
917 if instr.op == .phi {
918 continue
919 }
920
921 // Check dominance
922 if def_blk := def_block[op_id] {
923 if !dominates(m, func, def_blk, blk_id) {
924 errors << VerifyError{
925 msg: 'operand ${i} (value ${op_id}) defined in block ${def_blk} does not dominate use in block ${blk_id}'
926 func_id: func.id
927 func_name: func.name
928 block_id: blk_id
929 val_id: val_id
930 }
931 }
932 }
933 }
934 }
935 }
936
937 return errors
938}
939
940// dominates returns true if block a dominates block b
941fn dominates(m &ssa.Module, func &ssa.Function, a int, b int) bool {
942 if a == b {
943 return true
944 }
945
946 // Walk up dominator tree from b
947 mut curr := b
948 for {
949 if curr < 0 || curr >= m.blocks.len {
950 return false
951 }
952 idom := m.blocks[curr].idom
953 if idom < 0 {
954 // Unreachable block (idom = -1)
955 return false
956 }
957 if idom == curr {
958 // Reached entry block (or invalid state)
959 return a == curr
960 }
961 if idom == a {
962 return true
963 }
964 if idom == 0 && curr != 0 {
965 // Entry block case
966 return a == 0 || a == func.blocks[0]
967 }
968 curr = idom
969 }
970 return false
971}
972
973fn verify_use_def_chains(m &ssa.Module) []VerifyError {
974 mut errors := []VerifyError{}
975
976 // Build expected uses from instruction operands
977 mut expected_uses := map[int]map[int]bool{} // value -> set of users
978
979 for fi in 0 .. m.funcs.len {
980 for blk_id in m.funcs[fi].blocks {
981 if blk_id >= m.blocks.len {
982 continue
983 }
984 for val_id in m.blocks[blk_id].instrs {
985 if val_id >= m.values.len || m.values[val_id].kind != .instruction {
986 continue
987 }
988 if m.values[val_id].index >= m.instrs.len {
989 continue
990 }
991 instr := m.instrs[m.values[val_id].index]
992
993 for op_id in instr.operands {
994 if op_id < m.values.len {
995 if op_id !in expected_uses {
996 expected_uses[op_id] = map[int]bool{}
997 }
998 expected_uses[op_id][val_id] = true
999 }
1000 }
1001 }
1002 }
1003 }
1004
1005 // Verify actual uses match expected
1006 for val_id, users in expected_uses {
1007 if val_id >= m.values.len {
1008 continue
1009 }
1010 actual := m.values[val_id].uses
1011
1012 for user_id, _ in users {
1013 if user_id !in actual {
1014 errors << VerifyError{
1015 msg: 'value ${val_id} is used by ${user_id} but ${user_id} is not in uses list'
1016 val_id: val_id
1017 }
1018 }
1019 }
1020 }
1021
1022 // Check for spurious uses (uses list contains values that don't actually use this value)
1023 for i, val in m.values {
1024 if val.kind != .instruction && val.kind != .argument {
1025 continue
1026 }
1027 for user_id in val.uses {
1028 if user_id >= m.values.len {
1029 errors << VerifyError{
1030 msg: 'value ${i} has invalid user ${user_id} in uses list'
1031 val_id: i
1032 }
1033 continue
1034 }
1035 user_val := m.values[user_id]
1036 if user_val.kind != .instruction {
1037 continue
1038 }
1039 if user_val.index >= m.instrs.len {
1040 continue
1041 }
1042 instr := m.instrs[user_val.index]
1043 if i !in instr.operands {
1044 errors << VerifyError{
1045 msg: 'value ${i} has ${user_id} in uses list but ${user_id} does not use it'
1046 val_id: i
1047 }
1048 }
1049 }
1050 }
1051
1052 return errors
1053}
1054