Implement and archive Phase 5 debugger
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user