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