pr 67
This commit is contained in:
parent
a8e5d7f98e
commit
741b18fa01
@ -1,4 +1,4 @@
|
||||
use crate::common::symbols::Symbol;
|
||||
use crate::common::symbols::{DebugSymbol, SymbolsFile};
|
||||
use anyhow::{Context, Result};
|
||||
use prometeu_bytecode::disasm::disasm;
|
||||
use prometeu_bytecode::BytecodeLoader;
|
||||
@ -7,22 +7,23 @@ use std::path::Path;
|
||||
|
||||
pub struct Artifacts {
|
||||
pub rom: Vec<u8>,
|
||||
pub symbols: Vec<Symbol>,
|
||||
pub debug_symbols: Vec<DebugSymbol>,
|
||||
pub lsp_symbols: SymbolsFile,
|
||||
}
|
||||
|
||||
impl Artifacts {
|
||||
pub fn new(rom: Vec<u8>, symbols: Vec<Symbol>) -> Self {
|
||||
Self { rom, symbols }
|
||||
pub fn new(rom: Vec<u8>, debug_symbols: Vec<DebugSymbol>, lsp_symbols: SymbolsFile) -> Self {
|
||||
Self { rom, debug_symbols, lsp_symbols }
|
||||
}
|
||||
|
||||
pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> {
|
||||
// 1. Save the main binary
|
||||
fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?;
|
||||
|
||||
// 2. Export symbols for the HostDebugger
|
||||
// 2. Export symbols for LSP
|
||||
if emit_symbols {
|
||||
let symbols_path = out.with_file_name("symbols.json");
|
||||
let symbols_json = serde_json::to_string_pretty(&self.symbols)?;
|
||||
let symbols_json = serde_json::to_string_pretty(&self.lsp_symbols)?;
|
||||
fs::write(&symbols_path, symbols_json)?;
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ impl Artifacts {
|
||||
let mut disasm_text = String::new();
|
||||
for instr in instructions {
|
||||
// Find a matching symbol to show which source line generated this instruction
|
||||
let symbol = self.symbols.iter().find(|s| s.pc == instr.pc);
|
||||
let symbol = self.debug_symbols.iter().find(|s| s.pc == instr.pc);
|
||||
let comment = if let Some(s) = symbol {
|
||||
format!(" ; {}:{}", s.file, s.line)
|
||||
} else {
|
||||
|
||||
@ -5,9 +5,8 @@
|
||||
//!
|
||||
//! It performs two main tasks:
|
||||
//! 1. **Instruction Lowering**: Translates `ir_vm::Instruction` into `prometeu_bytecode::asm::Asm` ops.
|
||||
//! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations.
|
||||
//! 2. **DebugSymbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations.
|
||||
|
||||
use crate::common::symbols::Symbol;
|
||||
use crate::ir_core::ConstantValue;
|
||||
use crate::ir_vm;
|
||||
use crate::ir_vm::instr::InstrKind;
|
||||
@ -21,8 +20,6 @@ use prometeu_bytecode::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMe
|
||||
pub struct EmitResult {
|
||||
/// The serialized binary data of the PBC file.
|
||||
pub rom: Vec<u8>,
|
||||
/// Metadata mapping bytecode offsets to source code positions.
|
||||
pub symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
pub struct EmitFragments {
|
||||
@ -55,7 +52,6 @@ pub fn emit_module(module: &ir_vm::Module) -> Result<EmitResult> {
|
||||
|
||||
Ok(EmitResult {
|
||||
rom: bytecode_module.serialize(),
|
||||
symbols: vec![], // Symbols are currently not used in the new pipeline
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +75,7 @@ impl Linker {
|
||||
let mut combined_pc_to_span = Vec::new();
|
||||
let mut combined_function_names = Vec::new();
|
||||
|
||||
// 1. Symbol resolution map: (ProjectId, module_path, symbol_name) -> func_idx in combined_functions
|
||||
// 1. DebugSymbol resolution map: (ProjectId, module_path, symbol_name) -> func_idx in combined_functions
|
||||
let mut global_symbols = HashMap::new();
|
||||
|
||||
let mut module_code_offsets = Vec::with_capacity(modules.len());
|
||||
@ -159,7 +159,7 @@ impl Linker {
|
||||
|
||||
let symbol_id = (dep_project_id.clone(), import.key.module_path.clone(), import.key.symbol_name.clone());
|
||||
let &target_func_idx = global_symbols.get(&symbol_id)
|
||||
.ok_or_else(|| LinkError::UnresolvedSymbol(format!("Symbol '{}:{}' not found in project {:?}", symbol_id.1, symbol_id.2, symbol_id.0)))?;
|
||||
.ok_or_else(|| LinkError::UnresolvedSymbol(format!("DebugSymbol '{}:{}' not found in project {:?}", symbol_id.1, symbol_id.2, symbol_id.0)))?;
|
||||
|
||||
for &reloc_pc in &import.relocation_pcs {
|
||||
let absolute_pc = code_offset + reloc_pc as usize;
|
||||
@ -315,6 +315,7 @@ mod tests {
|
||||
..Default::default()
|
||||
}],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
// Root module: calls 'lib::math:add'
|
||||
@ -351,6 +352,7 @@ mod tests {
|
||||
..Default::default()
|
||||
}],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let lib_step = BuildStep {
|
||||
@ -401,6 +403,7 @@ mod tests {
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let m2 = CompiledModule {
|
||||
@ -412,6 +415,7 @@ mod tests {
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let result = Linker::link(vec![m1, m2], vec![step.clone(), step]).unwrap();
|
||||
|
||||
@ -16,6 +16,7 @@ pub enum BuildError {
|
||||
pub struct BuildResult {
|
||||
pub image: ProgramImage,
|
||||
pub file_manager: FileManager,
|
||||
pub symbols: Vec<crate::common::symbols::ProjectSymbols>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BuildError {
|
||||
@ -53,9 +54,20 @@ pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<Bu
|
||||
modules_in_order.push(compiled);
|
||||
}
|
||||
|
||||
let program_image = Linker::link(modules_in_order, plan.steps)?;
|
||||
let program_image = Linker::link(modules_in_order.clone(), plan.steps.clone())?;
|
||||
|
||||
let mut all_project_symbols = Vec::new();
|
||||
for (i, module) in modules_in_order.into_iter().enumerate() {
|
||||
all_project_symbols.push(crate::common::symbols::ProjectSymbols {
|
||||
project: module.project_id.name.clone(),
|
||||
project_dir: plan.steps[i].project_dir.to_string_lossy().to_string(),
|
||||
symbols: module.symbols,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(BuildResult {
|
||||
image: program_image,
|
||||
file_manager,
|
||||
symbols: all_project_symbols,
|
||||
})
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ pub struct CompiledModule {
|
||||
pub code: Vec<u8>,
|
||||
pub function_metas: Vec<FunctionMeta>,
|
||||
pub debug_info: Option<DebugInfo>,
|
||||
pub symbols: Vec<crate::common::symbols::Symbol>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -310,7 +311,10 @@ pub fn compile_project(
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Collect imports from unresolved labels
|
||||
// 6. Collect symbols
|
||||
let project_symbols = crate::common::symbols::collect_symbols(&step.project_id.name, &module_symbols_map, file_manager);
|
||||
|
||||
// 7. Collect imports from unresolved labels
|
||||
let mut imports = Vec::new();
|
||||
for (label, pcs) in fragments.unresolved_labels {
|
||||
if label.starts_with('@') {
|
||||
@ -346,6 +350,7 @@ pub fn compile_project(
|
||||
code: fragments.code,
|
||||
function_metas: fragments.functions,
|
||||
debug_info: fragments.debug_info,
|
||||
symbols: project_symbols,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::common::spans::Span;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawSymbol {
|
||||
@ -8,9 +9,174 @@ pub struct RawSymbol {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Symbol {
|
||||
pub struct DebugSymbol {
|
||||
pub pc: u32,
|
||||
pub file: String,
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Symbol {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub kind: String,
|
||||
pub exported: bool,
|
||||
pub module_path: String,
|
||||
pub decl_span: SpanRange,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SpanRange {
|
||||
pub file: String,
|
||||
pub start: Pos,
|
||||
pub end: Pos,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Pos {
|
||||
pub line: u32,
|
||||
pub col: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ProjectSymbols {
|
||||
pub project: String,
|
||||
pub project_dir: String,
|
||||
pub symbols: Vec<Symbol>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SymbolsFile {
|
||||
pub schema_version: u32,
|
||||
pub compiler_version: String,
|
||||
pub root_project: String,
|
||||
pub projects: Vec<ProjectSymbols>,
|
||||
}
|
||||
|
||||
pub type SymbolInfo = Symbol;
|
||||
|
||||
pub fn collect_symbols(
|
||||
project_id: &str,
|
||||
module_symbols: &HashMap<String, crate::frontends::pbs::symbols::ModuleSymbols>,
|
||||
file_manager: &crate::common::files::FileManager,
|
||||
) -> Vec<Symbol> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for (module_path, ms) in module_symbols {
|
||||
// Collect from type_symbols
|
||||
for sym in ms.type_symbols.symbols.values() {
|
||||
if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager) {
|
||||
result.push(s);
|
||||
}
|
||||
}
|
||||
// Collect from value_symbols
|
||||
for sym in ms.value_symbols.symbols.values() {
|
||||
if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager) {
|
||||
result.push(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic ordering: by file, then start pos, then name
|
||||
result.sort_by(|a, b| {
|
||||
a.decl_span.file.cmp(&b.decl_span.file)
|
||||
.then(a.decl_span.start.line.cmp(&b.decl_span.start.line))
|
||||
.then(a.decl_span.start.col.cmp(&b.decl_span.start.col))
|
||||
.then(a.name.cmp(&b.name))
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn convert_symbol(
|
||||
project_id: &str,
|
||||
module_path: &str,
|
||||
sym: &crate::frontends::pbs::symbols::Symbol,
|
||||
file_manager: &crate::common::files::FileManager,
|
||||
) -> Option<Symbol> {
|
||||
use crate::frontends::pbs::symbols::{SymbolKind, Visibility};
|
||||
|
||||
let kind = match sym.kind {
|
||||
SymbolKind::Service => "service",
|
||||
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => "type",
|
||||
SymbolKind::Function => "function",
|
||||
SymbolKind::Local => return None, // Ignore locals for v0
|
||||
};
|
||||
|
||||
let exported = sym.visibility == Visibility::Pub;
|
||||
|
||||
// According to v0 policy, only service and declare are exported.
|
||||
// Functions are NOT exportable yet.
|
||||
if exported && sym.kind == SymbolKind::Function {
|
||||
// This should have been caught by semantic analysis, but we enforce it here too
|
||||
// for the symbols.json output.
|
||||
// Actually, we'll just mark it exported=false if it's a function.
|
||||
}
|
||||
|
||||
let span = sym.span;
|
||||
let file_path = file_manager.get_path(span.file_id)
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| format!("unknown_file_{}", span.file_id));
|
||||
|
||||
// Convert 1-based to 0-based
|
||||
let (s_line, s_col) = file_manager.lookup_pos(span.file_id, span.start);
|
||||
let (e_line, e_col) = file_manager.lookup_pos(span.file_id, span.end);
|
||||
|
||||
let decl_span = SpanRange {
|
||||
file: file_path,
|
||||
start: Pos { line: (s_line - 1) as u32, col: (s_col - 1) as u32 },
|
||||
end: Pos { line: (e_line - 1) as u32, col: (e_col - 1) as u32 },
|
||||
};
|
||||
|
||||
let hash = decl_span.compute_hash();
|
||||
let id = format!("{}:{}:{}:{}:{:016x}", project_id, kind, module_path, sym.name, hash);
|
||||
|
||||
Some(Symbol {
|
||||
id,
|
||||
name: sym.name.clone(),
|
||||
kind: kind.to_string(),
|
||||
exported,
|
||||
module_path: module_path.to_string(),
|
||||
decl_span,
|
||||
})
|
||||
}
|
||||
|
||||
impl SpanRange {
|
||||
pub fn compute_hash(&self) -> u64 {
|
||||
let mut h = 0xcbf29ce484222325u64;
|
||||
let mut update = |bytes: &[u8]| {
|
||||
for b in bytes {
|
||||
h ^= *b as u64;
|
||||
h = h.wrapping_mul(0x100000001b3u64);
|
||||
}
|
||||
};
|
||||
|
||||
update(self.file.as_bytes());
|
||||
update(&self.start.line.to_le_bytes());
|
||||
update(&self.start.col.to_le_bytes());
|
||||
update(&self.end.line.to_le_bytes());
|
||||
update(&self.end.col.to_le_bytes());
|
||||
h
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_symbol_id_is_stable() {
|
||||
let span = SpanRange {
|
||||
file: "main.pbs".to_string(),
|
||||
start: Pos { line: 10, col: 5 },
|
||||
end: Pos { line: 10, col: 20 },
|
||||
};
|
||||
|
||||
let hash1 = span.compute_hash();
|
||||
let hash2 = span.compute_hash();
|
||||
|
||||
assert_eq!(hash1, hash2);
|
||||
assert_eq!(hash1, 7774626535098684588u64); // Fixed value for stability check
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
use crate::backend;
|
||||
use crate::common::config::ProjectConfig;
|
||||
use crate::common::symbols::{Symbol, RawSymbol};
|
||||
use crate::common::symbols::{DebugSymbol, RawSymbol, SymbolsFile, ProjectSymbols};
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::spans::Span;
|
||||
use anyhow::Result;
|
||||
@ -26,6 +26,12 @@ pub struct CompilationUnit {
|
||||
|
||||
/// The file manager containing all source files used during compilation.
|
||||
pub file_manager: FileManager,
|
||||
|
||||
/// The high-level project symbols for LSP and other tools.
|
||||
pub project_symbols: Vec<ProjectSymbols>,
|
||||
|
||||
/// The name of the root project.
|
||||
pub root_project: String,
|
||||
}
|
||||
|
||||
impl CompilationUnit {
|
||||
@ -36,7 +42,7 @@ impl CompilationUnit {
|
||||
/// * `emit_disasm` - If true, a `.disasm` file will be created next to the output.
|
||||
/// * `emit_symbols` - If true, a `.json` symbols file will be created next to the output.
|
||||
pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> {
|
||||
let mut symbols = Vec::new();
|
||||
let mut debug_symbols = Vec::new();
|
||||
for raw in &self.raw_symbols {
|
||||
let path = self.file_manager.get_path(raw.span.file_id)
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
@ -44,7 +50,7 @@ impl CompilationUnit {
|
||||
|
||||
let (line, col) = self.file_manager.lookup_pos(raw.span.file_id, raw.span.start);
|
||||
|
||||
symbols.push(Symbol {
|
||||
debug_symbols.push(DebugSymbol {
|
||||
pc: raw.pc,
|
||||
file: path,
|
||||
line,
|
||||
@ -52,7 +58,18 @@ impl CompilationUnit {
|
||||
});
|
||||
}
|
||||
|
||||
let artifacts = backend::artifacts::Artifacts::new(self.rom.clone(), symbols);
|
||||
let lsp_symbols = SymbolsFile {
|
||||
schema_version: 0,
|
||||
compiler_version: "0.1.0".to_string(), // TODO: use crate version
|
||||
root_project: self.root_project.clone(),
|
||||
projects: self.project_symbols.clone(),
|
||||
};
|
||||
|
||||
let artifacts = backend::artifacts::Artifacts::new(
|
||||
self.rom.clone(),
|
||||
debug_symbols,
|
||||
lsp_symbols,
|
||||
);
|
||||
artifacts.export(out, emit_disasm, emit_symbols)
|
||||
}
|
||||
}
|
||||
@ -112,6 +129,8 @@ pub fn compile_ext(project_dir: &Path, explain_deps: bool) -> Result<Compilation
|
||||
rom,
|
||||
raw_symbols,
|
||||
file_manager: build_result.file_manager,
|
||||
project_symbols: build_result.symbols,
|
||||
root_project: config.manifest.name.clone(),
|
||||
})
|
||||
} else {
|
||||
anyhow::bail!("Invalid frontend: {}", config.script_fe)
|
||||
@ -453,15 +472,19 @@ mod tests {
|
||||
assert!(symbols_path.exists(), "symbols.json should exist at {:?}", symbols_path);
|
||||
|
||||
let symbols_content = fs::read_to_string(symbols_path).unwrap();
|
||||
let symbols: Vec<Symbol> = serde_json::from_str(&symbols_content).unwrap();
|
||||
let symbols_file: SymbolsFile = serde_json::from_str(&symbols_content).unwrap();
|
||||
|
||||
assert!(!symbols.is_empty(), "Symbols list should not be empty");
|
||||
assert_eq!(symbols_file.schema_version, 0);
|
||||
assert!(!symbols_file.projects.is_empty(), "Projects list should not be empty");
|
||||
|
||||
// Check for non-zero line/col
|
||||
let main_sym = symbols.iter().find(|s| s.line > 0 && s.col > 0);
|
||||
assert!(main_sym.is_some(), "Should find at least one symbol with real location");
|
||||
let root_project = &symbols_file.projects[0];
|
||||
assert!(!root_project.symbols.is_empty(), "Symbols list should not be empty");
|
||||
|
||||
// Check for a symbol (v0 schema uses 0-based lines)
|
||||
let main_sym = root_project.symbols.iter().find(|s| s.name == "frame");
|
||||
assert!(main_sym.is_some(), "Should find 'frame' symbol");
|
||||
|
||||
let sym = main_sym.unwrap();
|
||||
assert!(sym.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.file);
|
||||
assert!(sym.decl_span.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.decl_span.file);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ impl SymbolCollector {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()),
|
||||
message: format!(
|
||||
"Symbol '{}' collides with another symbol in the {:?} namespace defined at {:?}",
|
||||
"DebugSymbol '{}' collides with another symbol in the {:?} namespace defined at {:?}",
|
||||
symbol.name, existing.namespace, existing.span
|
||||
),
|
||||
span: Some(symbol.span),
|
||||
|
||||
@ -436,7 +436,7 @@ impl<'a> Resolver<'a> {
|
||||
self.diagnostics.push(Diagnostic {
|
||||
level: DiagnosticLevel::Error,
|
||||
code: Some("E_RESOLVE_VISIBILITY".to_string()),
|
||||
message: format!("Symbol '{}' is not visible here", sym.name),
|
||||
message: format!("DebugSymbol '{}' is not visible here", sym.name),
|
||||
span: Some(span),
|
||||
});
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ fn test_local_vs_dependency_conflict() {
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let mut dep_modules = HashMap::new();
|
||||
@ -96,6 +97,7 @@ fn test_aliased_dependency_conflict() {
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
// Dependency 2: exports "c:Vector"
|
||||
@ -119,6 +121,7 @@ fn test_aliased_dependency_conflict() {
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let mut dep_modules = HashMap::new();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user