Implement and archive Phase 5 debugger

This commit is contained in:
2026-09-06 21:24:38 +02:00
parent ce97700a98
commit e236306657
25 changed files with 3102 additions and 52 deletions

View File

@@ -28,6 +28,9 @@ pub struct ProjectCompiler {
parsed: Vec<Parsed>,
products: Vec<Product>,
pub stats: CompileStats,
/// Vollständige Imports im Cache halten, damit spätere Debugkommandos sie binden können.
pub debug_symbols: bool,
debug_maps: Vec<DebugMap>,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CompileStats {
@@ -45,6 +48,7 @@ struct Parsed {
}
struct Product {
module: Module,
debug_module: Module,
catalog: FormCatalog,
code: CompiledModule,
commons: Vec<tb_frontend::hir::HCommon>,
@@ -211,6 +215,9 @@ impl ProjectCompiler {
import_declarations(&mut key, &parsed, &exports, Some(&self.parsed[index].names));
let mut module = module.clone();
import_declarations(&mut module, &parsed, &exports, None);
if self.debug_symbols {
key = module.clone();
}
if let Some(product) = self
.products
.iter()
@@ -230,6 +237,7 @@ impl ProjectCompiler {
commons.push(hir.commons.clone());
products.push(Product {
module: key,
debug_module: module,
catalog: catalog.clone(),
code,
commons: hir.commons,
@@ -246,11 +254,12 @@ impl ProjectCompiler {
&& !products.iter().any(|n| n.module.name == p.module.name)
});
self.products.extend(products);
let mut result = link(name, parts, &parsed, &commons).map_err(|error| {
let (mut result, maps) = link(name, parts, &parsed, &commons).map_err(|error| {
let mut errors = vec![error];
locate_diagnostics(&mut errors, &sources);
errors
})?;
self.debug_maps = maps;
result.sources = sources;
let objects = FormCatalog {
objects: result.objects.clone(),
@@ -546,7 +555,7 @@ fn link(
mut parts: Vec<CompiledModule>,
ast: &[Module],
module_commons: &[Vec<tb_frontend::hir::HCommon>],
) -> Result<CompiledModule, Diagnostic> {
) -> Result<(CompiledModule, Vec<DebugMap>), Diagnostic> {
let at = |pos, message| Diagnostic {
file: None,
pos,
@@ -625,6 +634,7 @@ fn link(
main.name = name.into();
let mut initializers = Vec::new();
let mut bodies = Vec::new();
let mut debug_maps = Vec::new();
let mut common: HashMap<_, (u16, tb_frontend::hir::HCommon)> = HashMap::new();
for (module_id, part) in parts.iter_mut().enumerate() {
let mut types = Vec::new();
@@ -715,6 +725,11 @@ fn link(
};
globals.push(id);
}
debug_maps.push(DebugMap {
globals: globals.clone(),
types: types.clone(),
procs: proc_maps[module_id].clone(),
});
let string_offset = result.strings.len();
if string_offset + part.strings.len() >= u16::MAX as usize {
return Err(at(
@@ -831,5 +846,232 @@ fn link(
}
}
}
Ok(result)
Ok((result, debug_maps))
}
/// Ephemeral symbol/link context, deliberately not part of TBC serialization.
#[derive(Clone)]
pub struct DebugCompiler {
modules: Vec<(Module, FormCatalog, DebugMap)>,
symbols: tb_frontend::sema::DebugSymbols,
slots: Vec<u16>,
error: Option<String>,
}
#[derive(Clone)]
struct DebugMap {
globals: Vec<u16>,
types: Vec<u16>,
procs: Vec<u16>,
}
#[derive(Clone)]
pub struct DebugCode {
pub procedure: crate::bytecode::ProcCode,
pub strings: Vec<std::rc::Rc<str>>,
pub expression: bool,
}
impl ProjectCompiler {
pub fn debug_compiler(&self) -> DebugCompiler {
let modules: Vec<_> = self
.parsed
.iter()
.zip(&self.debug_maps)
.filter_map(|(parsed, map)| {
self.products
.iter()
.find(|p| p.module.name == parsed.module.name)
.map(|p| (p.debug_module.clone(), p.catalog.clone(), map.clone()))
})
.collect();
let mut symbols = tb_frontend::sema::DebugSymbols::default();
let mut slots = Vec::new();
let mut error = None;
for (ast, catalog, map) in &modules {
if let Some(hir) = tb_frontend::sema::lower_with_forms(ast, catalog).0 {
if symbols.udts.len() + hir.udts.len() >= u16::MAX as usize
|| symbols.globals.len() + hir.globals.len() >= u16::MAX as usize
{
error = Some("Debug-Symboltabelle überschreitet die 16-Bit-Slotgrenze".into());
break;
}
let offset = symbols.udts.len() as u16;
for mut udt in hir.udts {
udt.name = format!("<Debug:{}!{}>", ast.name, udt.name);
for (_, ty) in &mut udt.fields {
if let HTy::Udt(id) = ty {
*id += offset;
}
}
symbols.udts.push(udt);
}
for (mut var, slot) in hir.globals.into_iter().zip(&map.globals) {
var.name = format!("{}!{}", ast.name, var.name);
if let HTy::Udt(id) = &mut var.ty {
*id += offset;
}
symbols.globals.push(var);
slots.push(*slot);
}
}
}
DebugCompiler {
modules,
symbols,
slots,
error,
}
}
}
impl DebugCompiler {
pub fn compile(
&self,
module: u16,
procedure: &str,
text: &str,
expression: bool,
) -> Result<DebugCode, String> {
if let Some(error) = &self.error {
return Err(error.clone());
}
let (ast, catalog, map) = self
.modules
.get(module as usize)
.ok_or("Kein Debug-Quellkontext für dieses Kompilat")?;
let name = if procedure == "<main>" {
&ast.name
} else {
procedure.rsplit('!').next().unwrap_or(procedure)
};
if map.globals.len() + self.symbols.globals.len() >= u16::MAX as usize
|| map.types.len() + self.symbols.udts.len() >= u16::MAX as usize
{
return Err("Debug-Kontext überschreitet die 16-Bit-Slotgrenze".into());
}
let mut symbols = self.symbols.clone();
symbols.original_globals = map.globals.len();
let (mut hir, debug) = tb_frontend::sema::lower_debug(
ast, catalog, name, text, expression, symbols,
)
.map_err(|e| {
e.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("\n")
})?;
hir.procs.push(debug);
let mut globals = map.globals.clone();
globals.extend(&self.slots);
let mut types = map.types.clone();
for (_, _, mapping) in &self.modules {
types.extend(&mapping.types);
}
let mut compiled = codegen::compile(&hir);
let mut proc = compiled.procs.pop().unwrap();
proc.module = module;
for ty in &mut proc.locals_init {
remap_type(ty, &types);
}
if let Some(ty) = &mut proc.ret_ty {
remap_signature(ty, &types);
}
for i in &mut proc.code {
use Instr::*;
match i {
LoadGlobal(id) | StoreGlobal(id) | MakeRefGlobal(id) => {
*id = *globals
.get(*id as usize)
.ok_or("Unbekannter globaler Slot")?
}
LoadArr(global, id, _, ty) => {
if *global {
*id = globals[*id as usize];
}
remap_type(ty, &types);
}
Call(id, _) => *id = map.procs[*id as usize],
PushUdtId(id) => *id = types[*id as usize],
_ => {}
}
}
Ok(DebugCode {
procedure: proc,
strings: compiled.strings,
expression,
})
}
}
impl DebugCompiler {
pub(crate) fn same_control_context(
&self,
module: u16,
procedure: &str,
from: SourcePos,
to: SourcePos,
) -> bool {
let Some((ast, _, _)) = self.modules.get(module as usize) else {
return false;
};
let body = if procedure == "<main>" {
&ast.body
} else {
let Some(proc) = ast.procs.iter().find(|p| {
p.sig
.name
.eq_ignore_ascii_case(procedure.rsplit('!').next().unwrap_or(procedure))
}) else {
return false;
};
&proc.body
};
let a = control_path(body, from);
let b = control_path(body, to);
a.is_some() && a == b
}
}
/// Structured arms supplement bytecode loop ranges: ELSE and CASE are distinct
/// even when their forward jump would otherwise look like a top-level GOTO.
fn control_path(body: &[Stmt], target: SourcePos) -> Option<Vec<(SourcePos, usize)>> {
for stmt in body {
let pos = tb_frontend::sema::stmt_pos(stmt);
if pos == target {
return Some(vec![]);
}
let mut children: Vec<(&[Stmt], Option<SourcePos>)> = Vec::new();
match stmt {
Stmt::If {
then_body,
elseifs,
else_body,
..
} => {
children.push((then_body, None));
for (_, body, pos) in elseifs {
children.push((body, Some(*pos)));
}
if let Some(body) = else_body {
children.push((body, None));
}
}
Stmt::Select { arms, .. } => {
for arm in arms {
children.push((&arm.body, Some(arm.pos)));
}
}
Stmt::For { body, end_pos, .. }
| Stmt::DoLoop { body, end_pos, .. }
| Stmt::While { body, end_pos, .. } => children.push((body, Some(*end_pos))),
_ => {}
}
for (index, (body, entry)) in children.into_iter().enumerate() {
if let Some(mut path) = if entry == Some(target) {
Some(vec![])
} else {
control_path(body, target)
} {
path.insert(0, (pos, index));
return Some(path);
}
}
}
None
}