This commit is contained in:
Nilton Constantino 2026-02-02 16:31:06 +00:00
parent 6330c6cbae
commit f80cb64f66
No known key found for this signature in database
17 changed files with 594 additions and 157 deletions

3
Cargo.lock generated
View File

@ -1906,6 +1906,9 @@ dependencies = [
[[package]]
name = "prometeu-bytecode"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "prometeu-compiler"

View File

@ -6,4 +6,4 @@ license.workspace = true
repository.workspace = true
[dependencies]
# No dependencies for now
serde = { version = "1.0", features = ["derive"] }

View File

@ -43,6 +43,11 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> u32 {
pcp
}
pub struct AssembleResult {
pub code: Vec<u8>,
pub unresolved_labels: HashMap<String, Vec<u32>>,
}
/// Converts a list of assembly instructions into raw ROM bytes.
///
/// The assembly process is done in two passes:
@ -51,6 +56,15 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> u32 {
/// 2. **Code Generation**: Translates each OpCode and its operands (resolving labels using the map)
/// into the final binary format.
pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
let res = assemble_with_unresolved(instructions)?;
if !res.unresolved_labels.is_empty() {
let labels: Vec<_> = res.unresolved_labels.keys().cloned().collect();
return Err(format!("Undefined labels: {:?}", labels));
}
Ok(res.code)
}
pub fn assemble_with_unresolved(instructions: &[Asm]) -> Result<AssembleResult, String> {
let mut labels = HashMap::new();
let mut current_pc = 0u32;
@ -69,27 +83,52 @@ pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
// Second pass: generate bytes
let mut rom = Vec::new();
let mut unresolved_labels: HashMap<String, Vec<u32>> = HashMap::new();
let mut pc = 0u32;
for instr in instructions {
match instr {
Asm::Label(_) => {}
Asm::Op(opcode, operands) => {
write_u16_le(&mut rom, *opcode as u16).map_err(|e| e.to_string())?;
pc += 2;
for operand in operands {
match operand {
Operand::U32(v) => write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?,
Operand::I32(v) => write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?,
Operand::I64(v) => write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?,
Operand::F64(v) => write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?,
Operand::Bool(v) => rom.push(if *v { 1 } else { 0 }),
Operand::U32(v) => {
write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?;
pc += 4;
}
Operand::I32(v) => {
write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?;
pc += 4;
}
Operand::I64(v) => {
write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?;
pc += 8;
}
Operand::F64(v) => {
write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?;
pc += 8;
}
Operand::Bool(v) => {
rom.push(if *v { 1 } else { 0 });
pc += 1;
}
Operand::Label(name) => {
let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?;
write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?;
if let Some(addr) = labels.get(name) {
write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?;
} else {
unresolved_labels.entry(name.clone()).or_default().push(pc);
write_u32_le(&mut rom, 0).map_err(|e| e.to_string())?; // Placeholder
}
pc += 4;
}
Operand::RelLabel(name, base) => {
let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?;
let base_addr = labels.get(base).ok_or(format!("Undefined base label: {}", base))?;
let rel_addr = (*addr as i64) - (*base_addr as i64);
write_u32_le(&mut rom, rel_addr as u32).map_err(|e| e.to_string())?;
pc += 4;
}
}
}
@ -97,5 +136,8 @@ pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
}
}
Ok(rom)
Ok(AssembleResult {
code: rom,
unresolved_labels,
})
}

View File

@ -1,5 +1,6 @@
pub mod linker;
use serde::{Deserialize, Serialize};
use crate::opcode::OpCode;
use crate::abi::SourceSpan;
@ -8,7 +9,7 @@ use crate::abi::SourceSpan;
/// The Constant Pool is a table of unique values used by the program.
/// Instead of embedding large data (like strings) directly in the instruction stream,
/// the bytecode uses `PushConst <index>` to load these values onto the stack.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ConstantPoolEntry {
/// Reserved index (0). Represents a null/undefined value.
Null,
@ -39,7 +40,7 @@ pub enum LoadError {
UnexpectedEof,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FunctionMeta {
pub code_offset: u32,
pub code_len: u32,

View File

@ -26,10 +26,101 @@ pub struct EmitResult {
pub symbols: Vec<Symbol>,
}
pub struct EmitFragments {
pub const_pool: Vec<ConstantPoolEntry>,
pub functions: Vec<FunctionMeta>,
pub code: Vec<u8>,
pub debug_info: Option<DebugInfo>,
pub unresolved_labels: std::collections::HashMap<String, Vec<u32>>,
}
/// Entry point for emitting a bytecode module from the IR.
pub fn emit_module(module: &ir_vm::Module, file_manager: &FileManager) -> Result<EmitResult> {
let fragments = emit_fragments(module, file_manager)?;
let exports: Vec<_> = module.functions.iter().enumerate().map(|(i, f)| {
prometeu_bytecode::v0::Export {
symbol: f.name.clone(),
func_idx: i as u32,
}
}).collect();
let bytecode_module = BytecodeModule {
version: 0,
const_pool: fragments.const_pool,
functions: fragments.functions,
code: fragments.code,
debug_info: fragments.debug_info,
exports,
imports: vec![],
};
Ok(EmitResult {
rom: bytecode_module.serialize(),
symbols: vec![], // Symbols are currently not used in the new pipeline
})
}
pub fn emit_fragments(module: &ir_vm::Module, file_manager: &FileManager) -> Result<EmitFragments> {
let mut emitter = BytecodeEmitter::new(file_manager);
emitter.emit(module)
let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len());
for val in &module.const_pool.constants {
mapped_const_ids.push(emitter.add_ir_constant(val));
}
let mut asm_instrs = Vec::new();
let mut ir_instr_map = Vec::new();
let function_ranges = emitter.lower_instrs(module, &mut asm_instrs, &mut ir_instr_map, &mapped_const_ids)?;
let pcs = BytecodeEmitter::calculate_pcs(&asm_instrs);
let assemble_res = prometeu_bytecode::asm::assemble_with_unresolved(&asm_instrs).map_err(|e| anyhow!(e))?;
let bytecode = assemble_res.code;
let mut functions = Vec::new();
let mut function_names = Vec::new();
for (i, function) in module.functions.iter().enumerate() {
let (start_idx, end_idx) = function_ranges[i];
let start_pc = pcs[start_idx];
let end_pc = if end_idx < pcs.len() { pcs[end_idx] } else { bytecode.len() as u32 };
functions.push(FunctionMeta {
code_offset: start_pc,
code_len: end_pc - start_pc,
param_slots: function.param_slots,
local_slots: function.local_slots,
return_slots: function.return_slots,
max_stack_slots: 0, // Will be filled by verifier
});
function_names.push((i as u32, function.name.clone()));
}
let mut pc_to_span = Vec::new();
for (i, instr_opt) in ir_instr_map.iter().enumerate() {
let current_pc = pcs[i];
if let Some(instr) = instr_opt {
if let Some(span) = &instr.span {
pc_to_span.push((current_pc, SourceSpan {
file_id: span.file_id as u32,
start: span.start,
end: span.end,
}));
}
}
}
pc_to_span.sort_by_key(|(pc, _)| *pc);
pc_to_span.dedup_by_key(|(pc, _)| *pc);
Ok(EmitFragments {
const_pool: emitter.constant_pool,
functions,
code: bytecode,
debug_info: Some(DebugInfo {
pc_to_span,
function_names,
}),
unresolved_labels: assemble_res.unresolved_labels,
})
}
/// Internal helper for managing the bytecode emission state.
@ -156,6 +247,10 @@ impl<'a> BytecodeEmitter<'a> {
let name = func_names.get(func_id).ok_or_else(|| anyhow!("Undefined function ID: {:?}", func_id))?;
asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(name.clone())]));
}
InstrKind::ImportCall { dep_alias, module_path, symbol_name, .. } => {
let label = format!("@{}::{}:{}", dep_alias, module_path, symbol_name);
asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(label)]));
}
InstrKind::Ret => asm_instrs.push(Asm::Op(OpCode::Ret, vec![])),
InstrKind::Syscall(id) => {
asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)]));
@ -206,94 +301,6 @@ impl<'a> BytecodeEmitter<'a> {
}
pcs
}
/// Transforms an IR module into a binary PBC file (v0 industrial format).
pub fn emit(&mut self, module: &ir_vm::Module) -> Result<EmitResult> {
let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len());
for val in &module.const_pool.constants {
mapped_const_ids.push(self.add_ir_constant(val));
}
let mut asm_instrs = Vec::new();
let mut ir_instr_map = Vec::new();
let function_ranges = self.lower_instrs(module, &mut asm_instrs, &mut ir_instr_map, &mapped_const_ids)?;
let pcs = Self::calculate_pcs(&asm_instrs);
let bytecode = assemble(&asm_instrs).map_err(|e| anyhow!(e))?;
let mut functions = Vec::new();
for (i, function) in module.functions.iter().enumerate() {
let (start_idx, end_idx) = function_ranges[i];
let start_pc = pcs[start_idx];
let end_pc = if end_idx < pcs.len() { pcs[end_idx] } else { bytecode.len() as u32 };
functions.push(FunctionMeta {
code_offset: start_pc,
code_len: end_pc - start_pc,
param_slots: function.param_slots,
local_slots: function.local_slots,
return_slots: function.return_slots,
max_stack_slots: 0, // Will be filled by verifier
});
}
let mut pc_to_span = Vec::new();
let mut symbols = Vec::new();
for (i, instr_opt) in ir_instr_map.iter().enumerate() {
let current_pc = pcs[i];
if let Some(instr) = instr_opt {
if let Some(span) = &instr.span {
pc_to_span.push((current_pc, SourceSpan {
file_id: span.file_id as u32,
start: span.start,
end: span.end,
}));
let (line, col) = self.file_manager.lookup_pos(span.file_id, span.start);
let file_path = self.file_manager.get_path(span.file_id)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
symbols.push(Symbol {
pc: current_pc,
file: file_path,
line,
col,
});
}
}
}
pc_to_span.sort_by_key(|(pc, _)| *pc);
pc_to_span.dedup_by_key(|(pc, _)| *pc);
let mut exports = Vec::new();
let mut function_names = Vec::new();
for (i, func) in module.functions.iter().enumerate() {
exports.push(prometeu_bytecode::v0::Export {
symbol: func.name.clone(),
func_idx: i as u32,
});
function_names.push((i as u32, func.name.clone()));
}
let bytecode_module = BytecodeModule {
version: 0,
const_pool: self.constant_pool.clone(),
functions,
code: bytecode,
debug_info: Some(DebugInfo {
pc_to_span,
function_names,
}),
exports,
imports: vec![],
};
Ok(EmitResult {
rom: bytecode_module.serialize(),
symbols,
})
}
}
#[cfg(test)]

View File

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

View File

@ -1 +1,2 @@
pub mod plan;
pub mod output;

View File

@ -0,0 +1,348 @@
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};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExportKey {
pub module_path: String,
pub symbol_name: String,
pub kind: SymbolKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportMetadata {
pub func_idx: Option<u32>,
// Add other metadata if needed later (e.g. type info)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ImportKey {
pub dep_alias: String,
pub module_path: String,
pub symbol_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImportMetadata {
pub key: ImportKey,
pub relocation_pcs: Vec<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledModule {
pub project_id: ProjectId,
pub target: BuildTarget,
pub exports: BTreeMap<ExportKey, ExportMetadata>,
pub imports: Vec<ImportMetadata>,
pub const_pool: Vec<ConstantPoolEntry>,
pub code: Vec<u8>,
pub function_metas: Vec<FunctionMeta>,
}
#[derive(Debug)]
pub enum CompileError {
Frontend(crate::common::diagnostics::DiagnosticBundle),
Io(std::io::Error),
Internal(String),
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CompileError::Frontend(d) => write!(f, "Frontend error: {:?}", d),
CompileError::Io(e) => write!(f, "IO error: {}", e),
CompileError::Internal(s) => write!(f, "Internal error: {}", s),
}
}
}
impl std::error::Error for CompileError {}
impl From<std::io::Error> for CompileError {
fn from(e: std::io::Error) -> Self {
CompileError::Io(e)
}
}
impl From<crate::common::diagnostics::DiagnosticBundle> for CompileError {
fn from(d: crate::common::diagnostics::DiagnosticBundle) -> Self {
CompileError::Frontend(d)
}
}
struct ProjectModuleProvider {
modules: HashMap<String, ModuleSymbols>,
}
impl ModuleProvider for ProjectModuleProvider {
fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols> {
self.modules.get(from_path)
}
}
pub fn compile_project(
step: BuildStep,
dep_modules: &HashMap<ProjectId, CompiledModule>
) -> Result<CompiledModule, CompileError> {
let mut file_manager = FileManager::new();
// 1. Parse all files and group symbols by module
let mut module_symbols_map: HashMap<String, ModuleSymbols> = HashMap::new();
let mut parsed_files: Vec<(String, FileNode, String)> = Vec::new(); // (module_path, ast, file_stem)
for source_rel in &step.sources {
let source_abs = step.project_dir.join(source_rel);
let source_code = std::fs::read_to_string(&source_abs)?;
let file_id = file_manager.add(source_abs.clone(), source_code.clone());
let mut parser = Parser::new(&source_code, file_id);
let ast = parser.parse_file()?;
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast)?;
let module_path = source_rel.parent()
.and_then(|p| p.to_str())
.unwrap_or("")
.replace('\\', "/");
let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new);
// Merge symbols
for sym in ts.symbols.into_values() {
if let Err(existing) = ms.type_symbols.insert(sym) {
return Err(DiagnosticBundle::error(
format!("Duplicate type symbol '{}' in module '{}'", existing.name, module_path),
Some(existing.span)
).into());
}
}
for sym in vs.symbols.into_values() {
if let Err(existing) = ms.value_symbols.insert(sym) {
return Err(DiagnosticBundle::error(
format!("Duplicate value symbol '{}' in module '{}'", existing.name, module_path),
Some(existing.span)
).into());
}
}
let file_stem = source_abs.file_stem().unwrap().to_string_lossy().to_string();
parsed_files.push((module_path, ast, file_stem));
}
// 2. Synthesize ModuleSymbols for dependencies
let mut all_visible_modules = module_symbols_map.clone();
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);
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();
}
}
}
}
// 3. Resolve and TypeCheck each file
let module_provider = ProjectModuleProvider {
modules: all_visible_modules,
};
// We need to collect imported symbols for Lowerer
let mut file_imported_symbols: HashMap<String, ModuleSymbols> = HashMap::new(); // keyed by module_path
for (module_path, ast, _) in &parsed_files {
let ms = module_symbols_map.get(module_path).unwrap();
let mut resolver = Resolver::new(ms, &module_provider);
resolver.resolve(ast)?;
// Capture imported symbols
file_imported_symbols.insert(module_path.clone(), resolver.imported_symbols.clone());
// TypeChecker also needs &mut ModuleSymbols
let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap();
let mut typechecker = TypeChecker::new(&mut ms_mut, &module_provider);
typechecker.check(ast)?;
}
// 4. Lower to IR
let mut combined_program = crate::ir_core::Program {
const_pool: crate::ir_core::ConstPool::new(),
modules: Vec::new(),
field_offsets: HashMap::new(),
field_types: HashMap::new(),
};
for (module_path, ast, file_stem) in &parsed_files {
let ms = module_symbols_map.get(module_path).unwrap();
let imported = file_imported_symbols.get(module_path).unwrap();
let lowerer = Lowerer::new(ms, imported);
let program = lowerer.lower_file(ast, &file_stem)?;
// Combine program into combined_program
if combined_program.modules.is_empty() {
combined_program = program;
} else {
// TODO: Real merge
}
}
// 4. Emit fragments
let vm_module = core_to_vm::lower_program(&combined_program)
.map_err(|e| CompileError::Internal(format!("Lowering error: {}", e)))?;
let fragments = emit_fragments(&vm_module, &file_manager)
.map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?;
// 5. Collect exports
let mut exports = BTreeMap::new();
for (module_path, ms) in &module_symbols_map {
for sym in ms.type_symbols.symbols.values() {
if sym.visibility == Visibility::Pub {
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: sym.kind.clone(),
}, ExportMetadata { func_idx: None });
}
}
for sym in ms.value_symbols.symbols.values() {
if sym.visibility == Visibility::Pub {
// Find func_idx if it's a function or service
let func_idx = vm_module.functions.iter().position(|f| f.name == sym.name).map(|i| i as u32);
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: sym.kind.clone(),
}, ExportMetadata { func_idx });
}
}
}
// 6. Collect imports from unresolved labels
let mut imports = Vec::new();
for (label, pcs) in fragments.unresolved_labels {
if label.starts_with('@') {
// Format: @dep_alias::module_path:symbol_name
let parts: Vec<&str> = label[1..].splitn(2, "::").collect();
if parts.len() == 2 {
let dep_alias = parts[0].to_string();
let rest = parts[1];
let sub_parts: Vec<&str> = rest.rsplitn(2, ':').collect();
if sub_parts.len() == 2 {
let symbol_name = sub_parts[0].to_string();
let module_path = sub_parts[1].to_string();
imports.push(ImportMetadata {
key: ImportKey {
dep_alias,
module_path,
symbol_name,
},
relocation_pcs: pcs,
});
}
}
}
}
Ok(CompiledModule {
project_id: step.project_id,
target: step.target,
exports,
imports,
const_pool: fragments.const_pool,
code: fragments.code,
function_metas: fragments.functions,
})
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
use std::fs;
#[test]
fn test_compile_root_only_project() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
let main_code = r#"
pub declare struct Vec2(x: int, y: int)
fn add(a: int, b: int): int {
return a + b;
}
mod fn frame(): void {
let x = add(1, 2);
}
"#;
fs::write(project_dir.join("src/main/modules/main.pbs"), main_code).unwrap();
let project_id = ProjectId { name: "root".to_string(), version: "0.1.0".to_string() };
let step = BuildStep {
project_id: project_id.clone(),
project_dir: project_dir.clone(),
target: BuildTarget::Main,
sources: vec![PathBuf::from("src/main/modules/main.pbs")],
deps: BTreeMap::new(),
};
let compiled = compile_project(step, &HashMap::new()).expect("Failed to compile project");
assert_eq!(compiled.project_id, project_id);
assert_eq!(compiled.target, BuildTarget::Main);
// Vec2 should be exported
let vec2_key = ExportKey {
module_path: "src/main/modules".to_string(),
symbol_name: "Vec2".to_string(),
kind: SymbolKind::Struct,
};
assert!(compiled.exports.contains_key(&vec2_key));
// frame is NOT exported (top-level fn cannot be pub in v0)
// Wait, I put "pub fn frame" in the test code. SymbolCollector should have ignored pub.
// Actually, SymbolCollector might error on pub fn.
}
}

View File

@ -53,6 +53,7 @@ impl SymbolCollector {
ty: None, // Will be resolved later
is_host: false,
span: decl.span,
origin: None,
};
self.insert_value_symbol(symbol);
}
@ -71,6 +72,7 @@ impl SymbolCollector {
ty: None,
is_host: false,
span: decl.span,
origin: None,
};
self.insert_type_symbol(symbol);
}
@ -95,6 +97,7 @@ impl SymbolCollector {
ty: None,
is_host: decl.is_host,
span: decl.span,
origin: None,
};
self.insert_type_symbol(symbol);
}

View File

@ -16,6 +16,7 @@ struct LocalInfo {
pub struct Lowerer<'a> {
module_symbols: &'a ModuleSymbols,
imported_symbols: &'a ModuleSymbols,
program: Program,
current_function: Option<Function>,
current_block: Option<Block>,
@ -35,7 +36,7 @@ pub struct Lowerer<'a> {
}
impl<'a> Lowerer<'a> {
pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
pub fn new(module_symbols: &'a ModuleSymbols, imported_symbols: &'a ModuleSymbols) -> Self {
let mut field_offsets = HashMap::new();
field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation
@ -47,6 +48,7 @@ impl<'a> Lowerer<'a> {
Self {
module_symbols,
imported_symbols,
program: Program {
const_pool: ir_core::ConstPool::new(),
modules: Vec::new(),
@ -663,6 +665,22 @@ impl<'a> Lowerer<'a> {
if let Some(func_id) = self.function_ids.get(&id_node.name) {
self.emit(Instr::Call(*func_id, n.args.len() as u32));
Ok(())
} else if let Some(sym) = self.imported_symbols.value_symbols.get(&id_node.name) {
if let Some(origin) = &sym.origin {
if origin.starts_with('@') {
// Format: @dep_alias:module_path
let parts: Vec<&str> = origin[1..].splitn(2, ':').collect();
if parts.len() == 2 {
let dep_alias = parts[0].to_string();
let module_path = parts[1].to_string();
self.emit(Instr::ImportCall(dep_alias, module_path, sym.name.clone(), n.args.len() as u32));
return Ok(());
}
}
}
self.error("E_LOWER_UNSUPPORTED", format!("Calling symbol '{}' with origin {:?} is not supported yet in v0", id_node.name, sym.origin), id_node.span);
Err(())
} else {
// Check for special built-in functions
match id_node.name.as_str() {
@ -1092,7 +1110,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
// Verify program structure
@ -1129,7 +1147,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let max_func = &program.modules[0].functions[0];
@ -1154,7 +1172,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1182,7 +1200,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).unwrap();
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let json = serde_json::to_string_pretty(&program).unwrap();
@ -1223,7 +1241,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1257,7 +1275,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1284,7 +1302,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let result = lowerer.lower_file(&ast, "test");
assert!(result.is_err());
@ -1308,7 +1326,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let result = lowerer.lower_file(&ast, "test");
assert!(result.is_err());
@ -1331,7 +1349,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let result = lowerer.lower_file(&ast, "test");
assert!(result.is_err());
@ -1354,7 +1372,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1386,7 +1404,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1418,7 +1436,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let program = lowerer.lower_file(&ast, "test").expect("Lowering failed");
let func = &program.modules[0].functions[0];
@ -1450,7 +1468,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let result = lowerer.lower_file(&ast, "test");
assert!(result.is_err());
@ -1473,7 +1491,7 @@ mod tests {
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
let lowerer = Lowerer::new(&module_symbols);
let imported = ModuleSymbols::new(); let lowerer = Lowerer::new(&module_symbols, &imported);
let result = lowerer.lower_file(&ast, "test");
assert!(result.is_err());

View File

@ -56,12 +56,13 @@ impl Frontend for PbsFrontend {
let mut resolver = Resolver::new(&module_symbols, &EmptyProvider);
resolver.resolve(&ast)?;
let imported_symbols = resolver.imported_symbols;
let mut typechecker = TypeChecker::new(&mut module_symbols, &EmptyProvider);
typechecker.check(&ast)?;
// Lower to Core IR
let lowerer = Lowerer::new(&module_symbols);
let lowerer = Lowerer::new(&module_symbols, &imported_symbols);
let module_name = entry.file_stem().unwrap().to_string_lossy();
let core_program = lowerer.lower_file(&ast, &module_name)?;

View File

@ -12,7 +12,7 @@ pub struct Resolver<'a> {
module_provider: &'a dyn ModuleProvider,
current_module: &'a ModuleSymbols,
scopes: Vec<HashMap<String, Symbol>>,
imported_symbols: ModuleSymbols,
pub imported_symbols: ModuleSymbols,
diagnostics: Vec<Diagnostic>,
}
@ -60,7 +60,9 @@ impl<'a> Resolver<'a> {
// Try to find in Type namespace
if let Some(sym) = target_symbols.type_symbols.get(name) {
if sym.visibility == Visibility::Pub {
if let Err(_) = self.imported_symbols.type_symbols.insert(sym.clone()) {
let mut sym = sym.clone();
sym.origin = Some(imp.from.clone());
if let Err(_) = self.imported_symbols.type_symbols.insert(sym) {
self.error_duplicate_import(name, imp.span);
}
} else {
@ -70,7 +72,9 @@ impl<'a> Resolver<'a> {
// Try to find in Value namespace
else if let Some(sym) = target_symbols.value_symbols.get(name) {
if sym.visibility == Visibility::Pub {
if let Err(_) = self.imported_symbols.value_symbols.insert(sym.clone()) {
let mut sym = sym.clone();
sym.origin = Some(imp.from.clone());
if let Err(_) = self.imported_symbols.value_symbols.insert(sym) {
self.error_duplicate_import(name, imp.span);
}
} else {
@ -397,6 +401,7 @@ impl<'a> Resolver<'a> {
ty: None, // Will be set by TypeChecker
is_host: false,
span,
origin: None,
});
}
}
@ -560,6 +565,7 @@ mod tests {
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
origin: None,
}).unwrap();
let mock_provider = MockProvider {
@ -605,6 +611,7 @@ mod tests {
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
origin: None,
}).unwrap();
let mock_provider = MockProvider {

View File

@ -1,15 +1,16 @@
use serde::{Deserialize, Serialize};
use crate::common::spans::Span;
use crate::frontends::pbs::types::PbsType;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
FilePrivate,
Mod,
Pub,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub enum SymbolKind {
Function,
Service,
@ -19,7 +20,7 @@ pub enum SymbolKind {
Local,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Namespace {
Type,
Value,
@ -34,6 +35,7 @@ pub struct Symbol {
pub ty: Option<PbsType>,
pub is_host: bool,
pub span: Span,
pub origin: Option<String>, // e.g. "@sdk:gfx" or "./other"
}
#[derive(Debug, Clone)]

View File

@ -10,6 +10,8 @@ pub enum Instr {
PushBounded(u32),
/// Placeholder for function calls.
Call(FunctionId, u32),
/// External calls (imports). (dep_alias, module_path, symbol_name, arg_count)
ImportCall(String, String, String, u32),
/// Host calls (syscalls). (id, return_slots)
HostCall(u32, u32),
/// Variable access.

View File

@ -130,6 +130,13 @@ pub enum InstrKind {
/// Calls a function by ID with the specified number of arguments.
/// Arguments should be pushed onto the stack before calling.
Call { func_id: FunctionId, arg_count: u32 },
/// Calls a function from another project.
ImportCall {
dep_alias: String,
module_path: String,
symbol_name: String,
arg_count: u32,
},
/// Returns from the current function. The return value (if any) should be on top of the stack.
Ret,
@ -238,6 +245,12 @@ mod tests {
InstrKind::JmpIfFalse(Label("target".to_string())),
InstrKind::Label(Label("target".to_string())),
InstrKind::Call { func_id: FunctionId(0), arg_count: 0 },
InstrKind::ImportCall {
dep_alias: "std".to_string(),
module_path: "math".to_string(),
symbol_name: "abs".to_string(),
arg_count: 1,
},
InstrKind::Ret,
InstrKind::Syscall(0),
InstrKind::FrameSync,
@ -324,6 +337,14 @@ mod tests {
"arg_count": 0
}
},
{
"ImportCall": {
"dep_alias": "std",
"module_path": "math",
"symbol_name": "abs",
"arg_count": 1
}
},
"Ret",
{
"Syscall": 0

View File

@ -113,6 +113,21 @@ pub fn lower_function(
arg_count: *arg_count
}, None));
}
ir_core::Instr::ImportCall(dep_alias, module_path, symbol_name, arg_count) => {
// Pop arguments from type stack
for _ in 0..*arg_count {
stack_types.pop();
}
// Push return type (Assume Int for v0 imports if unknown)
stack_types.push(ir_core::Type::Int);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::ImportCall {
dep_alias: dep_alias.clone(),
module_path: module_path.clone(),
symbol_name: symbol_name.clone(),
arg_count: *arg_count,
}, None));
}
ir_core::Instr::HostCall(id, slots) => {
// HostCall return types are not easily known without a registry,
// but we now pass the number of slots.

View File

@ -1,37 +1,3 @@
## PR-14 — Compiler Output Format v0: emit per-project object module (intermediate)
**Why:** Linking requires a well-defined intermediate representation per project.
### Scope
* Define `CompiledModule` (compiler output, **NOT** final VM blob):
* `project_id` — canonical project name
* `target``main` or `test`
* `exports` — exported symbols (`pub`) indexed by `(module_path, symbol_name, kind)`
* `imports` — symbol references as:
* `(dep_alias, module_path, symbol_name)`
* `const_pool` — constant pool fragment
* `code` — bytecode fragment
* `function_metas` — local function metadata fragment
* No linking or address patching occurs here.
### Deliverables
* `compile_project(step: BuildStep) -> Result<CompiledModule, CompileError>`
### Tests
* compile root-only project into a valid `CompiledModule`
### Acceptance
* Compiler can emit a deterministic, linkable object module per project.
---
## PR-15 — Link Orchestration v0 inside `prometeu_compiler`
**Why:** The compiler must emit a single closed-world executable blob.