This commit is contained in:
Nilton Constantino 2026-02-02 17:52:20 +00:00
parent f80cb64f66
commit 66a77709f0
No known key found for this signature in database
50 changed files with 806 additions and 225 deletions

View File

@ -50,8 +50,10 @@ pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B;
/// Executed RET with an incorrect stack height (mismatch with function metadata). /// Executed RET with an incorrect stack height (mismatch with function metadata).
pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C; pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C;
use serde::{Deserialize, Serialize};
/// Detailed information about a source code span. /// 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 struct SourceSpan {
pub file_id: u32, pub file_id: u32,
pub start: u32, pub start: u32,

View File

@ -1,5 +1,5 @@
use crate::v0::{BytecodeModule, DebugInfo, ConstantPoolEntry, FunctionMeta};
use crate::opcode::OpCode; use crate::opcode::OpCode;
use crate::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -158,8 +158,8 @@ impl Linker {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::v0::{BytecodeModule, FunctionMeta, Export, Import};
use crate::opcode::OpCode; use crate::opcode::OpCode;
use crate::v0::{BytecodeModule, Export, FunctionMeta, Import};
#[test] #[test]
fn test_linker_basic() { fn test_linker_basic() {

View File

@ -1,8 +1,8 @@
pub mod linker; pub mod linker;
use serde::{Deserialize, Serialize};
use crate::opcode::OpCode;
use crate::abi::SourceSpan; use crate::abi::SourceSpan;
use crate::opcode::OpCode;
use serde::{Deserialize, Serialize};
/// An entry in the Constant Pool. /// An entry in the Constant Pool.
/// ///
@ -50,7 +50,7 @@ pub struct FunctionMeta {
pub max_stack_slots: u16, pub max_stack_slots: u16,
} }
#[derive(Debug, Clone, Default, PartialEq, Eq)] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DebugInfo { pub struct DebugInfo {
pub pc_to_span: Vec<(u32, SourceSpan)>, // Sorted by PC pub pc_to_span: Vec<(u32, SourceSpan)>, // Sorted by PC
pub function_names: Vec<(u32, String)>, // (func_idx, name) pub function_names: Vec<(u32, String)>, // (func_idx, name)

View File

@ -9,14 +9,14 @@
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::ir_core::ConstantValue;
use crate::ir_vm; use crate::ir_vm;
use crate::ir_vm::instr::InstrKind; use crate::ir_vm::instr::InstrKind;
use crate::ir_core::ConstantValue;
use anyhow::{anyhow, Result}; 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::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. /// The final output of the code generation phase.
pub struct EmitResult { pub struct EmitResult {
@ -306,12 +306,12 @@ impl<'a> BytecodeEmitter<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; 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::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}; use prometeu_bytecode::v0::{BytecodeLoader, ConstantPoolEntry};
#[test] #[test]

View File

@ -1,6 +1,6 @@
pub mod emit_bytecode; pub mod emit_bytecode;
pub mod artifacts; pub mod artifacts;
pub use emit_bytecode::{emit_module, emit_fragments, EmitFragments};
pub use artifacts::Artifacts; pub use artifacts::Artifacts;
pub use emit_bytecode::EmitResult; pub use emit_bytecode::EmitResult;
pub use emit_bytecode::{emit_fragments, emit_module, EmitFragments};

View File

@ -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<u8>);
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<CompiledModule>, steps: Vec<BuildStep>) -> Result<ProgramImage, LinkError> {
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<ConstantPoolBitKey, u32> = 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));
}
}

View File

@ -1,2 +1,4 @@
pub mod plan; pub mod plan;
pub mod output; pub mod output;
pub mod linker;
pub mod orchestrator;

View File

@ -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<CompileError> for BuildError {
fn from(e: CompileError) -> Self {
BuildError::Compile(e)
}
}
impl From<LinkError> for BuildError {
fn from(e: LinkError) -> Self {
BuildError::Link(e)
}
}
pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<ProgramImage, BuildError> {
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)
}

View File

@ -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 serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf; use std::path::Path;
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};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExportKey { pub struct ExportKey {
@ -52,6 +52,7 @@ pub struct CompiledModule {
pub const_pool: Vec<ConstantPoolEntry>, pub const_pool: Vec<ConstantPoolEntry>,
pub code: Vec<u8>, pub code: Vec<u8>,
pub function_metas: Vec<FunctionMeta>, pub function_metas: Vec<FunctionMeta>,
pub debug_info: Option<DebugInfo>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -117,8 +118,8 @@ pub fn compile_project(
let (ts, vs) = collector.collect(&ast)?; let (ts, vs) = collector.collect(&ast)?;
let module_path = source_rel.parent() let module_path = source_rel.parent()
.and_then(|p| p.to_str()) .unwrap_or(Path::new(""))
.unwrap_or("") .to_string_lossy()
.replace('\\', "/"); .replace('\\', "/");
let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new); let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new);
@ -150,7 +151,14 @@ pub fn compile_project(
for (alias, project_id) in &step.deps { for (alias, project_id) in &step.deps {
if let Some(compiled) = dep_modules.get(project_id) { if let Some(compiled) = dep_modules.get(project_id) {
for (key, _) in &compiled.exports { for (key, _) in &compiled.exports {
let synthetic_module_path = format!("@{}:{}", alias, key.module_path); // 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),
];
for synthetic_module_path in synthetic_paths {
let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new); let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new);
let sym = Symbol { let sym = Symbol {
@ -176,6 +184,7 @@ pub fn compile_project(
} }
} }
} }
}
// 3. Resolve and TypeCheck each file // 3. Resolve and TypeCheck each file
let module_provider = ProjectModuleProvider { let module_provider = ProjectModuleProvider {
@ -289,14 +298,16 @@ pub fn compile_project(
const_pool: fragments.const_pool, const_pool: fragments.const_pool,
code: fragments.code, code: fragments.code,
function_metas: fragments.functions, function_metas: fragments.functions,
debug_info: fragments.debug_info,
}) })
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::tempdir;
use std::fs; use std::fs;
use std::path::PathBuf;
use tempfile::tempdir;
#[test] #[test]
fn test_compile_root_only_project() { fn test_compile_root_only_project() {

View File

@ -1,7 +1,7 @@
use crate::deps::resolver::{ProjectId, ResolvedGraph};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf; use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::deps::resolver::{ProjectId, ResolvedGraph};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -131,9 +131,9 @@ impl PartialOrd for ReverseProjectId {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::deps::resolver::{ProjectId, ResolvedNode, ResolvedEdge, ResolvedGraph}; use crate::deps::resolver::{ProjectId, ResolvedEdge, ResolvedGraph, ResolvedNode};
use crate::sources::ProjectSources;
use crate::manifest::Manifest; use crate::manifest::Manifest;
use crate::sources::ProjectSources;
use std::collections::HashMap; use std::collections::HashMap;
fn mock_node(name: &str, version: &str) -> ResolvedNode { fn mock_node(name: &str, version: &str) -> ResolvedNode {

View File

@ -1,7 +1,7 @@
use crate::manifest::Manifest;
use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::manifest::Manifest;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ProjectConfig { pub struct ProjectConfig {

View File

@ -1,6 +1,6 @@
use crate::common::files::FileManager;
use crate::common::spans::Span; use crate::common::spans::Span;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use crate::common::files::FileManager;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum DiagnosticLevel { pub enum DiagnosticLevel {
@ -112,9 +112,9 @@ impl From<Diagnostic> for DiagnosticBundle {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::common::files::FileManager;
use crate::frontends::pbs::PbsFrontend; use crate::frontends::pbs::PbsFrontend;
use crate::frontends::Frontend; use crate::frontends::Frontend;
use crate::common::files::FileManager;
use std::fs; use std::fs;
use tempfile::tempdir; use tempfile::tempdir;

View File

@ -5,11 +5,9 @@
use crate::backend; use crate::backend;
use crate::common::config::ProjectConfig; use crate::common::config::ProjectConfig;
use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::frontends::Frontend;
use crate::ir_vm;
use anyhow::Result; use anyhow::Result;
use prometeu_bytecode::v0::BytecodeModule;
use std::path::Path; use std::path::Path;
/// The result of a successful compilation process. /// 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<CompilationUnit> { pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
let config = ProjectConfig::load(project_dir)?; let config = ProjectConfig::load(project_dir)?;
// 1. Select Frontend if config.script_fe == "pbs" {
// The _frontend is responsible for parsing source code and producing the IR. let graph = crate::deps::resolver::resolve_graph(project_dir)
let _frontend: Box<dyn Frontend> = match config.script_fe.as_str() { .map_err(|e| anyhow::anyhow!("Dependency resolution failed: {}", e))?;
"pbs" => Box::new(crate::frontends::pbs::PbsFrontend),
_ => anyhow::bail!("Invalid frontend: {}", config.script_fe),
};
#[allow(unreachable_code, unused_variables, unused_mut)] let program_image = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main)
{ .map_err(|e| anyhow::anyhow!("Build failed: {}", e))?;
let entry = project_dir.join(&config.entry);
let mut file_manager = FileManager::new();
// 2. Compile to IR (Intermediate Representation) let module = BytecodeModule::from(program_image.clone());
// This step abstracts away source-specific syntax (like TypeScript) into a let rom = module.serialize();
// generic set of instructions that the backend can understand.
let ir_module = _frontend.compile_to_ir(&entry, &mut file_manager) let mut symbols = Vec::new();
.map_err(|bundle| { if let Some(debug) = &program_image.debug_info {
if let Some(diag) = bundle.diagnostics.first() { for (pc, span) in &debug.pc_to_span {
anyhow::anyhow!("{}", diag.message) symbols.push(Symbol {
} else { pc: *pc,
anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()) file: format!("file_{}", span.file_id),
line: 0,
col: 0,
});
}
} }
})?;
// 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)?;
Ok(CompilationUnit { Ok(CompilationUnit {
rom: result.rom, rom,
symbols: result.symbols, symbols,
}) })
} else {
anyhow::bail!("Invalid frontend: {}", config.script_fe)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::fs; use crate::ir_vm;
use tempfile::tempdir;
use prometeu_bytecode::v0::BytecodeLoader;
use prometeu_bytecode::disasm::disasm; use prometeu_bytecode::disasm::disasm;
use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::v0::BytecodeLoader;
use std::fs;
use tempfile::tempdir;
#[test] #[test]
fn test_invalid_frontend() { fn test_invalid_frontend() {

View File

@ -1,8 +1,8 @@
use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs; use std::fs;
use anyhow::Result; use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheManifest { pub struct CacheManifest {

View File

@ -1,8 +1,8 @@
use std::path::{Path, PathBuf}; use crate::deps::cache::{get_cache_root, get_git_worktree_path, CacheManifest, GitCacheEntry};
use std::fs;
use std::process::Command;
use crate::manifest::DependencySpec; 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)] #[derive(Debug)]
pub enum FetchError { pub enum FetchError {
@ -141,8 +141,8 @@ pub fn fetch_git(url: &str, version: &str, root_project_dir: &Path) -> Result<Pa
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::tempdir;
use std::fs; use std::fs;
use tempfile::tempdir;
#[test] #[test]
fn test_fetch_path_resolves_relative() { fn test_fetch_path_resolves_relative() {

View File

@ -1,9 +1,9 @@
use crate::deps::fetch::{fetch_dependency, FetchError};
use crate::manifest::{load_manifest, Manifest};
use crate::sources::{discover, ProjectSources, SourceError};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::manifest::{Manifest, load_manifest};
use crate::deps::fetch::{fetch_dependency, FetchError};
use crate::sources::{ProjectSources, discover, SourceError};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProjectId { pub struct ProjectId {

View File

@ -1,5 +1,5 @@
use std::collections::HashMap;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use std::collections::HashMap;
pub struct ContractMethod { pub struct ContractMethod {
pub id: u32, pub id: u32,

View File

@ -1,7 +1,7 @@
use crate::common::spans::Span;
use super::token::{Token, TokenKind}; use super::token::{Token, TokenKind};
use std::str::Chars; use crate::common::spans::Span;
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars;
pub struct Lexer<'a> { pub struct Lexer<'a> {
chars: Peekable<Chars<'a>>, chars: Peekable<Chars<'a>>,

View File

@ -1,7 +1,7 @@
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::frontends::pbs::ast::*; use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::contracts::ContractRegistry; use crate::frontends::pbs::contracts::ContractRegistry;
use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use crate::ir_core; use crate::ir_core;
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId}; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
@ -1088,8 +1088,8 @@ impl<'a> Lowerer<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::collector::SymbolCollector; use crate::frontends::pbs::collector::SymbolCollector;
use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::symbols::ModuleSymbols; use crate::frontends::pbs::symbols::ModuleSymbols;
use crate::ir_core; use crate::ir_core;

View File

@ -10,13 +10,13 @@ pub mod typecheck;
pub mod lowering; pub mod lowering;
pub mod contracts; 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 collector::SymbolCollector;
pub use resolver::{Resolver, ModuleProvider}; pub use lexer::Lexer;
pub use typecheck::TypeChecker;
pub use lowering::Lowerer; 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::diagnostics::DiagnosticBundle;
use crate::common::files::FileManager; use crate::common::files::FileManager;

View File

@ -108,6 +108,27 @@ impl Parser {
fn parse_import_spec(&mut self) -> Result<Node, DiagnosticBundle> { fn parse_import_spec(&mut self) -> Result<Node, DiagnosticBundle> {
let mut path = Vec::new(); let mut path = Vec::new();
let start_span = self.peek().span; let start_span = self.peek().span;
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 { loop {
if let TokenKind::Identifier(ref name) = self.peek().kind { if let TokenKind::Identifier(ref name) = self.peek().kind {
path.push(name.clone()); path.push(name.clone());
@ -122,10 +143,12 @@ impl Parser {
break; break;
} }
} }
let end_span = self.tokens[self.pos-1].span; }
let end_span = self.tokens[self.pos - 1].span;
Ok(Node::ImportSpec(ImportSpecNode { Ok(Node::ImportSpec(ImportSpecNode {
span: Span::new(self.file_id, start_span.start, end_span.end), span: Span::new(self.file_id, start_span.start, end_span.end),
path path,
})) }))
} }

View File

@ -445,9 +445,9 @@ impl<'a> Resolver<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::frontends::pbs::*;
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::common::spans::Span; use crate::common::spans::Span;
use crate::frontends::pbs::*;
use std::path::PathBuf; use std::path::PathBuf;
fn setup_test(source: &str) -> (ast::FileNode, usize) { fn setup_test(source: &str) -> (ast::FileNode, usize) {

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::common::spans::Span; use crate::common::spans::Span;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]

View File

@ -1,10 +1,10 @@
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::common::spans::Span; use crate::common::spans::Span;
use crate::frontends::pbs::ast::*; 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::symbols::*;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use crate::frontends::pbs::resolver::ModuleProvider;
use crate::frontends::pbs::contracts::ContractRegistry;
use std::collections::HashMap; use std::collections::HashMap;
pub struct TypeChecker<'a> { pub struct TypeChecker<'a> {
@ -835,9 +835,9 @@ impl<'a> TypeChecker<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::common::files::FileManager;
use crate::frontends::pbs::PbsFrontend; use crate::frontends::pbs::PbsFrontend;
use crate::frontends::Frontend; use crate::frontends::Frontend;
use crate::common::files::FileManager;
use std::fs; use std::fs;
fn check_code(code: &str) -> Result<(), String> { fn check_code(code: &str) -> Result<(), String> {

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use super::instr::Instr; use super::instr::Instr;
use super::terminator::Terminator; use super::terminator::Terminator;
use serde::{Deserialize, Serialize};
/// A basic block in a function's control flow graph. /// A basic block in a function's control flow graph.
/// Contains a sequence of instructions and ends with a terminator. /// Contains a sequence of instructions and ends with a terminator.

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use super::ids::ConstId; use super::ids::ConstId;
use serde::{Deserialize, Serialize};
/// Represents a constant value that can be stored in the constant pool. /// Represents a constant value that can be stored in the constant pool.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use super::ids::FunctionId;
use super::block::Block; use super::block::Block;
use super::ids::FunctionId;
use super::types::Type; use super::types::Type;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId}; use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId};
use serde::{Deserialize, Serialize};
/// Instructions within a basic block. /// Instructions within a basic block.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -9,15 +9,15 @@ pub mod instr;
pub mod terminator; pub mod terminator;
pub mod validate; 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 block::*;
pub use const_pool::*;
pub use function::*;
pub use ids::*;
pub use instr::*; pub use instr::*;
pub use module::*;
pub use program::*;
pub use terminator::*; pub use terminator::*;
pub use types::*;
pub use validate::*; pub use validate::*;
#[cfg(test)] #[cfg(test)]

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use super::function::Function; use super::function::Function;
use serde::{Deserialize, Serialize};
/// A module within a program, containing functions and other declarations. /// A module within a program, containing functions and other declarations.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use super::module::Module;
use super::const_pool::ConstPool; use super::const_pool::ConstPool;
use super::ids::FieldId; use super::ids::FieldId;
use super::module::Module;
use super::types::Type; use super::types::Type;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

View File

@ -5,8 +5,8 @@
//! easy to lower into VM-specific bytecode. //! easy to lower into VM-specific bytecode.
use crate::common::spans::Span; use crate::common::spans::Span;
use crate::ir_vm::types::{ConstId, TypeId};
use crate::ir_core::ids::FunctionId; use crate::ir_core::ids::FunctionId;
use crate::ir_vm::types::{ConstId, TypeId};
/// An `Instruction` combines an instruction's behavior (`kind`) with its /// An `Instruction` combines an instruction's behavior (`kind`) with its
/// source code location (`span`) for debugging and error reporting. /// source code location (`span`) for debugging and error reporting.

View File

@ -29,15 +29,15 @@ pub mod module;
pub mod instr; pub mod instr;
pub mod validate; pub mod validate;
pub use instr::{Instruction, InstrKind, Label}; pub use instr::{InstrKind, Instruction, Label};
pub use module::{Module, Function, Global, Param}; pub use module::{Function, Global, Module, Param};
pub use types::{Type, Value, GateId, ConstId, TypeId}; pub use types::{ConstId, GateId, Type, TypeId, Value};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ir_core::ids::{ConstId, FunctionId};
use crate::ir_core::const_pool::{ConstPool, ConstantValue}; use crate::ir_core::const_pool::{ConstPool, ConstantValue};
use crate::ir_core::ids::{ConstId, FunctionId};
use serde_json; use serde_json;
#[test] #[test]

View File

@ -4,10 +4,10 @@
//! The IR is a higher-level representation of the program than bytecode, but lower //! 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. //! 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::const_pool::ConstPool;
use crate::ir_core::ids::FunctionId; use crate::ir_core::ids::FunctionId;
use crate::ir_vm::instr::Instruction;
use crate::ir_vm::types::Type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A `Module` is the top-level container for a compiled program or library. /// A `Module` is the top-level container for a compiled program or library.

View File

@ -1,5 +1,5 @@
use crate::ir_vm;
use crate::ir_core; use crate::ir_core;
use crate::ir_vm;
use anyhow::Result; use anyhow::Result;
use std::collections::HashMap; use std::collections::HashMap;
@ -402,8 +402,8 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
mod tests { mod tests {
use super::*; use super::*;
use crate::ir_core; use crate::ir_core;
use crate::ir_core::{Block, Instr, Terminator, ConstantValue, Program, ConstPool}; use crate::ir_core::ids::{ConstId as CoreConstId, FunctionId};
use crate::ir_core::ids::{FunctionId, ConstId as CoreConstId}; use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, Program, Terminator};
use crate::ir_vm::*; use crate::ir_vm::*;
#[test] #[test]

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::fs; use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
@ -188,8 +188,8 @@ fn validate_manifest(manifest: &Manifest, path: &Path) -> Result<(), ManifestErr
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::tempdir;
use std::fs; use std::fs;
use tempfile::tempdir;
#[test] #[test]
fn test_parse_minimal_manifest() { fn test_parse_minimal_manifest() {

View File

@ -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::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)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectSources { pub struct ProjectSources {
@ -154,8 +154,8 @@ pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Resul
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use tempfile::tempdir;
use std::fs; use std::fs;
use tempfile::tempdir;
#[test] #[test]
fn test_discover_app_with_main() { fn test_discover_app_with_main() {

View File

@ -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::fs;
use std::path::Path; 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] #[test]
fn generate_canonical_goldens() { fn generate_canonical_goldens() {

View File

@ -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::backend::emit_bytecode::emit_module;
use prometeu_compiler::common::files::FileManager; 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; use std::collections::HashMap;
#[test] #[test]

View File

@ -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);
}
}

View File

@ -5,7 +5,7 @@ use crate::log::{LogLevel, LogService, LogSource};
use crate::model::{BankType, Cartridge, Color}; use crate::model::{BankType, Cartridge, Color};
use crate::prometeu_os::NativeInterface; use crate::prometeu_os::NativeInterface;
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; 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::collections::HashMap;
use std::time::Instant; use std::time::Instant;

View File

@ -1,5 +1,5 @@
use crate::virtual_machine::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::opcode::OpCode;
use crate::virtual_machine::opcode_spec::{OpcodeSpec, OpCodeSpecExt};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError { pub enum DecodeError {

View File

@ -1,6 +1,6 @@
use crate::virtual_machine::call_frame::CallFrame; use crate::virtual_machine::call_frame::CallFrame;
use prometeu_bytecode::v0::FunctionMeta;
use prometeu_bytecode::abi::{TrapInfo, TRAP_INVALID_LOCAL}; 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). /// Computes the absolute stack index for the start of the current frame's locals (including args).
pub fn local_base(frame: &CallFrame) -> usize { pub fn local_base(frame: &CallFrame) -> usize {

View File

@ -10,11 +10,11 @@ pub mod verifier;
use crate::hardware::HardwareBridge; use crate::hardware::HardwareBridge;
pub use program::ProgramImage; pub use program::ProgramImage;
pub use prometeu_bytecode::abi::TrapInfo;
pub use prometeu_bytecode::opcode::OpCode; pub use prometeu_bytecode::opcode::OpCode;
pub use value::Value; pub use value::Value;
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
pub use prometeu_bytecode::abi::TrapInfo;
pub use verifier::VerifierError; pub use verifier::VerifierError;
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
pub type SyscallId = u32; pub type SyscallId = u32;

View File

@ -1,8 +1,8 @@
use crate::virtual_machine::Value; use crate::virtual_machine::Value;
use prometeu_bytecode::v0::{FunctionMeta, DebugInfo, BytecodeModule, ConstantPoolEntry};
use prometeu_bytecode::abi::TrapInfo; use prometeu_bytecode::abi::TrapInfo;
use std::sync::Arc; use prometeu_bytecode::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ProgramImage { pub struct ProgramImage {
@ -91,3 +91,33 @@ impl From<BytecodeModule> for ProgramImage {
) )
} }
} }
impl From<ProgramImage> 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![],
}
}
}

View File

@ -1,7 +1,7 @@
use prometeu_bytecode::v0::FunctionMeta;
use crate::virtual_machine::bytecode::decoder::{decode_at, DecodeError}; use crate::virtual_machine::bytecode::decoder::{decode_at, DecodeError};
use prometeu_bytecode::opcode::OpCode; 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum VerifierError { pub enum VerifierError {

View File

@ -3,8 +3,8 @@ use crate::virtual_machine::call_frame::CallFrame;
use crate::virtual_machine::scope_frame::ScopeFrame; use crate::virtual_machine::scope_frame::ScopeFrame;
use crate::virtual_machine::value::Value; use crate::virtual_machine::value::Value;
use crate::virtual_machine::{NativeInterface, ProgramImage, VmInitError}; 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::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. /// 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 /// This allows the system to decide if it should continue execution in the next tick
@ -914,10 +914,10 @@ impl VirtualMachine {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use prometeu_bytecode::v0::FunctionMeta;
use prometeu_bytecode::abi::SourceSpan;
use crate::hardware::HardwareBridge; 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; struct MockNative;
impl NativeInterface for MockNative { impl NativeInterface for MockNative {

View File

@ -1,11 +1,11 @@
use prometeu_core::virtual_machine::{VirtualMachine, LogicalFrameEndingReason};
use prometeu_core::hardware::HardwareBridge; 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::NativeInterface;
use prometeu_core::virtual_machine::Value; use prometeu_core::virtual_machine::Value;
use prometeu_core::virtual_machine::HostReturn; use prometeu_core::virtual_machine::{LogicalFrameEndingReason, VirtualMachine};
use std::path::Path; use prometeu_core::Hardware;
use std::fs; use std::fs;
use std::path::Path;
struct MockNative; struct MockNative;
impl NativeInterface for MockNative { impl NativeInterface for MockNative {