diff --git a/crates/prometeu-bytecode/src/abi.rs b/crates/prometeu-bytecode/src/abi.rs index 88445f5c..6d4d54ac 100644 --- a/crates/prometeu-bytecode/src/abi.rs +++ b/crates/prometeu-bytecode/src/abi.rs @@ -50,8 +50,10 @@ pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B; /// Executed RET with an incorrect stack height (mismatch with function metadata). pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C; +use serde::{Deserialize, Serialize}; + /// Detailed information about a source code span. -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct SourceSpan { pub file_id: u32, pub start: u32, diff --git a/crates/prometeu-bytecode/src/v0/linker.rs b/crates/prometeu-bytecode/src/v0/linker.rs index 7ba198d8..75d383a4 100644 --- a/crates/prometeu-bytecode/src/v0/linker.rs +++ b/crates/prometeu-bytecode/src/v0/linker.rs @@ -1,5 +1,5 @@ -use crate::v0::{BytecodeModule, DebugInfo, ConstantPoolEntry, FunctionMeta}; use crate::opcode::OpCode; +use crate::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta}; use std::collections::HashMap; #[derive(Debug, Clone, PartialEq, Eq)] @@ -158,8 +158,8 @@ impl Linker { #[cfg(test)] mod tests { use super::*; - use crate::v0::{BytecodeModule, FunctionMeta, Export, Import}; use crate::opcode::OpCode; + use crate::v0::{BytecodeModule, Export, FunctionMeta, Import}; #[test] fn test_linker_basic() { diff --git a/crates/prometeu-bytecode/src/v0/mod.rs b/crates/prometeu-bytecode/src/v0/mod.rs index b396338b..0c7b2171 100644 --- a/crates/prometeu-bytecode/src/v0/mod.rs +++ b/crates/prometeu-bytecode/src/v0/mod.rs @@ -1,8 +1,8 @@ pub mod linker; -use serde::{Deserialize, Serialize}; -use crate::opcode::OpCode; use crate::abi::SourceSpan; +use crate::opcode::OpCode; +use serde::{Deserialize, Serialize}; /// An entry in the Constant Pool. /// @@ -50,7 +50,7 @@ pub struct FunctionMeta { pub max_stack_slots: u16, } -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct DebugInfo { pub pc_to_span: Vec<(u32, SourceSpan)>, // Sorted by PC pub function_names: Vec<(u32, String)>, // (func_idx, name) diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index 57a43f45..8c16f4e5 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -9,14 +9,14 @@ use crate::common::files::FileManager; use crate::common::symbols::Symbol; +use crate::ir_core::ConstantValue; use crate::ir_vm; use crate::ir_vm::instr::InstrKind; -use crate::ir_core::ConstantValue; use anyhow::{anyhow, Result}; -use prometeu_bytecode::asm::{assemble, update_pc_by_operand, Asm, Operand}; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::v0::{BytecodeModule, FunctionMeta, DebugInfo, ConstantPoolEntry}; use prometeu_bytecode::abi::SourceSpan; +use prometeu_bytecode::asm::{update_pc_by_operand, Asm, Operand}; +use prometeu_bytecode::opcode::OpCode; +use prometeu_bytecode::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta}; /// The final output of the code generation phase. pub struct EmitResult { @@ -306,12 +306,12 @@ impl<'a> BytecodeEmitter<'a> { #[cfg(test)] mod tests { use super::*; - use crate::ir_vm::module::{Module, Function}; - use crate::ir_vm::instr::{Instruction, InstrKind}; - use crate::ir_vm::types::Type; - use crate::ir_core::ids::FunctionId; - use crate::ir_core::const_pool::ConstantValue; use crate::common::files::FileManager; + use crate::ir_core::const_pool::ConstantValue; + use crate::ir_core::ids::FunctionId; + use crate::ir_vm::instr::{InstrKind, Instruction}; + use crate::ir_vm::module::{Function, Module}; + use crate::ir_vm::types::Type; use prometeu_bytecode::v0::{BytecodeLoader, ConstantPoolEntry}; #[test] diff --git a/crates/prometeu-compiler/src/backend/mod.rs b/crates/prometeu-compiler/src/backend/mod.rs index 25b4478f..94e217ed 100644 --- a/crates/prometeu-compiler/src/backend/mod.rs +++ b/crates/prometeu-compiler/src/backend/mod.rs @@ -1,6 +1,6 @@ pub mod emit_bytecode; pub mod artifacts; -pub use emit_bytecode::{emit_module, emit_fragments, EmitFragments}; pub use artifacts::Artifacts; pub use emit_bytecode::EmitResult; +pub use emit_bytecode::{emit_fragments, emit_module, EmitFragments}; diff --git a/crates/prometeu-compiler/src/building/linker.rs b/crates/prometeu-compiler/src/building/linker.rs new file mode 100644 index 00000000..b4d6c872 --- /dev/null +++ b/crates/prometeu-compiler/src/building/linker.rs @@ -0,0 +1,410 @@ +use crate::building::output::{CompiledModule, ExportKey, ImportKey}; +use crate::building::plan::BuildStep; +use prometeu_bytecode::opcode::OpCode; +use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta}; +use prometeu_core::virtual_machine::{ProgramImage, Value}; +use std::collections::{BTreeMap, HashMap}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum LinkError { + UnresolvedSymbol(String), + DuplicateExport(String), + IncompatibleSymbolSignature(String), +} + +impl std::fmt::Display for LinkError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LinkError::UnresolvedSymbol(s) => write!(f, "Unresolved symbol: {}", s), + LinkError::DuplicateExport(s) => write!(f, "Duplicate export: {}", s), + LinkError::IncompatibleSymbolSignature(s) => write!(f, "Incompatible symbol signature: {}", s), + } + } +} + +impl std::error::Error for LinkError {} + +pub struct Linker; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct ConstantPoolBitKey(Vec); + +impl ConstantPoolBitKey { + fn from_entry(entry: &ConstantPoolEntry) -> Self { + match entry { + ConstantPoolEntry::Null => Self(vec![0]), + ConstantPoolEntry::Int64(v) => { + let mut b = vec![1]; + b.extend_from_slice(&v.to_le_bytes()); + Self(b) + } + ConstantPoolEntry::Float64(v) => { + let mut b = vec![2]; + b.extend_from_slice(&v.to_bits().to_le_bytes()); + Self(b) + } + ConstantPoolEntry::Boolean(v) => { + Self(vec![3, if *v { 1 } else { 0 }]) + } + ConstantPoolEntry::String(v) => { + let mut b = vec![4]; + b.extend_from_slice(v.as_bytes()); + Self(b) + } + ConstantPoolEntry::Int32(v) => { + let mut b = vec![5]; + b.extend_from_slice(&v.to_le_bytes()); + Self(b) + } + } + } +} + +impl Linker { + pub fn link(modules: Vec, steps: Vec) -> Result { + if modules.len() != steps.len() { + return Err(LinkError::IncompatibleSymbolSignature(format!("Module count ({}) does not match build steps count ({})", modules.len(), steps.len()))); + } + + let mut combined_code = Vec::new(); + let mut combined_functions = Vec::new(); + let mut combined_constants = Vec::new(); + let mut constant_map: HashMap = HashMap::new(); + + // Debug info merging + 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 + let mut global_symbols = HashMap::new(); + + let mut module_code_offsets = Vec::with_capacity(modules.len()); + let mut module_function_offsets = Vec::with_capacity(modules.len()); + + // Map ProjectId to index + let _project_to_idx: HashMap<_, _> = modules.iter().enumerate().map(|(i, m)| (m.project_id.clone(), i)).collect(); + + // PASS 1: Collect exports and calculate offsets + for (_i, module) in modules.iter().enumerate() { + let code_offset = combined_code.len() as u32; + let function_offset = combined_functions.len() as u32; + + module_code_offsets.push(code_offset); + module_function_offsets.push(function_offset); + + for (key, meta) in &module.exports { + if let Some(local_func_idx) = meta.func_idx { + let global_func_idx = function_offset + local_func_idx; + // Note: Use a tuple as key for clarity + let symbol_id = (module.project_id.clone(), key.module_path.clone(), key.symbol_name.clone()); + + if global_symbols.contains_key(&symbol_id) { + return Err(LinkError::DuplicateExport(format!("Project {:?} export {}:{} already defined", symbol_id.0, symbol_id.1, symbol_id.2))); + } + global_symbols.insert(symbol_id, global_func_idx); + } + } + + combined_code.extend_from_slice(&module.code); + for func in &module.function_metas { + let mut relocated = func.clone(); + relocated.code_offset += code_offset; + combined_functions.push(relocated); + } + + if let Some(debug) = &module.debug_info { + for (pc, span) in &debug.pc_to_span { + combined_pc_to_span.push((code_offset + pc, span.clone())); + } + for (func_idx, name) in &debug.function_names { + combined_function_names.push((function_offset + func_idx, name.clone())); + } + } + } + + // PASS 2: Relocate constants and patch CALLs + for (i, module) in modules.iter().enumerate() { + let step = &steps[i]; + let code_offset = module_code_offsets[i] as usize; + + // Map local constant indices to global constant indices + let mut local_to_global_const = Vec::with_capacity(module.const_pool.len()); + for entry in &module.const_pool { + let bit_key = ConstantPoolBitKey::from_entry(entry); + if let Some(&global_idx) = constant_map.get(&bit_key) { + local_to_global_const.push(global_idx); + } else { + let global_idx = combined_constants.len() as u32; + combined_constants.push(match entry { + ConstantPoolEntry::Null => Value::Null, + ConstantPoolEntry::Int64(v) => Value::Int64(*v), + ConstantPoolEntry::Float64(v) => Value::Float(*v), + ConstantPoolEntry::Boolean(v) => Value::Boolean(*v), + ConstantPoolEntry::String(v) => Value::String(v.clone()), + ConstantPoolEntry::Int32(v) => Value::Int32(*v), + }); + constant_map.insert(bit_key, global_idx); + local_to_global_const.push(global_idx); + } + } + + // Patch imports + for import in &module.imports { + let dep_project_id = if import.key.dep_alias == "self" || import.key.dep_alias.is_empty() { + &module.project_id + } else { + step.deps.get(&import.key.dep_alias) + .ok_or_else(|| LinkError::UnresolvedSymbol(format!("Dependency alias '{}' not found in project {:?}", import.key.dep_alias, module.project_id)))? + }; + + 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)))?; + + for &reloc_pc in &import.relocation_pcs { + let absolute_pc = code_offset + reloc_pc as usize; + let imm_offset = absolute_pc + 2; + if imm_offset + 4 <= combined_code.len() { + combined_code[imm_offset..imm_offset+4].copy_from_slice(&target_func_idx.to_le_bytes()); + } + } + } + + // Internal call relocation (from module-local func_idx to global func_idx) + // And PUSH_CONST relocation. + let mut pos = code_offset; + let end = code_offset + module.code.len(); + while pos < end { + if pos + 2 > end { break; } + let op_val = u16::from_le_bytes([combined_code[pos], combined_code[pos+1]]); + let opcode = match OpCode::try_from(op_val) { + Ok(op) => op, + Err(_) => { + pos += 2; + continue; + } + }; + pos += 2; + + match opcode { + OpCode::PushConst => { + if pos + 4 <= end { + let local_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap()) as usize; + if let Some(&global_idx) = local_to_global_const.get(local_idx) { + combined_code[pos..pos+4].copy_from_slice(&global_idx.to_le_bytes()); + } + pos += 4; + } + } + OpCode::Call => { + if pos + 4 <= end { + let local_func_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap()); + + // Check if this PC was already patched by an import. + // If it wasn't, it's an internal call that needs relocation. + let reloc_pc = (pos - 2 - code_offset) as u32; + let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc)); + + if !is_import { + let global_func_idx = module_function_offsets[i] + local_func_idx; + combined_code[pos..pos+4].copy_from_slice(&global_func_idx.to_le_bytes()); + } + pos += 4; + } + } + OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue + | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal + | OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => { + pos += 4; + } + OpCode::PushI64 | OpCode::PushF64 | OpCode::Alloc => { + pos += 8; + } + OpCode::PushBool => { + pos += 1; + } + _ => {} + } + } + } + + // Final Exports map for ProgramImage (String -> func_idx) + // Only including exports from the ROOT project (the last one in build plan usually) + // Wait, the requirement says "emit final PBS v0 image". + // In PBS v0, exports are name -> func_id. + let mut final_exports = HashMap::new(); + if let Some(root_module) = modules.last() { + for (key, meta) in &root_module.exports { + if let Some(local_func_idx) = meta.func_idx { + let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx; + final_exports.insert(format!("{}:{}", key.module_path, key.symbol_name), global_func_idx); + } + } + } + + let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() { + None + } else { + Some(DebugInfo { + pc_to_span: combined_pc_to_span, + function_names: combined_function_names, + }) + }; + + Ok(ProgramImage::new( + combined_code, + combined_constants, + combined_functions, + combined_debug_info, + final_exports, + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata}; + use crate::building::plan::BuildTarget; + use crate::deps::resolver::ProjectId; + use crate::frontends::pbs::symbols::SymbolKind; + use prometeu_bytecode::opcode::OpCode; + + #[test] + fn test_link_root_and_lib() { + let lib_id = ProjectId { name: "lib".into(), version: "1.0.0".into() }; + let root_id = ProjectId { name: "root".into(), version: "1.0.0".into() }; + + // Lib module: exports 'add' + let mut lib_code = Vec::new(); + lib_code.extend_from_slice(&(OpCode::Add as u16).to_le_bytes()); + lib_code.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); + + let mut lib_exports = BTreeMap::new(); + lib_exports.insert(ExportKey { + module_path: "math".into(), + symbol_name: "add".into(), + kind: SymbolKind::Function, + }, ExportMetadata { func_idx: Some(0) }); + + let lib_module = CompiledModule { + project_id: lib_id.clone(), + target: BuildTarget::Main, + exports: lib_exports, + imports: vec![], + const_pool: vec![], + code: lib_code, + function_metas: vec![FunctionMeta { + code_offset: 0, + code_len: 4, + ..Default::default() + }], + debug_info: None, + }; + + // Root module: calls 'lib::math:add' + let mut root_code = Vec::new(); + root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + root_code.extend_from_slice(&10i32.to_le_bytes()); + root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + root_code.extend_from_slice(&20i32.to_le_bytes()); + // Call lib:math:add + let call_pc = root_code.len() as u32; + root_code.extend_from_slice(&(OpCode::Call as u16).to_le_bytes()); + root_code.extend_from_slice(&0u32.to_le_bytes()); // placeholder + root_code.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + + let root_imports = vec![ImportMetadata { + key: ImportKey { + dep_alias: "mylib".into(), + module_path: "math".into(), + symbol_name: "add".into(), + }, + relocation_pcs: vec![call_pc], + }]; + + let root_module = CompiledModule { + project_id: root_id.clone(), + target: BuildTarget::Main, + exports: BTreeMap::new(), + imports: root_imports, + const_pool: vec![], + code: root_code, + function_metas: vec![FunctionMeta { + code_offset: 0, + code_len: 20, + ..Default::default() + }], + debug_info: None, + }; + + let lib_step = BuildStep { + project_id: lib_id.clone(), + project_dir: "".into(), + target: BuildTarget::Main, + sources: vec![], + deps: BTreeMap::new(), + }; + + let mut root_deps = BTreeMap::new(); + root_deps.insert("mylib".into(), lib_id.clone()); + + let root_step = BuildStep { + project_id: root_id.clone(), + project_dir: "".into(), + target: BuildTarget::Main, + sources: vec![], + deps: root_deps, + }; + + let result = Linker::link(vec![lib_module, root_module], vec![lib_step, root_step]).unwrap(); + + assert_eq!(result.functions.len(), 2); + // lib:add is func 0 + // root:main is func 1 + + // lib_code length is 4. + // Root code starts at 4. + // CALL was at root_code offset 12. + // Absolute PC of CALL: 4 + 12 = 16. + // Immediate is at 16 + 2 = 18. + let patched_func_idx = u32::from_le_bytes(result.rom[18..22].try_into().unwrap()); + assert_eq!(patched_func_idx, 0); // Points to lib:add + } + + #[test] + fn test_link_const_deduplication() { + let id = ProjectId { name: "test".into(), version: "1.0.0".into() }; + let step = BuildStep { project_id: id.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() }; + + let m1 = CompiledModule { + project_id: id.clone(), + target: BuildTarget::Main, + exports: BTreeMap::new(), + imports: vec![], + const_pool: vec![ConstantPoolEntry::Int32(42), ConstantPoolEntry::String("hello".into())], + code: vec![], + function_metas: vec![], + debug_info: None, + }; + + let m2 = CompiledModule { + project_id: id.clone(), + target: BuildTarget::Main, + exports: BTreeMap::new(), + imports: vec![], + const_pool: vec![ConstantPoolEntry::String("hello".into()), ConstantPoolEntry::Int32(99)], + code: vec![], + function_metas: vec![], + debug_info: None, + }; + + let result = Linker::link(vec![m1, m2], vec![step.clone(), step]).unwrap(); + + // Constants should be: 42, "hello", 99 + assert_eq!(result.constant_pool.len(), 3); + assert_eq!(result.constant_pool[0], Value::Int32(42)); + assert_eq!(result.constant_pool[1], Value::String("hello".into())); + assert_eq!(result.constant_pool[2], Value::Int32(99)); + } +} diff --git a/crates/prometeu-compiler/src/building/mod.rs b/crates/prometeu-compiler/src/building/mod.rs index fc4ffdd3..857fb2b3 100644 --- a/crates/prometeu-compiler/src/building/mod.rs +++ b/crates/prometeu-compiler/src/building/mod.rs @@ -1,2 +1,4 @@ pub mod plan; pub mod output; +pub mod linker; +pub mod orchestrator; diff --git a/crates/prometeu-compiler/src/building/orchestrator.rs b/crates/prometeu-compiler/src/building/orchestrator.rs new file mode 100644 index 00000000..39d9e111 --- /dev/null +++ b/crates/prometeu-compiler/src/building/orchestrator.rs @@ -0,0 +1,50 @@ +use crate::building::linker::{LinkError, Linker}; +use crate::building::output::{compile_project, CompileError}; +use crate::building::plan::{BuildPlan, BuildTarget}; +use crate::deps::resolver::ResolvedGraph; +use prometeu_core::virtual_machine::ProgramImage; +use std::collections::HashMap; + +#[derive(Debug)] +pub enum BuildError { + Compile(CompileError), + Link(LinkError), +} + +impl std::fmt::Display for BuildError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BuildError::Compile(e) => write!(f, "Compile error: {}", e), + BuildError::Link(e) => write!(f, "Link error: {}", e), + } + } +} + +impl std::error::Error for BuildError {} + +impl From for BuildError { + fn from(e: CompileError) -> Self { + BuildError::Compile(e) + } +} + +impl From for BuildError { + fn from(e: LinkError) -> Self { + BuildError::Link(e) + } +} + +pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result { + let plan = BuildPlan::from_graph(graph, target); + let mut compiled_modules = HashMap::new(); + let mut modules_in_order = Vec::new(); + + for step in &plan.steps { + let compiled = compile_project(step.clone(), &compiled_modules)?; + compiled_modules.insert(step.project_id.clone(), compiled.clone()); + modules_in_order.push(compiled); + } + + let program_image = Linker::link(modules_in_order, plan.steps)?; + Ok(program_image) +} diff --git a/crates/prometeu-compiler/src/building/output.rs b/crates/prometeu-compiler/src/building/output.rs index cb31bafa..5c64049b 100644 --- a/crates/prometeu-compiler/src/building/output.rs +++ b/crates/prometeu-compiler/src/building/output.rs @@ -1,21 +1,21 @@ +use crate::backend::emit_fragments; +use crate::building::plan::{BuildStep, BuildTarget}; +use crate::common::diagnostics::DiagnosticBundle; +use crate::common::files::FileManager; +use crate::common::spans::Span; +use crate::deps::resolver::ProjectId; +use crate::frontends::pbs::ast::FileNode; +use crate::frontends::pbs::collector::SymbolCollector; +use crate::frontends::pbs::lowering::Lowerer; +use crate::frontends::pbs::parser::Parser; +use crate::frontends::pbs::resolver::{ModuleProvider, Resolver}; +use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility}; +use crate::frontends::pbs::typecheck::TypeChecker; +use crate::lowering::core_to_vm; +use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; -use crate::deps::resolver::ProjectId; -use crate::building::plan::{BuildStep, BuildTarget}; -use crate::frontends::pbs::symbols::{SymbolKind, ModuleSymbols, Visibility, Symbol, SymbolTable, Namespace}; -use crate::common::spans::Span; -use crate::frontends::pbs::parser::Parser; -use crate::frontends::pbs::collector::SymbolCollector; -use crate::frontends::pbs::resolver::{Resolver, ModuleProvider}; -use crate::frontends::pbs::typecheck::TypeChecker; -use crate::frontends::pbs::lowering::Lowerer; -use crate::frontends::pbs::ast::FileNode; -use crate::common::files::FileManager; -use crate::common::diagnostics::DiagnosticBundle; -use crate::lowering::core_to_vm; -use crate::backend::emit_fragments; -use prometeu_bytecode::v0::{ConstantPoolEntry, FunctionMeta}; +use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ExportKey { @@ -52,6 +52,7 @@ pub struct CompiledModule { pub const_pool: Vec, pub code: Vec, pub function_metas: Vec, + pub debug_info: Option, } #[derive(Debug)] @@ -117,8 +118,8 @@ pub fn compile_project( let (ts, vs) = collector.collect(&ast)?; let module_path = source_rel.parent() - .and_then(|p| p.to_str()) - .unwrap_or("") + .unwrap_or(Path::new("")) + .to_string_lossy() .replace('\\', "/"); let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new); @@ -150,28 +151,36 @@ pub fn compile_project( for (alias, project_id) in &step.deps { if let Some(compiled) = dep_modules.get(project_id) { for (key, _) in &compiled.exports { - let synthetic_module_path = format!("@{}:{}", alias, key.module_path); - let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); + // Support syntax: "alias/module" and "@alias:module" + let key_module_path = key.module_path.replace("src/main/modules/", ""); + let synthetic_paths = [ + format!("{}/{}", alias, key_module_path), + format!("@{}:{}", alias, key_module_path), + ]; - let sym = Symbol { - name: key.symbol_name.clone(), - kind: key.kind.clone(), - namespace: match key.kind { - SymbolKind::Function | SymbolKind::Service => Namespace::Value, - SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type, - _ => Namespace::Value, - }, - visibility: Visibility::Pub, - ty: None, - is_host: false, - span: Span::new(0, 0, 0), - origin: Some(synthetic_module_path.clone()), - }; - - if sym.namespace == Namespace::Type { - ms.type_symbols.insert(sym).ok(); - } else { - ms.value_symbols.insert(sym).ok(); + for synthetic_module_path in synthetic_paths { + let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); + + let sym = Symbol { + name: key.symbol_name.clone(), + kind: key.kind.clone(), + namespace: match key.kind { + SymbolKind::Function | SymbolKind::Service => Namespace::Value, + SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type, + _ => Namespace::Value, + }, + visibility: Visibility::Pub, + ty: None, + is_host: false, + span: Span::new(0, 0, 0), + origin: Some(synthetic_module_path.clone()), + }; + + if sym.namespace == Namespace::Type { + ms.type_symbols.insert(sym).ok(); + } else { + ms.value_symbols.insert(sym).ok(); + } } } } @@ -289,14 +298,16 @@ pub fn compile_project( const_pool: fragments.const_pool, code: fragments.code, function_metas: fragments.functions, + debug_info: fragments.debug_info, }) } #[cfg(test)] mod tests { use super::*; - use tempfile::tempdir; use std::fs; + use std::path::PathBuf; + use tempfile::tempdir; #[test] fn test_compile_root_only_project() { diff --git a/crates/prometeu-compiler/src/building/plan.rs b/crates/prometeu-compiler/src/building/plan.rs index 065e7402..16c61981 100644 --- a/crates/prometeu-compiler/src/building/plan.rs +++ b/crates/prometeu-compiler/src/building/plan.rs @@ -1,7 +1,7 @@ +use crate::deps::resolver::{ProjectId, ResolvedGraph}; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::path::PathBuf; -use serde::{Deserialize, Serialize}; -use crate::deps::resolver::{ProjectId, ResolvedGraph}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -131,9 +131,9 @@ impl PartialOrd for ReverseProjectId { #[cfg(test)] mod tests { use super::*; - use crate::deps::resolver::{ProjectId, ResolvedNode, ResolvedEdge, ResolvedGraph}; - use crate::sources::ProjectSources; + use crate::deps::resolver::{ProjectId, ResolvedEdge, ResolvedGraph, ResolvedNode}; use crate::manifest::Manifest; + use crate::sources::ProjectSources; use std::collections::HashMap; fn mock_node(name: &str, version: &str) -> ResolvedNode { diff --git a/crates/prometeu-compiler/src/common/config.rs b/crates/prometeu-compiler/src/common/config.rs index bb9aa13a..a62e802d 100644 --- a/crates/prometeu-compiler/src/common/config.rs +++ b/crates/prometeu-compiler/src/common/config.rs @@ -1,7 +1,7 @@ +use crate::manifest::Manifest; +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -use anyhow::Result; -use crate::manifest::Manifest; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ProjectConfig { diff --git a/crates/prometeu-compiler/src/common/diagnostics.rs b/crates/prometeu-compiler/src/common/diagnostics.rs index 50a46be4..b1b6cbc6 100644 --- a/crates/prometeu-compiler/src/common/diagnostics.rs +++ b/crates/prometeu-compiler/src/common/diagnostics.rs @@ -1,6 +1,6 @@ +use crate::common::files::FileManager; use crate::common::spans::Span; use serde::{Serialize, Serializer}; -use crate::common::files::FileManager; #[derive(Debug, Clone, PartialEq)] pub enum DiagnosticLevel { @@ -112,9 +112,9 @@ impl From for DiagnosticBundle { #[cfg(test)] mod tests { + use crate::common::files::FileManager; use crate::frontends::pbs::PbsFrontend; use crate::frontends::Frontend; - use crate::common::files::FileManager; use std::fs; use tempfile::tempdir; diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index ff8a9c9b..5e35f5c7 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -5,11 +5,9 @@ use crate::backend; use crate::common::config::ProjectConfig; -use crate::common::files::FileManager; use crate::common::symbols::Symbol; -use crate::frontends::Frontend; -use crate::ir_vm; use anyhow::Result; +use prometeu_bytecode::v0::BytecodeModule; use std::path::Path; /// The result of a successful compilation process. @@ -38,77 +36,50 @@ impl CompilationUnit { } } -/// Orchestrates the compilation of a Prometeu project starting from an entry file. -/// -/// This function executes the full compiler pipeline: -/// 1. **Frontend**: Loads and parses the entry file (and its dependencies). -/// Currently, it uses the `TypescriptFrontend`. -/// 2. **IR Generation**: The frontend produces a high-level Intermediate Representation (IR). -/// 3. **Validation**: Checks the IR for consistency and VM compatibility. -/// 4. **Backend**: Lowers the IR into final Prometeu ByteCode. -/// -/// # Errors -/// Returns an error if parsing fails, validation finds issues, or code generation fails. -/// -/// # Example -/// ```no_run -/// use std::path::Path; -/// let project_dir = Path::new("."); -/// let unit = prometeu_compiler::compiler::compile(project_dir).expect("Failed to compile"); -/// unit.export(Path::new("build/program.pbc"), true, true).unwrap(); -/// ``` + pub fn compile(project_dir: &Path) -> Result { let config = ProjectConfig::load(project_dir)?; - // 1. Select Frontend - // The _frontend is responsible for parsing source code and producing the IR. - let _frontend: Box = match config.script_fe.as_str() { - "pbs" => Box::new(crate::frontends::pbs::PbsFrontend), - _ => anyhow::bail!("Invalid frontend: {}", config.script_fe), - }; - - #[allow(unreachable_code, unused_variables, unused_mut)] - { - let entry = project_dir.join(&config.entry); - let mut file_manager = FileManager::new(); - - // 2. Compile to IR (Intermediate Representation) - // This step abstracts away source-specific syntax (like TypeScript) into a - // generic set of instructions that the backend can understand. - let ir_module = _frontend.compile_to_ir(&entry, &mut file_manager) - .map_err(|bundle| { - if let Some(diag) = bundle.diagnostics.first() { - anyhow::anyhow!("{}", diag.message) - } else { - anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()) - } - })?; - - // 3. IR Validation - // Ensures the generated IR is sound and doesn't violate any VM constraints - // before we spend time generating bytecode. - ir_vm::validate::validate_module(&ir_module) - .map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?; - - // 4. Emit Bytecode - // The backend takes the validated IR and produces the final binary executable. - let result = backend::emit_module(&ir_module, &file_manager)?; + if config.script_fe == "pbs" { + let graph = crate::deps::resolver::resolve_graph(project_dir) + .map_err(|e| anyhow::anyhow!("Dependency resolution failed: {}", e))?; + + let program_image = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main) + .map_err(|e| anyhow::anyhow!("Build failed: {}", e))?; + + let module = BytecodeModule::from(program_image.clone()); + let rom = module.serialize(); + + let mut symbols = Vec::new(); + if let Some(debug) = &program_image.debug_info { + for (pc, span) in &debug.pc_to_span { + symbols.push(Symbol { + pc: *pc, + file: format!("file_{}", span.file_id), + line: 0, + col: 0, + }); + } + } Ok(CompilationUnit { - rom: result.rom, - symbols: result.symbols, + rom, + symbols, }) + } else { + anyhow::bail!("Invalid frontend: {}", config.script_fe) } } #[cfg(test)] mod tests { use super::*; - use std::fs; - use tempfile::tempdir; - use prometeu_bytecode::v0::BytecodeLoader; + use crate::ir_vm; use prometeu_bytecode::disasm::disasm; use prometeu_bytecode::opcode::OpCode; + use prometeu_bytecode::v0::BytecodeLoader; + use std::fs; + use tempfile::tempdir; #[test] fn test_invalid_frontend() { diff --git a/crates/prometeu-compiler/src/deps/cache.rs b/crates/prometeu-compiler/src/deps/cache.rs index 101436e8..b85b09bf 100644 --- a/crates/prometeu-compiler/src/deps/cache.rs +++ b/crates/prometeu-compiler/src/deps/cache.rs @@ -1,8 +1,8 @@ +use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::{Path, PathBuf}; use std::fs; -use anyhow::Result; +use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CacheManifest { diff --git a/crates/prometeu-compiler/src/deps/fetch.rs b/crates/prometeu-compiler/src/deps/fetch.rs index ce5535c3..47000525 100644 --- a/crates/prometeu-compiler/src/deps/fetch.rs +++ b/crates/prometeu-compiler/src/deps/fetch.rs @@ -1,8 +1,8 @@ -use std::path::{Path, PathBuf}; -use std::fs; -use std::process::Command; +use crate::deps::cache::{get_cache_root, get_git_worktree_path, CacheManifest, GitCacheEntry}; use crate::manifest::DependencySpec; -use crate::deps::cache::{CacheManifest, get_cache_root, get_git_worktree_path, GitCacheEntry}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; #[derive(Debug)] pub enum FetchError { @@ -141,8 +141,8 @@ pub fn fetch_git(url: &str, version: &str, root_project_dir: &Path) -> Result { chars: Peekable>, diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index 3e42227d..ab8ac1fd 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -1,7 +1,7 @@ use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::frontends::pbs::ast::*; -use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::contracts::ContractRegistry; +use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::types::PbsType; use crate::ir_core; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId}; @@ -1088,8 +1088,8 @@ impl<'a> Lowerer<'a> { #[cfg(test)] mod tests { use super::*; - use crate::frontends::pbs::parser::Parser; use crate::frontends::pbs::collector::SymbolCollector; + use crate::frontends::pbs::parser::Parser; use crate::frontends::pbs::symbols::ModuleSymbols; use crate::ir_core; diff --git a/crates/prometeu-compiler/src/frontends/pbs/mod.rs b/crates/prometeu-compiler/src/frontends/pbs/mod.rs index 6a56206c..b8c9c7b4 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/mod.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/mod.rs @@ -10,13 +10,13 @@ pub mod typecheck; pub mod lowering; pub mod contracts; -pub use lexer::Lexer; -pub use token::{Token, TokenKind}; -pub use symbols::{Symbol, SymbolTable, ModuleSymbols, Visibility, SymbolKind, Namespace}; pub use collector::SymbolCollector; -pub use resolver::{Resolver, ModuleProvider}; -pub use typecheck::TypeChecker; +pub use lexer::Lexer; pub use lowering::Lowerer; +pub use resolver::{ModuleProvider, Resolver}; +pub use symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, SymbolTable, Visibility}; +pub use token::{Token, TokenKind}; +pub use typecheck::TypeChecker; use crate::common::diagnostics::DiagnosticBundle; use crate::common::files::FileManager; diff --git a/crates/prometeu-compiler/src/frontends/pbs/parser.rs b/crates/prometeu-compiler/src/frontends/pbs/parser.rs index c9e57316..76af9949 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/parser.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/parser.rs @@ -108,24 +108,47 @@ impl Parser { fn parse_import_spec(&mut self) -> Result { let mut path = Vec::new(); let start_span = self.peek().span; - loop { - if let TokenKind::Identifier(ref name) = self.peek().kind { - path.push(name.clone()); - self.advance(); - } else { - return Err(self.error("Expected identifier in import spec")); - } - if self.peek().kind == TokenKind::Dot { - self.advance(); - } else { - break; + if self.peek().kind == TokenKind::OpenBrace { + self.advance(); // { + loop { + if let TokenKind::Identifier(ref name) = self.peek().kind { + path.push(name.clone()); + self.advance(); + } else { + return Err(self.error("Expected identifier in import spec")); + } + + if self.peek().kind == TokenKind::Comma { + self.advance(); + } else if self.peek().kind == TokenKind::CloseBrace { + break; + } else { + return Err(self.error("Expected ',' or '}' in import spec")); + } + } + self.consume(TokenKind::CloseBrace)?; + } else { + loop { + if let TokenKind::Identifier(ref name) = self.peek().kind { + path.push(name.clone()); + self.advance(); + } else { + return Err(self.error("Expected identifier in import spec")); + } + + if self.peek().kind == TokenKind::Dot { + self.advance(); + } else { + break; + } } } - let end_span = self.tokens[self.pos-1].span; - Ok(Node::ImportSpec(ImportSpecNode { + + let end_span = self.tokens[self.pos - 1].span; + Ok(Node::ImportSpec(ImportSpecNode { span: Span::new(self.file_id, start_span.start, end_span.end), - path + path, })) } diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs index ce34874b..a1d863cb 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -445,9 +445,9 @@ impl<'a> Resolver<'a> { #[cfg(test)] mod tests { use super::*; - use crate::frontends::pbs::*; use crate::common::files::FileManager; use crate::common::spans::Span; + use crate::frontends::pbs::*; use std::path::PathBuf; fn setup_test(source: &str) -> (ast::FileNode, usize) { diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs index 4b88f969..511508a9 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize}; use crate::common::spans::Span; use crate::frontends::pbs::types::PbsType; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs index 5f57bb26..228c4200 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/typecheck.rs @@ -1,10 +1,10 @@ use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::common::spans::Span; use crate::frontends::pbs::ast::*; +use crate::frontends::pbs::contracts::ContractRegistry; +use crate::frontends::pbs::resolver::ModuleProvider; use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::types::PbsType; -use crate::frontends::pbs::resolver::ModuleProvider; -use crate::frontends::pbs::contracts::ContractRegistry; use std::collections::HashMap; pub struct TypeChecker<'a> { @@ -835,9 +835,9 @@ impl<'a> TypeChecker<'a> { #[cfg(test)] mod tests { + use crate::common::files::FileManager; use crate::frontends::pbs::PbsFrontend; use crate::frontends::Frontend; - use crate::common::files::FileManager; use std::fs; fn check_code(code: &str) -> Result<(), String> { diff --git a/crates/prometeu-compiler/src/ir_core/block.rs b/crates/prometeu-compiler/src/ir_core/block.rs index 071d6752..9af1f6e8 100644 --- a/crates/prometeu-compiler/src/ir_core/block.rs +++ b/crates/prometeu-compiler/src/ir_core/block.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize}; use super::instr::Instr; use super::terminator::Terminator; +use serde::{Deserialize, Serialize}; /// A basic block in a function's control flow graph. /// Contains a sequence of instructions and ends with a terminator. diff --git a/crates/prometeu-compiler/src/ir_core/const_pool.rs b/crates/prometeu-compiler/src/ir_core/const_pool.rs index 1ba8b437..5c1109c2 100644 --- a/crates/prometeu-compiler/src/ir_core/const_pool.rs +++ b/crates/prometeu-compiler/src/ir_core/const_pool.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use super::ids::ConstId; +use serde::{Deserialize, Serialize}; /// Represents a constant value that can be stored in the constant pool. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/crates/prometeu-compiler/src/ir_core/function.rs b/crates/prometeu-compiler/src/ir_core/function.rs index 11b6d6f0..7204e8c7 100644 --- a/crates/prometeu-compiler/src/ir_core/function.rs +++ b/crates/prometeu-compiler/src/ir_core/function.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize, Serialize}; -use super::ids::FunctionId; use super::block::Block; +use super::ids::FunctionId; use super::types::Type; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/crates/prometeu-compiler/src/ir_core/instr.rs b/crates/prometeu-compiler/src/ir_core/instr.rs index 788c6e5f..07498df1 100644 --- a/crates/prometeu-compiler/src/ir_core/instr.rs +++ b/crates/prometeu-compiler/src/ir_core/instr.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId}; +use serde::{Deserialize, Serialize}; /// Instructions within a basic block. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/prometeu-compiler/src/ir_core/mod.rs b/crates/prometeu-compiler/src/ir_core/mod.rs index 9b40f59b..401c3067 100644 --- a/crates/prometeu-compiler/src/ir_core/mod.rs +++ b/crates/prometeu-compiler/src/ir_core/mod.rs @@ -9,15 +9,15 @@ pub mod instr; pub mod terminator; pub mod validate; -pub use ids::*; -pub use const_pool::*; -pub use types::*; -pub use program::*; -pub use module::*; -pub use function::*; pub use block::*; +pub use const_pool::*; +pub use function::*; +pub use ids::*; pub use instr::*; +pub use module::*; +pub use program::*; pub use terminator::*; +pub use types::*; pub use validate::*; #[cfg(test)] diff --git a/crates/prometeu-compiler/src/ir_core/module.rs b/crates/prometeu-compiler/src/ir_core/module.rs index 0852011e..97b97d29 100644 --- a/crates/prometeu-compiler/src/ir_core/module.rs +++ b/crates/prometeu-compiler/src/ir_core/module.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use super::function::Function; +use serde::{Deserialize, Serialize}; /// A module within a program, containing functions and other declarations. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/prometeu-compiler/src/ir_core/program.rs b/crates/prometeu-compiler/src/ir_core/program.rs index db1f55d0..011a3aa8 100644 --- a/crates/prometeu-compiler/src/ir_core/program.rs +++ b/crates/prometeu-compiler/src/ir_core/program.rs @@ -1,8 +1,8 @@ -use serde::{Deserialize, Serialize}; -use super::module::Module; use super::const_pool::ConstPool; use super::ids::FieldId; +use super::module::Module; use super::types::Type; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/prometeu-compiler/src/ir_vm/instr.rs b/crates/prometeu-compiler/src/ir_vm/instr.rs index 58e5d32a..da116a90 100644 --- a/crates/prometeu-compiler/src/ir_vm/instr.rs +++ b/crates/prometeu-compiler/src/ir_vm/instr.rs @@ -5,8 +5,8 @@ //! easy to lower into VM-specific bytecode. use crate::common::spans::Span; -use crate::ir_vm::types::{ConstId, TypeId}; use crate::ir_core::ids::FunctionId; +use crate::ir_vm::types::{ConstId, TypeId}; /// An `Instruction` combines an instruction's behavior (`kind`) with its /// source code location (`span`) for debugging and error reporting. diff --git a/crates/prometeu-compiler/src/ir_vm/mod.rs b/crates/prometeu-compiler/src/ir_vm/mod.rs index 87676ccd..125879e8 100644 --- a/crates/prometeu-compiler/src/ir_vm/mod.rs +++ b/crates/prometeu-compiler/src/ir_vm/mod.rs @@ -29,15 +29,15 @@ pub mod module; pub mod instr; pub mod validate; -pub use instr::{Instruction, InstrKind, Label}; -pub use module::{Module, Function, Global, Param}; -pub use types::{Type, Value, GateId, ConstId, TypeId}; +pub use instr::{InstrKind, Instruction, Label}; +pub use module::{Function, Global, Module, Param}; +pub use types::{ConstId, GateId, Type, TypeId, Value}; #[cfg(test)] mod tests { use super::*; - use crate::ir_core::ids::{ConstId, FunctionId}; use crate::ir_core::const_pool::{ConstPool, ConstantValue}; + use crate::ir_core::ids::{ConstId, FunctionId}; use serde_json; #[test] diff --git a/crates/prometeu-compiler/src/ir_vm/module.rs b/crates/prometeu-compiler/src/ir_vm/module.rs index a34f151d..05753ac3 100644 --- a/crates/prometeu-compiler/src/ir_vm/module.rs +++ b/crates/prometeu-compiler/src/ir_vm/module.rs @@ -4,10 +4,10 @@ //! The IR is a higher-level representation of the program than bytecode, but lower //! than the source code AST. It is organized into Modules, Functions, and Globals. -use crate::ir_vm::instr::Instruction; -use crate::ir_vm::types::Type; use crate::ir_core::const_pool::ConstPool; use crate::ir_core::ids::FunctionId; +use crate::ir_vm::instr::Instruction; +use crate::ir_vm::types::Type; use serde::{Deserialize, Serialize}; /// A `Module` is the top-level container for a compiled program or library. diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index 6c479157..058349b5 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -1,5 +1,5 @@ -use crate::ir_vm; use crate::ir_core; +use crate::ir_vm; use anyhow::Result; use std::collections::HashMap; @@ -402,8 +402,8 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type { mod tests { use super::*; use crate::ir_core; - use crate::ir_core::{Block, Instr, Terminator, ConstantValue, Program, ConstPool}; - use crate::ir_core::ids::{FunctionId, ConstId as CoreConstId}; + use crate::ir_core::ids::{ConstId as CoreConstId, FunctionId}; + use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, Program, Terminator}; use crate::ir_vm::*; #[test] diff --git a/crates/prometeu-compiler/src/manifest.rs b/crates/prometeu-compiler/src/manifest.rs index c65cb354..f2a6095c 100644 --- a/crates/prometeu-compiler/src/manifest.rs +++ b/crates/prometeu-compiler/src/manifest.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::{Path, PathBuf}; use std::fs; +use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -188,8 +188,8 @@ fn validate_manifest(manifest: &Manifest, path: &Path) -> Result<(), ManifestErr #[cfg(test)] mod tests { use super::*; - use tempfile::tempdir; use std::fs; + use tempfile::tempdir; #[test] fn test_parse_minimal_manifest() { diff --git a/crates/prometeu-compiler/src/sources.rs b/crates/prometeu-compiler/src/sources.rs index 36a9fd5b..43abd7cf 100644 --- a/crates/prometeu-compiler/src/sources.rs +++ b/crates/prometeu-compiler/src/sources.rs @@ -1,11 +1,11 @@ -use std::path::{Path, PathBuf}; -use std::fs; -use std::collections::HashMap; -use serde::{Deserialize, Serialize}; -use crate::manifest::{load_manifest, ManifestKind}; -use crate::frontends::pbs::{Symbol, Visibility, parser::Parser, collector::SymbolCollector}; -use crate::common::files::FileManager; use crate::common::diagnostics::DiagnosticBundle; +use crate::common::files::FileManager; +use crate::frontends::pbs::{collector::SymbolCollector, parser::Parser, Symbol, Visibility}; +use crate::manifest::{load_manifest, ManifestKind}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProjectSources { @@ -154,8 +154,8 @@ pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Resul #[cfg(test)] mod tests { use super::*; - use tempfile::tempdir; use std::fs; + use tempfile::tempdir; #[test] fn test_discover_app_with_main() { diff --git a/crates/prometeu-compiler/tests/generate_canonical_goldens.rs b/crates/prometeu-compiler/tests/generate_canonical_goldens.rs index 0923a584..1f22d821 100644 --- a/crates/prometeu-compiler/tests/generate_canonical_goldens.rs +++ b/crates/prometeu-compiler/tests/generate_canonical_goldens.rs @@ -1,10 +1,10 @@ +use prometeu_bytecode::disasm::disasm; +use prometeu_bytecode::v0::BytecodeLoader; +use prometeu_compiler::compiler::compile; +use prometeu_compiler::frontends::pbs::ast::Node; +use prometeu_compiler::frontends::pbs::parser::Parser; use std::fs; use std::path::Path; -use prometeu_compiler::compiler::compile; -use prometeu_bytecode::v0::BytecodeLoader; -use prometeu_bytecode::disasm::disasm; -use prometeu_compiler::frontends::pbs::parser::Parser; -use prometeu_compiler::frontends::pbs::ast::Node; #[test] fn generate_canonical_goldens() { diff --git a/crates/prometeu-compiler/tests/hip_conformance.rs b/crates/prometeu-compiler/tests/hip_conformance.rs index ac8c3ba9..5966a100 100644 --- a/crates/prometeu-compiler/tests/hip_conformance.rs +++ b/crates/prometeu-compiler/tests/hip_conformance.rs @@ -1,9 +1,9 @@ -use prometeu_compiler::ir_core::{self, Program, Block, Instr, Terminator, ConstantValue, ConstPool}; -use prometeu_compiler::ir_core::ids::{FunctionId, ConstId as CoreConstId, TypeId as CoreTypeId, FieldId, ValueId}; -use prometeu_compiler::ir_vm::InstrKind; -use prometeu_compiler::lowering::lower_program; use prometeu_compiler::backend::emit_bytecode::emit_module; use prometeu_compiler::common::files::FileManager; +use prometeu_compiler::ir_core::ids::{ConstId as CoreConstId, FieldId, FunctionId, TypeId as CoreTypeId, ValueId}; +use prometeu_compiler::ir_core::{self, Block, ConstPool, ConstantValue, Instr, Program, Terminator}; +use prometeu_compiler::ir_vm::InstrKind; +use prometeu_compiler::lowering::lower_program; use std::collections::HashMap; #[test] diff --git a/crates/prometeu-compiler/tests/link_integration.rs b/crates/prometeu-compiler/tests/link_integration.rs new file mode 100644 index 00000000..3359bc4e --- /dev/null +++ b/crates/prometeu-compiler/tests/link_integration.rs @@ -0,0 +1,82 @@ +use prometeu_compiler::compiler::compile; +use prometeu_core::hardware::{AssetManager, Audio, Gfx, HardwareBridge, MemoryBanks, Pad, Touch}; +use prometeu_core::virtual_machine::{HostReturn, LogicalFrameEndingReason, NativeInterface, Value, VirtualMachine, VmFault}; +use std::path::PathBuf; +use std::sync::Arc; + +struct SimpleNative; +impl NativeInterface for SimpleNative { + fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _hw: &mut dyn HardwareBridge) -> Result<(), VmFault> { + Ok(()) + } +} + +struct SimpleHardware { + gfx: Gfx, + audio: Audio, + pad: Pad, + touch: Touch, + assets: AssetManager, +} + +impl SimpleHardware { + fn new() -> Self { + let banks = Arc::new(MemoryBanks::new()); + Self { + gfx: Gfx::new(320, 240, banks.clone()), + audio: Audio::new(banks.clone()), + pad: Pad::default(), + touch: Touch::default(), + assets: AssetManager::new(vec![], vec![], banks.clone(), banks.clone()), + } + } +} + +impl HardwareBridge for SimpleHardware { + fn gfx(&self) -> &Gfx { &self.gfx } + fn gfx_mut(&mut self) -> &mut Gfx { &mut self.gfx } + fn audio(&self) -> &Audio { &self.audio } + fn audio_mut(&mut self) -> &mut Audio { &mut self.audio } + fn pad(&self) -> &Pad { &self.pad } + fn pad_mut(&mut self) -> &mut Pad { &mut self.pad } + fn touch(&self) -> &Touch { &self.touch } + fn touch_mut(&mut self) -> &mut Touch { &mut self.touch } + fn assets(&self) -> &AssetManager { &self.assets } + fn assets_mut(&mut self) -> &mut AssetManager { &mut self.assets } +} + +#[test] +fn test_integration_test01_link() { + let project_dir = PathBuf::from("../../test-cartridges/test01"); + // Since the test runs from crates/prometeu-compiler, we need to adjust path if necessary. + // Actually, usually tests run from the workspace root if using cargo test --workspace, + // but if running from the crate dir, it's different. + + // Let's try absolute path or relative to project root. + let mut root_dir = std::env::current_dir().unwrap(); + while !root_dir.join("test-cartridges").exists() { + if let Some(parent) = root_dir.parent() { + root_dir = parent.to_path_buf(); + } else { + break; + } + } + let _project_dir = root_dir.join("test-cartridges/test01"); + + let unit = compile(&project_dir).expect("Failed to compile and link"); + + let mut vm = VirtualMachine::default(); + // Use initialize to load the ROM and resolve entrypoint + vm.initialize(unit.rom, "src/main/modules:frame").expect("Failed to initialize VM"); + + let mut native = SimpleNative; + let mut hw = SimpleHardware::new(); + + // Run for a bit + let report = vm.run_budget(1000, &mut native, &mut hw).expect("VM execution failed"); + + // It should not trap. test01 might loop or return. + if let LogicalFrameEndingReason::Trap(t) = report.reason { + panic!("VM trapped: {:?}", t); + } +} diff --git a/crates/prometeu-core/src/prometeu_hub/mod.rs b/crates/prometeu-core/src/prometeu_hub/mod.rs index 824717d0..ac963b9e 100644 --- a/crates/prometeu-core/src/prometeu_hub/mod.rs +++ b/crates/prometeu-core/src/prometeu_hub/mod.rs @@ -1,4 +1,4 @@ mod prometeu_hub; mod window_manager; -pub use prometeu_hub::PrometeuHub; \ No newline at end of file +pub use prometeu_hub::PrometeuHub; diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index f028424d..50279b29 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -5,7 +5,7 @@ use crate::log::{LogLevel, LogService, LogSource}; use crate::model::{BankType, Cartridge, Color}; use crate::prometeu_os::NativeInterface; use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; -use crate::virtual_machine::{Value, VirtualMachine, HostReturn, SyscallId, VmFault, expect_int, expect_bool}; +use crate::virtual_machine::{expect_bool, expect_int, HostReturn, SyscallId, Value, VirtualMachine, VmFault}; use std::collections::HashMap; use std::time::Instant; diff --git a/crates/prometeu-core/src/virtual_machine/bytecode/decoder.rs b/crates/prometeu-core/src/virtual_machine/bytecode/decoder.rs index 82ce773e..18b990d1 100644 --- a/crates/prometeu-core/src/virtual_machine/bytecode/decoder.rs +++ b/crates/prometeu-core/src/virtual_machine/bytecode/decoder.rs @@ -1,5 +1,5 @@ +use crate::virtual_machine::opcode_spec::{OpCodeSpecExt, OpcodeSpec}; use prometeu_bytecode::opcode::OpCode; -use crate::virtual_machine::opcode_spec::{OpcodeSpec, OpCodeSpecExt}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum DecodeError { diff --git a/crates/prometeu-core/src/virtual_machine/local_addressing.rs b/crates/prometeu-core/src/virtual_machine/local_addressing.rs index 0a872c05..49814ada 100644 --- a/crates/prometeu-core/src/virtual_machine/local_addressing.rs +++ b/crates/prometeu-core/src/virtual_machine/local_addressing.rs @@ -1,6 +1,6 @@ use crate::virtual_machine::call_frame::CallFrame; -use prometeu_bytecode::v0::FunctionMeta; use prometeu_bytecode::abi::{TrapInfo, TRAP_INVALID_LOCAL}; +use prometeu_bytecode::v0::FunctionMeta; /// Computes the absolute stack index for the start of the current frame's locals (including args). pub fn local_base(frame: &CallFrame) -> usize { diff --git a/crates/prometeu-core/src/virtual_machine/mod.rs b/crates/prometeu-core/src/virtual_machine/mod.rs index ca184fb0..f5a4f249 100644 --- a/crates/prometeu-core/src/virtual_machine/mod.rs +++ b/crates/prometeu-core/src/virtual_machine/mod.rs @@ -10,11 +10,11 @@ pub mod verifier; use crate::hardware::HardwareBridge; pub use program::ProgramImage; +pub use prometeu_bytecode::abi::TrapInfo; pub use prometeu_bytecode::opcode::OpCode; pub use value::Value; -pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; -pub use prometeu_bytecode::abi::TrapInfo; pub use verifier::VerifierError; +pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; pub type SyscallId = u32; diff --git a/crates/prometeu-core/src/virtual_machine/program.rs b/crates/prometeu-core/src/virtual_machine/program.rs index 024ba214..10e02545 100644 --- a/crates/prometeu-core/src/virtual_machine/program.rs +++ b/crates/prometeu-core/src/virtual_machine/program.rs @@ -1,8 +1,8 @@ use crate::virtual_machine::Value; -use prometeu_bytecode::v0::{FunctionMeta, DebugInfo, BytecodeModule, ConstantPoolEntry}; use prometeu_bytecode::abi::TrapInfo; -use std::sync::Arc; +use prometeu_bytecode::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; use std::collections::HashMap; +use std::sync::Arc; #[derive(Debug, Clone, Default)] pub struct ProgramImage { @@ -91,3 +91,33 @@ impl From for ProgramImage { ) } } + +impl From for BytecodeModule { + fn from(program: ProgramImage) -> Self { + let const_pool = program.constant_pool.iter().map(|v| match v { + Value::Null => ConstantPoolEntry::Null, + Value::Int64(v) => ConstantPoolEntry::Int64(*v), + Value::Float(v) => ConstantPoolEntry::Float64(*v), + Value::Boolean(v) => ConstantPoolEntry::Boolean(*v), + Value::String(v) => ConstantPoolEntry::String(v.clone()), + Value::Int32(v) => ConstantPoolEntry::Int32(*v), + Value::Bounded(v) => ConstantPoolEntry::Int32(*v as i32), + Value::Gate(_) => ConstantPoolEntry::Null, + }).collect(); + + let exports = program.exports.iter().map(|(symbol, &func_idx)| Export { + symbol: symbol.clone(), + func_idx, + }).collect(); + + BytecodeModule { + version: 0, + const_pool, + functions: program.functions.as_ref().to_vec(), + code: program.rom.as_ref().to_vec(), + debug_info: program.debug_info.clone(), + exports, + imports: vec![], + } + } +} diff --git a/crates/prometeu-core/src/virtual_machine/verifier.rs b/crates/prometeu-core/src/virtual_machine/verifier.rs index 38530297..f61815f3 100644 --- a/crates/prometeu-core/src/virtual_machine/verifier.rs +++ b/crates/prometeu-core/src/virtual_machine/verifier.rs @@ -1,7 +1,7 @@ -use prometeu_bytecode::v0::FunctionMeta; use crate::virtual_machine::bytecode::decoder::{decode_at, DecodeError}; use prometeu_bytecode::opcode::OpCode; -use std::collections::{HashMap, VecDeque, HashSet}; +use prometeu_bytecode::v0::FunctionMeta; +use std::collections::{HashMap, HashSet, VecDeque}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum VerifierError { diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index af43efd4..4f3fc5b8 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -3,8 +3,8 @@ use crate::virtual_machine::call_frame::CallFrame; use crate::virtual_machine::scope_frame::ScopeFrame; use crate::virtual_machine::value::Value; use crate::virtual_machine::{NativeInterface, ProgramImage, VmInitError}; +use prometeu_bytecode::abi::{TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_OOB, TRAP_TYPE}; use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO, TRAP_TYPE, TRAP_INVALID_FUNC, TRAP_BAD_RET_SLOTS}; /// Reason why the Virtual Machine stopped execution during a specific run. /// This allows the system to decide if it should continue execution in the next tick @@ -914,10 +914,10 @@ impl VirtualMachine { #[cfg(test)] mod tests { use super::*; - use prometeu_bytecode::v0::FunctionMeta; - use prometeu_bytecode::abi::SourceSpan; use crate::hardware::HardwareBridge; - use crate::virtual_machine::{Value, HostReturn, VmFault, expect_int}; + use crate::virtual_machine::{expect_int, HostReturn, Value, VmFault}; + use prometeu_bytecode::abi::SourceSpan; + use prometeu_bytecode::v0::FunctionMeta; struct MockNative; impl NativeInterface for MockNative { diff --git a/crates/prometeu-core/tests/heartbeat.rs b/crates/prometeu-core/tests/heartbeat.rs index 07b4274e..2e070056 100644 --- a/crates/prometeu-core/tests/heartbeat.rs +++ b/crates/prometeu-core/tests/heartbeat.rs @@ -1,11 +1,11 @@ -use prometeu_core::virtual_machine::{VirtualMachine, LogicalFrameEndingReason}; use prometeu_core::hardware::HardwareBridge; -use prometeu_core::Hardware; +use prometeu_core::virtual_machine::HostReturn; use prometeu_core::virtual_machine::NativeInterface; use prometeu_core::virtual_machine::Value; -use prometeu_core::virtual_machine::HostReturn; -use std::path::Path; +use prometeu_core::virtual_machine::{LogicalFrameEndingReason, VirtualMachine}; +use prometeu_core::Hardware; use std::fs; +use std::path::Path; struct MockNative; impl NativeInterface for MockNative { diff --git a/test-cartridges/canonical/golden/program.pbc b/test-cartridges/canonical/golden/program.pbc index 648f8529..63d6cc54 100644 Binary files a/test-cartridges/canonical/golden/program.pbc and b/test-cartridges/canonical/golden/program.pbc differ