Skip to main content

vyre_runtime/megakernel/protocol/codec/
debug_log.rs

1use super::{debug, read_required_word, read_word_from_optional_words, validate_word_aligned};
2use super::{DebugRecord, ProtocolError};
3
4/// Decode PRINTF records out of the debug-log buffer.
5#[must_use]
6pub fn read_debug_log(debug_bytes: &[u8]) -> Vec<DebugRecord> {
7    try_read_debug_log(debug_bytes).unwrap_or_default()
8}
9
10/// Decode PRINTF records into caller-owned storage.
11///
12/// Clears `out`, then reuses its allocation.
13pub fn read_debug_log_into(debug_bytes: &[u8], out: &mut Vec<DebugRecord>) {
14    // Clearing to empty on a decode failure silently drops the PRINTF records
15    // the kernel emitted — the operator sees "no debug output" instead of the
16    // decode error (Law 10). Fail loud; callers use try_read_debug_log_into.
17    if let Err(error) = try_read_debug_log_into(debug_bytes, out) {
18        panic!("vyre-runtime debug-log decode failed: {error}");
19    }
20}
21
22/// Strictly decode PRINTF records out of the debug-log buffer.
23///
24/// # Errors
25///
26/// Returns [`ProtocolError`] when the buffer is not word-aligned, too short for
27/// the cursor word, or the cursor points at a partial record.
28pub fn try_read_debug_log(debug_bytes: &[u8]) -> Result<Vec<DebugRecord>, ProtocolError> {
29    let mut records = Vec::new();
30    try_read_debug_log_into(debug_bytes, &mut records)?;
31    Ok(records)
32}
33
34/// Strictly decode PRINTF records into caller-owned storage.
35///
36/// Clears `out`, then reuses its allocation.
37///
38/// # Errors
39///
40/// Returns [`ProtocolError`] when the buffer is not word-aligned, too short for
41/// the cursor word, or the cursor points at a partial record.
42pub fn try_read_debug_log_into(
43    debug_bytes: &[u8],
44    out: &mut Vec<DebugRecord>,
45) -> Result<(), ProtocolError> {
46    validate_word_aligned("debug_log", debug_bytes)?;
47    let cursor = read_required_word(
48        "debug_log",
49        debug_bytes,
50        debug_word_index(debug::CURSOR_WORD, "cursor word")?,
51    )?;
52    let record_words = debug_word_index(debug::RECORD_WORDS, "record word count")?;
53    let records_start = debug_word_index(debug::RECORDS_BASE, "records base word")?;
54    let total_word_capacity = debug_bytes.len() / 4;
55    if total_word_capacity < records_start {
56        return Err(ProtocolError::MissingWord {
57            buffer: "debug_log",
58            word_idx: records_start,
59            byte_len: debug_bytes.len(),
60            fix: "build debug-log bytes with encode_empty_debug_log",
61        });
62    }
63    let capacity_words = total_word_capacity - records_start;
64    let cursor = usize::try_from(cursor).map_err(|_| ProtocolError::ByteLengthOverflow {
65        buffer: "debug_log",
66        fix: "debug-log cursor does not fit host usize; keep protocol buffers within host addressable range",
67    })?;
68    if cursor > capacity_words {
69        return Err(ProtocolError::MissingWord {
70            buffer: "debug_log",
71            word_idx: records_start + cursor,
72            byte_len: debug_bytes.len(),
73            fix: "debug-log cursor must stay within the encoded record capacity",
74        });
75    }
76    let available = cursor;
77    if available % record_words != 0 {
78        return Err(ProtocolError::MissingWord {
79            buffer: "debug_log",
80            word_idx: records_start + available,
81            byte_len: debug_bytes.len(),
82            fix: "debug-log cursor must advance in whole PRINTF records",
83        });
84    }
85    let record_count = available / record_words;
86    out.clear();
87    try_reserve_record_capacity(out, record_count)?;
88    let words = bytemuck::try_cast_slice::<u8, u32>(debug_bytes).ok();
89    for i in 0..record_count {
90        let w = records_start + i * record_words;
91        out.push(DebugRecord {
92            fmt_id: read_word_from_optional_words(words, debug_bytes, w).ok_or(
93                ProtocolError::MissingWord {
94                buffer: "debug_log",
95                word_idx: w,
96                byte_len: debug_bytes.len(),
97                fix: "decode only debug-log buffers produced by the matching megakernel protocol encoder",
98            })?,
99            args: [
100                read_word_from_optional_words(words, debug_bytes, w + 1).ok_or(
101                    ProtocolError::MissingWord {
102                    buffer: "debug_log",
103                    word_idx: w + 1,
104                    byte_len: debug_bytes.len(),
105                    fix: "decode only debug-log buffers produced by the matching megakernel protocol encoder",
106                })?,
107                read_word_from_optional_words(words, debug_bytes, w + 2).ok_or(
108                    ProtocolError::MissingWord {
109                    buffer: "debug_log",
110                    word_idx: w + 2,
111                    byte_len: debug_bytes.len(),
112                    fix: "decode only debug-log buffers produced by the matching megakernel protocol encoder",
113                })?,
114                read_word_from_optional_words(words, debug_bytes, w + 3).ok_or(
115                    ProtocolError::MissingWord {
116                    buffer: "debug_log",
117                    word_idx: w + 3,
118                    byte_len: debug_bytes.len(),
119                    fix: "decode only debug-log buffers produced by the matching megakernel protocol encoder",
120                })?,
121            ],
122        });
123    }
124    Ok(())
125}
126
127fn debug_word_index(word: u32, label: &'static str) -> Result<usize, ProtocolError> {
128    usize::try_from(word).map_err(|_| ProtocolError::ByteLengthOverflow {
129        buffer: "debug_log",
130        fix: match label {
131            "cursor word" => "debug-log cursor word cannot fit host usize",
132            "record word count" => "debug-log record word count cannot fit host usize",
133            "records base word" => "debug-log records base word cannot fit host usize",
134            _ => "debug-log word index cannot fit host usize",
135        },
136    })
137}
138
139fn try_reserve_record_capacity(
140    out: &mut Vec<DebugRecord>,
141    target_capacity: usize,
142) -> Result<(), ProtocolError> {
143    vyre_foundation::allocation::try_reserve_vec_to_capacity(out, target_capacity).map_err(|_| {
144        ProtocolError::ByteLengthOverflow {
145            buffer: "debug_log",
146            fix: "host debug-log decode could not reserve output records; reduce debug-log capacity or decode into a reused scratch vector",
147        }
148    })
149}