pr 58
This commit is contained in:
parent
6330c6cbae
commit
f80cb64f66
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1906,6 +1906,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "prometeu-bytecode"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-compiler"
|
||||
|
||||
@ -6,4 +6,4 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# No dependencies for now
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1 +1,2 @@
|
||||
pub mod plan;
|
||||
pub mod output;
|
||||
|
||||
348
crates/prometeu-compiler/src/building/output.rs
Normal file
348
crates/prometeu-compiler/src/building/output.rs
Normal 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.
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user