hara-native 0.1.17

HAL-free native host runtime and package launcher for Hara
Documentation
use crate::{eval_bytecode_native, Runtime};

fn agrees(source: &str) {
    let expected = Runtime::new().eval_native(source).unwrap();
    assert_eq!(eval_bytecode_native(source).unwrap(), expected, "{source}");
}

#[test]
fn hot_arithmetic_branch_and_nested_loops_match_the_evaluator() {
    for source in [
        "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc i)) acc))",
        "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (if (< (mod i 2) 1) 3 7))) acc))",
        "(loop [i 0 acc 1] (if (< i 5000) (recur (+ i 1) (mod (+ acc i) 1000003)) acc))",
        "(loop [i 0 total 0] (if (< i 100) (recur (+ i 1) (+ total (loop [j 0 subtotal 0] (if (< j 100) (recur (+ j 1) (+ subtotal j)) subtotal)))) total))",
    ] {
        agrees(source);
    }
}

#[test]
fn hot_loop_overflow_deopts_to_promoted_integer_arithmetic() {
    let source = "(loop [i 0 x 1] (if (< i 30) (recur (+ i 1) (* x 1000000000)) x))";
    agrees(source);
    let value = eval_bytecode_native(source).unwrap();
    assert!(value.len() > 200, "{value}");
}

#[test]
fn compiled_traces_survive_repeated_execution_of_one_program() {
    let program =
        crate::compile_bytecode("(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc i)) acc))")
            .unwrap();
    assert_eq!(crate::execute_bytecode(&program).unwrap(), "12497500");
    assert!(crate::vm::machine::cached_trace_count(&program) > 0);
    assert_eq!(crate::execute_bytecode(&program).unwrap(), "12497500");
    assert!(crate::vm::machine::cached_trace_count(&program) > 0);
}

#[test]
fn telemetry_distinguishes_hot_compilation_and_execution() {
    let program =
        crate::compile_bytecode("(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc i)) acc))")
            .unwrap();
    assert_eq!(crate::execute_bytecode(&program).unwrap(), "12497500");
    let telemetry = crate::bytecode_jit_telemetry(&program);
    assert!(telemetry.backedges >= 16, "{telemetry:?}");
    assert_eq!(telemetry.compile_attempts, 1, "{telemetry:?}");
    assert_eq!(telemetry.compiled, 1, "{telemetry:?}");
    assert_eq!(telemetry.rejected, 0, "{telemetry:?}");
    assert!(telemetry.entries > 0, "{telemetry:?}");
}

#[test]
fn indexed_numeric_vectors_trace_from_constants_and_locals() {
    for source in [
        "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (nth [3 5 7 11] (mod i 4)))) acc))",
        "(let [values [3 5 7 11]] (loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (nth values (mod i 4)))) acc)))",
    ] {
        agrees(source);
        let program = crate::compile_bytecode(source).unwrap();
        let function = &program.functions[usize::from(program.entry)];
        let (backedge, header) = function
            .code
            .iter()
            .enumerate()
            .rev()
            .find_map(|(ip, instruction)| match instruction {
                crate::vm::Instruction::Jump(target) if usize::try_from(*target).ok()? <= ip => {
                    Some((ip as u32, *target))
                }
                _ => None,
            })
            .unwrap();
        let mut locals = vec![crate::jit::TraceValue::I64(64); usize::from(function.local_count)];
        if source.starts_with("(let") {
            locals[0] = crate::jit::TraceValue::Indexed(Box::new(crate::core::Value::Vector(
                [3, 5, 7, 11]
                    .into_iter()
                    .map(crate::core::Value::Number)
                    .collect(),
            )));
        }
        let recorded = crate::jit::TraceRecorder::new(4096).record_loop(
            &program,
            program.entry,
            header,
            backedge,
            &locals,
        );
        assert!(
            recorded.is_ok(),
            "vector loop was rejected: {recorded:?}; code: {:?}; constants: {:?}",
            function.code, program.constants
        );
        assert_eq!(crate::execute_bytecode(&program).unwrap(), "32500");
        assert!(
            crate::vm::machine::cached_trace_count(&program) > 0,
            "vector loop did not compile: {source}"
        );
    }
}

#[test]
fn unsupported_vectors_and_late_bounds_errors_fall_back_to_vm_semantics() {
    agrees(
        "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (count (nth [\"ab\"] 0)))) acc))",
    );

    let values = (0..256)
        .map(|value| value.to_string())
        .collect::<Vec<_>>()
        .join(" ");
    let source = format!("(loop [i 0] (if (< i 5000) (do (nth [{values}] i) (recur (+ i 1))) i))");
    let evaluator = Runtime::new().eval_native(&source).unwrap_err();
    let vm = eval_bytecode_native(&source).unwrap_err();
    assert!(evaluator.contains("nth index out of bounds"), "{evaluator}");
    assert!(vm.contains("nth index out of bounds"), "{vm}");
}

#[test]
fn unsupported_primary_path_disables_repeated_trace_collection() {
    let source = "(loop [i 0 value {}] (if (< i 500) (recur (+ i 1) (assoc value i (+ i 1))) (get value 499)))";
    let program = crate::compile_bytecode(source).unwrap();

    assert_eq!(crate::execute_bytecode(&program).unwrap(), "500");
    let first = crate::bytecode_jit_telemetry(&program);
    assert_eq!(first.compile_attempts, 1, "{first:?}");
    assert_eq!(first.compiled, 0, "{first:?}");
    assert_eq!(first.rejected, 1, "{first:?}");
    assert_eq!(first.disabled_loops, 1, "{first:?}");

    assert_eq!(crate::execute_bytecode(&program).unwrap(), "500");
    let second = crate::bytecode_jit_telemetry(&program);
    assert_eq!(
        second.compile_attempts, first.compile_attempts,
        "{second:?}"
    );
    assert_eq!(second.rejected, first.rejected, "{second:?}");
    assert_eq!(second.disabled_loops, first.disabled_loops, "{second:?}");
}

#[test]
fn dynamic_paths_compile_both_directions_of_an_alternating_branch() {
    let source = "(loop [i 0 flag true acc 0] (if (< i 5000) (if flag (recur (+ i 1) false (+ acc 3)) (recur (+ i 1) true (+ acc 7))) acc))";
    agrees(source);
    let program = crate::compile_bytecode(source).unwrap();
    for path in [
        vec![6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
        vec![6, 7, 8, 9, 17, 18, 19, 20, 21, 22, 23],
    ] {
        let recorded = crate::jit::TraceRecorder::new(4096).record_path(
            &program,
            program.entry,
            6,
            &path,
            &[
                crate::jit::TraceValue::I64(64),
                crate::jit::TraceValue::Bool(true),
                crate::jit::TraceValue::I64(0),
            ],
        );
        assert!(recorded.is_ok(), "{path:?}: {recorded:?}");
        #[cfg(all(feature = "native-jit", not(target_arch = "wasm32")))]
        {
            use crate::jit::TraceBackend;
            let compiled = crate::jit::NativeBackend::default().compile(&recorded.unwrap());
            assert!(compiled.is_ok(), "{path:?}: {:?}", compiled.err());
        }
    }
    assert_eq!(crate::execute_bytecode(&program).unwrap(), "25000");
    let telemetry = crate::bytecode_jit_telemetry(&program);
    assert!(
        telemetry.trace_paths >= 2,
        "{telemetry:?}\n{}",
        crate::vm::disassemble(&program)
    );
    assert!(telemetry.branch_exits > 0, "{telemetry:?}");
    assert_eq!(telemetry.disabled_loops, 1, "{telemetry:?}");
}

#[test]
fn division_and_numeric_sequence_navigation_trace() {
    for (source, expected) in [
        (
            "(loop [i 1 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (/ i 3))) acc))",
            "4164167",
        ),
        (
            "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (count [3 5 7 11]))) acc))",
            "20000",
        ),
        (
            "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (first (rest [3 5 7 11])))) acc))",
            "25000",
        ),
        (
            "(loop [i 0 acc 0] (if (< i 5000) (recur (+ i 1) (+ acc (second [3 5 7 11]))) acc))",
            "25000",
        ),
    ] {
        agrees(source);
        let program = crate::compile_bytecode(source).unwrap();
        assert_eq!(crate::execute_bytecode(&program).unwrap(), expected);
        assert!(
            crate::bytecode_jit_telemetry(&program).compiled > 0,
            "loop did not compile: {source}"
        );
    }
}

#[test]
fn divide_and_remainder_edges_deopt_to_exact_semantics() {
    let division = "(loop [i 0 x -9223372036854775808] (if (< i 100) (recur (+ i 1) (/ x -1)) x))";
    agrees(division);
    assert_eq!(
        eval_bytecode_native(division).unwrap(),
        "-9223372036854775808"
    );

    let modulo = "(loop [i 0 x -9223372036854775808] (if (< i 100) (recur (+ i 1) (mod x -1)) x))";
    agrees(modulo);
    assert_eq!(eval_bytecode_native(modulo).unwrap(), "0");
}