This commit is contained in:
Nilton Constantino 2026-02-02 19:20:21 +00:00
parent 66a77709f0
commit f5dafc403f
No known key found for this signature in database
11 changed files with 137 additions and 68 deletions

View File

@ -7,7 +7,6 @@
//! 1. **Instruction Lowering**: Translates `ir_vm::Instruction` into `prometeu_bytecode::asm::Asm` ops. //! 1. **Instruction Lowering**: Translates `ir_vm::Instruction` into `prometeu_bytecode::asm::Asm` ops.
//! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations. //! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations.
use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::ir_core::ConstantValue; use crate::ir_core::ConstantValue;
use crate::ir_vm; use crate::ir_vm;
@ -35,8 +34,8 @@ pub struct EmitFragments {
} }
/// Entry point for emitting a bytecode module from the IR. /// Entry point for emitting a bytecode module from the IR.
pub fn emit_module(module: &ir_vm::Module, file_manager: &FileManager) -> Result<EmitResult> { pub fn emit_module(module: &ir_vm::Module) -> Result<EmitResult> {
let fragments = emit_fragments(module, file_manager)?; let fragments = emit_fragments(module)?;
let exports: Vec<_> = module.functions.iter().enumerate().map(|(i, f)| { let exports: Vec<_> = module.functions.iter().enumerate().map(|(i, f)| {
prometeu_bytecode::v0::Export { prometeu_bytecode::v0::Export {
@ -61,8 +60,8 @@ pub fn emit_module(module: &ir_vm::Module, file_manager: &FileManager) -> Result
}) })
} }
pub fn emit_fragments(module: &ir_vm::Module, file_manager: &FileManager) -> Result<EmitFragments> { pub fn emit_fragments(module: &ir_vm::Module) -> Result<EmitFragments> {
let mut emitter = BytecodeEmitter::new(file_manager); let mut emitter = BytecodeEmitter::new();
let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len()); let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len());
for val in &module.const_pool.constants { for val in &module.const_pool.constants {
@ -124,19 +123,16 @@ pub fn emit_fragments(module: &ir_vm::Module, file_manager: &FileManager) -> Res
} }
/// Internal helper for managing the bytecode emission state. /// Internal helper for managing the bytecode emission state.
struct BytecodeEmitter<'a> { struct BytecodeEmitter {
/// Stores constant values (like strings) that are referenced by instructions. /// Stores constant values (like strings) that are referenced by instructions.
constant_pool: Vec<ConstantPoolEntry>, constant_pool: Vec<ConstantPoolEntry>,
/// Used to look up source code positions for symbol generation.
file_manager: &'a FileManager,
} }
impl<'a> BytecodeEmitter<'a> { impl BytecodeEmitter {
fn new(file_manager: &'a FileManager) -> Self { fn new() -> Self {
Self { Self {
// Index 0 is traditionally reserved for Null in many VMs // Index 0 is traditionally reserved for Null in many VMs
constant_pool: vec![ConstantPoolEntry::Null], constant_pool: vec![ConstantPoolEntry::Null],
file_manager,
} }
} }
@ -306,7 +302,6 @@ impl<'a> BytecodeEmitter<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::common::files::FileManager;
use crate::ir_core::const_pool::ConstantValue; use crate::ir_core::const_pool::ConstantValue;
use crate::ir_core::ids::FunctionId; use crate::ir_core::ids::FunctionId;
use crate::ir_vm::instr::{InstrKind, Instruction}; use crate::ir_vm::instr::{InstrKind, Instruction};
@ -338,8 +333,7 @@ mod tests {
module.functions.push(function); module.functions.push(function);
let file_manager = FileManager::new(); let result = emit_module(&module).expect("Failed to emit module");
let result = emit_module(&module, &file_manager).expect("Failed to emit module");
let pbc = BytecodeLoader::load(&result.rom).expect("Failed to parse emitted PBC"); let pbc = BytecodeLoader::load(&result.rom).expect("Failed to parse emitted PBC");

View File

@ -1,9 +1,9 @@
use crate::building::output::{CompiledModule, ExportKey, ImportKey}; use crate::building::output::{CompiledModule};
use crate::building::plan::BuildStep; use crate::building::plan::BuildStep;
use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo};
use prometeu_core::virtual_machine::{ProgramImage, Value}; use prometeu_core::virtual_machine::{ProgramImage, Value};
use std::collections::{BTreeMap, HashMap}; use std::collections::{HashMap};
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum LinkError { pub enum LinkError {
@ -241,6 +241,14 @@ impl Linker {
} }
} }
} }
// v0: Fallback export for entrypoint `src/main/modules:frame` (root module)
if !final_exports.contains_key("src/main/modules:frame") {
if let Some(&root_offset) = module_function_offsets.last() {
if let Some((idx, _)) = combined_function_names.iter().find(|(i, name)| *i >= root_offset && name == "frame") {
final_exports.insert("src/main/modules:frame".to_string(), *idx);
}
}
}
let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() { let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() {
None None
@ -263,12 +271,14 @@ impl Linker {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::collections::BTreeMap;
use super::*; use super::*;
use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata}; use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata};
use crate::building::plan::BuildTarget; use crate::building::plan::BuildTarget;
use crate::deps::resolver::ProjectId; use crate::deps::resolver::ProjectId;
use crate::frontends::pbs::symbols::SymbolKind; use crate::frontends::pbs::symbols::SymbolKind;
use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::v0::FunctionMeta;
#[test] #[test]
fn test_link_root_and_lib() { fn test_link_root_and_lib() {
@ -285,7 +295,7 @@ mod tests {
module_path: "math".into(), module_path: "math".into(),
symbol_name: "add".into(), symbol_name: "add".into(),
kind: SymbolKind::Function, kind: SymbolKind::Function,
}, ExportMetadata { func_idx: Some(0) }); }, ExportMetadata { func_idx: Some(0), is_host: false, ty: None });
let lib_module = CompiledModule { let lib_module = CompiledModule {
project_id: lib_id.clone(), project_id: lib_id.clone(),

View File

@ -11,6 +11,7 @@ use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::resolver::{ModuleProvider, Resolver}; use crate::frontends::pbs::resolver::{ModuleProvider, Resolver};
use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility}; use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility};
use crate::frontends::pbs::typecheck::TypeChecker; use crate::frontends::pbs::typecheck::TypeChecker;
use crate::frontends::pbs::types::PbsType;
use crate::lowering::core_to_vm; use crate::lowering::core_to_vm;
use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta}; use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -27,7 +28,8 @@ pub struct ExportKey {
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportMetadata { pub struct ExportMetadata {
pub func_idx: Option<u32>, pub func_idx: Option<u32>,
// Add other metadata if needed later (e.g. type info) pub is_host: bool,
pub ty: Option<PbsType>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
@ -150,7 +152,7 @@ pub fn compile_project(
let mut all_visible_modules = module_symbols_map.clone(); let mut all_visible_modules = module_symbols_map.clone();
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, meta) in &compiled.exports {
// Support syntax: "alias/module" and "@alias:module" // Support syntax: "alias/module" and "@alias:module"
let key_module_path = key.module_path.replace("src/main/modules/", ""); let key_module_path = key.module_path.replace("src/main/modules/", "");
let synthetic_paths = [ let synthetic_paths = [
@ -170,8 +172,8 @@ pub fn compile_project(
_ => Namespace::Value, _ => Namespace::Value,
}, },
visibility: Visibility::Pub, visibility: Visibility::Pub,
ty: None, ty: meta.ty.clone(),
is_host: false, is_host: meta.is_host,
span: Span::new(0, 0, 0), span: Span::new(0, 0, 0),
origin: Some(synthetic_module_path.clone()), origin: Some(synthetic_module_path.clone()),
}; };
@ -204,7 +206,8 @@ pub fn compile_project(
// TypeChecker also needs &mut ModuleSymbols // TypeChecker also needs &mut ModuleSymbols
let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap(); let mut ms_mut = module_symbols_map.get_mut(module_path).unwrap();
let mut typechecker = TypeChecker::new(&mut ms_mut, &module_provider); let imported = file_imported_symbols.get(module_path).unwrap();
let mut typechecker = TypeChecker::new(&mut ms_mut, imported, &module_provider);
typechecker.check(ast)?; typechecker.check(ast)?;
} }
@ -234,7 +237,7 @@ pub fn compile_project(
let vm_module = core_to_vm::lower_program(&combined_program) let vm_module = core_to_vm::lower_program(&combined_program)
.map_err(|e| CompileError::Internal(format!("Lowering error: {}", e)))?; .map_err(|e| CompileError::Internal(format!("Lowering error: {}", e)))?;
let fragments = emit_fragments(&vm_module, &file_manager) let fragments = emit_fragments(&vm_module)
.map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?; .map_err(|e| CompileError::Internal(format!("Emission error: {}", e)))?;
// 5. Collect exports // 5. Collect exports
@ -246,7 +249,11 @@ pub fn compile_project(
module_path: module_path.clone(), module_path: module_path.clone(),
symbol_name: sym.name.clone(), symbol_name: sym.name.clone(),
kind: sym.kind.clone(), kind: sym.kind.clone(),
}, ExportMetadata { func_idx: None }); }, ExportMetadata {
func_idx: None,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
} }
} }
for sym in ms.value_symbols.symbols.values() { for sym in ms.value_symbols.symbols.values() {
@ -258,7 +265,11 @@ pub fn compile_project(
module_path: module_path.clone(), module_path: module_path.clone(),
symbol_name: sym.name.clone(), symbol_name: sym.name.clone(),
kind: sym.kind.clone(), kind: sym.kind.clone(),
}, ExportMetadata { func_idx }); }, ExportMetadata {
func_idx,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
} }
} }
} }

View File

@ -326,8 +326,7 @@ mod tests {
assert!(kinds.contains(&&ir_vm::InstrKind::GateRelease), "Must contain GateRelease (on cleanup or Pop)"); assert!(kinds.contains(&&ir_vm::InstrKind::GateRelease), "Must contain GateRelease (on cleanup or Pop)");
// --- 4. EMIT BYTECODE --- // --- 4. EMIT BYTECODE ---
let file_manager = crate::common::files::FileManager::new(); let emit_result = backend::emit_module(&vm_module).expect("Emission failed");
let emit_result = backend::emit_module(&vm_module, &file_manager).expect("Emission failed");
let rom = emit_result.rom; let rom = emit_result.rom;

View File

@ -722,6 +722,31 @@ impl<'a> Lowerer<'a> {
} }
} }
// Host contract static calls: Contract.method(...)
if let Node::Ident(obj_id) = &*ma.object {
let is_local = self.find_local(&obj_id.name).is_some();
if !is_local {
// Check type symbol (current or imported) for a host contract
let sym_opt = self.module_symbols.type_symbols.get(&obj_id.name)
.or_else(|| self.imported_symbols.type_symbols.get(&obj_id.name));
if let Some(sym) = sym_opt {
if sym.kind == SymbolKind::Contract && sym.is_host {
// Lower arguments first to avoid borrowing conflicts
for arg in &n.args {
self.lower_node(arg)?;
}
if let Some(method) = self.contract_registry.get_method(&obj_id.name, &ma.member) {
let id = method.id;
let return_slots = if matches!(method.return_type, PbsType::Void) { 0 } else { 1 };
self.emit(Instr::HostCall(id, return_slots));
return Ok(());
}
}
}
}
}
// Check for .raw() // Check for .raw()
if ma.member == "raw" { if ma.member == "raw" {
self.lower_node(&ma.object)?; self.lower_node(&ma.object)?;

View File

@ -58,7 +58,7 @@ impl Frontend for PbsFrontend {
resolver.resolve(&ast)?; resolver.resolve(&ast)?;
let imported_symbols = resolver.imported_symbols; let imported_symbols = resolver.imported_symbols;
let mut typechecker = TypeChecker::new(&mut module_symbols, &EmptyProvider); let mut typechecker = TypeChecker::new(&mut module_symbols, &imported_symbols, &EmptyProvider);
typechecker.check(&ast)?; typechecker.check(&ast)?;
// Lower to Core IR // Lower to Core IR

View File

@ -9,6 +9,7 @@ use std::collections::HashMap;
pub struct TypeChecker<'a> { pub struct TypeChecker<'a> {
module_symbols: &'a mut ModuleSymbols, module_symbols: &'a mut ModuleSymbols,
imported_symbols: &'a ModuleSymbols,
_module_provider: &'a dyn ModuleProvider, _module_provider: &'a dyn ModuleProvider,
scopes: Vec<HashMap<String, PbsType>>, scopes: Vec<HashMap<String, PbsType>>,
mut_bindings: Vec<HashMap<String, bool>>, mut_bindings: Vec<HashMap<String, bool>>,
@ -23,10 +24,12 @@ pub struct TypeChecker<'a> {
impl<'a> TypeChecker<'a> { impl<'a> TypeChecker<'a> {
pub fn new( pub fn new(
module_symbols: &'a mut ModuleSymbols, module_symbols: &'a mut ModuleSymbols,
imported_symbols: &'a ModuleSymbols,
module_provider: &'a dyn ModuleProvider, module_provider: &'a dyn ModuleProvider,
) -> Self { ) -> Self {
Self { Self {
module_symbols, module_symbols,
imported_symbols,
_module_provider: module_provider, _module_provider: module_provider,
scopes: Vec::new(), scopes: Vec::new(),
mut_bindings: Vec::new(), mut_bindings: Vec::new(),
@ -213,45 +216,63 @@ impl<'a> TypeChecker<'a> {
fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType { fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType {
if let Node::Ident(id) = &*n.object { if let Node::Ident(id) = &*n.object {
// Check if it's a known host contract // Check if it's a local first
if let Some(sym) = self.module_symbols.type_symbols.get(&id.name) { let is_local = self.scopes.iter().any(|s| s.contains_key(&id.name));
if sym.kind == SymbolKind::Contract && sym.is_host {
// Check if the method exists in registry if !is_local {
if let Some(method) = self.contract_registry.get_method(&id.name, &n.member) { // Check if it's a known host contract
return PbsType::Function { let sym_opt = self.module_symbols.type_symbols.get(&id.name)
params: method.params.clone(), .or_else(|| self.imported_symbols.type_symbols.get(&id.name));
return_type: Box::new(method.return_type.clone()),
}; if let Some(sym) = sym_opt {
} else { if sym.kind == SymbolKind::Contract && sym.is_host {
self.diagnostics.push(Diagnostic { // Check if the method exists in registry
level: DiagnosticLevel::Error, if let Some(method) = self.contract_registry.get_method(&id.name, &n.member) {
code: Some("E_RESOLVE_UNDEFINED".to_string()), return PbsType::Function {
message: format!("Method '{}' not found on host contract '{}'", n.member, id.name), params: method.params.clone(),
span: Some(n.span), return_type: Box::new(method.return_type.clone()),
}); };
} else {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
code: Some("E_RESOLVE_UNDEFINED".to_string()),
message: format!("Method '{}' not found on host contract '{}'", n.member, id.name),
span: Some(n.span),
});
}
return PbsType::Void;
} }
return PbsType::Void;
}
}
// Builtin Struct Associated Members (Static/Constants) // v0: Suporte explícito às constantes de Color
if let Some(constants) = self.struct_constants.get(&id.name) { if sym.kind == SymbolKind::Struct && id.name == "Color" {
if let Some(ty) = constants.get(&n.member) { match n.member.as_str() {
return ty.clone(); "BLACK" | "WHITE" | "RED" | "GREEN" | "BLUE" => {
return PbsType::Struct("Color".to_string());
}
_ => {}
}
}
} }
}
// Fallback for constructors if used as Type.alias(...) // Builtin Struct Associated Members (Static/Constants)
if let Some(ctors) = self.struct_constructors.get(&id.name) { if let Some(constants) = self.struct_constants.get(&id.name) {
if let Some(ty) = ctors.get(&n.member) { if let Some(ty) = constants.get(&n.member) {
return ty.clone(); return ty.clone();
}
} }
}
// Fallback for static methods if used as Type.method(...) // Fallback for constructors if used as Type.alias(...)
if let Some(methods) = self.struct_methods.get(&id.name) { if let Some(ctors) = self.struct_constructors.get(&id.name) {
if let Some(ty) = methods.get(&n.member) { if let Some(ty) = ctors.get(&n.member) {
return ty.clone(); return ty.clone();
}
}
// Fallback for static methods if used as Type.method(...)
if let Some(methods) = self.struct_methods.get(&id.name) {
if let Some(ty) = methods.get(&n.member) {
return ty.clone();
}
} }
} }
} }
@ -449,6 +470,13 @@ impl<'a> TypeChecker<'a> {
} }
} }
// Check imported symbols
if let Some(sym) = self.imported_symbols.value_symbols.get(&n.name) {
if let Some(ty) = &sym.ty {
return ty.clone();
}
}
// Fallback for default constructor: check if it's a struct name // Fallback for default constructor: check if it's a struct name
if let Some(ctors) = self.struct_constructors.get(&n.name) { if let Some(ctors) = self.struct_constructors.get(&n.name) {
if let Some(ty) = ctors.get(&n.name) { if let Some(ty) = ctors.get(&n.name) {
@ -746,6 +774,9 @@ impl<'a> TypeChecker<'a> {
if let Some(sym) = self.module_symbols.type_symbols.get(name) { if let Some(sym) = self.module_symbols.type_symbols.get(name) {
return Some(sym); return Some(sym);
} }
if let Some(sym) = self.imported_symbols.type_symbols.get(name) {
return Some(sym);
}
None None
} }

View File

@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PbsType { pub enum PbsType {
Int, Int,
Float, Float,

View File

@ -1,5 +1,4 @@
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::ir_core::ids::{ConstId as CoreConstId, FieldId, FunctionId, TypeId as CoreTypeId, ValueId}; 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_core::{self, Block, ConstPool, ConstantValue, Instr, Program, Terminator};
use prometeu_compiler::ir_vm::InstrKind; use prometeu_compiler::ir_vm::InstrKind;
@ -83,8 +82,7 @@ fn test_hip_conformance_core_to_vm_to_bytecode() {
assert!(kinds.contains(&&InstrKind::GateRelease), "Missing GateRelease"); assert!(kinds.contains(&&InstrKind::GateRelease), "Missing GateRelease");
// 3. Emit Bytecode // 3. Emit Bytecode
let file_manager = FileManager::new(); let emit_result = emit_module(&vm_module).expect("Emission failed");
let emit_result = emit_module(&vm_module, &file_manager).expect("Emission failed");
let bytecode = emit_result.rom; let bytecode = emit_result.rom;
// 4. Assert industrial PBS\0 format // 4. Assert industrial PBS\0 format

View File

@ -39,8 +39,8 @@ fn test_canonical_cartridge_heartbeat() {
let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?"); let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?");
let mut vm = VirtualMachine::new(vec![], vec![]); let mut vm = VirtualMachine::new(vec![], vec![]);
vm.initialize(pbc_bytes, "frame").expect("Failed to initialize VM with canonical cartridge"); vm.initialize(pbc_bytes, "src/main/modules:frame").expect("Failed to initialize VM with canonical cartridge");
vm.prepare_call("frame"); vm.prepare_call("src/main/modules:frame");
let mut native = MockNative; let mut native = MockNative;
let mut hw = Hardware::new(); let mut hw = Hardware::new();