hara-native 0.1.13

HAL-free native host runtime and package launcher for Hara
Documentation
use std::collections::BTreeMap;

use crate::core::Value;
use crate::vm::machine::observation::{
    InstructionSnapshot, MachineSnapshot, ObservationEventKind, ObservationEventStatus,
    SourcePositionSnapshot,
};

pub const BYTECODE_METRICS_SCHEMA: &str = "hal.bytecode-metrics/0-alpha";
pub const BYTECODE_EVENTS_SCHEMA: &str = "hal.bytecode-events/0-alpha";
pub const BYTECODE_TRACE_SCHEMA: &str = "hal.bytecode-trace/0-alpha";

const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;

#[derive(Clone, Debug)]
pub(super) struct TraceStepRecord {
    pub id: String,
    pub sequence: u64,
    pub kind: ObservationEventKind,
    pub status: ObservationEventStatus,
    pub before: MachineSnapshot,
    pub after: MachineSnapshot,
    pub instruction: Option<InstructionSnapshot>,
    pub source: Option<SourcePositionSnapshot>,
    pub error: Option<String>,
}

impl TraceStepRecord {
    pub fn new(
        trace_id: &str,
        sequence: u64,
        kind: ObservationEventKind,
        status: ObservationEventStatus,
        before: MachineSnapshot,
        after: MachineSnapshot,
        instruction: Option<InstructionSnapshot>,
        source: Option<SourcePositionSnapshot>,
    ) -> Self {
        let error = after.error.clone();
        Self {
            id: format!("{trace_id}/step/{sequence}"),
            sequence,
            kind,
            status,
            before,
            after,
            instruction,
            source,
            error,
        }
    }
}

#[derive(Clone, Copy, Debug)]
pub(super) struct EvidenceLabel(&'static str);

impl From<&EvidenceLabel> for String {
    fn from(value: &EvidenceLabel) -> Self {
        value.0.into()
    }
}

#[derive(Clone, Debug)]
pub(super) enum CompactEventRecord {
    Instruction {
        id: String,
        sequence: u64,
        function: usize,
        ip: usize,
        opcode: String,
        stack_depth: usize,
        call_depth: usize,
    },
    Transition {
        id: String,
        sequence: u64,
        transition: EvidenceLabel,
        from_function: usize,
        from_ip: usize,
        to_function: usize,
        to_ip: usize,
        stack_depth: usize,
        call_depth: usize,
    },
    Terminal {
        id: String,
        sequence: u64,
        terminal: EvidenceLabel,
        function: usize,
        ip: usize,
        stack_depth: usize,
        call_depth: usize,
    },
}

impl CompactEventRecord {
    pub fn from_trace(trace_id: &str, step: &TraceStepRecord) -> Self {
        let id = format!("{trace_id}/event/{}", step.sequence);
        match step.kind {
            ObservationEventKind::InstructionExecute => Self::Instruction {
                id,
                sequence: step.sequence,
                function: step.before.function,
                ip: step.before.ip,
                opcode: step
                    .instruction
                    .as_ref()
                    .map_or_else(|| "unknown".into(), |instruction| instruction.opcode.into()),
                stack_depth: stack_depth(&step.before),
                call_depth: call_depth(&step.before),
            },
            ObservationEventKind::CallEnter
            | ObservationEventKind::CallReturn
            | ObservationEventKind::ExceptionUnwind
            | ObservationEventKind::MachineSuspend
            | ObservationEventKind::MachineYield
            | ObservationEventKind::MachineResume => Self::Transition {
                id,
                sequence: step.sequence,
                transition: EvidenceLabel(step.kind.as_keyword()),
                from_function: step.before.function,
                from_ip: step.before.ip,
                to_function: step.after.function,
                to_ip: step.after.ip,
                stack_depth: stack_depth(&step.after),
                call_depth: call_depth(&step.after),
            },
            ObservationEventKind::MachineReturn | ObservationEventKind::MachineFail => {
                Self::Terminal {
                    id,
                    sequence: step.sequence,
                    terminal: EvidenceLabel(step.kind.as_keyword()),
                    function: step.after.function,
                    ip: step.after.ip,
                    stack_depth: stack_depth(&step.after),
                    call_depth: call_depth(&step.after),
                }
            }
        }
    }
}

#[derive(Clone, Debug, Default)]
pub(super) struct SessionMetrics {
    pub instructions: u64,
    pub opcode_counts: BTreeMap<String, u64>,
    pub calls: u64,
    pub returns: u64,
    pub unwinds: u64,
    pub suspensions: u64,
    pub resumptions: u64,
    pub terminal_returns: u64,
    pub failures: u64,
    pub max_stack_depth: usize,
    pub max_call_depth: usize,
}

impl SessionMetrics {
    pub fn observe(&mut self, step: &TraceStepRecord, counted_instruction: bool) {
        if counted_instruction {
            if let Some(instruction) = &step.instruction {
                self.instructions = self.instructions.saturating_add(1);
                let count = self
                    .opcode_counts
                    .entry(instruction.opcode.into())
                    .or_default();
                *count = (*count).saturating_add(1);
                self.observe_depths(stack_depth(&step.before), call_depth(&step.before));
            }
        }
        match step.kind {
            ObservationEventKind::InstructionExecute => {}
            ObservationEventKind::CallEnter => {
                self.calls = self.calls.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::CallReturn => {
                self.returns = self.returns.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::ExceptionUnwind => {
                self.unwinds = self.unwinds.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::MachineSuspend => {
                self.suspensions = self.suspensions.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::MachineYield => {
                self.suspensions = self.suspensions.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::MachineResume => {
                self.resumptions = self.resumptions.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::MachineReturn => {
                self.terminal_returns = self.terminal_returns.saturating_add(1);
                self.observe_after(step);
            }
            ObservationEventKind::MachineFail => {
                self.failures = self.failures.saturating_add(1);
                self.observe_after(step);
            }
        }
    }

    fn observe_after(&mut self, step: &TraceStepRecord) {
        self.observe_depths(stack_depth(&step.after), call_depth(&step.after));
    }

    fn observe_depths(&mut self, stack_depth: usize, call_depth: usize) {
        self.max_stack_depth = self.max_stack_depth.max(stack_depth);
        self.max_call_depth = self.max_call_depth.max(call_depth);
    }
}

#[path = "evidence/document.rs"]
mod document;

pub(super) fn metrics_document(
    session_id: &str,
    trace_id: &str,
    sequence: u64,
    status: &str,
    metrics: &SessionMetrics,
) -> Value {
    document::metrics_document(session_id, trace_id, sequence, status, metrics)
}

pub(super) fn events_document<'a>(
    session_id: &str,
    trace_id: &str,
    sequence: u64,
    status: &str,
    events: impl IntoIterator<Item = &'a CompactEventRecord>,
    dropped: u64,
) -> Value {
    document::events_document(session_id, trace_id, sequence, status, events, dropped)
}

pub(super) fn trace_document<'a>(
    session_id: &str,
    trace_id: &str,
    source_id: &str,
    sequence: u64,
    status: &str,
    steps: impl IntoIterator<Item = &'a TraceStepRecord>,
    dropped: u64,
) -> Value {
    document::trace_document(
        session_id, trace_id, source_id, sequence, status, steps, dropped,
    )
}

pub(super) fn snapshot_value(snapshot: &MachineSnapshot, source_id: &str) -> Value {
    document::snapshot_value(snapshot, source_id)
}

fn stack_depth(snapshot: &MachineSnapshot) -> usize {
    snapshot.stack.len().saturating_add(snapshot.stack_omitted)
}

fn call_depth(snapshot: &MachineSnapshot) -> usize {
    snapshot.calls.len().saturating_add(snapshot.calls_omitted)
}