runmat-vm 0.4.5

RunMat virtual machine and bytecode interpreter
Documentation
use crate::bytecode::instr::Instr;
use crate::bytecode::program::Bytecode;
use crate::interpreter::timing::InterpreterTiming;
use crate::runtime::gc::InterpretContext;
use crate::runtime::workspace::{
    refresh_workspace_state, set_workspace_state, take_pending_workspace_state, WorkspaceStateGuard,
};
use runmat_builtins::Value;
use runmat_runtime::RuntimeError;
use std::collections::HashSet;

pub fn prepare_workspace_guard(vars: &mut Vec<Value>) -> Option<WorkspaceStateGuard> {
    let pending_state = take_pending_workspace_state();
    let guard = pending_state.map(|(names, assigned)| {
        let filtered_assigned: HashSet<String> = assigned
            .into_iter()
            .filter(|name| names.contains_key(name))
            .collect();
        set_workspace_state(names, filtered_assigned, vars)
    });
    refresh_workspace_state(vars);
    guard
}

pub fn create_gc_context(
    stack: &Vec<Value>,
    vars: &Vec<Value>,
    thread_roots: Vec<Value>,
) -> Result<InterpretContext, String> {
    let mut gc_context = InterpretContext::new(stack, vars)?;
    let _ = gc_context.register_global_values(thread_roots, "thread_globals_persistents");
    Ok(gc_context)
}

pub fn debug_stack_enabled() -> bool {
    std::env::var("RUNMAT_DEBUG_STACK")
        .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
        .unwrap_or(false)
}

pub fn check_cancelled() -> Result<(), RuntimeError> {
    if runmat_runtime::interrupt::is_cancelled() {
        return Err(crate::interpreter::errors::mex(
            "ExecutionCancelled",
            "Execution cancelled by user",
        ));
    }
    Ok(())
}

pub fn note_pre_dispatch(
    interpreter_timing: &mut InterpreterTiming,
    debug_stack: bool,
    pc: usize,
    instr: &Instr,
    stack_len: usize,
) {
    interpreter_timing.note_host_instr(pc);
    if debug_stack {
        log::debug!(
            "[vm] instr pc={} instr={:?} stack_len={}",
            pc,
            instr,
            stack_len
        );
    }
}

#[cfg(feature = "native-accel")]
#[inline]
pub fn fusion_debug_enabled() -> bool {
    static FLAG: once_cell::sync::OnceCell<bool> = once_cell::sync::OnceCell::new();
    *FLAG.get_or_init(|| match std::env::var("RUNMAT_DEBUG_FUSION") {
        Ok(v) => v == "1" || v.eq_ignore_ascii_case("true") || v.eq_ignore_ascii_case("yes"),
        Err(_) => false,
    })
}

#[cfg(feature = "native-accel")]
pub fn log_fusion_span_window(
    plan: &runmat_accelerate::FusionGroupPlan,
    bytecode: &Bytecode,
    pc: usize,
) {
    if !fusion_debug_enabled() || !log::log_enabled!(log::Level::Debug) {
        return;
    }
    if bytecode.instructions.is_empty() {
        return;
    }
    let window = 3usize;
    let span = plan.group.span.clone();
    let total = bytecode.instructions.len();
    let start = span.start.saturating_sub(window);
    let mut end = span.end + window;
    if end >= total {
        end = total.saturating_sub(1);
    }
    if end < span.end {
        end = span.end;
    }
    let mut ops: Vec<String> = Vec::new();
    for idx in start..=end {
        let instr = &bytecode.instructions[idx];
        let mut tags: Vec<&'static str> = Vec::new();
        if idx == pc {
            tags.push("pc");
        }
        if idx == span.start {
            tags.push("start");
        }
        if idx == span.end {
            tags.push("end");
        }
        let tag_str = if tags.is_empty() {
            String::new()
        } else {
            format!("<{}>", tags.join(","))
        };
        ops.push(format!("{}{} {:?}", idx, tag_str, instr));
    }
    log::debug!(
        "fusion plan {} span window [{}..{}]: {}",
        plan.index,
        start,
        end,
        ops.join(" | ")
    );
}

#[cfg(feature = "native-accel")]
pub fn note_fusion_gate(
    interpreter_timing: &mut InterpreterTiming,
    plan: &runmat_accelerate::FusionGroupPlan,
    bytecode: &Bytecode,
    pc: usize,
    has_barrier: bool,
    live_result_count: Option<usize>,
) {
    let detail = format!(
        "plan={} kind={:?} span=[{}..{}]",
        plan.index, plan.group.kind, plan.group.span.start, plan.group.span.end
    );
    interpreter_timing.flush_host_span("before_fusion", Some(detail.as_str()));
    log_fusion_span_window(plan, bytecode, pc);
    if fusion_debug_enabled() {
        log::trace!(
            "fusion gate pc={} kind={:?} span={}..{} has_barrier={} live_results={:?}",
            pc,
            plan.group.kind,
            plan.group.span.start,
            plan.group.span.end,
            has_barrier,
            live_result_count
        );
    }
}

#[cfg(feature = "native-accel")]
pub fn note_fusion_skip(pc: usize, span: &runmat_accelerate::InstrSpan) {
    if fusion_debug_enabled() {
        log::debug!(
            "fusion skip at pc {}: side-effecting instrs in span {}..{}",
            pc,
            span.start,
            span.end
        );
    }
}