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::linker::{LinkError, Linker};
use crate::building::output::{compile_project, CompileError}; use crate::building::output::{compile_project, CompileError};
use crate::building::plan::{BuildPlan, BuildTarget}; use crate::building::plan::{BuildPlan, BuildTarget};
use crate::common::files::FileManager;
use crate::deps::resolver::ResolvedGraph; use crate::deps::resolver::ResolvedGraph;
use prometeu_core::virtual_machine::ProgramImage; use prometeu_core::virtual_machine::ProgramImage;
use std::collections::HashMap; use std::collections::HashMap;
@ -11,6 +12,12 @@ pub enum BuildError {
Link(LinkError), Link(LinkError),
} }
#[derive(Debug, Clone)]
pub struct BuildResult {
pub image: ProgramImage,
pub file_manager: FileManager,
}
impl std::fmt::Display for BuildError { impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { 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 plan = BuildPlan::from_graph(graph, target);
let mut compiled_modules = HashMap::new(); let mut compiled_modules = HashMap::new();
let mut modules_in_order = Vec::new(); let mut modules_in_order = Vec::new();
let mut file_manager = FileManager::new();
for step in &plan.steps { 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()); compiled_modules.insert(step.project_id.clone(), compiled.clone());
modules_in_order.push(compiled); modules_in_order.push(compiled);
} }
let program_image = Linker::link(modules_in_order, plan.steps)?; 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( pub fn compile_project(
step: BuildStep, step: BuildStep,
dep_modules: &HashMap<ProjectId, CompiledModule> dep_modules: &HashMap<ProjectId, CompiledModule>,
file_manager: &mut FileManager,
) -> Result<CompiledModule, CompileError> { ) -> Result<CompiledModule, CompileError> {
let mut file_manager = FileManager::new();
// 1. Parse all files and group symbols by module // 1. Parse all files and group symbols by module
let mut module_symbols_map: HashMap<String, ModuleSymbols> = HashMap::new(); let mut module_symbols_map: HashMap<String, ModuleSymbols> = HashMap::new();
let mut parsed_files: Vec<(String, FileNode)> = Vec::new(); // (module_path, ast) let mut parsed_files: Vec<(String, FileNode)> = Vec::new(); // (module_path, ast)
@ -387,7 +386,8 @@ mod tests {
deps: BTreeMap::new(), 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.project_id, project_id);
assert_eq!(compiled.target, BuildTarget::Main); assert_eq!(compiled.target, BuildTarget::Main);

View File

@ -1,12 +1,14 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct SourceFile { pub struct SourceFile {
pub id: usize, pub id: usize,
pub path: PathBuf, pub path: PathBuf,
pub source: Arc<str>, pub source: Arc<str>,
} }
#[derive(Debug, Clone)]
pub struct FileManager { pub struct FileManager {
files: Vec<SourceFile>, files: Vec<SourceFile>,
} }
@ -57,3 +59,28 @@ impl FileManager {
(line, col) (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 struct Symbol {
pub pc: u32, pub pc: u32,
pub file: String, pub file: String,

View File

@ -5,7 +5,9 @@
use crate::backend; use crate::backend;
use crate::common::config::ProjectConfig; 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 anyhow::Result;
use prometeu_bytecode::BytecodeModule; use prometeu_bytecode::BytecodeModule;
use std::path::Path; use std::path::Path;
@ -20,7 +22,10 @@ pub struct CompilationUnit {
/// The list of debug symbols discovered during compilation. /// The list of debug symbols discovered during compilation.
/// These are used to map bytecode offsets back to source code locations. /// 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 { impl CompilationUnit {
@ -31,7 +36,23 @@ impl CompilationUnit {
/// * `emit_disasm` - If true, a `.disasm` file will be created next to the output. /// * `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. /// * `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<()> { 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) 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 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))?; .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 rom = module.serialize();
let mut symbols = Vec::new(); let mut raw_symbols = Vec::new();
if let Some(debug) = &program_image.debug_info { if let Some(debug) = &build_result.image.debug_info {
for (pc, span) in &debug.pc_to_span { for (pc, span) in &debug.pc_to_span {
symbols.push(Symbol { raw_symbols.push(RawSymbol {
pc: *pc, pc: *pc,
file: format!("file_{}", span.file_id), span: Span {
line: 0, file_id: span.file_id as usize,
col: 0, start: span.start,
end: span.end,
},
}); });
} }
} }
Ok(CompilationUnit { Ok(CompilationUnit {
rom, rom,
symbols, raw_symbols,
file_manager: build_result.file_manager,
}) })
} else { } else {
anyhow::bail!("Invalid frontend: {}", config.script_fe) anyhow::bail!("Invalid frontend: {}", config.script_fe)
@ -303,21 +327,21 @@ mod tests {
id: 0, id: 0,
instrs: vec![ instrs: vec![
// 1. allocates a storage struct // 1. allocates a storage struct
Instr::Alloc { ty: TypeId(1), slots: 2 }, Instr::from(InstrKind::Alloc { ty: TypeId(1), slots: 2 }),
Instr::SetLocal(0), Instr::from(InstrKind::SetLocal(0)),
// 2. mutates a field (offset 0) // 2. mutates a field (offset 0)
Instr::BeginMutate { gate: ValueId(0) }, Instr::from(InstrKind::BeginMutate { gate: ValueId(0) }),
Instr::PushConst(val_42), Instr::from(InstrKind::PushConst(val_42)),
Instr::SetLocal(1), Instr::from(InstrKind::SetLocal(1)),
Instr::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) }, Instr::from(InstrKind::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) }),
Instr::EndMutate, Instr::from(InstrKind::EndMutate),
// 3. peeks value (offset 0) // 3. peeks value (offset 0)
Instr::BeginPeek { gate: ValueId(0) }, Instr::from(InstrKind::BeginPeek { gate: ValueId(0) }),
Instr::GateLoadField { gate: ValueId(0), field: f1 }, Instr::from(InstrKind::GateLoadField { gate: ValueId(0), field: f1 }),
Instr::SetLocal(2), Instr::from(InstrKind::SetLocal(2)),
Instr::EndPeek, Instr::from(InstrKind::EndPeek),
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],
@ -331,7 +355,6 @@ mod tests {
// --- 2. LOWER TO VM IR --- // --- 2. LOWER TO VM IR ---
let vm_module = lower_program(&program).expect("Lowering failed"); let vm_module = lower_program(&program).expect("Lowering failed");
// --- 3. ASSERT VM IR (Instructions + RC) ---
let func = &vm_module.functions[0]; let func = &vm_module.functions[0];
let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); 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()); 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::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::common::spans::Span;
use crate::frontends::pbs::ast::*; use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::contracts::ContractRegistry; use crate::frontends::pbs::contracts::ContractRegistry;
use crate::frontends::pbs::symbols::*; use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::types::PbsType; use crate::frontends::pbs::types::PbsType;
use crate::ir_core; use crate::ir_core;
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId}; use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
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; use std::collections::HashMap;
#[derive(Clone)] #[derive(Clone)]
@ -33,6 +34,7 @@ pub struct Lowerer<'a> {
contract_registry: ContractRegistry, contract_registry: ContractRegistry,
diagnostics: Vec<Diagnostic>, diagnostics: Vec<Diagnostic>,
max_slots_used: u32, max_slots_used: u32,
current_span: Option<Span>,
} }
impl<'a> Lowerer<'a> { impl<'a> Lowerer<'a> {
@ -70,6 +72,7 @@ impl<'a> Lowerer<'a> {
contract_registry: ContractRegistry::new(), contract_registry: ContractRegistry::new(),
diagnostics: Vec::new(), diagnostics: Vec::new(),
max_slots_used: 0, max_slots_used: 0,
current_span: None,
} }
} }
@ -264,28 +267,31 @@ impl<'a> Lowerer<'a> {
} }
fn lower_node(&mut self, node: &Node) -> Result<(), ()> { 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::Block(n) => self.lower_block(n),
Node::LetStmt(n) => self.lower_let_stmt(n), Node::LetStmt(n) => self.lower_let_stmt(n),
Node::ExprStmt(n) => self.lower_node(&n.expr), Node::ExprStmt(n) => self.lower_node(&n.expr),
Node::ReturnStmt(n) => self.lower_return_stmt(n), Node::ReturnStmt(n) => self.lower_return_stmt(n),
Node::IntLit(n) => { Node::IntLit(n) => {
let id = self.program.const_pool.add_int(n.value); let id = self.program.const_pool.add_int(n.value);
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
Ok(()) Ok(())
} }
Node::FloatLit(n) => { Node::FloatLit(n) => {
let id = self.program.const_pool.add_float(n.value); let id = self.program.const_pool.add_float(n.value);
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
Ok(()) Ok(())
} }
Node::StringLit(n) => { Node::StringLit(n) => {
let id = self.program.const_pool.add_string(n.value.clone()); let id = self.program.const_pool.add_string(n.value.clone());
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
Ok(()) Ok(())
} }
Node::BoundedLit(n) => { Node::BoundedLit(n) => {
self.emit(Instr::PushBounded(n.value)); self.emit(InstrKind::PushBounded(n.value));
Ok(()) Ok(())
} }
Node::Ident(n) => self.lower_ident(n), 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()); self.error("E_LOWER_UNSUPPORTED", format!("Lowering for node kind {:?} not supported", node), node.span());
Err(()) Err(())
} }
} };
self.current_span = old_span;
res
} }
fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> { fn lower_alloc(&mut self, n: &AllocNode) -> Result<(), ()> {
let (ty_id, slots) = self.get_type_id_and_slots(&n.ty)?; 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(()) Ok(())
} }
@ -359,22 +368,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); 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 // 3. Begin Operation
self.emit(Instr::BeginPeek { gate: ValueId(gate_slot) }); self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local // 4. Bind view to local
self.local_vars.push(HashMap::new()); self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int); 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 // 5. Body
self.lower_node(&n.body)?; self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndPeek); self.emit(InstrKind::EndPeek);
self.local_vars.pop(); self.local_vars.pop();
Ok(()) Ok(())
@ -386,22 +395,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); 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 // 3. Begin Operation
self.emit(Instr::BeginBorrow { gate: ValueId(gate_slot) }); self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local // 4. Bind view to local
self.local_vars.push(HashMap::new()); self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int); 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 // 5. Body
self.lower_node(&n.body)?; self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndBorrow); self.emit(InstrKind::EndBorrow);
self.local_vars.pop(); self.local_vars.pop();
Ok(()) Ok(())
@ -413,22 +422,22 @@ impl<'a> Lowerer<'a> {
// 2. Preserve gate identity // 2. Preserve gate identity
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int); 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 // 3. Begin Operation
self.emit(Instr::BeginMutate { gate: ValueId(gate_slot) }); self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) });
self.emit(Instr::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) }); self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
// 4. Bind view to local // 4. Bind view to local
self.local_vars.push(HashMap::new()); self.local_vars.push(HashMap::new());
let view_slot = self.add_local_to_scope(n.binding.to_string(), Type::Int); 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 // 5. Body
self.lower_node(&n.body)?; self.lower_node(&n.body)?;
// 6. End Operation // 6. End Operation
self.emit(Instr::EndMutate); self.emit(InstrKind::EndMutate);
self.local_vars.pop(); self.local_vars.pop();
Ok(()) Ok(())
@ -470,7 +479,7 @@ impl<'a> Lowerer<'a> {
let slot = self.add_local_to_scope(n.name.clone(), ty); let slot = self.add_local_to_scope(n.name.clone(), ty);
for i in (0..slots).rev() { for i in (0..slots).rev() {
self.emit(Instr::SetLocal(slot + i)); self.emit(InstrKind::SetLocal(slot + i));
} }
Ok(()) Ok(())
} }
@ -487,7 +496,7 @@ impl<'a> Lowerer<'a> {
if let Some(info) = self.find_local(&n.name) { if let Some(info) = self.find_local(&n.name) {
let slots = self.get_type_slots(&info.ty); let slots = self.get_type_slots(&info.ty);
for i in 0..slots { for i in 0..slots {
self.emit(Instr::GetLocal(info.slot + i)); self.emit(InstrKind::GetLocal(info.slot + i));
} }
Ok(()) Ok(())
} else { } else {
@ -495,18 +504,18 @@ impl<'a> Lowerer<'a> {
match n.name.as_str() { match n.name.as_str() {
"true" => { "true" => {
let id = self.program.const_pool.add_int(1); let id = self.program.const_pool.add_int(1);
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
return Ok(()); return Ok(());
} }
"false" => { "false" => {
let id = self.program.const_pool.add_int(0); let id = self.program.const_pool.add_int(0);
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
return Ok(()); return Ok(());
} }
"none" => { "none" => {
// For now, treat none as 0. This should be refined when optional is fully implemented. // For now, treat none as 0. This should be refined when optional is fully implemented.
let id = self.program.const_pool.add_int(0); let id = self.program.const_pool.add_int(0);
self.emit(Instr::PushConst(id)); self.emit(InstrKind::PushConst(id));
return Ok(()); return Ok(());
} }
_ => {} _ => {}
@ -550,7 +559,7 @@ impl<'a> Lowerer<'a> {
return Ok(()); return Ok(());
} }
}; };
self.emit(Instr::PushBounded(val)); self.emit(InstrKind::PushBounded(val));
return Ok(()); return Ok(());
} }
} }
@ -558,7 +567,7 @@ impl<'a> Lowerer<'a> {
if let Some((slot, ty)) = self.resolve_member_access(n) { if let Some((slot, ty)) = self.resolve_member_access(n) {
let slots = self.get_type_slots(&ty); let slots = self.get_type_slots(&ty);
for i in 0..slots { for i in 0..slots {
self.emit(Instr::GetLocal(slot + i)); self.emit(InstrKind::GetLocal(slot + i));
} }
return Ok(()); return Ok(());
} }
@ -663,7 +672,7 @@ impl<'a> Lowerer<'a> {
self.lower_node(arg)?; self.lower_node(arg)?;
} }
if let Some(func_id) = self.function_ids.get(&id_node.name) { 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(()) Ok(())
} else if let Some(sym) = self.imported_symbols.value_symbols.get(&id_node.name) { } else if let Some(sym) = self.imported_symbols.value_symbols.get(&id_node.name) {
if let Some(origin) = &sym.origin { if let Some(origin) = &sym.origin {
@ -673,7 +682,7 @@ impl<'a> Lowerer<'a> {
if parts.len() == 2 { if parts.len() == 2 {
let dep_alias = parts[0].to_string(); let dep_alias = parts[0].to_string();
let module_path = parts[1].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(()); return Ok(());
} }
} }
@ -739,7 +748,7 @@ impl<'a> Lowerer<'a> {
if let Some(method) = self.contract_registry.get_method(&obj_id.name, &ma.member) { if let Some(method) = self.contract_registry.get_method(&obj_id.name, &ma.member) {
let id = method.id; let id = method.id;
let return_slots = if matches!(method.return_type, PbsType::Void) { 0 } else { 1 }; 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(()); return Ok(());
} }
} }
@ -773,7 +782,7 @@ impl<'a> Lowerer<'a> {
let g6 = (g & 0xFF) >> 2; let g6 = (g & 0xFF) >> 2;
let b5 = (b & 0xFF) >> 3; let b5 = (b & 0xFF) >> 3;
let rgb565 = (r5 << 11) | (g6 << 5) | b5; let rgb565 = (r5 << 11) | (g6 << 5) | b5;
self.emit(Instr::PushBounded(rgb565 as u32)); self.emit(InstrKind::PushBounded(rgb565 as u32));
return Ok(()); return Ok(());
} else { } else {
self.error("E_LOWER_UNSUPPORTED", "Color.rgb only supports literal arguments in this version".to_string(), n.span); 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) { 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 ir_ty = self.convert_pbs_type(&method.return_type);
let return_slots = self.get_type_slots(&ir_ty); 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(()); return Ok(());
} else { } else {
self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span); 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) { fn lower_pad_any(&mut self, base_slot: u32) {
for i in 0..12 { for i in 0..12 {
let btn_base = base_slot + (i * 4); let btn_base = base_slot + (i * 4);
self.emit(Instr::GetLocal(btn_base)); // pressed self.emit(InstrKind::GetLocal(btn_base)); // pressed
self.emit(Instr::GetLocal(btn_base + 1)); // released self.emit(InstrKind::GetLocal(btn_base + 1)); // released
self.emit(Instr::Or); self.emit(InstrKind::Or);
self.emit(Instr::GetLocal(btn_base + 2)); // down self.emit(InstrKind::GetLocal(btn_base + 2)); // down
self.emit(Instr::Or); self.emit(InstrKind::Or);
if i > 0 { 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.left)?;
self.lower_node(&n.right)?; self.lower_node(&n.right)?;
match n.op.as_str() { match n.op.as_str() {
"+" => self.emit(Instr::Add), "+" => self.emit(InstrKind::Add),
"-" => self.emit(Instr::Sub), "-" => self.emit(InstrKind::Sub),
"*" => self.emit(Instr::Mul), "*" => self.emit(InstrKind::Mul),
"/" => self.emit(Instr::Div), "/" => self.emit(InstrKind::Div),
"==" => self.emit(Instr::Eq), "==" => self.emit(InstrKind::Eq),
"!=" => self.emit(Instr::Neq), "!=" => self.emit(InstrKind::Neq),
"<" => self.emit(Instr::Lt), "<" => self.emit(InstrKind::Lt),
"<=" => self.emit(Instr::Lte), "<=" => self.emit(InstrKind::Lte),
">" => self.emit(Instr::Gt), ">" => self.emit(InstrKind::Gt),
">=" => self.emit(Instr::Gte), ">=" => self.emit(InstrKind::Gte),
"&&" => self.emit(Instr::And), "&&" => self.emit(InstrKind::And),
"||" => self.emit(Instr::Or), "||" => self.emit(InstrKind::Or),
_ => { _ => {
self.error("E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), n.span); self.error("E_LOWER_UNSUPPORTED", format!("Binary operator '{}' not supported", n.op), n.span);
return Err(()); return Err(());
@ -912,8 +921,8 @@ impl<'a> Lowerer<'a> {
fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> { fn lower_unary(&mut self, n: &UnaryNode) -> Result<(), ()> {
self.lower_node(&n.expr)?; self.lower_node(&n.expr)?;
match n.op.as_str() { match n.op.as_str() {
"-" => self.emit(Instr::Neg), "-" => self.emit(InstrKind::Neg),
"!" => self.emit(Instr::Not), "!" => self.emit(InstrKind::Not),
_ => { _ => {
self.error("E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), n.span); self.error("E_LOWER_UNSUPPORTED", format!("Unary operator '{}' not supported", n.op), n.span);
return Err(()); return Err(());
@ -1013,9 +1022,9 @@ impl<'a> Lowerer<'a> {
id id
} }
fn emit(&mut self, instr: Instr) { fn emit(&mut self, kind: InstrKind) {
if let Some(block) = &mut self.current_block { 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); assert!(add_func.blocks.len() >= 1);
let first_block = &add_func.blocks[0]; let first_block = &add_func.blocks[0];
// Check for Add instruction // 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] #[test]
@ -1203,9 +1212,9 @@ mod tests {
let func = &program.modules[0].functions[0]; let func = &program.modules[0].functions[0];
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); 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.kind, ir_core::InstrKind::Alloc { .. })));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::BeginMutate { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::EndMutate))); assert!(instrs.iter().any(|i| matches!(i.kind, ir_core::InstrKind::EndMutate)));
} }
#[test] #[test]
@ -1273,14 +1282,14 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
// Assert distinct Core IR instruction sequences // Assert distinct Core IR instruction sequences
assert!(instrs.iter().any(|i| matches!(i, Instr::BeginPeek { .. }))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndPeek))); 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.kind, InstrKind::BeginBorrow { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndBorrow))); 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.kind, InstrKind::BeginMutate { .. })));
assert!(instrs.iter().any(|i| matches!(i, Instr::EndMutate))); assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate)));
} }
#[test] #[test]
@ -1307,9 +1316,9 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
// Gfx.clear -> 0x1010 // 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 // 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] #[test]
@ -1404,7 +1413,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| { 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)) Some((ty, slots))
} else { } else {
None None
@ -1436,7 +1445,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| { 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)) Some((ty, slots))
} else { } else {
None None
@ -1468,7 +1477,7 @@ mod tests {
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect(); let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
let alloc = instrs.iter().find_map(|i| { 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)) Some((ty, slots))
} else { } else {
None None

View File

@ -1,9 +1,22 @@
use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId}; use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId};
use crate::common::spans::Span;
use serde::{Deserialize, Serialize}; 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. /// Instructions within a basic block.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Instr { pub enum InstrKind {
/// Placeholder for constant loading. /// Placeholder for constant loading.
PushConst(ConstId), PushConst(ConstId),
/// Push a bounded value (0..0xFFFF). /// Push a bounded value (0..0xFFFF).
@ -54,3 +67,9 @@ pub enum Instr {
GateStoreIndex { gate: ValueId, index: ValueId, value: ValueId }, GateStoreIndex { gate: ValueId, index: ValueId, value: ValueId },
Free, 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 { blocks: vec![Block {
id: 0, id: 0,
instrs: vec![ instrs: vec![
Instr::PushConst(ConstId(0)), Instr::from(InstrKind::PushConst(ConstId(0))),
Instr::Call(FunctionId(11), 0), Instr::from(InstrKind::Call(FunctionId(11), 0)),
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],
@ -81,13 +81,19 @@ mod tests {
"id": 0, "id": 0,
"instrs": [ "instrs": [
{ {
"kind": {
"PushConst": 0 "PushConst": 0
}, },
"span": null
},
{ {
"kind": {
"Call": [ "Call": [
11, 11,
0 0
] ]
},
"span": null
} }
], ],
"terminator": "Return" "terminator": "Return"

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata}; use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata};
use prometeu_compiler::building::plan::{BuildStep, BuildTarget}; use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
use prometeu_compiler::common::files::FileManager;
use prometeu_compiler::deps::resolver::ProjectId; use prometeu_compiler::deps::resolver::ProjectId;
use prometeu_compiler::semantics::export_surface::ExportSurfaceKind; use prometeu_compiler::semantics::export_surface::ExportSurfaceKind;
use prometeu_compiler::building::output::CompiledModule; use prometeu_compiler::building::output::CompiledModule;
@ -58,7 +59,8 @@ fn test_local_vs_dependency_conflict() {
deps, 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 { match result {
Err(CompileError::DuplicateExport { symbol, .. }) => { Err(CompileError::DuplicateExport { symbol, .. }) => {
@ -136,7 +138,8 @@ fn test_aliased_dependency_conflict() {
deps, 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 { match result {
Err(CompileError::DuplicateExport { symbol, .. }) => { Err(CompileError::DuplicateExport { symbol, .. }) => {
@ -169,7 +172,8 @@ fn test_mixed_main_test_modules() {
deps: BTreeMap::new(), 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 // Both should be in exports with normalized paths
assert!(compiled.exports.keys().any(|k| k.module_path == "math")); assert!(compiled.exports.keys().any(|k| k.module_path == "math"));
@ -197,7 +201,8 @@ fn test_module_merging_same_directory() {
deps: BTreeMap::new(), 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" // Both should be in the same module "gfx"
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && k.symbol_name == "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(), 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()); assert!(result.is_err());
// Should be a frontend error (duplicate symbol) // Should be a frontend error (duplicate symbol)
} }
@ -251,7 +257,8 @@ fn test_root_module_merging() {
deps: BTreeMap::new(), 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 "" // Both should be in the root module ""
assert!(compiled.exports.keys().any(|k| k.module_path == "" && k.symbol_name == "Main")); 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::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::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::ir_vm::InstrKind;
use prometeu_compiler::lowering::lower_program; use prometeu_compiler::lowering::lower_program;
use std::collections::HashMap; use std::collections::HashMap;
@ -37,22 +37,22 @@ fn test_hip_conformance_core_to_vm_to_bytecode() {
id: 0, id: 0,
instrs: vec![ instrs: vec![
// allocates a storage struct // allocates a storage struct
Instr::Alloc { ty: type_id, slots: 2 }, Instr::from(CoreInstrKind::Alloc { ty: type_id, slots: 2 }),
Instr::SetLocal(0), // x = alloc Instr::from(CoreInstrKind::SetLocal(0)), // x = alloc
// mutates a field // mutates a field
Instr::BeginMutate { gate: ValueId(0) }, Instr::from(CoreInstrKind::BeginMutate { gate: ValueId(0) }),
Instr::PushConst(CoreConstId(0)), Instr::from(CoreInstrKind::PushConst(CoreConstId(0))),
Instr::SetLocal(1), // v = 42 Instr::from(CoreInstrKind::SetLocal(1)), // v = 42
Instr::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) }, Instr::from(CoreInstrKind::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) }),
Instr::EndMutate, Instr::from(CoreInstrKind::EndMutate),
// peeks value // peeks value
Instr::BeginPeek { gate: ValueId(0) }, Instr::from(CoreInstrKind::BeginPeek { gate: ValueId(0) }),
Instr::GateLoadField { gate: ValueId(0), field: field_id }, Instr::from(CoreInstrKind::GateLoadField { gate: ValueId(0), field: field_id }),
Instr::EndPeek, Instr::from(CoreInstrKind::EndPeek),
Instr::Pop, // clean up the peeked value Instr::from(CoreInstrKind::Pop), // clean up the peeked value
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],