celox-backend-x86 0.8.2

Celox x86-64 machine-code backend
Documentation
use super::*;

#[test]
fn vreg_compaction_removes_dead_ids_and_rewrites_phi_and_spill_metadata() {
    let mut vregs = VRegAllocator::new();
    for _ in 0..8 {
        vregs.alloc();
    }
    let mut spill_descs = vec![SpillDesc::transient(); 8];
    spill_descs[6] = SpillDesc::transient().with_state_insert(VReg(4), 0, 8);
    spill_descs[7] = SpillDesc::transient().with_state_insert(VReg(5), 0, 8);
    let mut function = MFunction::new(vregs, spill_descs);
    function.push_block(MBlock {
        id: BlockId(0),
        phis: Vec::new(),
        insts: vec![
            MInst::Load {
                dst: VReg(2),
                base: BaseReg::SimState,
                offset: 0,
                size: OpSize::S64,
            },
            MInst::LoadImm {
                dst: VReg(5),
                value: 1,
            },
            MInst::Jump { target: BlockId(1) },
        ],
    });
    function.push_block(MBlock {
        id: BlockId(1),
        phis: vec![PhiNode {
            dst: VReg(6),
            sources: vec![(BlockId(0), VReg(2))],
        }],
        insts: vec![
            MInst::Add {
                dst: VReg(7),
                lhs: VReg(6),
                rhs: VReg(5),
            },
            MInst::Store {
                base: BaseReg::SimState,
                offset: 8,
                src: VReg(7),
                size: OpSize::S64,
            },
            MInst::Return,
        ],
    });

    assert_eq!(
        compact_vregs(&mut function),
        VRegCompaction {
            before: 8,
            after: 4,
        }
    );
    assert_eq!(function.vregs.count(), 4);
    assert_eq!(function.spill_descs.len(), 4);
    assert_eq!(function.blocks[1].phis[0].dst, VReg(2));
    assert_eq!(function.blocks[1].phis[0].sources[0].1, VReg(0));
    assert!(matches!(
        function.blocks[1].insts[0],
        MInst::Add {
            dst: VReg(3),
            lhs: VReg(2),
            rhs: VReg(1),
        }
    ));
    assert_eq!(
        function.spill_descs[3]
            .state_insert
            .expect("live provenance")
            .value,
        VReg(1)
    );
    assert!(
        function.spill_descs[2].state_insert.is_none(),
        "provenance for a DCE-removed definition must not survive"
    );
    function.verify();
}

#[test]
fn copy_propagates_only_redundant_word32_snapshots() {
    let mut func = make_func(
        vec![
            MInst::Load {
                dst: VReg(0),
                base: BaseReg::SimState,
                offset: 0,
                size: OpSize::S64,
            },
            MInst::Mov32 {
                dst: VReg(1),
                src: VReg(0),
            },
            MInst::Mov32 {
                dst: VReg(2),
                src: VReg(1),
            },
            MInst::Mov32 {
                dst: VReg(3),
                src: VReg(1),
            },
            MInst::Store {
                base: BaseReg::SimState,
                offset: 8,
                src: VReg(2),
                size: OpSize::S64,
            },
            MInst::Store {
                base: BaseReg::SimState,
                offset: 16,
                src: VReg(3),
                size: OpSize::S64,
            },
            MInst::Return,
        ],
        4,
    );

    copy_propagate(&mut func);

    assert_eq!(
        func.blocks[0]
            .insts
            .iter()
            .filter(|inst| matches!(inst, MInst::Mov32 { .. }))
            .count(),
        1,
        "the first Mov32 is a real 64-to-32 truncation"
    );
    let stored = func.blocks[0]
        .insts
        .iter()
        .filter_map(|inst| match inst {
            MInst::Store { src, .. } => Some(*src),
            _ => None,
        })
        .collect::<Vec<_>>();
    assert_eq!(stored, vec![VReg(1), VReg(1)]);
}

#[test]
fn dead_code_elimination_removes_unused_phi_chains() {
    let mut func = make_func(
        vec![
            MInst::LoadImm {
                dst: VReg(0),
                value: 1,
            },
            MInst::LoadImm {
                dst: VReg(1),
                value: 2,
            },
            MInst::Return,
        ],
        4,
    );
    func.blocks[0].phis.extend([
        PhiNode {
            dst: VReg(2),
            sources: vec![(BlockId(1), VReg(0)), (BlockId(2), VReg(1))],
        },
        PhiNode {
            dst: VReg(3),
            sources: vec![(BlockId(1), VReg(2)), (BlockId(2), VReg(2))],
        },
    ]);

    dead_code_eliminate(&mut func);

    assert!(func.blocks[0].phis.is_empty());
    assert!(matches!(func.blocks[0].insts.as_slice(), [MInst::Return]));
}

#[test]
fn simplify_cfg_does_not_collapse_distinct_phi_edges() {
    let mut func = make_func(Vec::new(), 3);
    let mut entry = MBlock::new(BlockId(0));
    entry.push(MInst::LoadImm {
        dst: VReg(0),
        value: 1,
    });
    entry.push(MInst::LoadImm {
        dst: VReg(1),
        value: 10,
    });
    entry.push(MInst::LoadImm {
        dst: VReg(2),
        value: 20,
    });
    entry.push(MInst::Branch {
        cond: VReg(0),
        true_bb: BlockId(1),
        false_bb: BlockId(2),
    });
    let mut left = MBlock::new(BlockId(1));
    left.push(MInst::Jump { target: BlockId(3) });
    let mut right = MBlock::new(BlockId(2));
    right.push(MInst::Jump { target: BlockId(3) });
    let mut merge = MBlock::new(BlockId(3));
    merge.phis.push(PhiNode {
        dst: VReg(3),
        sources: vec![(BlockId(1), VReg(1)), (BlockId(2), VReg(2))],
    });
    merge.push(MInst::Return);
    func.vregs.alloc();
    func.spill_descs.push(SpillDesc::transient());
    func.blocks = vec![entry, left, right, merge];

    simplify_cfg(&mut func);

    assert_eq!(func.verify_result(), Ok(()));
    assert_eq!(func.blocks.len(), 4);
}

#[test]
fn simplify_cfg_folds_a_jump_table_whose_redirected_targets_are_equal() {
    let mut func = make_func(Vec::new(), 3);
    let mut entry = MBlock::new(BlockId(0));
    entry.push(MInst::LoadImm {
        dst: VReg(0),
        value: 7,
    });
    entry.push(MInst::LoadImm {
        dst: VReg(1),
        value: 0,
    });
    entry.push(MInst::LoadImm {
        dst: VReg(2),
        value: 0,
    });
    entry.push(MInst::JumpTable {
        index: VReg(0),
        table_base: VReg(1),
        target: VReg(2),
        targets: vec![BlockId(1), BlockId(2)].into_boxed_slice(),
    });
    let mut case_a = MBlock::new(BlockId(1));
    case_a.push(MInst::Jump { target: BlockId(3) });
    let mut case_b = MBlock::new(BlockId(2));
    case_b.push(MInst::Jump { target: BlockId(3) });
    let mut target = MBlock::new(BlockId(3));
    target.push(MInst::Return);
    func.blocks = vec![entry, case_a, case_b, target];

    simplify_cfg(&mut func);
    dead_code_eliminate(&mut func);

    assert_eq!(func.verify_result(), Ok(()));
    assert!(matches!(
        func.blocks[0].insts.as_slice(),
        [MInst::Jump { target: BlockId(3) }]
    ));
    assert_eq!(func.blocks.len(), 2);
}

#[test]
fn simplify_cfg_folds_an_equal_target_branch_without_redirects() {
    let mut func = make_func(
        vec![
            MInst::LoadImm {
                dst: VReg(0),
                value: 1,
            },
            MInst::Branch {
                cond: VReg(0),
                true_bb: BlockId(1),
                false_bb: BlockId(1),
            },
        ],
        1,
    );
    let mut target = MBlock::new(BlockId(1));
    target.push(MInst::Return);
    func.blocks.push(target);

    simplify_cfg(&mut func);
    dead_code_eliminate(&mut func);

    assert_eq!(func.verify_result(), Ok(()));
    assert!(matches!(
        func.blocks[0].insts.as_slice(),
        [MInst::Jump { target: BlockId(1) }]
    ));
}