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