use crate::ir::{IrModule, IrValue, TypeKind};
use crate::JitResult;
use indexmap::IndexMap;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct DebugSymbolManager {
symbol_tables: IndexMap<String, SymbolTable>,
source_mappings: IndexMap<String, SourceMapping>,
dwarf_info: DwarfDebugInfo,
config: DebugSymbolConfig,
stats: DebugSymbolStats,
}
#[derive(Debug, Clone)]
pub struct SymbolTable {
pub module_name: String,
pub functions: IndexMap<String, FunctionSymbol>,
pub variables: IndexMap<String, VariableSymbol>,
pub types: IndexMap<String, TypeSymbol>,
pub address_ranges: Vec<AddressRange>,
pub line_info: LineNumberTable,
}
#[derive(Debug, Clone)]
pub struct FunctionSymbol {
pub name: String,
pub mangled_name: Option<String>,
pub start_address: u64,
pub end_address: u64,
pub size: usize,
pub return_type: TypeSymbol,
pub parameters: Vec<ParameterSymbol>,
pub locals: Vec<LocalVariableSymbol>,
pub source_location: SourceLocation,
pub inlined_functions: Vec<InlinedFunction>,
}
#[derive(Debug, Clone)]
pub struct VariableSymbol {
pub name: String,
pub var_type: TypeSymbol,
pub location: VariableLocation,
pub declaration_location: SourceLocation,
pub scope: Scope,
pub live_ranges: Vec<LiveRange>,
}
#[derive(Debug, Clone)]
pub struct TypeSymbol {
pub name: String,
pub kind: TypeKind,
pub size: usize,
pub alignment: usize,
pub members: Vec<TypeMember>,
pub definition_location: Option<SourceLocation>,
}
#[derive(Debug, Clone)]
pub struct ParameterSymbol {
pub name: String,
pub param_type: TypeSymbol,
pub index: usize,
pub location: VariableLocation,
}
#[derive(Debug, Clone)]
pub struct LocalVariableSymbol {
pub name: String,
pub var_type: TypeSymbol,
pub location: VariableLocation,
pub scope: Scope,
pub live_ranges: Vec<LiveRange>,
}
#[derive(Debug, Clone)]
pub enum VariableLocation {
Register { register: RegisterId },
Stack { offset: i32 },
Memory { address: u64 },
Constant { value: ConstantValue },
Composite { locations: Vec<VariableLocation> },
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RegisterId {
X64(X64Register),
Arm64(Arm64Register),
Generic(u32),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum X64Register {
RAX,
RBX,
RCX,
RDX,
RSI,
RDI,
RBP,
RSP,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
XMM0,
XMM1,
XMM2,
XMM3,
XMM4,
XMM5,
XMM6,
XMM7,
XMM8,
XMM9,
XMM10,
XMM11,
XMM12,
XMM13,
XMM14,
XMM15,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Arm64Register {
X0,
X1,
X2,
X3,
X4,
X5,
X6,
X7,
X8,
X9,
X10,
X11,
X12,
X13,
X14,
X15,
X16,
X17,
X18,
X19,
X20,
X21,
X22,
X23,
X24,
X25,
X26,
X27,
X28,
X29,
X30,
SP,
V0,
V1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
V16,
V17,
V18,
V19,
V20,
V21,
V22,
V23,
V24,
V25,
V26,
V27,
V28,
V29,
V30,
V31,
}
#[derive(Debug, Clone)]
pub enum ConstantValue {
Int(i64),
UInt(u64),
Float(f64),
Bool(bool),
String(String),
Null,
}
#[derive(Debug, Clone)]
pub struct TypeMember {
pub name: String,
pub member_type: TypeSymbol,
pub offset: usize,
pub size: usize,
}
#[derive(Debug, Clone)]
pub struct Scope {
pub start_address: u64,
pub end_address: u64,
pub parent: Option<Box<Scope>>,
pub kind: ScopeKind,
}
#[derive(Debug, Clone)]
pub enum ScopeKind {
Function,
Block,
Exception,
Loop,
Conditional,
}
#[derive(Debug, Clone)]
pub struct LiveRange {
pub start: u64,
pub end: u64,
pub location: VariableLocation,
}
#[derive(Debug, Clone)]
pub struct AddressRange {
pub start: u64,
pub end: u64,
pub symbol: String,
pub attributes: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct LineNumberTable {
pub entries: Vec<LineNumberEntry>,
pub source_files: Vec<SourceFile>,
}
#[derive(Debug, Clone)]
pub struct LineNumberEntry {
pub address: u64,
pub file_index: usize,
pub line: u32,
pub column: u32,
pub is_statement: bool,
pub is_basic_block: bool,
}
#[derive(Debug, Clone)]
pub struct SourceFile {
pub path: String,
pub size: usize,
pub mtime: Option<u64>,
pub md5_hash: Option<[u8; 16]>,
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
pub length: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct InlinedFunction {
pub original_name: String,
pub inlined_at: SourceLocation,
pub address_ranges: Vec<AddressRange>,
pub call_site: SourceLocation,
}
#[derive(Debug, Clone)]
pub struct SourceMapping {
pub module_name: String,
pub address_to_source: HashMap<u64, SourceLocation>,
pub source_to_address: HashMap<(String, u32, u32), Vec<u64>>,
pub inline_stacks: HashMap<u64, Vec<InlinedFunction>>,
}
#[derive(Debug, Clone, Default)]
pub struct DwarfDebugInfo {
pub compilation_units: Vec<CompilationUnit>,
pub debug_sections: HashMap<String, Vec<u8>>,
pub string_table: Vec<String>,
pub abbreviation_tables: Vec<AbbreviationTable>,
}
#[derive(Debug, Clone)]
pub struct CompilationUnit {
pub offset: u64,
pub length: u64,
pub version: u16,
pub producer: String,
pub language: u32,
pub low_pc: u64,
pub high_pc: u64,
pub entries: Vec<DebugInfoEntry>,
}
#[derive(Debug, Clone)]
pub struct DebugInfoEntry {
pub offset: u64,
pub tag: DwarfTag,
pub attributes: HashMap<DwarfAttribute, DwarfValue>,
pub children: Vec<DebugInfoEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DwarfTag {
CompileUnit,
Subprogram,
Variable,
Parameter,
BaseType,
PointerType,
ArrayType,
StructureType,
UnionType,
EnumerationType,
LexicalBlock,
InlinedSubroutine,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DwarfAttribute {
Name,
Type,
Location,
LowPc,
HighPc,
FrameBase,
ByteSize,
Encoding,
DeclarationFile,
DeclarationLine,
CallFile,
CallLine,
InlineStatus,
}
#[derive(Debug, Clone)]
pub enum DwarfValue {
String(String),
Address(u64),
Constant(u64),
Block(Vec<u8>),
Reference(u64),
Flag(bool),
}
#[derive(Debug, Clone)]
pub struct AbbreviationTable {
pub offset: u64,
pub entries: HashMap<u64, AbbreviationEntry>,
}
#[derive(Debug, Clone)]
pub struct AbbreviationEntry {
pub code: u64,
pub tag: DwarfTag,
pub has_children: bool,
pub attributes: Vec<(DwarfAttribute, DwarfForm)>,
}
#[derive(Debug, Clone)]
pub enum DwarfForm {
Addr,
Block1,
Block2,
Block4,
Data1,
Data2,
Data4,
Data8,
String,
Strp,
Ref1,
Ref2,
Ref4,
Ref8,
RefAddr,
Flag,
FlagPresent,
}
#[derive(Debug, Clone)]
pub struct DebugSymbolConfig {
pub enable_dwarf: bool,
pub enable_source_mapping: bool,
pub include_variable_locations: bool,
pub include_inline_info: bool,
pub debug_level: u8,
pub compress_debug_sections: bool,
pub include_optimization_remarks: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DebugSymbolStats {
pub total_symbols: usize,
pub debug_info_size: usize,
pub source_mappings: usize,
pub generation_time_ns: u64,
pub compression_ratio: f32,
}
impl Default for DebugSymbolConfig {
fn default() -> Self {
Self {
enable_dwarf: true,
enable_source_mapping: true,
include_variable_locations: true,
include_inline_info: true,
debug_level: 2,
compress_debug_sections: false,
include_optimization_remarks: false,
}
}
}
impl DebugSymbolManager {
pub fn new(config: DebugSymbolConfig) -> Self {
Self {
symbol_tables: IndexMap::new(),
source_mappings: IndexMap::new(),
dwarf_info: DwarfDebugInfo::default(),
config,
stats: DebugSymbolStats::default(),
}
}
pub fn with_defaults() -> Self {
Self::new(DebugSymbolConfig::default())
}
pub fn generate_symbols(
&mut self,
module: &IrModule,
code_address: u64,
code_size: usize,
) -> JitResult<()> {
let start_time = std::time::Instant::now();
let symbol_table = self.create_symbol_table(module, code_address, code_size)?;
let source_mapping = self.create_source_mapping(module, code_address)?;
if self.config.enable_dwarf {
self.generate_dwarf_info(module, &symbol_table)?;
}
self.symbol_tables.insert(module.name.clone(), symbol_table);
self.source_mappings
.insert(module.name.clone(), source_mapping);
let generation_time = start_time.elapsed().as_nanos() as u64;
self.stats.generation_time_ns += generation_time;
self.stats.total_symbols += 1;
Ok(())
}
fn create_symbol_table(
&self,
module: &IrModule,
code_address: u64,
code_size: usize,
) -> JitResult<SymbolTable> {
let mut symbol_table = SymbolTable {
module_name: module.name.clone(),
functions: IndexMap::new(),
variables: IndexMap::new(),
types: IndexMap::new(),
address_ranges: Vec::new(),
line_info: LineNumberTable {
entries: Vec::new(),
source_files: Vec::new(),
},
};
let function_symbol = FunctionSymbol {
name: module.name.clone(),
mangled_name: None,
start_address: code_address,
end_address: code_address + code_size as u64,
size: code_size,
return_type: TypeSymbol::void_type(),
parameters: Vec::new(),
locals: Vec::new(),
source_location: SourceLocation {
file: "<generated>".to_string(),
line: 1,
column: 1,
length: None,
},
inlined_functions: Vec::new(),
};
symbol_table
.functions
.insert(module.name.clone(), function_symbol);
symbol_table.address_ranges.push(AddressRange {
start: code_address,
end: code_address + code_size as u64,
symbol: module.name.clone(),
attributes: HashMap::new(),
});
for (type_id, type_def) in &module.types {
let type_symbol = self.create_type_symbol(type_id, type_def)?;
symbol_table
.types
.insert(format!("type_{}", type_id.0), type_symbol);
}
for (value_id, value_def) in &module.values {
if let Some(variable_symbol) = self.create_variable_symbol(value_id, value_def)? {
symbol_table
.variables
.insert(format!("var_{}", value_id.0), variable_symbol);
}
}
Ok(symbol_table)
}
fn create_source_mapping(
&self,
module: &IrModule,
code_address: u64,
) -> JitResult<SourceMapping> {
let mut source_mapping = SourceMapping {
module_name: module.name.clone(),
address_to_source: HashMap::new(),
source_to_address: HashMap::new(),
inline_stacks: HashMap::new(),
};
let entry_location = SourceLocation {
file: "<generated>".to_string(),
line: 1,
column: 1,
length: None,
};
source_mapping
.address_to_source
.insert(code_address, entry_location.clone());
let key = (
entry_location.file.clone(),
entry_location.line,
entry_location.column,
);
source_mapping
.source_to_address
.entry(key)
.or_default()
.push(code_address);
Ok(source_mapping)
}
fn generate_dwarf_info(
&mut self,
_module: &IrModule,
symbol_table: &SymbolTable,
) -> JitResult<()> {
let compilation_unit = CompilationUnit {
offset: 0,
length: 0, version: 4,
producer: "ToRSh JIT Compiler".to_string(),
language: 0x8001, low_pc: symbol_table
.address_ranges
.first()
.map(|r| r.start)
.unwrap_or(0),
high_pc: symbol_table
.address_ranges
.last()
.map(|r| r.end)
.unwrap_or(0),
entries: self.create_debug_entries(symbol_table)?,
};
self.dwarf_info.compilation_units.push(compilation_unit);
Ok(())
}
fn create_debug_entries(&self, symbol_table: &SymbolTable) -> JitResult<Vec<DebugInfoEntry>> {
let mut entries = Vec::new();
for (name, function) in &symbol_table.functions {
let mut attributes = HashMap::new();
attributes.insert(DwarfAttribute::Name, DwarfValue::String(name.clone()));
attributes.insert(
DwarfAttribute::LowPc,
DwarfValue::Address(function.start_address),
);
attributes.insert(
DwarfAttribute::HighPc,
DwarfValue::Address(function.end_address),
);
let entry = DebugInfoEntry {
offset: 0,
tag: DwarfTag::Subprogram,
attributes,
children: Vec::new(),
};
entries.push(entry);
}
for (name, type_symbol) in &symbol_table.types {
let mut attributes = HashMap::new();
attributes.insert(DwarfAttribute::Name, DwarfValue::String(name.clone()));
attributes.insert(
DwarfAttribute::ByteSize,
DwarfValue::Constant(type_symbol.size as u64),
);
let entry = DebugInfoEntry {
offset: 0,
tag: DwarfTag::BaseType,
attributes,
children: Vec::new(),
};
entries.push(entry);
}
Ok(entries)
}
fn create_type_symbol(
&self,
_type_id: &crate::ir::IrType,
type_def: &crate::ir::TypeDef,
) -> JitResult<TypeSymbol> {
Ok(TypeSymbol {
name: format!("{:?}", type_def.kind),
kind: type_def.kind.clone(),
size: type_def.size.unwrap_or(0),
alignment: type_def.align.unwrap_or(1),
members: Vec::new(),
definition_location: None,
})
}
fn create_variable_symbol(
&self,
value_id: &IrValue,
value_def: &crate::ir::ValueDef,
) -> JitResult<Option<VariableSymbol>> {
use crate::ir::ValueKind;
match &value_def.kind {
ValueKind::Parameter { index } => {
let var_type = self.ir_type_to_type_symbol(&value_def.ty);
let name = format!("param_{}", index);
Ok(Some(VariableSymbol {
name,
var_type,
location: VariableLocation::Unknown,
declaration_location: SourceLocation {
file: "".to_string(),
line: 0,
column: 0,
length: None,
},
scope: Scope {
start_address: 0,
end_address: u64::MAX,
parent: None,
kind: ScopeKind::Function,
},
live_ranges: Vec::new(),
}))
}
ValueKind::Instruction { block, index } => {
let var_type = self.ir_type_to_type_symbol(&value_def.ty);
let name = format!("tmp_{}_{}", block, index);
Ok(Some(VariableSymbol {
name,
var_type,
location: VariableLocation::Unknown,
declaration_location: SourceLocation {
file: "".to_string(),
line: 0,
column: 0,
length: None,
},
scope: Scope {
start_address: 0,
end_address: u64::MAX,
parent: None,
kind: ScopeKind::Block,
},
live_ranges: Vec::new(),
}))
}
ValueKind::Constant { data } => {
let var_type = self.ir_type_to_type_symbol(&value_def.ty);
let name = format!("const_{:?}", value_id);
let const_location = match data {
crate::ir::ConstantData::Int(v) => VariableLocation::Constant {
value: ConstantValue::Int(*v),
},
crate::ir::ConstantData::Float(v) => VariableLocation::Constant {
value: ConstantValue::Float(*v),
},
crate::ir::ConstantData::Bool(v) => VariableLocation::Constant {
value: ConstantValue::Bool(*v),
},
_ => VariableLocation::Unknown,
};
Ok(Some(VariableSymbol {
name,
var_type,
location: const_location,
declaration_location: SourceLocation {
file: "".to_string(),
line: 0,
column: 0,
length: None,
},
scope: Scope {
start_address: 0,
end_address: u64::MAX,
parent: None,
kind: ScopeKind::Block, },
live_ranges: Vec::new(),
}))
}
ValueKind::Undef => {
Ok(None)
}
}
}
fn ir_type_to_type_symbol(&self, ir_type: &crate::ir::IrType) -> TypeSymbol {
use crate::ir::TypeKind;
let type_id = ir_type.0;
TypeSymbol {
name: format!("type_{}", type_id),
kind: TypeKind::I32, size: 4, alignment: 4, members: Vec::new(),
definition_location: None,
}
}
pub fn get_symbol_table(&self, module_name: &str) -> Option<&SymbolTable> {
self.symbol_tables.get(module_name)
}
pub fn get_source_mapping(&self, module_name: &str) -> Option<&SourceMapping> {
self.source_mappings.get(module_name)
}
pub fn lookup_source_location(&self, address: u64) -> Option<SourceLocation> {
for source_mapping in self.source_mappings.values() {
if let Some(location) = source_mapping.address_to_source.get(&address) {
return Some(location.clone());
}
}
None
}
pub fn lookup_addresses(&self, file: &str, line: u32, column: u32) -> Vec<u64> {
let mut addresses = Vec::new();
let key = (file.to_string(), line, column);
for source_mapping in self.source_mappings.values() {
if let Some(addrs) = source_mapping.source_to_address.get(&key) {
addresses.extend(addrs);
}
}
addresses
}
pub fn get_dwarf_info(&self) -> &DwarfDebugInfo {
&self.dwarf_info
}
pub fn stats(&self) -> &DebugSymbolStats {
&self.stats
}
pub fn clear(&mut self) {
self.symbol_tables.clear();
self.source_mappings.clear();
self.dwarf_info = DwarfDebugInfo::default();
self.stats = DebugSymbolStats::default();
}
}
impl TypeSymbol {
pub fn void_type() -> Self {
Self {
name: "void".to_string(),
kind: TypeKind::Void,
size: 0,
alignment: 1,
members: Vec::new(),
definition_location: None,
}
}
pub fn basic_type(kind: TypeKind) -> Self {
let (name, size, alignment) = match kind {
TypeKind::Bool => ("bool".to_string(), 1, 1),
TypeKind::I8 => ("i8".to_string(), 1, 1),
TypeKind::I16 => ("i16".to_string(), 2, 2),
TypeKind::I32 => ("i32".to_string(), 4, 4),
TypeKind::I64 => ("i64".to_string(), 8, 8),
TypeKind::U8 => ("u8".to_string(), 1, 1),
TypeKind::U16 => ("u16".to_string(), 2, 2),
TypeKind::U32 => ("u32".to_string(), 4, 4),
TypeKind::U64 => ("u64".to_string(), 8, 8),
TypeKind::F16 => ("f16".to_string(), 2, 2),
TypeKind::F32 => ("f32".to_string(), 4, 4),
TypeKind::F64 => ("f64".to_string(), 8, 8),
TypeKind::C64 => ("c64".to_string(), 8, 4),
TypeKind::C128 => ("c128".to_string(), 16, 8),
TypeKind::Void => ("void".to_string(), 0, 1),
_ => ("unknown".to_string(), 0, 1),
};
Self {
name,
kind,
size,
alignment,
members: Vec::new(),
definition_location: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug_symbol_manager_creation() {
let manager = DebugSymbolManager::with_defaults();
assert_eq!(manager.symbol_tables.len(), 0);
assert_eq!(manager.source_mappings.len(), 0);
}
#[test]
fn test_type_symbol_creation() {
let void_type = TypeSymbol::void_type();
assert_eq!(void_type.name, "void");
assert_eq!(void_type.size, 0);
let i32_type = TypeSymbol::basic_type(TypeKind::I32);
assert_eq!(i32_type.name, "i32");
assert_eq!(i32_type.size, 4);
assert_eq!(i32_type.alignment, 4);
}
#[test]
fn test_register_identification() {
let x64_reg = RegisterId::X64(X64Register::RAX);
let arm64_reg = RegisterId::Arm64(Arm64Register::X0);
let generic_reg = RegisterId::Generic(0);
assert_ne!(x64_reg, arm64_reg);
assert_ne!(arm64_reg, generic_reg);
}
#[test]
fn test_source_location() {
let location = SourceLocation {
file: "test.rs".to_string(),
line: 42,
column: 10,
length: Some(15),
};
assert_eq!(location.file, "test.rs");
assert_eq!(location.line, 42);
assert_eq!(location.column, 10);
}
}