1use crate::format_printer::FormatPrinter;
2use crate::trace_context::TraceContext;
3use crate::trace_event::*;
4use crate::TypeKind;
5use tracing::{debug, warn};
6use zerocopy::FromBytes;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum EventSource {
11 #[default]
14 RingBuf,
15 PerfEventArray,
18}
19
20#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
22pub enum ParsedInstruction {
23 PrintString {
24 content: String,
25 },
26 PrintVariable {
27 name: String,
28 type_encoding: TypeKind,
29 formatted_value: String,
30 raw_data: Vec<u8>,
31 },
32 ExprError {
34 expr: String,
35 error_code: u8,
36 flags: u8,
37 failing_addr: u64,
38 },
39 PrintComplexFormat {
40 formatted_output: String,
41 },
42 PrintComplexVariable {
43 name: String,
44 access_path: String,
45 type_index: u16,
46 formatted_value: String,
47 raw_data: Vec<u8>,
48 },
49 Backtrace {
50 depth: u8,
51 },
52 EndInstruction {
53 total_instructions: u16,
54 execution_status: u8,
55 },
56}
57
58#[derive(Debug, Clone)]
60pub struct ParsedTraceEvent {
61 pub trace_id: u64,
62 pub timestamp: u64,
63 pub pid: u32,
64 pub tid: u32,
65 pub instructions: Vec<ParsedInstruction>,
66}
67
68impl ParsedTraceEvent {
69 pub fn to_formatted_output(&self) -> Vec<String> {
72 let mut output = Vec::new();
73 let mut i = 0;
74
75 while i < self.instructions.len() {
76 match &self.instructions[i] {
77 ParsedInstruction::PrintString { content } => {
78 if content.contains("{}") {
80 let (formatted, consumed) =
82 self.format_string_with_variables(content, i + 1);
83 output.push(formatted);
84 i += consumed; } else {
86 output.push(content.clone());
88 i += 1;
89 }
90 }
91
92 ParsedInstruction::EndInstruction { .. } => {
93 i += 1;
95 }
96 instruction => {
97 output.push(instruction.to_display_string());
99 i += 1;
100 }
101 }
102 }
103
104 output
105 }
106
107 fn format_string_with_variables(
109 &self,
110 format_string: &str,
111 start_index: usize,
112 ) -> (String, usize) {
113 let placeholder_count = format_string.matches("{}").count();
115
116 let mut variables = Vec::new();
118 let mut consumed = 1; for i in start_index..(start_index + placeholder_count).min(self.instructions.len()) {
121 if let ParsedInstruction::PrintVariable {
122 formatted_value, ..
123 } = &self.instructions[i]
124 {
125 variables.push(formatted_value.clone());
126 consumed += 1;
127 } else {
128 break; }
130 }
131
132 let mut result = format_string.to_string();
134 for value in variables {
135 if let Some(pos) = result.find("{}") {
136 result.replace_range(pos..pos + 2, &value);
137 }
138 }
139
140 (result, consumed)
141 }
142}
143
144#[derive(Debug, Clone)]
146pub enum ParseState {
147 WaitingForHeader,
148 WaitingForMessage {
149 header: TraceEventHeader,
150 },
151 WaitingForInstructions {
152 header: TraceEventHeader,
153 message: TraceEventMessage,
154 instructions: Vec<ParsedInstruction>,
155 },
156 Complete,
157}
158
159pub struct StreamingTraceParser {
162 parse_state: ParseState,
163 buffer: Vec<u8>,
164 event_source: EventSource,
165}
166
167impl Default for StreamingTraceParser {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl StreamingTraceParser {
174 pub fn new() -> Self {
177 Self::with_event_source(EventSource::RingBuf)
178 }
179
180 pub fn with_event_source(event_source: EventSource) -> Self {
182 Self {
183 parse_state: ParseState::WaitingForHeader,
184 buffer: Vec::with_capacity(1024),
185 event_source,
186 }
187 }
188
189 pub fn process_segment(
192 &mut self,
193 data: &[u8],
194 trace_context: &TraceContext,
195 ) -> Result<Option<ParsedTraceEvent>, String> {
196 self.buffer.extend_from_slice(data);
198
199 debug!(
200 "Processing segment of {} bytes, buffer now has {} bytes, state: {:?}",
201 data.len(),
202 self.buffer.len(),
203 self.parse_state
204 );
205
206 loop {
208 let consumed = match &self.parse_state {
209 ParseState::WaitingForHeader => {
210 let (header, _rest) = match TraceEventHeader::read_from_prefix(&self.buffer) {
212 Ok((h, r)) => (h, r),
213 Err(_) => {
214 debug!(
215 "Waiting for more data for header (have {} bytes, need {})",
216 self.buffer.len(),
217 std::mem::size_of::<TraceEventHeader>()
218 );
219 return Ok(None);
220 }
221 };
222
223 let magic = header.magic;
225 if magic != crate::consts::MAGIC {
226 return Err(format!("Invalid magic number: 0x{magic:x}"));
227 }
228
229 debug!("Received valid header: magic=0x{magic:x}");
230 self.parse_state = ParseState::WaitingForMessage { header };
231 std::mem::size_of::<TraceEventHeader>()
232 }
233
234 ParseState::WaitingForMessage { header } => {
235 let (message, _rest) = match TraceEventMessage::read_from_prefix(&self.buffer) {
237 Ok((m, r)) => (m, r),
238 Err(_) => {
239 debug!(
240 "Waiting for more data for message (have {} bytes, need {})",
241 self.buffer.len(),
242 std::mem::size_of::<TraceEventMessage>()
243 );
244 return Ok(None);
245 }
246 };
247
248 let trace_id = message.trace_id;
250 let pid = message.pid;
251 let tid = message.tid;
252 debug!(
253 "Received message: trace_id={}, pid={}, tid={}",
254 trace_id, pid, tid
255 );
256
257 self.parse_state = ParseState::WaitingForInstructions {
258 header: *header,
259 message,
260 instructions: Vec::new(),
261 };
262 std::mem::size_of::<TraceEventMessage>()
263 }
264
265 ParseState::WaitingForInstructions {
266 header,
267 message,
268 instructions,
269 } => {
270 match self.try_parse_instruction(&self.buffer, trace_context)? {
272 Some((parsed_instruction, consumed_bytes)) => {
273 let mut new_instructions = instructions.clone();
274
275 if matches!(
277 parsed_instruction,
278 ParsedInstruction::EndInstruction { .. }
279 ) {
280 new_instructions.push(parsed_instruction);
281
282 let complete_event = ParsedTraceEvent {
284 trace_id: message.trace_id,
285 timestamp: message.timestamp,
286 pid: message.pid,
287 tid: message.tid,
288 instructions: new_instructions,
289 };
290
291 debug!(
292 "Completed trace event with {} instructions",
293 complete_event.instructions.len()
294 );
295
296 self.parse_state = ParseState::WaitingForHeader;
298
299 match self.event_source {
301 EventSource::RingBuf => {
302 self.buffer.drain(..consumed_bytes);
304 debug!(
305 "RingBuf mode: consumed {} bytes, {} bytes remain in buffer",
306 consumed_bytes,
307 self.buffer.len()
308 );
309 }
310 EventSource::PerfEventArray => {
311 let residual = self.buffer.len() - consumed_bytes;
313 if residual > 0 {
314 warn!(
315 "PerfEventArray mode: discarding {} residual bytes after event",
316 residual
317 );
318 }
319 self.buffer.clear();
320 debug!("PerfEventArray mode: cleared buffer after complete event");
321 }
322 }
323
324 return Ok(Some(complete_event));
325 } else {
326 new_instructions.push(parsed_instruction);
328
329 self.parse_state = ParseState::WaitingForInstructions {
330 header: *header,
331 message: *message,
332 instructions: new_instructions,
333 };
334 consumed_bytes
335 }
336 }
337 None => {
338 debug!("Waiting for more data for instruction");
339 return Ok(None);
340 }
341 }
342 }
343
344 ParseState::Complete => {
345 warn!("Received data while in Complete state, resetting");
346 self.parse_state = ParseState::WaitingForHeader;
347 continue;
348 }
349 };
350
351 if consumed > 0 {
353 self.buffer.drain(..consumed);
354 debug!(
355 "Consumed {} bytes, buffer now has {} bytes",
356 consumed,
357 self.buffer.len()
358 );
359 }
360 }
361 }
362
363 fn try_parse_instruction(
366 &self,
367 data: &[u8],
368 trace_context: &TraceContext,
369 ) -> Result<Option<(ParsedInstruction, usize)>, String> {
370 let (inst_header, _rest) = match InstructionHeader::read_from_prefix(data) {
372 Ok((h, r)) => (h, r),
373 Err(_) => return Ok(None),
374 };
375
376 let expected_total_size =
377 std::mem::size_of::<InstructionHeader>() + inst_header.data_length as usize;
378 if data.len() < expected_total_size {
379 debug!(
380 "Waiting for complete instruction: have {} bytes, need {} bytes",
381 data.len(),
382 expected_total_size
383 );
384 return Ok(None);
385 }
386
387 let inst_data = &data[std::mem::size_of::<InstructionHeader>()..expected_total_size];
388
389 let instruction = match inst_header.inst_type {
390 t if t == InstructionType::PrintStringIndex as u8 => {
391 let (data_struct, _) = PrintStringIndexData::read_from_prefix(inst_data)
392 .map_err(|_| "Invalid PrintStringIndex data".to_string())?;
393
394 let string_index = data_struct.string_index;
395 let string_content = trace_context
396 .get_string(string_index)
397 .ok_or_else(|| format!("Invalid string index: {string_index}"))?;
398
399 ParsedInstruction::PrintString {
400 content: string_content.to_string(),
401 }
402 }
403
404 t if t == InstructionType::PrintVariableIndex as u8 => {
405 let (data_struct, _) = PrintVariableIndexData::read_from_prefix(inst_data)
406 .map_err(|_| "Invalid PrintVariableIndex data".to_string())?;
407
408 let var_name_index = data_struct.var_name_index;
409 let var_name = trace_context
410 .get_variable_name(var_name_index)
411 .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
412
413 let var_data_offset = std::mem::size_of::<PrintVariableIndexData>();
414 if inst_data.len() < var_data_offset + data_struct.data_len as usize {
415 return Err("Invalid variable data length".to_string());
416 }
417
418 let var_data =
419 &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
420
421 let type_encoding =
422 TypeKind::from_u8(data_struct.type_encoding).unwrap_or(TypeKind::Unknown);
423
424 let type_index = data_struct.type_index; tracing::debug!("streaming_parser - type_index = {}", type_index);
427 tracing::debug!(
428 "streaming_parser - TraceContext has {} types",
429 trace_context.types.len()
430 );
431
432 let formatted_value = match trace_context.get_type(type_index) {
433 Some(type_info) => {
434 tracing::debug!(
435 "streaming_parser - Found type_info for index {}",
436 type_index
437 );
438 crate::format_printer::FormatPrinter::format_data_with_type_info(
440 var_data, type_info,
441 )
442 }
443 None => {
444 tracing::debug!(
445 "streaming_parser - No type_info found for index {}",
446 type_index
447 );
448 format!(
450 "<COMPILER_ERROR: type_index {type_index} not found in TraceContext>"
451 )
452 }
453 };
454
455 ParsedInstruction::PrintVariable {
456 name: var_name.to_string(),
457 type_encoding,
458 formatted_value,
459 raw_data: var_data.to_vec(),
460 }
461 }
462
463 t if t == InstructionType::ExprError as u8 => {
464 let (data_struct, _) =
465 crate::trace_event::ExprErrorData::read_from_prefix(inst_data)
466 .map_err(|_| "Invalid ExprError data".to_string())?;
467 let si = data_struct.string_index;
468 let expr = match trace_context.get_string(si) {
469 Some(s) => s.to_string(),
470 None => format!("<INVALID_EXPR_INDEX_{si}>"),
471 };
472 ParsedInstruction::ExprError {
473 expr,
474 error_code: data_struct.error_code,
475 flags: data_struct.flags,
476 failing_addr: data_struct.failing_addr,
477 }
478 }
479
480 t if t == InstructionType::PrintComplexFormat as u8 => {
481 let (format_data, _) = PrintComplexFormatData::read_from_prefix(inst_data)
482 .map_err(|_| "Invalid PrintComplexFormat data".to_string())?;
483
484 let mut complex_variables = Vec::new();
486 let mut data_offset = std::mem::size_of::<PrintComplexFormatData>();
487
488 for _ in 0..format_data.arg_count {
489 if data_offset + 7 > inst_data.len() {
490 return Err("Invalid PrintComplexFormat argument data".to_string());
491 }
492
493 let var_name_index =
495 u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
496 let type_index = u16::from_le_bytes([
497 inst_data[data_offset + 2],
498 inst_data[data_offset + 3],
499 ]);
500 let access_path_len = inst_data[data_offset + 4] as usize;
501 let status = inst_data[data_offset + 5];
502 data_offset += 6; if data_offset + access_path_len > inst_data.len() {
506 return Err("Invalid PrintComplexFormat access path".to_string());
507 }
508 let access_path_bytes = &inst_data[data_offset..data_offset + access_path_len];
509 let access_path = String::from_utf8_lossy(access_path_bytes).to_string();
510 data_offset += access_path_len;
511
512 if data_offset + 2 > inst_data.len() {
514 return Err("Invalid PrintComplexFormat data length".to_string());
515 }
516 let data_len =
517 u16::from_le_bytes([inst_data[data_offset], inst_data[data_offset + 1]]);
518 data_offset += 2;
519
520 if data_offset + data_len as usize > inst_data.len() {
522 return Err("Invalid PrintComplexFormat variable data".to_string());
523 }
524 let var_data = inst_data[data_offset..data_offset + data_len as usize].to_vec();
525 data_offset += data_len as usize;
526
527 complex_variables.push(crate::format_printer::ParsedComplexVariable {
528 var_name_index,
529 type_index,
530 access_path,
531 status,
532 data: var_data,
533 });
534 }
535
536 let formatted_output =
538 crate::format_printer::FormatPrinter::format_complex_print_data(
539 format_data.format_string_index,
540 &complex_variables,
541 trace_context,
542 );
543
544 ParsedInstruction::PrintComplexFormat { formatted_output }
545 }
546
547 t if t == InstructionType::Backtrace as u8 => {
548 if inst_data.is_empty() {
549 return Err("Invalid Backtrace data".to_string());
550 }
551
552 let depth = inst_data[0];
553 ParsedInstruction::Backtrace { depth }
554 }
555
556 t if t == InstructionType::PrintComplexVariable as u8 => {
557 let (data_struct, _) = PrintComplexVariableData::read_from_prefix(inst_data)
558 .map_err(|_| "Invalid PrintComplexVariable data".to_string())?;
559
560 let var_name_index = data_struct.var_name_index;
562 let var_name = trace_context
563 .get_variable_name(var_name_index)
564 .ok_or_else(|| format!("Invalid variable index: {var_name_index}"))?;
565
566 let access_path_len = data_struct.access_path_len as usize;
568 let struct_size = std::mem::size_of::<PrintComplexVariableData>();
569
570 if inst_data.len() < struct_size + access_path_len {
571 return Err("Invalid PrintComplexVariable access path length".to_string());
572 }
573
574 let access_path_bytes = &inst_data[struct_size..struct_size + access_path_len];
575 let access_path = String::from_utf8_lossy(access_path_bytes);
576
577 let var_data_offset = struct_size + access_path_len;
579 if inst_data.len() < var_data_offset + data_struct.data_len as usize {
580 return Err("Invalid PrintComplexVariable data length".to_string());
581 }
582
583 let var_data =
584 &inst_data[var_data_offset..var_data_offset + data_struct.data_len as usize];
585
586 let formatted_value = FormatPrinter::format_complex_variable_with_status(
588 var_name_index,
589 data_struct.type_index,
590 &access_path,
591 var_data,
592 data_struct.status,
593 trace_context,
594 );
595
596 ParsedInstruction::PrintComplexVariable {
597 name: var_name.to_string(),
598 access_path: access_path.to_string(),
599 type_index: data_struct.type_index,
600 formatted_value,
601 raw_data: var_data.to_vec(),
602 }
603 }
604
605 t if t == InstructionType::EndInstruction as u8 => {
606 let (data_struct, _) = EndInstructionData::read_from_prefix(inst_data)
607 .map_err(|_| "Invalid EndInstruction data".to_string())?;
608
609 ParsedInstruction::EndInstruction {
610 total_instructions: data_struct.total_instructions,
611 execution_status: data_struct.execution_status,
612 }
613 }
614
615 _ => {
616 return Err(format!(
617 "Unknown instruction type: {}",
618 inst_header.inst_type
619 ))
620 }
621 };
622
623 Ok(Some((instruction, expected_total_size)))
624 }
625
626 pub fn reset(&mut self) {
628 self.parse_state = ParseState::WaitingForHeader;
629 self.buffer.clear();
630 }
631
632 pub fn get_state(&self) -> &ParseState {
634 &self.parse_state
635 }
636}
637
638impl ParsedInstruction {
639 pub fn to_display_string(&self) -> String {
641 match self {
642 ParsedInstruction::PrintString { content } => {
643 format!("print \"{content}\"")
644 }
645 ParsedInstruction::PrintVariable {
646 name,
647 type_encoding,
648 formatted_value,
649 raw_data: _,
650 } => {
651 format!("{name} ({type_encoding:?}): {formatted_value}")
652 }
653 ParsedInstruction::ExprError {
654 expr,
655 error_code,
656 flags,
657 failing_addr,
658 } => {
659 let reason = match *error_code {
662 1 => "null deref",
663 2 => "read error",
664 3 => "access error",
665 4 => "truncated",
666 5 => "offsets unavailable",
667 6 => "zero length",
668 _ => "error",
669 };
670
671 fn readable_flags(expr: &str, flags: u8) -> Option<String> {
673 if flags == 0 {
674 return None;
675 }
676 let mut tags: Vec<&'static str> = Vec::new();
677 let is_memcmp = expr.contains("memcmp(");
678 let is_strncmp = expr.contains("strncmp(") || expr.contains("starts_with(");
679 if is_memcmp {
680 if (flags & 0x01) != 0 {
681 tags.push("first-arg read-fail");
682 }
683 if (flags & 0x02) != 0 {
684 tags.push("second-arg read-fail");
685 }
686 if (flags & 0x04) != 0 {
687 tags.push("len-clamped");
688 }
689 if (flags & 0x08) != 0 {
690 tags.push("len=0");
691 }
692 } else if is_strncmp {
693 if (flags & 0x01) != 0 {
694 tags.push("read-fail");
695 }
696 if (flags & 0x04) != 0 {
697 tags.push("len-clamped");
698 }
699 if (flags & 0x08) != 0 {
700 tags.push("len=0");
701 }
702 } else {
703 return Some(format!("0x{flags:02x}"));
705 }
706 if tags.is_empty() {
707 None
708 } else {
709 Some(tags.join(","))
710 }
711 }
712
713 let flags_text = readable_flags(expr, *flags);
714 let addr_text = if *failing_addr != 0 {
715 format!("at 0x{failing_addr:016x}")
716 } else {
717 "at NULL".to_string()
718 };
719 let base = format!("ExprError: {expr} ({reason} {addr_text}");
720 match flags_text {
721 Some(f) => format!("{base}, flags: {f})"),
722 None => format!("{base})"),
723 }
724 }
725
726 ParsedInstruction::PrintComplexFormat { formatted_output } => formatted_output.clone(),
727 ParsedInstruction::PrintComplexVariable {
728 name: _,
729 access_path: _,
730 type_index: _,
731 formatted_value,
732 raw_data: _,
733 } => {
734 formatted_value.clone()
736 }
737 ParsedInstruction::Backtrace { depth } => {
738 format!("backtrace({depth})")
739 }
740 ParsedInstruction::EndInstruction {
741 total_instructions,
742 execution_status,
743 } => {
744 let status_str = match *execution_status {
745 0 => "success",
746 1 => "partial_failure",
747 2 => "complete_failure",
748 _ => "unknown",
749 };
750 format!("end({total_instructions} instructions, {status_str})")
751 }
752 }
753 }
754
755 pub fn instruction_type(&self) -> String {
757 match self {
758 ParsedInstruction::PrintString { .. } => "PrintString".to_string(),
759 ParsedInstruction::PrintVariable { .. } => "PrintVariable".to_string(),
760 ParsedInstruction::ExprError { .. } => "ExprError".to_string(),
761
762 ParsedInstruction::PrintComplexFormat { .. } => "PrintComplexFormat".to_string(),
763 ParsedInstruction::PrintComplexVariable { .. } => "PrintComplexVariable".to_string(),
764 ParsedInstruction::Backtrace { .. } => "Backtrace".to_string(),
765 ParsedInstruction::EndInstruction { .. } => "EndInstruction".to_string(),
766 }
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 #[test]
775 fn test_streaming_parser() {
776 let mut trace_context = TraceContext::new();
777 let _str_idx = trace_context.add_string("hello world".to_string());
778
779 let mut parser = StreamingTraceParser::new();
780
781 let header = TraceEventHeader {
783 magic: crate::consts::MAGIC,
784 };
785
786 let message = TraceEventMessage {
787 trace_id: 12345,
788 timestamp: 1000,
789 pid: 1001,
790 tid: 2002,
791 };
792
793 let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
795 let result = parser
796 .process_segment(header_bytes, &trace_context)
797 .unwrap();
798 assert!(result.is_none()); let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
802 let result = parser
803 .process_segment(message_bytes, &trace_context)
804 .unwrap();
805 assert!(result.is_none()); }
811
812 #[test]
813 fn test_parse_exprerror_instruction() {
814 let mut trace_context = TraceContext::new();
815 let expr_idx = trace_context.add_string("memcmp(buf, hex(\"504f\"), 2)".to_string());
816
817 let mut parser = StreamingTraceParser::new();
818
819 let header = TraceEventHeader {
821 magic: crate::consts::MAGIC,
822 };
823 let header_bytes = zerocopy::IntoBytes::as_bytes(&header);
824 assert!(parser
825 .process_segment(header_bytes, &trace_context)
826 .unwrap()
827 .is_none());
828
829 let message = TraceEventMessage {
831 trace_id: 1,
832 timestamp: 0,
833 pid: 123,
834 tid: 456,
835 };
836 let message_bytes = zerocopy::IntoBytes::as_bytes(&message);
837 assert!(parser
838 .process_segment(message_bytes, &trace_context)
839 .unwrap()
840 .is_none());
841
842 let mut inst = Vec::new();
844 inst.push(InstructionType::ExprError as u8); inst.extend_from_slice(
847 &(std::mem::size_of::<crate::trace_event::ExprErrorData>() as u16).to_le_bytes(),
848 ); inst.push(0u8); inst.extend_from_slice(&expr_idx.to_le_bytes()); inst.push(1u8); inst.push(0u8); inst.extend_from_slice(&0x1234_5678_9abc_def0u64.to_le_bytes()); inst.push(InstructionType::EndInstruction as u8);
858 inst.extend_from_slice(&(std::mem::size_of::<EndInstructionData>() as u16).to_le_bytes());
859 inst.push(0u8); inst.extend_from_slice(&1u16.to_le_bytes()); inst.push(1u8); inst.push(0u8); let event = parser
867 .process_segment(&inst, &trace_context)
868 .unwrap()
869 .expect("complete event");
870 assert_eq!(event.trace_id, 1);
871 assert_eq!(event.pid, 123);
872 assert_eq!(event.tid, 456);
873 assert_eq!(event.instructions.len(), 2);
874 match &event.instructions[0] {
875 ParsedInstruction::ExprError {
876 expr,
877 error_code,
878 flags,
879 failing_addr,
880 } => {
881 assert_eq!(expr, "memcmp(buf, hex(\"504f\"), 2)");
882 assert_eq!(*error_code, 1);
883 assert_eq!(*flags, 0);
884 assert_eq!(*failing_addr, 0x1234_5678_9abc_def0u64);
885 }
886 other => panic!("unexpected first instruction: {other:?}"),
887 }
888 match &event.instructions[1] {
889 ParsedInstruction::EndInstruction {
890 total_instructions,
891 execution_status,
892 } => {
893 assert_eq!(*total_instructions, 1);
894 assert_eq!(*execution_status, 1); }
896 other => panic!("unexpected last instruction: {other:?}"),
897 }
898 }
899}