sim-lib-machine 0.1.0

Neutral policy contracts for bounded decoded-instruction machines.
Documentation
use sim_kernel::{CodecId, Origin, SourceId, Span};
use sim_lib_machine::{
    BranchTarget, CodeError, CoverageMetadata, InstructionPolicy, LocatedCode, LocatedInstruction,
    RegionSpec, SourceLocation, TargetLocation,
};

#[derive(Clone, Debug, PartialEq, Eq)]
struct Instruction {
    id: u16,
}

struct Policy;

impl InstructionPolicy for Policy {
    type Instruction = Instruction;
    type InstructionId = u16;

    fn instruction_id(instruction: &Instruction) -> u16 {
        instruction.id
    }
}

#[test]
fn freezes_ids_locations_targets_regions_safepoints_and_coverage() {
    let code = LocatedCode::<Policy>::freeze(
        vec![
            instruction(10, 0, 2, false, None),
            instruction(20, 2, 5, true, Some(CoverageMetadata { counter: 7 })),
            instruction(30, 5, 6, false, None),
        ],
        vec![BranchTarget {
            from: 10,
            to: TargetLocation::Byte {
                source: source(),
                offset: 5,
            },
        }],
        vec![RegionSpec {
            start: 10,
            end: Some(20),
            handler: TargetLocation::Instruction(30),
        }],
    )
    .unwrap();

    let entry = code.entry();
    assert_eq!(code.instruction(entry).id(), &10);
    let second = code.next(entry).unwrap();
    assert!(code.instruction(second).is_safepoint());
    assert_eq!(
        code.instruction(second).coverage(),
        Some(CoverageMetadata { counter: 7 })
    );
    assert_eq!(code.branch_targets(10), &[code.cursor(30).unwrap()]);
    assert_eq!(
        code.protected_regions()[0].handler,
        code.cursor(30).unwrap()
    );
}

#[test]
fn branch_target_inside_instruction_names_branch_and_container() {
    let result = LocatedCode::<Policy>::freeze(
        vec![
            instruction(10, 0, 2, false, None),
            instruction(20, 2, 5, false, None),
        ],
        vec![BranchTarget {
            from: 10,
            to: TargetLocation::Byte {
                source: source(),
                offset: 3,
            },
        }],
        vec![],
    );

    assert!(matches!(
        result,
        Err(CodeError::InteriorTarget {
            from: 10,
            target: 3,
            containing: 20
        })
    ));
}

#[test]
fn validates_unreachable_locations_and_region_boundaries() {
    let malformed = LocatedCode::<Policy>::freeze(
        vec![
            instruction(10, 0, 2, false, None),
            instruction(20, 4, 4, false, None),
        ],
        vec![],
        vec![],
    );
    assert!(matches!(
        malformed,
        Err(CodeError::MalformedLocation {
            instruction: 20,
            start: 4,
            end: 4
        })
    ));

    let bad_handler = LocatedCode::<Policy>::freeze(
        vec![
            instruction(10, 0, 2, false, None),
            instruction(20, 2, 5, false, None),
        ],
        vec![],
        vec![RegionSpec {
            start: 10,
            end: Some(20),
            handler: TargetLocation::Byte {
                source: source(),
                offset: 3,
            },
        }],
    );
    assert!(matches!(
        bad_handler,
        Err(CodeError::InteriorTarget {
            from: 10,
            target: 3,
            containing: 20
        })
    ));
}

#[test]
fn token_locations_and_overlapping_regions_are_checked() {
    let instructions = vec![
        token_instruction(10, 0, 1),
        token_instruction(20, 1, 2),
        token_instruction(30, 2, 3),
    ];
    let result = LocatedCode::<Policy>::freeze(
        instructions,
        vec![],
        vec![
            RegionSpec {
                start: 10,
                end: Some(30),
                handler: TargetLocation::Token {
                    source: source(),
                    index: 2,
                },
            },
            RegionSpec {
                start: 20,
                end: None,
                handler: TargetLocation::Instruction(10),
            },
        ],
    );
    assert!(matches!(
        result,
        Err(CodeError::OverlappingRegions {
            first_start: 10,
            second_start: 20
        })
    ));
}

fn instruction(
    id: u16,
    start: usize,
    end: usize,
    safepoint: bool,
    coverage: Option<CoverageMetadata>,
) -> LocatedInstruction<Instruction, u16> {
    LocatedInstruction::new(
        Instruction { id },
        id,
        SourceLocation::Bytes(origin(start, end)),
        safepoint,
        coverage,
    )
}

fn token_instruction(id: u16, start: usize, end: usize) -> LocatedInstruction<Instruction, u16> {
    LocatedInstruction::new(
        Instruction { id },
        id,
        SourceLocation::Tokens {
            origin: origin(0, 10),
            start,
            end,
        },
        false,
        None,
    )
}

fn source() -> SourceId {
    SourceId("machine-test".into())
}

fn origin(start: usize, end: usize) -> Origin {
    Origin {
        codec: CodecId(1),
        source: source(),
        span: Span { start, end },
        trivia: vec![],
    }
}