use alloc::{boxed::Box, sync::Arc, vec::Vec};
use miden_core::{
Word,
serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
};
use miden_debug_types::{ColumnNumber, LineNumber};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct DebugTypeIdx(u32);
impl DebugTypeIdx {
pub fn as_u32(self) -> u32 {
self.0
}
}
impl From<u32> for DebugTypeIdx {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<DebugTypeIdx> for u32 {
fn from(value: DebugTypeIdx) -> Self {
value.0
}
}
impl Serializable for DebugTypeIdx {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u32(self.0);
}
fn get_size_hint(&self) -> usize {
4
}
}
impl Deserializable for DebugTypeIdx {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
Ok(Self(source.read_u32()?))
}
fn min_serialized_size() -> usize {
4
}
}
pub const DEBUG_TYPES_VERSION: u8 = 1;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugTypesSection {
pub version: u8,
pub strings: Vec<Arc<str>>,
pub types: Vec<DebugTypeInfo>,
}
impl DebugTypesSection {
pub fn new() -> Self {
Self {
version: DEBUG_TYPES_VERSION,
strings: Vec::new(),
types: Vec::new(),
}
}
pub fn add_string(&mut self, s: Arc<str>) -> u32 {
if let Some(idx) = self.strings.iter().position(|existing| **existing == *s) {
return idx as u32;
}
let idx = self.strings.len() as u32;
self.strings.push(s);
idx
}
pub fn get_string(&self, idx: u32) -> Option<Arc<str>> {
self.strings.get(idx as usize).cloned()
}
pub fn add_type(&mut self, ty: DebugTypeInfo) -> DebugTypeIdx {
let idx = DebugTypeIdx(self.types.len() as u32);
self.types.push(ty);
idx
}
pub fn get_type(&self, idx: DebugTypeIdx) -> Option<&DebugTypeInfo> {
self.types.get(idx.0 as usize)
}
pub fn is_empty(&self) -> bool {
self.types.is_empty()
}
}
pub const DEBUG_SOURCES_VERSION: u8 = 1;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugSourcesSection {
pub version: u8,
pub strings: Vec<Arc<str>>,
pub files: Vec<DebugFileInfo>,
}
impl DebugSourcesSection {
pub fn new() -> Self {
Self {
version: DEBUG_SOURCES_VERSION,
strings: Vec::new(),
files: Vec::new(),
}
}
pub fn add_string(&mut self, s: Arc<str>) -> u32 {
if let Some(idx) = self.strings.iter().position(|existing| **existing == *s) {
return idx as u32;
}
let idx = self.strings.len() as u32;
self.strings.push(s);
idx
}
pub fn get_string(&self, idx: u32) -> Option<Arc<str>> {
self.strings.get(idx as usize).cloned()
}
pub fn add_file(&mut self, file: DebugFileInfo) -> u32 {
if let Some(idx) = self.files.iter().position(|existing| existing.path_idx == file.path_idx)
{
return idx as u32;
}
let idx = self.files.len() as u32;
self.files.push(file);
idx
}
pub fn get_file(&self, idx: u32) -> Option<&DebugFileInfo> {
self.files.get(idx as usize)
}
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
}
pub const DEBUG_FUNCTIONS_VERSION: u8 = 1;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugFunctionsSection {
pub version: u8,
pub strings: Vec<Arc<str>>,
pub functions: Vec<DebugFunctionInfo>,
}
impl DebugFunctionsSection {
pub fn new() -> Self {
Self {
version: DEBUG_FUNCTIONS_VERSION,
strings: Vec::new(),
functions: Vec::new(),
}
}
pub fn add_string(&mut self, s: Arc<str>) -> u32 {
if let Some(idx) = self.strings.iter().position(|existing| **existing == *s) {
return idx as u32;
}
let idx = self.strings.len() as u32;
self.strings.push(s);
idx
}
pub fn get_string(&self, idx: u32) -> Option<Arc<str>> {
self.strings.get(idx as usize).cloned()
}
pub fn add_function(&mut self, func: DebugFunctionInfo) {
self.functions.push(func);
}
pub fn is_empty(&self) -> bool {
self.functions.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DebugTypeInfo {
Primitive(DebugPrimitiveType),
Pointer {
pointee_type_idx: DebugTypeIdx,
},
Array {
element_type_idx: DebugTypeIdx,
count: Option<u32>,
},
Struct {
name_idx: u32,
size: u32,
fields: Vec<DebugFieldInfo>,
},
Function {
return_type_idx: Option<DebugTypeIdx>,
param_type_indices: Vec<DebugTypeIdx>,
},
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum DebugPrimitiveType {
Void = 0,
Bool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
I128,
U128,
F32,
F64,
Felt,
Word,
}
impl DebugPrimitiveType {
pub const fn size_in_bytes(self) -> u32 {
match self {
Self::Void => 0,
Self::Bool | Self::I8 | Self::U8 => 1,
Self::I16 | Self::U16 => 2,
Self::I32 | Self::U32 | Self::F32 => 4,
Self::I64 | Self::U64 | Self::F64 | Self::Felt => 8,
Self::I128 | Self::U128 => 16,
Self::Word => 32,
}
}
pub const fn size_in_felts(self) -> u32 {
match self {
Self::Void => 0,
Self::Bool
| Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::Felt => 1,
Self::I64 | Self::U64 | Self::F32 | Self::F64 => 2,
Self::I128 | Self::U128 | Self::Word => 4,
}
}
pub fn from_discriminant(discriminant: u8) -> Option<Self> {
match discriminant {
0 => Some(Self::Void),
1 => Some(Self::Bool),
2 => Some(Self::I8),
3 => Some(Self::U8),
4 => Some(Self::I16),
5 => Some(Self::U16),
6 => Some(Self::I32),
7 => Some(Self::U32),
8 => Some(Self::I64),
9 => Some(Self::U64),
10 => Some(Self::I128),
11 => Some(Self::U128),
12 => Some(Self::F32),
13 => Some(Self::F64),
14 => Some(Self::Felt),
15 => Some(Self::Word),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugFieldInfo {
pub name_idx: u32,
pub type_idx: DebugTypeIdx,
pub offset: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugFileInfo {
pub path_idx: u32,
pub checksum: Option<Box<[u8; 32]>>,
}
impl DebugFileInfo {
pub fn new(path_idx: u32) -> Self {
Self { path_idx, checksum: None }
}
pub fn with_checksum(mut self, checksum: [u8; 32]) -> Self {
self.checksum = Some(Box::new(checksum));
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugFunctionInfo {
pub name_idx: u32,
pub linkage_name_idx: Option<u32>,
pub file_idx: u32,
pub line: LineNumber,
pub column: ColumnNumber,
pub type_idx: Option<DebugTypeIdx>,
pub mast_root: Option<Word>,
pub variables: Vec<DebugVariableInfo>,
pub inlined_calls: Vec<DebugInlinedCallInfo>,
}
impl DebugFunctionInfo {
pub fn new(name_idx: u32, file_idx: u32, line: LineNumber, column: ColumnNumber) -> Self {
Self {
name_idx,
linkage_name_idx: None,
file_idx,
line,
column,
type_idx: None,
mast_root: None,
variables: Vec::new(),
inlined_calls: Vec::new(),
}
}
pub fn with_linkage_name(mut self, linkage_name_idx: u32) -> Self {
self.linkage_name_idx = Some(linkage_name_idx);
self
}
pub fn with_type(mut self, type_idx: DebugTypeIdx) -> Self {
self.type_idx = Some(type_idx);
self
}
pub fn with_mast_root(mut self, mast_root: Word) -> Self {
self.mast_root = Some(mast_root);
self
}
pub fn add_variable(&mut self, variable: DebugVariableInfo) {
self.variables.push(variable);
}
pub fn add_inlined_call(&mut self, call: DebugInlinedCallInfo) {
self.inlined_calls.push(call);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugVariableInfo {
pub name_idx: u32,
pub type_idx: DebugTypeIdx,
pub arg_index: u32,
pub line: LineNumber,
pub column: ColumnNumber,
pub scope_depth: u32,
}
impl DebugVariableInfo {
pub fn new(
name_idx: u32,
type_idx: DebugTypeIdx,
line: LineNumber,
column: ColumnNumber,
) -> Self {
Self {
name_idx,
type_idx,
arg_index: 0,
line,
column,
scope_depth: 0,
}
}
pub fn with_arg_index(mut self, arg_index: u32) -> Self {
self.arg_index = arg_index;
self
}
pub fn with_scope_depth(mut self, scope_depth: u32) -> Self {
self.scope_depth = scope_depth;
self
}
pub fn is_parameter(&self) -> bool {
self.arg_index > 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DebugInlinedCallInfo {
pub callee_idx: u32,
pub file_idx: u32,
pub line: LineNumber,
pub column: ColumnNumber,
}
impl DebugInlinedCallInfo {
pub fn new(callee_idx: u32, file_idx: u32, line: LineNumber, column: ColumnNumber) -> Self {
Self { callee_idx, file_idx, line, column }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug_types_section_string_dedup() {
let mut section = DebugTypesSection::new();
let idx1 = section.add_string(Arc::from("test.rs"));
let idx2 = section.add_string(Arc::from("main.rs"));
let idx3 = section.add_string(Arc::from("test.rs"));
assert_eq!(idx1, 0);
assert_eq!(idx2, 1);
assert_eq!(idx3, 0); assert_eq!(section.strings.len(), 2);
}
#[test]
fn test_debug_sources_section_string_dedup() {
let mut section = DebugSourcesSection::new();
let idx1 = section.add_string(Arc::from("test.rs"));
let idx2 = section.add_string(Arc::from("main.rs"));
let idx3 = section.add_string(Arc::from("test.rs"));
assert_eq!(idx1, 0);
assert_eq!(idx2, 1);
assert_eq!(idx3, 0); assert_eq!(section.strings.len(), 2);
}
#[test]
fn test_debug_functions_section_string_dedup() {
let mut section = DebugFunctionsSection::new();
let idx1 = section.add_string(Arc::from("foo"));
let idx2 = section.add_string(Arc::from("bar"));
let idx3 = section.add_string(Arc::from("foo"));
assert_eq!(idx1, 0);
assert_eq!(idx2, 1);
assert_eq!(idx3, 0); assert_eq!(section.strings.len(), 2);
}
#[test]
fn test_primitive_type_sizes() {
assert_eq!(DebugPrimitiveType::Void.size_in_bytes(), 0);
assert_eq!(DebugPrimitiveType::I32.size_in_bytes(), 4);
assert_eq!(DebugPrimitiveType::I64.size_in_bytes(), 8);
assert_eq!(DebugPrimitiveType::Felt.size_in_bytes(), 8);
assert_eq!(DebugPrimitiveType::Word.size_in_bytes(), 32);
assert_eq!(DebugPrimitiveType::Void.size_in_felts(), 0);
assert_eq!(DebugPrimitiveType::I32.size_in_felts(), 1);
assert_eq!(DebugPrimitiveType::I64.size_in_felts(), 2);
assert_eq!(DebugPrimitiveType::Word.size_in_felts(), 4);
}
#[test]
fn test_primitive_type_roundtrip() {
for discriminant in 0..=15 {
let ty = DebugPrimitiveType::from_discriminant(discriminant).unwrap();
assert_eq!(ty as u8, discriminant);
}
assert!(DebugPrimitiveType::from_discriminant(16).is_none());
}
}