This commit is contained in:
Nilton Constantino 2026-02-02 23:37:40 +00:00
parent 45696b99e7
commit a8e5d7f98e
No known key found for this signature in database
16 changed files with 463 additions and 295 deletions

View File

@ -1,6 +1,7 @@
use crate::building::linker::{LinkError, Linker};
use crate::building::output::{compile_project, CompileError};
use crate::building::plan::{BuildPlan, BuildTarget};
use crate::common::files::FileManager;
use crate::deps::resolver::ResolvedGraph;
use prometeu_core::virtual_machine::ProgramImage;
use std::collections::HashMap;
@ -11,6 +12,12 @@ pub enum BuildError {
Link(LinkError),
}
#[derive(Debug, Clone)]
pub struct BuildResult {
pub image: ProgramImage,
pub file_manager: FileManager,
}
impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -34,17 +41,21 @@ impl From<LinkError> for BuildError {
}
}
pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<ProgramImage, BuildError> {
pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<BuildResult, BuildError> {
let plan = BuildPlan::from_graph(graph, target);
let mut compiled_modules = HashMap::new();
let mut modules_in_order = Vec::new();
let mut file_manager = FileManager::new();
for step in &plan.steps {
let compiled = compile_project(step.clone(), &compiled_modules)?;
let compiled = compile_project(step.clone(), &compiled_modules, &mut file_manager)?;
compiled_modules.insert(step.project_id.clone(), compiled.clone());
modules_in_order.push(compiled);
}
let program_image = Linker::link(modules_in_order, plan.steps)?;
Ok(program_image)
Ok(BuildResult {
image: program_image,
file_manager,
})
}

View File

@ -108,10 +108,9 @@ impl ModuleProvider for ProjectModuleProvider {
pub fn compile_project(
step: BuildStep,
dep_modules: &HashMap<ProjectId, CompiledModule>
dep_modules: &HashMap<ProjectId, CompiledModule>,
file_manager: &mut FileManager,
) -> 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)> = Vec::new(); // (module_path, ast)
@ -387,7 +386,8 @@ mod tests {
deps: BTreeMap::new(),
};
let compiled = compile_project(step, &HashMap::new()).expect("Failed to compile project");
let mut file_manager = FileManager::new();
let compiled = compile_project(step, &HashMap::new(), &mut file_manager).expect("Failed to compile project");
assert_eq!(compiled.project_id, project_id);
assert_eq!(compiled.target, BuildTarget::Main);

View File

@ -1,12 +1,14 @@
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct SourceFile {
pub id: usize,
pub path: PathBuf,
pub source: Arc<str>,
}
#[derive(Debug, Clone)]
pub struct FileManager {
files: Vec<SourceFile>,
}
@ -57,3 +59,28 @@ impl FileManager {
(line, col)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lookup_pos() {
let mut fm = FileManager::new();
let source = "line1\nline2\n line3".to_string();
let file_id = fm.add(PathBuf::from("test.pbs"), source);
// "l" in line 1
assert_eq!(fm.lookup_pos(file_id, 0), (1, 1));
// "e" in line 1
assert_eq!(fm.lookup_pos(file_id, 3), (1, 4));
// "\n" after line 1
assert_eq!(fm.lookup_pos(file_id, 5), (1, 6));
// "l" in line 2
assert_eq!(fm.lookup_pos(file_id, 6), (2, 1));
// first space in line 3
assert_eq!(fm.lookup_pos(file_id, 12), (3, 1));
// "l" in line 3
assert_eq!(fm.lookup_pos(file_id, 14), (3, 3));
}
}

View File

@ -1,6 +1,13 @@
use serde::Serialize;
use serde::{Serialize, Deserialize};
use crate::common::spans::Span;
#[derive(Serialize, Debug, Clone)]
#[derive(Debug, Clone)]
pub struct RawSymbol {
pub pc: u32,
pub span: Span,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Symbol {
pub pc: u32,
pub file: String,

View File

@ -5,7 +5,9 @@
use crate::backend;
use crate::common::config::ProjectConfig;
use crate::common::symbols::Symbol;
use crate::common::symbols::{Symbol, RawSymbol};
use crate::common::files::FileManager;
use crate::common::spans::Span;
use anyhow::Result;
use prometeu_bytecode::BytecodeModule;
use std::path::Path;
@ -20,7 +22,10 @@ pub struct CompilationUnit {
/// The list of debug symbols discovered during compilation.
/// These are used to map bytecode offsets back to source code locations.
pub symbols: Vec<Symbol>,
pub raw_symbols: Vec<RawSymbol>,
/// The file manager containing all source files used during compilation.
pub file_manager: FileManager,
}
impl CompilationUnit {
@ -31,7 +36,23 @@ impl CompilationUnit {
/// * `emit_disasm` - If true, a `.disasm` file will be created next to the output.
/// * `emit_symbols` - If true, a `.json` symbols file will be created next to the output.
pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> {
let artifacts = backend::artifacts::Artifacts::new(self.rom.clone(), self.symbols.clone());
let mut symbols = Vec::new();
for raw in &self.raw_symbols {
let path = self.file_manager.get_path(raw.span.file_id)
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| format!("file_{}", raw.span.file_id));
let (line, col) = self.file_manager.lookup_pos(raw.span.file_id, raw.span.start);
symbols.push(Symbol {
pc: raw.pc,
file: path,
line,
col,
});
}
let artifacts = backend::artifacts::Artifacts::new(self.rom.clone(), symbols);
artifacts.export(out, emit_disasm, emit_symbols)
}
}
@ -67,27 +88,30 @@ pub fn compile_ext(project_dir: &Path, explain_deps: bool) -> Result<Compilation
let graph = graph_res.map_err(|e| anyhow::anyhow!("Dependency resolution failed: {}", e))?;
let program_image = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main)
let build_result = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main)
.map_err(|e| anyhow::anyhow!("Build failed: {}", e))?;
let module = BytecodeModule::from(program_image.clone());
let module = BytecodeModule::from(build_result.image.clone());
let rom = module.serialize();
let mut symbols = Vec::new();
if let Some(debug) = &program_image.debug_info {
let mut raw_symbols = Vec::new();
if let Some(debug) = &build_result.image.debug_info {
for (pc, span) in &debug.pc_to_span {
symbols.push(Symbol {
raw_symbols.push(RawSymbol {
pc: *pc,
file: format!("file_{}", span.file_id),
line: 0,
col: 0,
span: Span {
file_id: span.file_id as usize,
start: span.start,
end: span.end,
},
});
}
}
Ok(CompilationUnit {
rom,
symbols,
raw_symbols,
file_manager: build_result.file_manager,
})
} else {
anyhow::bail!("Invalid frontend: {}", config.script_fe)
@ -303,21 +327,21 @@ mod tests {
id: 0,
instrs: vec![
// 1. allocates a storage struct
Instr::Alloc { ty: TypeId(1), slots: 2 },
Instr::SetLocal(0),
Instr::from(InstrKind::Alloc { ty: TypeId(1), slots: 2 }),
Instr::from(InstrKind::SetLocal(0)),
// 2. mutates a field (offset 0)
Instr::BeginMutate { gate: ValueId(0) },
Instr::PushConst(val_42),
Instr::SetLocal(1),
Instr::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) },
Instr::EndMutate,
Instr::from(InstrKind::BeginMutate { gate: ValueId(0) }),
Instr::from(InstrKind::PushConst(val_42)),
Instr::from(InstrKind::SetLocal(1)),
Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) }),
Instr::from(InstrKind::EndMutate),
// 3. peeks value (offset 0)
Instr::BeginPeek { gate: ValueId(0) },
Instr::GateLoadField { gate: ValueId(0), field: f1 },
Instr::SetLocal(2),
Instr::EndPeek,
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: f1 }),
Instr::from(InstrKind::SetLocal(2)),
Instr::from(InstrKind::EndPeek),
],
terminator: Terminator::Return,
}],
@ -331,7 +355,6 @@ mod tests {
// --- 2. LOWER TO VM IR ---
let vm_module = lower_program(&program).expect("Lowering failed");
// --- 3. ASSERT VM IR (Instructions + RC) ---
let func = &vm_module.functions[0];
let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect();
@ -396,4 +419,49 @@ mod tests {
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
}
#[test]
fn test_symbols_emission_integration() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
fs::write(
project_dir.join("prometeu.json"),
r#"{
"name": "symbols_test",
"version": "0.1.0",
"script_fe": "pbs",
"entry": "src/main/modules/main.pbs"
}"#,
).unwrap();
let code = r#"
fn frame(): void {
let x = 10;
}
"#;
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
fs::write(project_dir.join("src/main/modules/main.pbs"), code).unwrap();
let unit = compile(project_dir).expect("Failed to compile");
let out_pbc = project_dir.join("build/program.pbc");
fs::create_dir_all(out_pbc.parent().unwrap()).unwrap();
unit.export(&out_pbc, false, true).expect("Failed to export");
let symbols_path = project_dir.join("build/symbols.json");
assert!(symbols_path.exists(), "symbols.json should exist at {:?}", symbols_path);
let symbols_content = fs::read_to_string(symbols_path).unwrap();
let symbols: Vec<Symbol> = serde_json::from_str(&symbols_content).unwrap();
assert!(!symbols.is_empty(), "Symbols list should not be empty");
// Check for non-zero line/col
let main_sym = symbols.iter().find(|s| s.line > 0 && s.col > 0);
assert!(main_sym.is_some(), "Should find at least one symbol with real location");
let sym = main_sym.unwrap();
assert!(sym.file.contains("main.pbs"), "Symbol file should point to main.pbs, got {}", sym.file);
}
}

View File

@ -1,11 +1,12 @@
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::common::spans::Span;
use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::contracts::ContractRegistry;
use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::types::PbsType;
use crate::ir_core;
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
use crate::ir_core::{Block, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type};
use std::collections::HashMap;
#[derive(Clone)]
@ -33,6 +34,7 @@ pub struct Lowerer<'a> {
contract_registry: ContractRegistry,
diagnostics: Vec<Diagnostic>,
max_slots_used: u32,
current_span: Option<Span>,
}
impl<'a> Lowerer<'a> {
@ -70,6 +72,7 @@ impl<'a> Lowerer<'a> {
contract_registry: ContractRegistry::new(),
diagnostics: Vec::new(),
max_slots_used: 0,
current_span: None,
}
}
@ -264,28 +267,31 @@ impl<'a> Lowerer<'a> {
}
fn lower_node(&mut self, node: &Node) -> Result<(), ()> {
match node {
let old_span = self.current_span;
self.current_span = Some(node.span());
let res = match node {
Node::Block(n) => self.lower_block(n),
Node::LetStmt(n) => self.lower_let_stmt(n),
Node::ExprStmt(n) => self.lower_node(&n.expr),
Node::ReturnStmt(n) => self.lower_return_stmt(n),
Node::IntLit(n) => {
let id = self.program.const_pool.add_int(n.value);
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::FloatLit(n) => {
let id = self.program.const_pool.add_float(n.value);
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::StringLit(n) => {
let id = self.program.const_pool.add_string(n.value.clone());
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
Ok(())
}
Node::BoundedLit(n) => {
self.emit(Instr::PushBounded(n.value));
self.emit(InstrKind::PushBounded(n.value));
Ok(())
}
Node::Ident(n) => self.lower_ident(n),
@ -304,12 +310,15 @@ impl<'a> Lowerer<'a> {
self.error("E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", node), node.span());
Err(())
}
}
};
self.current_span = old_span;
res
}
fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> {
let (ty_id, slots) = self.get_type_id_and_slots(&n.ty)?;
self.emit(Instr::Alloc { ty: ty_id, slots });
self.emit(InstrKind::Alloc { ty: ty_id, slots });
Ok(())
}
@ -359,22 +368,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
self.emit(Instr::SetLocal(gate_slot));
self.emit(InstrKind::SetLocal(gate_slot));
// 3. Begin Operation
self.emit(Instr::BeginPeek { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) });
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local
self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int);
self.emit(Instr::SetLocal(view_slot));
self.emit(InstrKind::SetLocal(view_slot));
// 5. Body
self.lower_node(&n.body)?;
// 6. End Operation
self.emit(Instr::EndPeek);
self.emit(InstrKind::EndPeek);
self.local_vars.pop();
Ok(())
@ -386,22 +395,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
self.emit(Instr::SetLocal(gate_slot));
self.emit(InstrKind::SetLocal(gate_slot));
// 3. Begin Operation
self.emit(Instr::BeginBorrow { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) });
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local
self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int);
self.emit(Instr::SetLocal(view_slot));
self.emit(InstrKind::SetLocal(view_slot));
// 5. Body
self.lower_node(&n.body)?;
// 6. End Operation
self.emit(Instr::EndBorrow);
self.emit(InstrKind::EndBorrow);
self.local_vars.pop();
Ok(())
@ -413,22 +422,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
self.emit(Instr::SetLocal(gate_slot));
self.emit(InstrKind::SetLocal(gate_slot));
// 3. Begin Operation
self.emit(Instr::BeginMutate { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) });
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local
self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int);
self.emit(Instr::SetLocal(view_slot));
self.emit(InstrKind::SetLocal(view_slot));
// 5. Body
self.lower_node(&n.body)?;
// 6. End Operation
self.emit(Instr::EndMutate);
self.emit(InstrKind::EndMutate);
self.local_vars.pop();
Ok(())
@ -470,7 +479,7 @@ impl<'a> Lowerer<'a> {
let slot = self.add_local_to_scope(n.name.clone(), ty);
for i in (0..slots).rev() {
self.emit(Instr::SetLocal(slot + i));
self.emit(InstrKind::SetLocal(slot + i));
}
Ok(())
}
@ -487,7 +496,7 @@ impl<'a> Lowerer<'a> {
if let Some(info) = self.find_local(&n.name) {
let slots = self.get_type_slots(&info.ty);
for i in 0..slots {
self.emit(Instr::GetLocal(info.slot + i));
self.emit(InstrKind::GetLocal(info.slot + i));
}
Ok(())
} else {
@ -495,18 +504,18 @@ impl<'a> Lowerer<'a> {
match n.name.as_str() {
"true" => {
let id = self.program.const_pool.add_int(1);
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
return Ok(());
}
"false" => {
let id = self.program.const_pool.add_int(0);
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
return Ok(());
}
"none" => {
// For now, treat none as 0. This should be refined when optional is fully implemented.
let id = self.program.const_pool.add_int(0);
self.emit(Instr::PushConst(id));
self.emit(InstrKind::PushConst(id));
return Ok(());
}
_ => {}
@ -550,7 +559,7 @@ impl<'a> Lowerer<'a> {
return Ok(());
}
};
self.emit(Instr::PushBounded(val));
self.emit(InstrKind::PushBounded(val));
return Ok(());
}
}
@ -558,7 +567,7 @@ impl<'a> Lowerer<'a> {
if let Some((slot, ty)) = self.resolve_member_access(n) {
let slots = self.get_type_slots(&ty);
for i in 0..slots {
self.emit(Instr::GetLocal(slot + i));
self.emit(InstrKind::GetLocal(slot + i));
}
return Ok(());
}
@ -663,7 +672,7 @@ impl<'a> Lowerer<'a> {
self.lower_node(arg)?;
}
if let Some(func_id) = self.function_ids.get(&id_node.name) {
self.emit(Instr::Call(*func_id, n.args.len() as u32));
self.emit(InstrKind::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 {
@ -673,7 +682,7 @@ impl<'a> Lowerer<'a> {
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));
self.emit(InstrKind::ImportCall(dep_alias, module_path, sym.name.clone(), n.args.len() as u32));
return Ok(());
}
}
@ -739,7 +748,7 @@ impl<'a> Lowerer<'a> {
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));
self.emit(InstrKind::HostCall(id, return_slots));
return Ok(());
}
}
@ -773,7 +782,7 @@ impl<'a> Lowerer<'a> {
let g6 = (g & 0xFF) >> 2;
let b5 = (b & 0xFF) >> 3;
let rgb565 = (r5 << 11) | (g6 << 5) | b5;
self.emit(Instr::PushBounded(rgb565 as u32));
self.emit(InstrKind::PushBounded(rgb565 as u32));
return Ok(());
} else {
self.error("E_LOWER_UNSUPPORTED", "Color.rgb only supports literal arguments in this version".to_string(), n.span);
@ -799,7 +808,7 @@ impl<'a> Lowerer<'a> {
if let Some(method) = self.contract_registry.get_method(&obj_id.name, &ma.member) {
let ir_ty = self.convert_pbs_type(&method.return_type);
let return_slots = self.get_type_slots(&ir_ty);
self.emit(Instr::HostCall(method.id, return_slots));
self.emit(InstrKind::HostCall(method.id, return_slots));
return Ok(());
} else {
self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span);
@ -874,13 +883,13 @@ impl<'a> Lowerer<'a> {
fn lower_pad_any(&mut self, base_slot: u32) {
for i in 0..12 {
let btn_base = base_slot + (i * 4);
self.emit(Instr::GetLocal(btn_base)); // pressed
self.emit(Instr::GetLocal(btn_base + 1)); // released
self.emit(Instr::Or);
self.emit(Instr::GetLocal(btn_base + 2)); // down
self.emit(Instr::Or);
self.emit(InstrKind::GetLocal(btn_base)); // pressed
self.emit(InstrKind::GetLocal(btn_base + 1)); // released
self.emit(InstrKind::Or);
self.emit(InstrKind::GetLocal(btn_base + 2)); // down
self.emit(InstrKind::Or);
if i > 0 {
self.emit(Instr::Or);
self.emit(InstrKind::Or);
}
}
}
@ -889,18 +898,18 @@ impl<'a> Lowerer<'a> {
self.lower_node(&n.left)?;
self.lower_node(&n.right)?;
match n.op.as_str() {
"+" => self.emit(Instr::Add),
"-" => self.emit(Instr::Sub),
"*" => self.emit(Instr::Mul),
"/" => self.emit(Instr::Div),
"==" => self.emit(Instr::Eq),
"!=" => self.emit(Instr::Neq),
"<" => self.emit(Instr::Lt),
"<=" => self.emit(Instr::Lte),
">" => self.emit(Instr::Gt),
">=" => self.emit(Instr::Gte),
"&&" => self.emit(Instr::And),
"||" => self.emit(Instr::Or),
"+" => self.emit(InstrKind::Add),
"-" => self.emit(InstrKind::Sub),
"*" => self.emit(InstrKind::Mul),
"/" => self.emit(InstrKind::Div),
"==" => self.emit(InstrKind::Eq),
"!=" => self.emit(InstrKind::Neq),
"<" => self.emit(InstrKind::Lt),
"<=" => self.emit(InstrKind::Lte),
">" => self.emit(InstrKind::Gt),
">=" => self.emit(InstrKind::Gte),
"&&" => self.emit(InstrKind::And),
"||" => self.emit(InstrKind::Or),
_ => {
self.error("E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), n.span);
return Err(());
@ -912,8 +921,8 @@ impl<'a> Lowerer<'a> {
fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> {
self.lower_node(&n.expr)?;
match n.op.as_str() {
"-" => self.emit(Instr::Neg),
"!" => self.emit(Instr::Not),
"-" => self.emit(InstrKind::Neg),
"!" => self.emit(InstrKind::Not),
_ => {
self.error("E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), n.span);
return Err(());
@ -1013,9 +1022,9 @@ impl<'a> Lowerer<'a> {
id
}
fn emit(&mut self, instr: Instr) {
fn emit(&mut self, kind: InstrKind) {
if let Some(block) = &mut self.current_block {
block.instrs.push(instr);
block.instrs.push(Instr::new(kind, self.current_span));
}
}
@ -1151,7 +1160,7 @@ mod tests {
assert!(add_func.blocks.len() >= 1);
let first_block = &add_func.blocks[0];
// Check for Add instruction
assert!(first_block.instrs.iter().any(|i| matches!(i, ir_core::Instr::Add)));
assert!(first_block.instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Add)));
}
#[test]
@ -1203,9 +1212,9 @@ mod tests {
let func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc { .. })));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::EndMutate)));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::Alloc { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::EndMutate)));
}
#[test]
@ -1273,14 +1282,14 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
// Assert distinct Core IR instruction sequences
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginPeek { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndPeek)));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndPeek)));
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginBorrow { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndBorrow)));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginBorrow { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndBorrow)));
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndMutate)));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate)));
}
#[test]
@ -1307,9 +1316,9 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
// Gfx.clear -> 0x1010
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x1010, 0))));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(0x1010, 0))));
// Log.write -> 0x5001
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001, 0))));
assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::HostCall(0x5001, 0))));
}
#[test]
@ -1404,7 +1413,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| {
if let Instr::Alloc { ty, slots } = i {
if let InstrKind::Alloc { ty, slots } = &i.kind {
Some((ty, slots))
} else {
None
@ -1436,7 +1445,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| {
if let Instr::Alloc { ty, slots } = i {
if let InstrKind::Alloc { ty, slots } = &i.kind {
Some((ty, slots))
} else {
None
@ -1468,7 +1477,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| {
if let Instr::Alloc { ty, slots } = i {
if let InstrKind::Alloc { ty, slots } = &i.kind {
Some((ty, slots))
} else {
None

View File

@ -1,9 +1,22 @@
use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId};
use crate::common::spans::Span;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Instr {
pub kind: InstrKind,
pub span: Option<Span>,
}
impl Instr {
pub fn new(kind: InstrKind, span: Option<Span>) -> Self {
Self { kind, span }
}
}
/// Instructions within a basic block.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Instr {
pub enum InstrKind {
/// Placeholder for constant loading.
PushConst(ConstId),
/// Push a bounded value (0..0xFFFF).
@ -54,3 +67,9 @@ pub enum Instr {
GateStoreIndex { gate: ValueId, index: ValueId, value: ValueId },
Free,
}
impl From<InstrKind> for Instr {
fn from(kind: InstrKind) -> Self {
Self::new(kind, None)
}
}

View File

@ -45,8 +45,8 @@ mod tests {
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::Call(FunctionId(11), 0),
Instr::from(InstrKind::PushConst(ConstId(0))),
Instr::from(InstrKind::Call(FunctionId(11), 0)),
],
terminator: Terminator::Return,
}],
@ -81,13 +81,19 @@ mod tests {
"id": 0,
"instrs": [
{
"PushConst": 0
"kind": {
"PushConst": 0
},
"span": null
},
{
"Call": [
11,
0
]
"kind": {
"Call": [
11,
0
]
},
"span": null
}
],
"terminator": "Return"

View File

@ -1,5 +1,5 @@
use super::ids::ValueId;
use super::instr::Instr;
use super::instr::{InstrKind};
use super::program::Program;
use super::terminator::Terminator;
use std::collections::{HashMap, VecDeque};
@ -57,54 +57,54 @@ fn validate_function(func: &super::function::Function) -> Result<(), String> {
visited_with_stack.insert(block_id, current_stack.clone());
for instr in &block.instrs {
match instr {
Instr::BeginPeek { gate } => {
match &instr.kind {
InstrKind::BeginPeek { gate } => {
current_stack.push(HipOp { kind: HipOpKind::Peek, gate: *gate });
}
Instr::BeginBorrow { gate } => {
InstrKind::BeginBorrow { gate } => {
current_stack.push(HipOp { kind: HipOpKind::Borrow, gate: *gate });
}
Instr::BeginMutate { gate } => {
InstrKind::BeginMutate { gate } => {
current_stack.push(HipOp { kind: HipOpKind::Mutate, gate: *gate });
}
Instr::EndPeek => {
InstrKind::EndPeek => {
match current_stack.pop() {
Some(op) if op.kind == HipOpKind::Peek => {},
Some(op) => return Err(format!("EndPeek doesn't match current HIP op: {:?}", op)),
None => return Err("EndPeek without matching BeginPeek".to_string()),
}
}
Instr::EndBorrow => {
InstrKind::EndBorrow => {
match current_stack.pop() {
Some(op) if op.kind == HipOpKind::Borrow => {},
Some(op) => return Err(format!("EndBorrow doesn't match current HIP op: {:?}", op)),
None => return Err("EndBorrow without matching BeginBorrow".to_string()),
}
}
Instr::EndMutate => {
InstrKind::EndMutate => {
match current_stack.pop() {
Some(op) if op.kind == HipOpKind::Mutate => {},
Some(op) => return Err(format!("EndMutate doesn't match current HIP op: {:?}", op)),
None => return Err("EndMutate without matching BeginMutate".to_string()),
}
}
Instr::GateLoadField { .. } | Instr::GateLoadIndex { .. } => {
InstrKind::GateLoadField { .. } | InstrKind::GateLoadIndex { .. } => {
if current_stack.is_empty() {
return Err("GateLoad outside of HIP operation".to_string());
}
}
Instr::GateStoreField { .. } | Instr::GateStoreIndex { .. } => {
InstrKind::GateStoreField { .. } | InstrKind::GateStoreIndex { .. } => {
match current_stack.last() {
Some(op) if op.kind == HipOpKind::Mutate => {},
_ => return Err("GateStore outside of BeginMutate".to_string()),
}
}
Instr::Call(id, _) => {
InstrKind::Call(id, _) => {
if id.0 == 0 {
return Err("Call to FunctionId(0)".to_string());
}
}
Instr::Alloc { ty, .. } => {
InstrKind::Alloc { ty, .. } => {
if ty.0 == 0 {
return Err("Alloc with TypeId(0)".to_string());
}
@ -185,12 +185,12 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::BeginPeek { gate: ValueId(0) },
Instr::GateLoadField { gate: ValueId(0), field: FieldId(0) },
Instr::BeginMutate { gate: ValueId(1) },
Instr::GateStoreField { gate: ValueId(1), field: FieldId(0), value: ValueId(2) },
Instr::EndMutate,
Instr::EndPeek,
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }),
Instr::from(InstrKind::BeginMutate { gate: ValueId(1) }),
Instr::from(InstrKind::GateStoreField { gate: ValueId(1), field: FieldId(0), value: ValueId(2) }),
Instr::from(InstrKind::EndMutate),
Instr::from(InstrKind::EndPeek),
],
terminator: Terminator::Return,
};
@ -203,7 +203,7 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::BeginPeek { gate: ValueId(0) },
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
],
terminator: Terminator::Return,
};
@ -218,8 +218,8 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::BeginPeek { gate: ValueId(0) },
Instr::EndMutate,
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
Instr::from(InstrKind::EndMutate),
],
terminator: Terminator::Return,
};
@ -234,9 +234,9 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::BeginBorrow { gate: ValueId(0) },
Instr::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) },
Instr::EndBorrow,
Instr::from(InstrKind::BeginBorrow { gate: ValueId(0) }),
Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) }),
Instr::from(InstrKind::EndBorrow),
],
terminator: Terminator::Return,
};
@ -251,9 +251,9 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::BeginMutate { gate: ValueId(0) },
Instr::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) },
Instr::EndMutate,
Instr::from(InstrKind::BeginMutate { gate: ValueId(0) }),
Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: FieldId(0), value: ValueId(1) }),
Instr::from(InstrKind::EndMutate),
],
terminator: Terminator::Return,
};
@ -266,7 +266,7 @@ mod tests {
let block = Block {
id: 0,
instrs: vec![
Instr::GateLoadField { gate: ValueId(0), field: FieldId(0) },
Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }),
],
terminator: Terminator::Return,
};
@ -281,15 +281,15 @@ mod tests {
let block0 = Block {
id: 0,
instrs: vec![
Instr::BeginPeek { gate: ValueId(0) },
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
],
terminator: Terminator::Jump(1),
};
let block1 = Block {
id: 1,
instrs: vec![
Instr::GateLoadField { gate: ValueId(0), field: FieldId(0) },
Instr::EndPeek,
Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: FieldId(0) }),
Instr::from(InstrKind::EndPeek),
],
terminator: Terminator::Return,
};
@ -302,14 +302,14 @@ mod tests {
let block0 = Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)), // cond
Instr::from(InstrKind::PushConst(ConstId(0))), // cond
],
terminator: Terminator::JumpIfFalse { target: 2, else_target: 1 },
};
let block1 = Block {
id: 1,
instrs: vec![
Instr::BeginPeek { gate: ValueId(0) },
Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
],
terminator: Terminator::Jump(3),
};
@ -323,7 +323,7 @@ mod tests {
let block3 = Block {
id: 3,
instrs: vec![
Instr::EndPeek, // ERROR: block 2 reaches here with empty stack
Instr::from(InstrKind::EndPeek), // ERROR: block 2 reaches here with empty stack
],
terminator: Terminator::Return,
};
@ -338,7 +338,7 @@ mod tests {
let block_func0 = Block {
id: 0,
instrs: vec![
Instr::Call(FunctionId(0), 0),
Instr::from(InstrKind::Call(FunctionId(0), 0)),
],
terminator: Terminator::Return,
};
@ -348,7 +348,7 @@ mod tests {
let block_ty0 = Block {
id: 0,
instrs: vec![
Instr::Alloc { ty: TypeId(0), slots: 1 },
Instr::from(InstrKind::Alloc { ty: TypeId(0), slots: 1 }),
],
terminator: Terminator::Return,
};

View File

@ -136,7 +136,7 @@ mod tests {
blocks: vec![ir_core::Block {
id: 0,
instrs: vec![
ir_core::Instr::PushConst(ConstId(0)),
ir_core::Instr::from(ir_core::InstrKind::PushConst(ConstId(0))),
],
terminator: ir_core::Terminator::Return,
}],

View File

@ -80,13 +80,21 @@ pub enum Commands {
#[arg(short, long)]
out: Option<PathBuf>,
/// Whether to generate a .json symbols file for source mapping.
#[arg(long, default_value_t = true)]
emit_symbols: bool,
/// Disable symbol generation.
#[arg(long)]
no_symbols: bool,
/// Whether to generate a .disasm file for debugging.
#[arg(long, default_value_t = true)]
emit_disasm: bool,
/// Whether to generate a .json symbols file for source mapping.
#[arg(long, default_value_t = true)]
emit_symbols: bool,
/// Disable disassembly generation.
#[arg(long)]
no_disasm: bool,
/// Whether to explain the dependency resolution process.
#[arg(long)]
@ -113,13 +121,18 @@ pub fn run() -> Result<()> {
project_dir,
out,
emit_disasm,
no_disasm,
emit_symbols,
no_symbols,
explain_deps,
..
} => {
let build_dir = project_dir.join("build");
let out = out.unwrap_or_else(|| build_dir.join("program.pbc"));
let emit_symbols = emit_symbols && !no_symbols;
let emit_disasm = emit_disasm && !no_disasm;
if !build_dir.exists() {
std::fs::create_dir_all(&build_dir)?;
}

View File

@ -81,8 +81,9 @@ pub fn lower_function(
let mut stack_types = Vec::new();
for instr in &block.instrs {
match instr {
ir_core::Instr::PushConst(id) => {
let span = instr.span;
match &instr.kind {
ir_core::InstrKind::PushConst(id) => {
let ty = if let Some(val) = program.const_pool.get(ir_core::ConstId(id.0)) {
match val {
ir_core::ConstantValue::Int(_) => ir_core::Type::Int,
@ -93,13 +94,13 @@ pub fn lower_function(
ir_core::Type::Void
};
stack_types.push(ty);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)), None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)), span));
}
ir_core::Instr::PushBounded(val) => {
ir_core::InstrKind::PushBounded(val) => {
stack_types.push(ir_core::Type::Bounded);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushBounded(*val), None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushBounded(*val), span));
}
ir_core::Instr::Call(func_id, arg_count) => {
ir_core::InstrKind::Call(func_id, arg_count) => {
// Pop arguments from type stack
for _ in 0..*arg_count {
stack_types.pop();
@ -113,7 +114,7 @@ pub fn lower_function(
arg_count: *arg_count
}, None));
}
ir_core::Instr::ImportCall(dep_alias, module_path, symbol_name, arg_count) => {
ir_core::InstrKind::ImportCall(dep_alias, module_path, symbol_name, arg_count) => {
// Pop arguments from type stack
for _ in 0..*arg_count {
stack_types.pop();
@ -128,19 +129,19 @@ pub fn lower_function(
arg_count: *arg_count,
}, None));
}
ir_core::Instr::HostCall(id, slots) => {
ir_core::InstrKind::HostCall(id, slots) => {
// HostCall return types are not easily known without a registry,
// but we now pass the number of slots.
for _ in 0..*slots {
stack_types.push(ir_core::Type::Int);
}
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), span));
}
ir_core::Instr::GetLocal(slot) => {
ir_core::InstrKind::GetLocal(slot) => {
let ty = local_types.get(slot).cloned().unwrap_or(ir_core::Type::Void);
stack_types.push(ty.clone());
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, span));
// If it's a gate, we should retain it if we just pushed it onto stack?
// "on assigning a gate to a local/global"
@ -149,18 +150,18 @@ pub fn lower_function(
// Wait, if I Load it, I have a new handle on the stack. I should Retain it.
if is_gate_type(&ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
}
}
ir_core::Instr::SetLocal(slot) => {
ir_core::InstrKind::SetLocal(slot) => {
let new_ty = stack_types.pop().unwrap_or(ir_core::Type::Void);
let old_ty = local_types.get(slot).cloned();
// 1. Release old value if it was a gate
if let Some(old_ty) = old_ty {
if is_gate_type(&old_ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
}
}
@ -173,121 +174,121 @@ pub fn lower_function(
// Actually, if we Pop it later, we Release it.
local_types.insert(*slot, new_ty);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalStore { slot: *slot }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalStore { slot: *slot }, span));
}
ir_core::Instr::Pop => {
ir_core::InstrKind::Pop => {
let ty = stack_types.pop().unwrap_or(ir_core::Type::Void);
if is_gate_type(&ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
} else {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Pop, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Pop, span));
}
}
ir_core::Instr::Dup => {
ir_core::InstrKind::Dup => {
let ty = stack_types.last().cloned().unwrap_or(ir_core::Type::Void);
stack_types.push(ty.clone());
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Dup, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Dup, span));
if is_gate_type(&ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
}
}
ir_core::Instr::Add | ir_core::Instr::Sub | ir_core::Instr::Mul | ir_core::Instr::Div => {
ir_core::InstrKind::Add | ir_core::InstrKind::Sub | ir_core::InstrKind::Mul | ir_core::InstrKind::Div => {
stack_types.pop();
stack_types.pop();
stack_types.push(ir_core::Type::Int); // Assume Int for arithmetic
let kind = match instr {
ir_core::Instr::Add => ir_vm::InstrKind::Add,
ir_core::Instr::Sub => ir_vm::InstrKind::Sub,
ir_core::Instr::Mul => ir_vm::InstrKind::Mul,
ir_core::Instr::Div => ir_vm::InstrKind::Div,
let kind = match &instr.kind {
ir_core::InstrKind::Add => ir_vm::InstrKind::Add,
ir_core::InstrKind::Sub => ir_vm::InstrKind::Sub,
ir_core::InstrKind::Mul => ir_vm::InstrKind::Mul,
ir_core::InstrKind::Div => ir_vm::InstrKind::Div,
_ => unreachable!(),
};
vm_func.body.push(ir_vm::Instruction::new(kind, None));
vm_func.body.push(ir_vm::Instruction::new(kind, span));
}
ir_core::Instr::Neg => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neg, None));
ir_core::InstrKind::Neg => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neg, span));
}
ir_core::Instr::Eq | ir_core::Instr::Neq | ir_core::Instr::Lt | ir_core::Instr::Lte | ir_core::Instr::Gt | ir_core::Instr::Gte => {
ir_core::InstrKind::Eq | ir_core::InstrKind::Neq | ir_core::InstrKind::Lt | ir_core::InstrKind::Lte | ir_core::InstrKind::Gt | ir_core::InstrKind::Gte => {
stack_types.pop();
stack_types.pop();
stack_types.push(ir_core::Type::Bool);
let kind = match instr {
ir_core::Instr::Eq => ir_vm::InstrKind::Eq,
ir_core::Instr::Neq => ir_vm::InstrKind::Neq,
ir_core::Instr::Lt => ir_vm::InstrKind::Lt,
ir_core::Instr::Lte => ir_vm::InstrKind::Lte,
ir_core::Instr::Gt => ir_vm::InstrKind::Gt,
ir_core::Instr::Gte => ir_vm::InstrKind::Gte,
let kind = match &instr.kind {
ir_core::InstrKind::Eq => ir_vm::InstrKind::Eq,
ir_core::InstrKind::Neq => ir_vm::InstrKind::Neq,
ir_core::InstrKind::Lt => ir_vm::InstrKind::Lt,
ir_core::InstrKind::Lte => ir_vm::InstrKind::Lte,
ir_core::InstrKind::Gt => ir_vm::InstrKind::Gt,
ir_core::InstrKind::Gte => ir_vm::InstrKind::Gte,
_ => unreachable!(),
};
vm_func.body.push(ir_vm::Instruction::new(kind, None));
vm_func.body.push(ir_vm::Instruction::new(kind, span));
}
ir_core::Instr::And | ir_core::Instr::Or => {
ir_core::InstrKind::And | ir_core::InstrKind::Or => {
stack_types.pop();
stack_types.pop();
stack_types.push(ir_core::Type::Bool);
let kind = match instr {
ir_core::Instr::And => ir_vm::InstrKind::And,
ir_core::Instr::Or => ir_vm::InstrKind::Or,
let kind = match &instr.kind {
ir_core::InstrKind::And => ir_vm::InstrKind::And,
ir_core::InstrKind::Or => ir_vm::InstrKind::Or,
_ => unreachable!(),
};
vm_func.body.push(ir_vm::Instruction::new(kind, None));
vm_func.body.push(ir_vm::Instruction::new(kind, span));
}
ir_core::Instr::Not => {
ir_core::InstrKind::Not => {
stack_types.pop();
stack_types.push(ir_core::Type::Bool);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Not, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Not, span));
}
ir_core::Instr::Alloc { ty, slots } => {
ir_core::InstrKind::Alloc { ty, slots } => {
stack_types.push(ir_core::Type::Struct("".to_string())); // It's a gate
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Alloc {
type_id: ir_vm::TypeId(ty.0),
slots: *slots
}, None));
}
ir_core::Instr::BeginPeek { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None));
ir_core::InstrKind::BeginPeek { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, span));
}
ir_core::Instr::BeginBorrow { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None));
ir_core::InstrKind::BeginBorrow { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, span));
}
ir_core::Instr::BeginMutate { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None));
ir_core::InstrKind::BeginMutate { gate } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, span));
}
ir_core::Instr::EndPeek => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndPeek, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
ir_core::InstrKind::EndPeek => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndPeek, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
}
ir_core::Instr::EndBorrow => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndBorrow, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
ir_core::InstrKind::EndBorrow => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndBorrow, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
}
ir_core::Instr::EndMutate => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndMutate, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
ir_core::InstrKind::EndMutate => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndMutate, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
}
ir_core::Instr::GateLoadField { gate, field } => {
ir_core::InstrKind::GateLoadField { gate, field } => {
let offset = program.field_offsets.get(field)
.ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?;
let field_ty = program.field_types.get(field).cloned().unwrap_or(ir_core::Type::Int);
stack_types.push(field_ty.clone());
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, span));
if is_gate_type(&field_ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
}
}
ir_core::Instr::GateStoreField { gate, field, value } => {
ir_core::InstrKind::GateStoreField { gate, field, value } => {
let offset = program.field_offsets.get(field)
.ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?;
@ -295,32 +296,32 @@ pub fn lower_function(
// 1. Release old value in HIP if it was a gate
if is_gate_type(&field_ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, span));
}
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: value.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: value.0 }, span));
// 2. Retain new value if it's a gate
if let Some(val_ty) = local_types.get(&value.0) {
if is_gate_type(val_ty) {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, span));
}
}
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateStore { offset: *offset }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateStore { offset: *offset }, span));
}
ir_core::Instr::GateLoadIndex { .. } => {
ir_core::InstrKind::GateLoadIndex { .. } => {
anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering");
}
ir_core::Instr::GateStoreIndex { .. } => {
ir_core::InstrKind::GateStoreIndex { .. } => {
anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering");
}
ir_core::Instr::Free => anyhow::bail!("Instruction 'Free' cannot be represented in ir_vm v0"),
ir_core::InstrKind::Free => anyhow::bail!("Instruction 'Free' cannot be represented in ir_vm v0"),
}
}
@ -403,8 +404,8 @@ mod tests {
use super::*;
use crate::ir_core;
use crate::ir_core::ids::{ConstId as CoreConstId, FunctionId};
use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, Program, Terminator};
use crate::ir_vm::*;
use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, InstrKind, Program, Terminator};
use crate::ir_vm::{InstrKind as VmInstrKind, Label};
#[test]
fn test_full_lowering() {
@ -424,15 +425,15 @@ mod tests {
Block {
id: 0,
instrs: vec![
Instr::PushConst(CoreConstId(0)),
Instr::Call(FunctionId(2), 1),
Instr::from(InstrKind::PushConst(CoreConstId(0))),
Instr::from(InstrKind::Call(FunctionId(2), 1)),
],
terminator: Terminator::Jump(1),
},
Block {
id: 1,
instrs: vec![
Instr::HostCall(42, 1),
Instr::from(InstrKind::HostCall(42, 1)),
],
terminator: Terminator::Return,
},
@ -456,34 +457,34 @@ mod tests {
assert_eq!(func.body.len(), 7);
match &func.body[0].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_0"),
VmInstrKind::Label(Label(l)) => assert_eq!(l, "block_0"),
_ => panic!("Expected label block_0"),
}
match &func.body[1].kind {
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
VmInstrKind::PushConst(id) => assert_eq!(id.0, 0),
_ => panic!("Expected PushConst 0"),
}
match &func.body[2].kind {
InstrKind::Call { func_id, arg_count } => {
VmInstrKind::Call { func_id, arg_count } => {
assert_eq!(func_id.0, 2);
assert_eq!(*arg_count, 1);
}
_ => panic!("Expected Call"),
}
match &func.body[3].kind {
InstrKind::Jmp(Label(l)) => assert_eq!(l, "block_1"),
VmInstrKind::Jmp(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected Jmp block_1"),
}
match &func.body[4].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_1"),
VmInstrKind::Label(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected label block_1"),
}
match &func.body[5].kind {
InstrKind::Syscall(id) => assert_eq!(*id, 42),
VmInstrKind::Syscall(id) => assert_eq!(*id, 42),
_ => panic!("Expected HostCall 42"),
}
match &func.body[6].kind {
InstrKind::Ret => (),
VmInstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}
@ -507,8 +508,8 @@ mod tests {
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::GateLoadField { gate: ir_core::ValueId(0), field: field_id },
Instr::GateStoreField { gate: ir_core::ValueId(0), field: field_id, value: ir_core::ValueId(1) },
Instr::from(InstrKind::GateLoadField { gate: ir_core::ValueId(0), field: field_id }),
Instr::from(InstrKind::GateStoreField { gate: ir_core::ValueId(0), field: field_id, value: ir_core::ValueId(1) }),
],
terminator: Terminator::Return,
}],
@ -536,23 +537,23 @@ mod tests {
assert_eq!(func.body.len(), 9);
match &func.body[1].kind {
ir_vm::InstrKind::LocalLoad { slot } => assert_eq!(*slot, 0),
VmInstrKind::LocalLoad { slot } => assert_eq!(*slot, 0),
_ => panic!("Expected LocalLoad 0"),
}
match &func.body[2].kind {
ir_vm::InstrKind::GateRetain => (),
VmInstrKind::GateRetain => (),
_ => panic!("Expected GateRetain"),
}
match &func.body[3].kind {
ir_vm::InstrKind::GateLoad { offset } => assert_eq!(*offset, 100),
VmInstrKind::GateLoad { offset } => assert_eq!(*offset, 100),
_ => panic!("Expected GateLoad 100"),
}
match &func.body[7].kind {
ir_vm::InstrKind::GateStore { offset } => assert_eq!(*offset, 100),
VmInstrKind::GateStore { offset } => assert_eq!(*offset, 100),
_ => panic!("Expected GateStore 100"),
}
match &func.body[8].kind {
ir_vm::InstrKind::Ret => (),
VmInstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}
@ -571,7 +572,7 @@ mod tests {
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::GateLoadField { gate: ir_core::ValueId(0), field: ir_core::FieldId(999) },
Instr::from(InstrKind::GateLoadField { gate: ir_core::ValueId(0), field: ir_core::FieldId(999) }),
],
terminator: Terminator::Return,
}],
@ -610,16 +611,16 @@ mod tests {
id: 0,
instrs: vec![
// 1. allocates a gate
Instr::Alloc { ty: type_id, slots: 1 },
Instr::SetLocal(0), // x = alloc
Instr::from(InstrKind::Alloc { ty: type_id, slots: 1 }),
Instr::from(InstrKind::SetLocal(0)), // x = alloc
// 2. copies it
Instr::GetLocal(0),
Instr::SetLocal(1), // y = x
Instr::from(InstrKind::GetLocal(0)),
Instr::from(InstrKind::SetLocal(1)), // y = x
// 3. overwrites one copy
Instr::PushConst(CoreConstId(0)),
Instr::SetLocal(0), // x = 0 (overwrites gate)
Instr::from(InstrKind::PushConst(CoreConstId(0))),
Instr::from(InstrKind::SetLocal(0)), // x = 0 (overwrites gate)
],
terminator: Terminator::Return,
}],
@ -638,14 +639,14 @@ mod tests {
let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect();
assert!(kinds.contains(&&InstrKind::GateRetain));
assert!(kinds.contains(&&InstrKind::GateRelease));
assert!(kinds.contains(&&VmInstrKind::GateRetain));
assert!(kinds.contains(&&VmInstrKind::GateRelease));
// Check specific sequence for overwrite:
// LocalLoad 0, GateRelease, LocalStore 0
let mut found_overwrite = false;
for i in 0..kinds.len() - 2 {
if let (InstrKind::LocalLoad { slot: 0 }, InstrKind::GateRelease, InstrKind::LocalStore { slot: 0 }) = (kinds[i], kinds[i+1], kinds[i+2]) {
if let (VmInstrKind::LocalLoad { slot: 0 }, VmInstrKind::GateRelease, VmInstrKind::LocalStore { slot: 0 }) = (kinds[i], kinds[i+1], kinds[i+2]) {
found_overwrite = true;
break;
}
@ -656,7 +657,7 @@ mod tests {
// LocalLoad 1, GateRelease, Ret
let mut found_cleanup = false;
for i in 0..kinds.len() - 2 {
if let (InstrKind::LocalLoad { slot: 1 }, InstrKind::GateRelease, InstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2]) {
if let (VmInstrKind::LocalLoad { slot: 1 }, VmInstrKind::GateRelease, VmInstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2]) {
found_cleanup = true;
break;
}
@ -681,10 +682,10 @@ mod tests {
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::PushConst(CoreConstId(0)),
Instr::SetLocal(0), // x = 42
Instr::GetLocal(0),
Instr::Pop,
Instr::from(InstrKind::PushConst(CoreConstId(0))),
Instr::from(InstrKind::SetLocal(0)), // x = 42
Instr::from(InstrKind::GetLocal(0)),
Instr::from(InstrKind::Pop),
],
terminator: Terminator::Return,
}],
@ -703,7 +704,7 @@ mod tests {
for instr in &func.body {
match &instr.kind {
InstrKind::GateRetain | InstrKind::GateRelease => {
VmInstrKind::GateRetain | VmInstrKind::GateRelease => {
panic!("Non-gate program should not contain RC instructions: {:?}", instr);
}
_ => {}
@ -717,8 +718,8 @@ mod tests {
// Since we are using struct variants with mandatory 'offset' field, this is
// enforced by the type system, but we can also check the serialized form.
let instructions = vec![
ir_vm::InstrKind::GateLoad { offset: 123 },
ir_vm::InstrKind::GateStore { offset: 456 },
VmInstrKind::GateLoad { offset: 123 },
VmInstrKind::GateStore { offset: 456 },
];
let json = serde_json::to_string(&instructions).unwrap();
assert!(json.contains("\"GateLoad\":{\"offset\":123}"));

View File

@ -3,6 +3,7 @@ use std::path::PathBuf;
use tempfile::tempdir;
use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata};
use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
use prometeu_compiler::common::files::FileManager;
use prometeu_compiler::deps::resolver::ProjectId;
use prometeu_compiler::semantics::export_surface::ExportSurfaceKind;
use prometeu_compiler::building::output::CompiledModule;
@ -58,7 +59,8 @@ fn test_local_vs_dependency_conflict() {
deps,
};
let result = compile_project(step, &dep_modules);
let mut file_manager = FileManager::new();
let result = compile_project(step, &dep_modules, &mut file_manager);
match result {
Err(CompileError::DuplicateExport { symbol, .. }) => {
@ -136,7 +138,8 @@ fn test_aliased_dependency_conflict() {
deps,
};
let result = compile_project(step, &dep_modules);
let mut file_manager = FileManager::new();
let result = compile_project(step, &dep_modules, &mut file_manager);
match result {
Err(CompileError::DuplicateExport { symbol, .. }) => {
@ -169,7 +172,8 @@ fn test_mixed_main_test_modules() {
deps: BTreeMap::new(),
};
let compiled = compile_project(step, &HashMap::new()).unwrap();
let mut file_manager = FileManager::new();
let compiled = compile_project(step, &HashMap::new(), &mut file_manager).unwrap();
// Both should be in exports with normalized paths
assert!(compiled.exports.keys().any(|k| k.module_path == "math"));
@ -197,7 +201,8 @@ fn test_module_merging_same_directory() {
deps: BTreeMap::new(),
};
let compiled = compile_project(step, &HashMap::new()).unwrap();
let mut file_manager = FileManager::new();
let compiled = compile_project(step, &HashMap::new(), &mut file_manager).unwrap();
// Both should be in the same module "gfx"
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "Gfx"));
@ -225,7 +230,8 @@ fn test_duplicate_symbol_in_same_module_different_files() {
deps: BTreeMap::new(),
};
let result = compile_project(step, &HashMap::new());
let mut file_manager = FileManager::new();
let result = compile_project(step, &HashMap::new(), &mut file_manager);
assert!(result.is_err());
// Should be a frontend error (duplicate symbol)
}
@ -251,7 +257,8 @@ fn test_root_module_merging() {
deps: BTreeMap::new(),
};
let compiled = compile_project(step, &HashMap::new()).unwrap();
let mut file_manager = FileManager::new();
let compiled = compile_project(step, &HashMap::new(), &mut file_manager).unwrap();
// Both should be in the root module ""
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Main"));

View File

@ -1,6 +1,6 @@
use prometeu_compiler::backend::emit_bytecode::emit_module;
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, InstrKind as CoreInstrKind, Program, Terminator};
use prometeu_compiler::ir_vm::InstrKind;
use prometeu_compiler::lowering::lower_program;
use std::collections::HashMap;
@ -37,22 +37,22 @@ fn test_hip_conformance_core_to_vm_to_bytecode() {
id: 0,
instrs: vec![
// allocates a storage struct
Instr::Alloc { ty: type_id, slots: 2 },
Instr::SetLocal(0), // x = alloc
Instr::from(CoreInstrKind::Alloc { ty: type_id, slots: 2 }),
Instr::from(CoreInstrKind::SetLocal(0)), // x = alloc
// mutates a field
Instr::BeginMutate { gate: ValueId(0) },
Instr::PushConst(CoreConstId(0)),
Instr::SetLocal(1), // v = 42
Instr::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) },
Instr::EndMutate,
Instr::from(CoreInstrKind::BeginMutate { gate: ValueId(0) }),
Instr::from(CoreInstrKind::PushConst(CoreConstId(0))),
Instr::from(CoreInstrKind::SetLocal(1)), // v = 42
Instr::from(CoreInstrKind::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) }),
Instr::from(CoreInstrKind::EndMutate),
// peeks value
Instr::BeginPeek { gate: ValueId(0) },
Instr::GateLoadField { gate: ValueId(0), field: field_id },
Instr::EndPeek,
Instr::from(CoreInstrKind::BeginPeek { gate: ValueId(0) }),
Instr::from(CoreInstrKind::GateLoadField { gate: ValueId(0), field: field_id }),
Instr::from(CoreInstrKind::EndPeek),
Instr::Pop, // clean up the peeked value
Instr::from(CoreInstrKind::Pop), // clean up the peeked value
],
terminator: Terminator::Return,
}],