Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,789 @@
|
||||
module optimize
|
||||
|
||||
import v3.ssa
|
||||
|
||||
// VerifyError is a structured SSA verification failure. Unlike verify_ssa()
|
||||
// (which panics on the first problem), the structured API collects every error so
|
||||
// callers can decide which are fatal.
|
||||
pub struct VerifyError {
|
||||
pub:
|
||||
msg string
|
||||
func_id int = -1
|
||||
func_name string
|
||||
block_id int = -1
|
||||
val_id int = -1
|
||||
}
|
||||
|
||||
pub fn (e VerifyError) str() string {
|
||||
mut s := 'SSA verify error'
|
||||
if e.func_id >= 0 {
|
||||
s += ' in func ${e.func_id}'
|
||||
if e.func_name != '' {
|
||||
s += ' (${e.func_name})'
|
||||
}
|
||||
}
|
||||
if e.block_id >= 0 {
|
||||
s += ' block ${e.block_id}'
|
||||
}
|
||||
if e.val_id >= 0 {
|
||||
s += ' value ${e.val_id}'
|
||||
}
|
||||
return '${s}: ${e.msg}'
|
||||
}
|
||||
|
||||
pub struct VerifyPanicOptions {
|
||||
pub:
|
||||
allow_noncritical bool
|
||||
}
|
||||
|
||||
// verify performs comprehensive validation of SSA invariants and returns every
|
||||
// error found (empty slice == valid). v3 stores branch/phi/switch targets as raw
|
||||
// block ids, so block operands are validated as block ids, not block values.
|
||||
pub fn verify(m &ssa.Module) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
for fi in 0 .. m.funcs.len {
|
||||
errors << verify_function(m, m.funcs[fi])
|
||||
}
|
||||
errors << verify_use_def_chains(m)
|
||||
return errors
|
||||
}
|
||||
|
||||
// verify_and_panic runs verification and panics on critical errors only.
|
||||
pub fn verify_and_panic(m &ssa.Module, pass_name string) {
|
||||
verify_and_panic_with_options(m, pass_name, VerifyPanicOptions{
|
||||
allow_noncritical: true
|
||||
})
|
||||
}
|
||||
|
||||
// verify_and_panic_with_options runs verification with an explicit warning policy.
|
||||
// Declaration-only functions (C externs / prototypes) are always accepted.
|
||||
pub fn verify_and_panic_with_options(m &ssa.Module, pass_name string, opts VerifyPanicOptions) {
|
||||
errors := verify(m)
|
||||
if errors.len == 0 {
|
||||
return
|
||||
}
|
||||
mut msg := 'SSA verification failed after ${pass_name}:\n'
|
||||
mut critical := []VerifyError{}
|
||||
mut warnings := 0
|
||||
for err in errors {
|
||||
if is_accepted_declaration_verify_error(m, err) {
|
||||
continue
|
||||
}
|
||||
if opts.allow_noncritical && is_legacy_noncritical_verify_error(err) {
|
||||
warnings++
|
||||
} else {
|
||||
critical << err
|
||||
}
|
||||
}
|
||||
for err in critical {
|
||||
msg += ' ${err.msg}\n'
|
||||
}
|
||||
if warnings > 0 {
|
||||
msg += ' (${warnings} non-critical warnings suppressed)\n'
|
||||
}
|
||||
if critical.len > 0 {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_accepted_declaration_verify_error(m &ssa.Module, err VerifyError) bool {
|
||||
if err.msg == 'function has no blocks' {
|
||||
if err.func_id < 0 || err.func_id >= m.funcs.len {
|
||||
return false
|
||||
}
|
||||
func := m.funcs[err.func_id]
|
||||
return func.is_c_extern || func.is_prototype
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn is_legacy_noncritical_verify_error(err VerifyError) bool {
|
||||
// Dominance / use-def / phi mismatches are commonly transient between passes.
|
||||
if err.msg.contains('does not dominate') || err.msg.contains('uses list')
|
||||
|| err.msg.contains('phi') || err.msg.contains('block mismatch') {
|
||||
return true
|
||||
}
|
||||
// Structural-normalization artifacts emitted by the builder before the
|
||||
// optimizer cleans them up (empty/unreachable blocks, dead code that follows
|
||||
// a terminator within the same block). These are tolerated by the lenient
|
||||
// verify_ssa() and removed by remove_unreachable_blocks / DCE.
|
||||
if err.msg.contains('has no instructions') || err.msg.contains('not at end of block')
|
||||
|| err.msg.contains('multiple terminators')
|
||||
|| err.msg.contains('does not end with terminator') {
|
||||
return true
|
||||
}
|
||||
// Type-precision findings. The v3 builder does not maintain strict SSA typing
|
||||
// on every load/store/binary operand (e.g. aggregates loaded by value), and
|
||||
// the backends tolerate it, so these are advisory rather than fatal.
|
||||
return err.msg.contains('should be pointer type') || err.msg.contains('mismatched types')
|
||||
|| err.msg.contains('result type should be pointer')
|
||||
}
|
||||
|
||||
fn verify_function(m &ssa.Module, func ssa.Function) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if func.blocks.len == 0 {
|
||||
errors << VerifyError{
|
||||
msg: 'function has no blocks'
|
||||
func_id: func.id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
for blk_id in func.blocks {
|
||||
if blk_id < 0 || blk_id >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'invalid block id ${blk_id}'
|
||||
func_id: func.id
|
||||
}
|
||||
continue
|
||||
}
|
||||
errors << verify_block(m, func, blk_id)
|
||||
}
|
||||
errors << verify_dominance(m, func)
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_block(m &ssa.Module, func ssa.Function, blk_id int) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
blk := m.blocks[blk_id]
|
||||
if blk.parent != func.id {
|
||||
errors << VerifyError{
|
||||
msg: 'block parent mismatch: expected ${func.id}, got ${blk.parent}'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
}
|
||||
}
|
||||
if blk.instrs.len == 0 {
|
||||
errors << VerifyError{
|
||||
msg: 'block has no instructions (missing terminator)'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
mut seen_terminator := false
|
||||
mut seen_non_phi := false
|
||||
for i, val_id in blk.instrs {
|
||||
if val_id < 0 || val_id >= m.values.len {
|
||||
errors << VerifyError{
|
||||
msg: 'invalid value id ${val_id} in instruction list'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
}
|
||||
continue
|
||||
}
|
||||
val := m.values[val_id]
|
||||
if val.kind != .instruction {
|
||||
continue
|
||||
}
|
||||
if val.index < 0 || val.index >= m.instrs.len {
|
||||
errors << VerifyError{
|
||||
msg: 'value ${val_id} has invalid instruction index ${val.index}'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
continue
|
||||
}
|
||||
instr := m.instrs[val.index]
|
||||
if instr.block != blk_id {
|
||||
errors << VerifyError{
|
||||
msg: 'instruction block mismatch: expected ${blk_id}, got ${instr.block}'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
if instr.op == .phi {
|
||||
if seen_non_phi {
|
||||
errors << VerifyError{
|
||||
msg: 'phi node after non-phi instruction'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
seen_non_phi = true
|
||||
}
|
||||
is_terminator := instr.op in [.ret, .br, .jmp, .switch_, .unreachable]
|
||||
if is_terminator {
|
||||
if seen_terminator {
|
||||
errors << VerifyError{
|
||||
msg: 'multiple terminators in block'
|
||||
func_id: func.id
|
||||
func_name: func.name
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
if i != blk.instrs.len - 1 {
|
||||
errors << VerifyError{
|
||||
msg: 'terminator not at end of block'
|
||||
func_id: func.id
|
||||
func_name: func.name
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
seen_terminator = true
|
||||
}
|
||||
errors << verify_instruction(m, func.id, blk_id, val_id, instr)
|
||||
}
|
||||
if !seen_terminator {
|
||||
errors << VerifyError{
|
||||
msg: 'block does not end with terminator'
|
||||
func_id: func.id
|
||||
block_id: blk_id
|
||||
}
|
||||
}
|
||||
errors << verify_cfg_consistency(m, func.id, blk_id)
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_instruction(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
// Only value operands are validated as value ids; block ids (branch/phi/switch
|
||||
// targets) are validated by the op-specific checks below.
|
||||
for i, op_id in instr.value_operands() {
|
||||
if op_id < 0 || op_id >= m.values.len {
|
||||
errors << VerifyError{
|
||||
msg: 'operand ${i} has invalid value id ${op_id}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
match instr.op {
|
||||
.add, .sub, .mul, .sdiv, .udiv, .srem, .urem {
|
||||
errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.fadd, .fsub, .fmul, .fdiv, .frem {
|
||||
errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.shl, .lshr, .ashr, .and_, .or_, .xor {
|
||||
errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.lt, .gt, .le, .ge, .eq, .ne, .ult, .ugt, .ule, .uge {
|
||||
errors << verify_binary_op(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.load {
|
||||
errors << verify_load(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.store {
|
||||
errors << verify_store(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.alloca, .heap_alloc {
|
||||
if instr.typ > 0 && instr.typ < m.type_store.types.len {
|
||||
typ := m.type_store.types[instr.typ]
|
||||
if typ.kind != .ptr_t {
|
||||
errors << VerifyError{
|
||||
msg: '${instr.op} result type should be pointer, got ${typ.kind}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.phi {
|
||||
errors << verify_phi(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.br {
|
||||
errors << verify_branch(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.jmp {
|
||||
errors << verify_jump(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.switch_ {
|
||||
errors << verify_switch(m, func_id, blk_id, val_id, instr)
|
||||
}
|
||||
.ret {
|
||||
if instr.operands.len > 1 {
|
||||
errors << VerifyError{
|
||||
msg: 'ret has ${instr.operands.len} operands, expected 0 or 1'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
.call, .call_indirect, .call_sret, .go_call, .spawn_call {
|
||||
if instr.operands.len == 0 {
|
||||
errors << VerifyError{
|
||||
msg: '${instr.op} has no operands'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
.select {
|
||||
if instr.operands.len != 3 {
|
||||
errors << VerifyError{
|
||||
msg: 'select has ${instr.operands.len} operands, expected 3'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
.assign {
|
||||
if instr.operands.len != 2 {
|
||||
errors << VerifyError{
|
||||
msg: 'assign has ${instr.operands.len} operands, expected 2'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_binary_op(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len != 2 {
|
||||
errors << VerifyError{
|
||||
msg: 'binary op has ${instr.operands.len} operands, expected 2'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
op0 := instr.operands[0]
|
||||
op1 := instr.operands[1]
|
||||
if op0 < m.values.len && op1 < m.values.len {
|
||||
t0 := m.values[op0].typ
|
||||
t1 := m.values[op1].typ
|
||||
if t0 != t1 && t0 != 0 && t1 != 0 {
|
||||
if t0 < m.type_store.types.len && t1 < m.type_store.types.len {
|
||||
k0 := m.type_store.types[t0].kind
|
||||
k1 := m.type_store.types[t1].kind
|
||||
// Allow common mixed-kind binary patterns (different widths, ptr
|
||||
// arithmetic, struct/nil comparisons, int/float coercions).
|
||||
if k0 == .int_t && k1 == .int_t {
|
||||
return errors
|
||||
}
|
||||
if k0 == .float_t && k1 == .float_t {
|
||||
return errors
|
||||
}
|
||||
if (k0 == .int_t && k1 == .float_t) || (k0 == .float_t && k1 == .int_t) {
|
||||
return errors
|
||||
}
|
||||
if (k0 == .ptr_t && k1 == .int_t) || (k0 == .int_t && k1 == .ptr_t) {
|
||||
return errors
|
||||
}
|
||||
if k0 == .ptr_t && k1 == .ptr_t {
|
||||
return errors
|
||||
}
|
||||
if (k0 == .ptr_t && k1 == .struct_t) || (k0 == .struct_t && k1 == .ptr_t) {
|
||||
return errors
|
||||
}
|
||||
if k0 == .struct_t && k1 == .struct_t {
|
||||
return errors
|
||||
}
|
||||
if (k0 == .struct_t && k1 == .int_t) || (k0 == .int_t && k1 == .struct_t) {
|
||||
return errors
|
||||
}
|
||||
errors << VerifyError{
|
||||
msg: 'binary op operands have mismatched types: ${t0} (${k0}) vs ${t1} (${k1})'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_load(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len != 1 {
|
||||
errors << VerifyError{
|
||||
msg: 'load has ${instr.operands.len} operands, expected 1 (pointer)'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
ptr_id := instr.operands[0]
|
||||
if ptr_id < m.values.len {
|
||||
ptr_typ_id := m.values[ptr_id].typ
|
||||
if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
|
||||
if m.type_store.types[ptr_typ_id].kind != .ptr_t {
|
||||
errors << VerifyError{
|
||||
msg: 'load operand should be pointer type, got ${m.type_store.types[ptr_typ_id].kind}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_store(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len != 2 {
|
||||
errors << VerifyError{
|
||||
msg: 'store has ${instr.operands.len} operands, expected 2 (value, pointer)'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
ptr_id := instr.operands[1]
|
||||
if ptr_id < m.values.len {
|
||||
ptr_typ_id := m.values[ptr_id].typ
|
||||
if ptr_typ_id > 0 && ptr_typ_id < m.type_store.types.len {
|
||||
if m.type_store.types[ptr_typ_id].kind != .ptr_t {
|
||||
errors << VerifyError{
|
||||
msg: 'store destination should be pointer type, got ${m.type_store.types[ptr_typ_id].kind}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_phi(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len % 2 != 0 {
|
||||
errors << VerifyError{
|
||||
msg: 'phi has odd number of operands (${instr.operands.len}), expected (value, block) pairs'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
if instr.operands.len == 0 {
|
||||
errors << VerifyError{
|
||||
msg: 'phi has no operands'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
blk := m.blocks[blk_id]
|
||||
for i := 0; i < instr.operands.len; i += 2 {
|
||||
val_in := instr.operands[i]
|
||||
pred_blk_id := int(instr.operands[i + 1])
|
||||
if val_in < 0 || val_in >= m.values.len {
|
||||
errors << VerifyError{
|
||||
msg: 'phi operand ${i} has invalid value id ${val_in}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
if pred_blk_id < 0 || pred_blk_id >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'phi operand ${i + 1} has invalid block id ${pred_blk_id}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
} else if blk.preds.len > 0 && pred_blk_id !in blk.preds {
|
||||
errors << VerifyError{
|
||||
msg: 'phi references block ${pred_blk_id} which is not a predecessor'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
if blk.preds.len > 0 {
|
||||
actual_pairs := instr.operands.len / 2
|
||||
if actual_pairs != blk.preds.len {
|
||||
errors << VerifyError{
|
||||
msg: 'phi has ${actual_pairs} operand pairs but block has ${blk.preds.len} predecessors'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_branch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len != 3 {
|
||||
errors << VerifyError{
|
||||
msg: 'br has ${instr.operands.len} operands, expected 3 (cond, true_blk, false_blk)'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
for i in [1, 2] {
|
||||
b := int(instr.operands[i])
|
||||
if b < 0 || b >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'br operand ${i} has invalid block id ${b}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_jump(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len != 1 {
|
||||
errors << VerifyError{
|
||||
msg: 'jmp has ${instr.operands.len} operands, expected 1 (target_blk)'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
b := int(instr.operands[0])
|
||||
if b < 0 || b >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'jmp operand has invalid block id ${b}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_switch(m &ssa.Module, func_id int, blk_id int, val_id int, instr ssa.Instruction) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
if instr.operands.len < 2 {
|
||||
errors << VerifyError{
|
||||
msg: 'switch has ${instr.operands.len} operands, expected at least 2 (cond, default)'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
return errors
|
||||
}
|
||||
def_blk := int(instr.operands[1])
|
||||
if def_blk < 0 || def_blk >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'switch default has invalid block id ${def_blk}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
remaining := instr.operands.len - 2
|
||||
if remaining % 2 != 0 {
|
||||
errors << VerifyError{
|
||||
msg: 'switch has odd number of case operands (${remaining})'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
for i := 3; i < instr.operands.len; i += 2 {
|
||||
b := int(instr.operands[i])
|
||||
if b < 0 || b >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'switch case has invalid block id ${b}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_cfg_consistency(m &ssa.Module, func_id int, blk_id int) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
blk := m.blocks[blk_id]
|
||||
if blk.succs.len == 0 && blk.preds.len == 0 {
|
||||
return errors
|
||||
}
|
||||
for succ_id in blk.succs {
|
||||
if succ_id < 0 || succ_id >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'invalid successor block id ${succ_id}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
}
|
||||
continue
|
||||
}
|
||||
if blk_id !in m.blocks[succ_id].preds {
|
||||
errors << VerifyError{
|
||||
msg: 'block ${blk_id} has successor ${succ_id} but is not in its predecessors'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
}
|
||||
}
|
||||
}
|
||||
for pred_id in blk.preds {
|
||||
if pred_id < 0 || pred_id >= m.blocks.len {
|
||||
errors << VerifyError{
|
||||
msg: 'invalid predecessor block id ${pred_id}'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
}
|
||||
continue
|
||||
}
|
||||
if blk_id !in m.blocks[pred_id].succs {
|
||||
errors << VerifyError{
|
||||
msg: 'block ${blk_id} has predecessor ${pred_id} but is not in its successors'
|
||||
func_id: func_id
|
||||
block_id: blk_id
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn verify_dominance(m &ssa.Module, func ssa.Function) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
mut def_block := map[int]int{}
|
||||
for param_id in func.params {
|
||||
if func.blocks.len > 0 {
|
||||
def_block[param_id] = func.blocks[0]
|
||||
}
|
||||
}
|
||||
for blk_id in func.blocks {
|
||||
if blk_id >= m.blocks.len {
|
||||
continue
|
||||
}
|
||||
for val_id in m.blocks[blk_id].instrs {
|
||||
def_block[val_id] = blk_id
|
||||
}
|
||||
}
|
||||
|
||||
entry_block := func.blocks[0]
|
||||
dominators_computed := func.blocks.len == 1 || m.blocks[entry_block].dom_tree.len > 0
|
||||
if !dominators_computed {
|
||||
return errors
|
||||
}
|
||||
|
||||
for blk_id in func.blocks {
|
||||
if blk_id >= m.blocks.len {
|
||||
continue
|
||||
}
|
||||
for val_id in m.blocks[blk_id].instrs {
|
||||
if val_id >= m.values.len || m.values[val_id].kind != .instruction {
|
||||
continue
|
||||
}
|
||||
if m.values[val_id].index >= m.instrs.len {
|
||||
continue
|
||||
}
|
||||
instr := m.instrs[m.values[val_id].index]
|
||||
// Phi operands legitimately come from predecessors, not dominators.
|
||||
if instr.op == .phi {
|
||||
continue
|
||||
}
|
||||
for i, op_id in instr.value_operands() {
|
||||
if op_id >= m.values.len {
|
||||
continue
|
||||
}
|
||||
op_val := m.values[op_id]
|
||||
if op_val.kind in [.basic_block, .constant, .global, .string_literal,
|
||||
.c_string_literal, .func_ref, .argument] {
|
||||
continue
|
||||
}
|
||||
if def_blk := def_block[op_id] {
|
||||
if !dominates(m, func, def_blk, blk_id) {
|
||||
errors << VerifyError{
|
||||
msg: 'operand ${i} (value ${op_id}) defined in block ${def_blk} does not dominate use in block ${blk_id}'
|
||||
func_id: func.id
|
||||
func_name: func.name
|
||||
block_id: blk_id
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
fn dominates(m &ssa.Module, _func ssa.Function, a int, b int) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
mut curr := b
|
||||
for {
|
||||
if curr < 0 || curr >= m.blocks.len {
|
||||
return false
|
||||
}
|
||||
idom := m.blocks[curr].idom
|
||||
if idom < 0 {
|
||||
return false
|
||||
}
|
||||
if idom == curr {
|
||||
return a == curr
|
||||
}
|
||||
if idom == a {
|
||||
return true
|
||||
}
|
||||
curr = idom
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn verify_use_def_chains(m &ssa.Module) []VerifyError {
|
||||
mut errors := []VerifyError{}
|
||||
mut expected_uses := map[int]map[int]bool{}
|
||||
for fi in 0 .. m.funcs.len {
|
||||
for blk_id in m.funcs[fi].blocks {
|
||||
if blk_id >= m.blocks.len {
|
||||
continue
|
||||
}
|
||||
for val_id in m.blocks[blk_id].instrs {
|
||||
if val_id >= m.values.len || m.values[val_id].kind != .instruction {
|
||||
continue
|
||||
}
|
||||
if m.values[val_id].index >= m.instrs.len {
|
||||
continue
|
||||
}
|
||||
instr := m.instrs[m.values[val_id].index]
|
||||
for op_id in instr.value_operands() {
|
||||
if op_id >= 0 && op_id < m.values.len {
|
||||
if op_id !in expected_uses {
|
||||
expected_uses[op_id] = map[int]bool{}
|
||||
}
|
||||
expected_uses[op_id][val_id] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for val_id, users in expected_uses {
|
||||
if val_id >= m.values.len {
|
||||
continue
|
||||
}
|
||||
actual := m.values[val_id].uses
|
||||
for user_id, _ in users {
|
||||
if user_id !in actual {
|
||||
errors << VerifyError{
|
||||
msg: 'value ${val_id} is used by ${user_id} but ${user_id} is not in uses list'
|
||||
val_id: val_id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
Reference in new issue
Block a user