dev/pbs #8
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}],
|
||||
|
||||
@ -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)?;
|
||||
}
|
||||
|
||||
@ -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}"));
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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,
|
||||
}],
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user