use std::collections::HashMap;
use brink_format::{
DEBUG_FLAG_IS_STMT, DEBUG_FLAG_PROLOGUE_END, DebugContainerTable, DebugEntry, DebugFileEntry,
DebugInfoSection, DebugLocalEntry, FileSurface, NameId,
};
use brink_ir::{FileId, Provenance, lir};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct EmitOptions<'a> {
pub emit_debug_info: bool,
pub debug_sources: Option<&'a std::collections::BTreeMap<brink_ir::FileId, String>>,
}
pub(crate) struct RawDebugEntry {
pub offset: u32,
pub provenance: Provenance,
pub prologue_end: bool,
}
pub(crate) struct RawLocal {
pub slot: u16,
pub name: NameId,
pub declaring_range: Option<Provenance>,
pub synthetic: bool,
}
pub(crate) struct DebugCollector {
containers: Vec<Vec<RawDebugEntry>>,
locals: Vec<Vec<RawLocal>>,
files: FileTableBuilder,
}
impl DebugCollector {
pub(crate) fn new() -> Self {
Self {
containers: Vec::new(),
locals: Vec::new(),
files: FileTableBuilder::new(),
}
}
pub(crate) fn push_container(&mut self, raw: Vec<RawDebugEntry>, locals: Vec<RawLocal>) {
for entry in &raw {
self.files.intern(entry.provenance.file);
}
for local in &locals {
if let Some(range) = local.declaring_range {
self.files.intern(range.file);
}
}
self.containers.push(raw);
self.locals.push(locals);
}
pub(crate) fn finish(
self,
program: &lir::Program,
sources: Option<&std::collections::BTreeMap<FileId, String>>,
errors: &mut Vec<crate::CodegenError>,
) -> DebugInfoSection {
let files = self.files.to_entries(program, sources, errors);
let index_of = |file: FileId| -> u32 { self.files.index_of(file) };
let name_of = |id: NameId| -> String {
program
.name_table
.get(id.0 as usize)
.cloned()
.unwrap_or_default()
};
let containers = self
.containers
.into_iter()
.zip(self.locals)
.map(|(raw, raw_locals)| {
let entries = raw
.into_iter()
.map(|e| {
let mut flags = DEBUG_FLAG_IS_STMT;
if e.prologue_end {
flags |= DEBUG_FLAG_PROLOGUE_END;
}
let range = e.provenance.range;
DebugEntry {
bytecode_offset: e.offset,
file_idx: index_of(e.provenance.file),
range_start: u32::from(range.start()),
range_len: u32::from(range.len()),
kind_token: e.provenance.kind.as_u32(),
flags,
}
})
.collect();
let locals = raw_locals
.into_iter()
.map(|l| DebugLocalEntry {
slot: l.slot,
name: name_of(l.name),
declaring_range: l.declaring_range.map(|p| {
(
index_of(p.file),
u32::from(p.range.start()),
u32::from(p.range.len()),
)
}),
synthetic: l.synthetic,
})
.collect();
DebugContainerTable { entries, locals }
})
.collect();
DebugInfoSection { files, containers }
}
}
struct FileTableBuilder {
order: Vec<FileId>,
index: HashMap<FileId, u32>,
}
impl FileTableBuilder {
fn new() -> Self {
let mut b = Self {
order: Vec::new(),
index: HashMap::new(),
};
b.order.push(FileId(u32::MAX));
b.index.insert(FileId(u32::MAX), 0);
b
}
fn intern(&mut self, file: FileId) {
if self.index.contains_key(&file) {
return;
}
#[expect(clippy::cast_possible_truncation)]
let idx = self.order.len() as u32;
self.order.push(file);
self.index.insert(file, idx);
}
fn index_of(&self, file: FileId) -> u32 {
self.index.get(&file).copied().unwrap_or(0)
}
fn to_entries(
&self,
program: &lir::Program,
sources: Option<&std::collections::BTreeMap<FileId, String>>,
errors: &mut Vec<crate::CodegenError>,
) -> Vec<DebugFileEntry> {
self.order
.iter()
.map(|file| {
if *file == FileId(u32::MAX) {
return DebugFileEntry {
surface: FileSurface::Synthetic,
path: String::new(),
source_hash: 0,
line_starts: Vec::new(),
};
}
if let Some(path) = program.file_paths.get(file) {
let (source_hash, line_starts) = sources.and_then(|m| m.get(file)).map_or_else(
|| (0, Vec::new()),
|text| (brink_format::content_hash(text), line_starts_of(text)),
);
DebugFileEntry {
surface: surface_from_path(path),
path: path.clone(),
source_hash,
line_starts,
}
} else {
errors.push(crate::CodegenError::new(format!(
"codegen: DebugInfo file table references {file:?}, which has no \
entry in Program.file_paths — cannot resolve its path or surface \
for the debug-info section (#3219)"
)));
DebugFileEntry {
surface: FileSurface::Synthetic,
path: String::new(),
source_hash: 0,
line_starts: Vec::new(),
}
}
})
.collect()
}
}
fn line_starts_of(text: &str) -> Vec<u32> {
let mut starts = vec![0_u32];
for (i, byte) in text.bytes().enumerate() {
if byte == b'\n' {
let next = i + 1;
if next < text.len()
&& let Ok(next) = u32::try_from(next)
{
starts.push(next);
}
}
}
starts
}
fn surface_from_path(path: &str) -> FileSurface {
let is_native = std::path::Path::new(path)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("brink"));
if is_native {
FileSurface::Native
} else {
FileSurface::Ink
}
}