pr 59
This commit is contained in:
parent
f80cb64f66
commit
66a77709f0
@ -50,8 +50,10 @@ pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B;
|
||||
/// Executed RET with an incorrect stack height (mismatch with function metadata).
|
||||
pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Detailed information about a source code span.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct SourceSpan {
|
||||
pub file_id: u32,
|
||||
pub start: u32,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::v0::{BytecodeModule, DebugInfo, ConstantPoolEntry, FunctionMeta};
|
||||
use crate::opcode::OpCode;
|
||||
use crate::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -158,8 +158,8 @@ impl Linker {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::v0::{BytecodeModule, FunctionMeta, Export, Import};
|
||||
use crate::opcode::OpCode;
|
||||
use crate::v0::{BytecodeModule, Export, FunctionMeta, Import};
|
||||
|
||||
#[test]
|
||||
fn test_linker_basic() {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
pub mod linker;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::opcode::OpCode;
|
||||
use crate::abi::SourceSpan;
|
||||
use crate::opcode::OpCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An entry in the Constant Pool.
|
||||
///
|
||||
@ -50,7 +50,7 @@ pub struct FunctionMeta {
|
||||
pub max_stack_slots: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct DebugInfo {
|
||||
pub pc_to_span: Vec<(u32, SourceSpan)>, // Sorted by PC
|
||||
pub function_names: Vec<(u32, String)>, // (func_idx, name)
|
||||
|
||||
@ -9,14 +9,14 @@
|
||||
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::symbols::Symbol;
|
||||
use crate::ir_core::ConstantValue;
|
||||
use crate::ir_vm;
|
||||
use crate::ir_vm::instr::InstrKind;
|
||||
use crate::ir_core::ConstantValue;
|
||||
use anyhow::{anyhow, Result};
|
||||
use prometeu_bytecode::asm::{assemble, update_pc_by_operand, Asm, Operand};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::v0::{BytecodeModule, FunctionMeta, DebugInfo, ConstantPoolEntry};
|
||||
use prometeu_bytecode::abi::SourceSpan;
|
||||
use prometeu_bytecode::asm::{update_pc_by_operand, Asm, Operand};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||
|
||||
/// The final output of the code generation phase.
|
||||
pub struct EmitResult {
|
||||
@ -306,12 +306,12 @@ impl<'a> BytecodeEmitter<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ir_vm::module::{Module, Function};
|
||||
use crate::ir_vm::instr::{Instruction, InstrKind};
|
||||
use crate::ir_vm::types::Type;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_core::const_pool::ConstantValue;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::ir_core::const_pool::ConstantValue;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_vm::instr::{InstrKind, Instruction};
|
||||
use crate::ir_vm::module::{Function, Module};
|
||||
use crate::ir_vm::types::Type;
|
||||
use prometeu_bytecode::v0::{BytecodeLoader, ConstantPoolEntry};
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
pub mod emit_bytecode;
|
||||
pub mod artifacts;
|
||||
|
||||
pub use emit_bytecode::{emit_module, emit_fragments, EmitFragments};
|
||||
pub use artifacts::Artifacts;
|
||||
pub use emit_bytecode::EmitResult;
|
||||
pub use emit_bytecode::{emit_fragments, emit_module, EmitFragments};
|
||||
|
||||
410
crates/prometeu-compiler/src/building/linker.rs
Normal file
410
crates/prometeu-compiler/src/building/linker.rs
Normal file
@ -0,0 +1,410 @@
|
||||
use crate::building::output::{CompiledModule, ExportKey, ImportKey};
|
||||
use crate::building::plan::BuildStep;
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||
use prometeu_core::virtual_machine::{ProgramImage, Value};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum LinkError {
|
||||
UnresolvedSymbol(String),
|
||||
DuplicateExport(String),
|
||||
IncompatibleSymbolSignature(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LinkError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LinkError::UnresolvedSymbol(s) => write!(f, "Unresolved symbol: {}", s),
|
||||
LinkError::DuplicateExport(s) => write!(f, "Duplicate export: {}", s),
|
||||
LinkError::IncompatibleSymbolSignature(s) => write!(f, "Incompatible symbol signature: {}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for LinkError {}
|
||||
|
||||
pub struct Linker;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
struct ConstantPoolBitKey(Vec<u8>);
|
||||
|
||||
impl ConstantPoolBitKey {
|
||||
fn from_entry(entry: &ConstantPoolEntry) -> Self {
|
||||
match entry {
|
||||
ConstantPoolEntry::Null => Self(vec![0]),
|
||||
ConstantPoolEntry::Int64(v) => {
|
||||
let mut b = vec![1];
|
||||
b.extend_from_slice(&v.to_le_bytes());
|
||||
Self(b)
|
||||
}
|
||||
ConstantPoolEntry::Float64(v) => {
|
||||
let mut b = vec![2];
|
||||
b.extend_from_slice(&v.to_bits().to_le_bytes());
|
||||
Self(b)
|
||||
}
|
||||
ConstantPoolEntry::Boolean(v) => {
|
||||
Self(vec![3, if *v { 1 } else { 0 }])
|
||||
}
|
||||
ConstantPoolEntry::String(v) => {
|
||||
let mut b = vec![4];
|
||||
b.extend_from_slice(v.as_bytes());
|
||||
Self(b)
|
||||
}
|
||||
ConstantPoolEntry::Int32(v) => {
|
||||
let mut b = vec![5];
|
||||
b.extend_from_slice(&v.to_le_bytes());
|
||||
Self(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Linker {
|
||||
pub fn link(modules: Vec<CompiledModule>, steps: Vec<BuildStep>) -> Result<ProgramImage, LinkError> {
|
||||
if modules.len() != steps.len() {
|
||||
return Err(LinkError::IncompatibleSymbolSignature(format!("Module count ({}) does not match build steps count ({})", modules.len(), steps.len())));
|
||||
}
|
||||
|
||||
let mut combined_code = Vec::new();
|
||||
let mut combined_functions = Vec::new();
|
||||
let mut combined_constants = Vec::new();
|
||||
let mut constant_map: HashMap<ConstantPoolBitKey, u32> = HashMap::new();
|
||||
|
||||
// Debug info merging
|
||||
let mut combined_pc_to_span = Vec::new();
|
||||
let mut combined_function_names = Vec::new();
|
||||
|
||||
// 1. Symbol resolution map: (ProjectId, module_path, symbol_name) -> func_idx in combined_functions
|
||||
let mut global_symbols = HashMap::new();
|
||||
|
||||
let mut module_code_offsets = Vec::with_capacity(modules.len());
|
||||
let mut module_function_offsets = Vec::with_capacity(modules.len());
|
||||
|
||||
// Map ProjectId to index
|
||||
let _project_to_idx: HashMap<_, _> = modules.iter().enumerate().map(|(i, m)| (m.project_id.clone(), i)).collect();
|
||||
|
||||
// PASS 1: Collect exports and calculate offsets
|
||||
for (_i, module) in modules.iter().enumerate() {
|
||||
let code_offset = combined_code.len() as u32;
|
||||
let function_offset = combined_functions.len() as u32;
|
||||
|
||||
module_code_offsets.push(code_offset);
|
||||
module_function_offsets.push(function_offset);
|
||||
|
||||
for (key, meta) in &module.exports {
|
||||
if let Some(local_func_idx) = meta.func_idx {
|
||||
let global_func_idx = function_offset + local_func_idx;
|
||||
// Note: Use a tuple as key for clarity
|
||||
let symbol_id = (module.project_id.clone(), key.module_path.clone(), key.symbol_name.clone());
|
||||
|
||||
if global_symbols.contains_key(&symbol_id) {
|
||||
return Err(LinkError::DuplicateExport(format!("Project {:?} export {}:{} already defined", symbol_id.0, symbol_id.1, symbol_id.2)));
|
||||
}
|
||||
global_symbols.insert(symbol_id, global_func_idx);
|
||||
}
|
||||
}
|
||||
|
||||
combined_code.extend_from_slice(&module.code);
|
||||
for func in &module.function_metas {
|
||||
let mut relocated = func.clone();
|
||||
relocated.code_offset += code_offset;
|
||||
combined_functions.push(relocated);
|
||||
}
|
||||
|
||||
if let Some(debug) = &module.debug_info {
|
||||
for (pc, span) in &debug.pc_to_span {
|
||||
combined_pc_to_span.push((code_offset + pc, span.clone()));
|
||||
}
|
||||
for (func_idx, name) in &debug.function_names {
|
||||
combined_function_names.push((function_offset + func_idx, name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PASS 2: Relocate constants and patch CALLs
|
||||
for (i, module) in modules.iter().enumerate() {
|
||||
let step = &steps[i];
|
||||
let code_offset = module_code_offsets[i] as usize;
|
||||
|
||||
// Map local constant indices to global constant indices
|
||||
let mut local_to_global_const = Vec::with_capacity(module.const_pool.len());
|
||||
for entry in &module.const_pool {
|
||||
let bit_key = ConstantPoolBitKey::from_entry(entry);
|
||||
if let Some(&global_idx) = constant_map.get(&bit_key) {
|
||||
local_to_global_const.push(global_idx);
|
||||
} else {
|
||||
let global_idx = combined_constants.len() as u32;
|
||||
combined_constants.push(match entry {
|
||||
ConstantPoolEntry::Null => Value::Null,
|
||||
ConstantPoolEntry::Int64(v) => Value::Int64(*v),
|
||||
ConstantPoolEntry::Float64(v) => Value::Float(*v),
|
||||
ConstantPoolEntry::Boolean(v) => Value::Boolean(*v),
|
||||
ConstantPoolEntry::String(v) => Value::String(v.clone()),
|
||||
ConstantPoolEntry::Int32(v) => Value::Int32(*v),
|
||||
});
|
||||
constant_map.insert(bit_key, global_idx);
|
||||
local_to_global_const.push(global_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Patch imports
|
||||
for import in &module.imports {
|
||||
let dep_project_id = if import.key.dep_alias == "self" || import.key.dep_alias.is_empty() {
|
||||
&module.project_id
|
||||
} else {
|
||||
step.deps.get(&import.key.dep_alias)
|
||||
.ok_or_else(|| LinkError::UnresolvedSymbol(format!("Dependency alias '{}' not found in project {:?}", import.key.dep_alias, module.project_id)))?
|
||||
};
|
||||
|
||||
let symbol_id = (dep_project_id.clone(), import.key.module_path.clone(), import.key.symbol_name.clone());
|
||||
let &target_func_idx = global_symbols.get(&symbol_id)
|
||||
.ok_or_else(|| LinkError::UnresolvedSymbol(format!("Symbol '{}:{}' not found in project {:?}", symbol_id.1, symbol_id.2, symbol_id.0)))?;
|
||||
|
||||
for &reloc_pc in &import.relocation_pcs {
|
||||
let absolute_pc = code_offset + reloc_pc as usize;
|
||||
let imm_offset = absolute_pc + 2;
|
||||
if imm_offset + 4 <= combined_code.len() {
|
||||
combined_code[imm_offset..imm_offset+4].copy_from_slice(&target_func_idx.to_le_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal call relocation (from module-local func_idx to global func_idx)
|
||||
// And PUSH_CONST relocation.
|
||||
let mut pos = code_offset;
|
||||
let end = code_offset + module.code.len();
|
||||
while pos < end {
|
||||
if pos + 2 > end { break; }
|
||||
let op_val = u16::from_le_bytes([combined_code[pos], combined_code[pos+1]]);
|
||||
let opcode = match OpCode::try_from(op_val) {
|
||||
Ok(op) => op,
|
||||
Err(_) => {
|
||||
pos += 2;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
pos += 2;
|
||||
|
||||
match opcode {
|
||||
OpCode::PushConst => {
|
||||
if pos + 4 <= end {
|
||||
let local_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap()) as usize;
|
||||
if let Some(&global_idx) = local_to_global_const.get(local_idx) {
|
||||
combined_code[pos..pos+4].copy_from_slice(&global_idx.to_le_bytes());
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
}
|
||||
OpCode::Call => {
|
||||
if pos + 4 <= end {
|
||||
let local_func_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap());
|
||||
|
||||
// Check if this PC was already patched by an import.
|
||||
// If it wasn't, it's an internal call that needs relocation.
|
||||
let reloc_pc = (pos - 2 - code_offset) as u32;
|
||||
let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc));
|
||||
|
||||
if !is_import {
|
||||
let global_func_idx = module_function_offsets[i] + local_func_idx;
|
||||
combined_code[pos..pos+4].copy_from_slice(&global_func_idx.to_le_bytes());
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
}
|
||||
OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue
|
||||
| OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal
|
||||
| OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => {
|
||||
pos += 4;
|
||||
}
|
||||
OpCode::PushI64 | OpCode::PushF64 | OpCode::Alloc => {
|
||||
pos += 8;
|
||||
}
|
||||
OpCode::PushBool => {
|
||||
pos += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Final Exports map for ProgramImage (String -> func_idx)
|
||||
// Only including exports from the ROOT project (the last one in build plan usually)
|
||||
// Wait, the requirement says "emit final PBS v0 image".
|
||||
// In PBS v0, exports are name -> func_id.
|
||||
let mut final_exports = HashMap::new();
|
||||
if let Some(root_module) = modules.last() {
|
||||
for (key, meta) in &root_module.exports {
|
||||
if let Some(local_func_idx) = meta.func_idx {
|
||||
let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx;
|
||||
final_exports.insert(format!("{}:{}", key.module_path, key.symbol_name), global_func_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let combined_debug_info = if combined_pc_to_span.is_empty() && combined_function_names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(DebugInfo {
|
||||
pc_to_span: combined_pc_to_span,
|
||||
function_names: combined_function_names,
|
||||
})
|
||||
};
|
||||
|
||||
Ok(ProgramImage::new(
|
||||
combined_code,
|
||||
combined_constants,
|
||||
combined_functions,
|
||||
combined_debug_info,
|
||||
final_exports,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata};
|
||||
use crate::building::plan::BuildTarget;
|
||||
use crate::deps::resolver::ProjectId;
|
||||
use crate::frontends::pbs::symbols::SymbolKind;
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
|
||||
#[test]
|
||||
fn test_link_root_and_lib() {
|
||||
let lib_id = ProjectId { name: "lib".into(), version: "1.0.0".into() };
|
||||
let root_id = ProjectId { name: "root".into(), version: "1.0.0".into() };
|
||||
|
||||
// Lib module: exports 'add'
|
||||
let mut lib_code = Vec::new();
|
||||
lib_code.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
|
||||
lib_code.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
|
||||
let mut lib_exports = BTreeMap::new();
|
||||
lib_exports.insert(ExportKey {
|
||||
module_path: "math".into(),
|
||||
symbol_name: "add".into(),
|
||||
kind: SymbolKind::Function,
|
||||
}, ExportMetadata { func_idx: Some(0) });
|
||||
|
||||
let lib_module = CompiledModule {
|
||||
project_id: lib_id.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: lib_exports,
|
||||
imports: vec![],
|
||||
const_pool: vec![],
|
||||
code: lib_code,
|
||||
function_metas: vec![FunctionMeta {
|
||||
code_offset: 0,
|
||||
code_len: 4,
|
||||
..Default::default()
|
||||
}],
|
||||
debug_info: None,
|
||||
};
|
||||
|
||||
// Root module: calls 'lib::math:add'
|
||||
let mut root_code = Vec::new();
|
||||
root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
root_code.extend_from_slice(&10i32.to_le_bytes());
|
||||
root_code.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
root_code.extend_from_slice(&20i32.to_le_bytes());
|
||||
// Call lib:math:add
|
||||
let call_pc = root_code.len() as u32;
|
||||
root_code.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
|
||||
root_code.extend_from_slice(&0u32.to_le_bytes()); // placeholder
|
||||
root_code.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
|
||||
let root_imports = vec![ImportMetadata {
|
||||
key: ImportKey {
|
||||
dep_alias: "mylib".into(),
|
||||
module_path: "math".into(),
|
||||
symbol_name: "add".into(),
|
||||
},
|
||||
relocation_pcs: vec![call_pc],
|
||||
}];
|
||||
|
||||
let root_module = CompiledModule {
|
||||
project_id: root_id.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: BTreeMap::new(),
|
||||
imports: root_imports,
|
||||
const_pool: vec![],
|
||||
code: root_code,
|
||||
function_metas: vec![FunctionMeta {
|
||||
code_offset: 0,
|
||||
code_len: 20,
|
||||
..Default::default()
|
||||
}],
|
||||
debug_info: None,
|
||||
};
|
||||
|
||||
let lib_step = BuildStep {
|
||||
project_id: lib_id.clone(),
|
||||
project_dir: "".into(),
|
||||
target: BuildTarget::Main,
|
||||
sources: vec![],
|
||||
deps: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let mut root_deps = BTreeMap::new();
|
||||
root_deps.insert("mylib".into(), lib_id.clone());
|
||||
|
||||
let root_step = BuildStep {
|
||||
project_id: root_id.clone(),
|
||||
project_dir: "".into(),
|
||||
target: BuildTarget::Main,
|
||||
sources: vec![],
|
||||
deps: root_deps,
|
||||
};
|
||||
|
||||
let result = Linker::link(vec![lib_module, root_module], vec![lib_step, root_step]).unwrap();
|
||||
|
||||
assert_eq!(result.functions.len(), 2);
|
||||
// lib:add is func 0
|
||||
// root:main is func 1
|
||||
|
||||
// lib_code length is 4.
|
||||
// Root code starts at 4.
|
||||
// CALL was at root_code offset 12.
|
||||
// Absolute PC of CALL: 4 + 12 = 16.
|
||||
// Immediate is at 16 + 2 = 18.
|
||||
let patched_func_idx = u32::from_le_bytes(result.rom[18..22].try_into().unwrap());
|
||||
assert_eq!(patched_func_idx, 0); // Points to lib:add
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_link_const_deduplication() {
|
||||
let id = ProjectId { name: "test".into(), version: "1.0.0".into() };
|
||||
let step = BuildStep { project_id: id.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() };
|
||||
|
||||
let m1 = CompiledModule {
|
||||
project_id: id.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: BTreeMap::new(),
|
||||
imports: vec![],
|
||||
const_pool: vec![ConstantPoolEntry::Int32(42), ConstantPoolEntry::String("hello".into())],
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
};
|
||||
|
||||
let m2 = CompiledModule {
|
||||
project_id: id.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: BTreeMap::new(),
|
||||
imports: vec![],
|
||||
const_pool: vec![ConstantPoolEntry::String("hello".into()), ConstantPoolEntry::Int32(99)],
|
||||
code: vec![],
|
||||
function_metas: vec![],
|
||||
debug_info: None,
|
||||
};
|
||||
|
||||
let result = Linker::link(vec![m1, m2], vec![step.clone(), step]).unwrap();
|
||||
|
||||
// Constants should be: 42, "hello", 99
|
||||
assert_eq!(result.constant_pool.len(), 3);
|
||||
assert_eq!(result.constant_pool[0], Value::Int32(42));
|
||||
assert_eq!(result.constant_pool[1], Value::String("hello".into()));
|
||||
assert_eq!(result.constant_pool[2], Value::Int32(99));
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,4 @@
|
||||
pub mod plan;
|
||||
pub mod output;
|
||||
pub mod linker;
|
||||
pub mod orchestrator;
|
||||
|
||||
50
crates/prometeu-compiler/src/building/orchestrator.rs
Normal file
50
crates/prometeu-compiler/src/building/orchestrator.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::building::linker::{LinkError, Linker};
|
||||
use crate::building::output::{compile_project, CompileError};
|
||||
use crate::building::plan::{BuildPlan, BuildTarget};
|
||||
use crate::deps::resolver::ResolvedGraph;
|
||||
use prometeu_core::virtual_machine::ProgramImage;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BuildError {
|
||||
Compile(CompileError),
|
||||
Link(LinkError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BuildError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
BuildError::Compile(e) => write!(f, "Compile error: {}", e),
|
||||
BuildError::Link(e) => write!(f, "Link error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for BuildError {}
|
||||
|
||||
impl From<CompileError> for BuildError {
|
||||
fn from(e: CompileError) -> Self {
|
||||
BuildError::Compile(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LinkError> for BuildError {
|
||||
fn from(e: LinkError) -> Self {
|
||||
BuildError::Link(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_from_graph(graph: &ResolvedGraph, target: BuildTarget) -> Result<ProgramImage, BuildError> {
|
||||
let plan = BuildPlan::from_graph(graph, target);
|
||||
let mut compiled_modules = HashMap::new();
|
||||
let mut modules_in_order = Vec::new();
|
||||
|
||||
for step in &plan.steps {
|
||||
let compiled = compile_project(step.clone(), &compiled_modules)?;
|
||||
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)
|
||||
}
|
||||
@ -1,21 +1,21 @@
|
||||
use crate::backend::emit_fragments;
|
||||
use crate::building::plan::{BuildStep, BuildTarget};
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::spans::Span;
|
||||
use crate::deps::resolver::ProjectId;
|
||||
use crate::frontends::pbs::ast::FileNode;
|
||||
use crate::frontends::pbs::collector::SymbolCollector;
|
||||
use crate::frontends::pbs::lowering::Lowerer;
|
||||
use crate::frontends::pbs::parser::Parser;
|
||||
use crate::frontends::pbs::resolver::{ModuleProvider, Resolver};
|
||||
use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility};
|
||||
use crate::frontends::pbs::typecheck::TypeChecker;
|
||||
use crate::lowering::core_to_vm;
|
||||
use prometeu_bytecode::v0::{ConstantPoolEntry, DebugInfo, FunctionMeta};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::PathBuf;
|
||||
use crate::deps::resolver::ProjectId;
|
||||
use crate::building::plan::{BuildStep, BuildTarget};
|
||||
use crate::frontends::pbs::symbols::{SymbolKind, ModuleSymbols, Visibility, Symbol, SymbolTable, Namespace};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::parser::Parser;
|
||||
use crate::frontends::pbs::collector::SymbolCollector;
|
||||
use crate::frontends::pbs::resolver::{Resolver, ModuleProvider};
|
||||
use crate::frontends::pbs::typecheck::TypeChecker;
|
||||
use crate::frontends::pbs::lowering::Lowerer;
|
||||
use crate::frontends::pbs::ast::FileNode;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
use crate::lowering::core_to_vm;
|
||||
use crate::backend::emit_fragments;
|
||||
use prometeu_bytecode::v0::{ConstantPoolEntry, FunctionMeta};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ExportKey {
|
||||
@ -52,6 +52,7 @@ pub struct CompiledModule {
|
||||
pub const_pool: Vec<ConstantPoolEntry>,
|
||||
pub code: Vec<u8>,
|
||||
pub function_metas: Vec<FunctionMeta>,
|
||||
pub debug_info: Option<DebugInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -117,8 +118,8 @@ pub fn compile_project(
|
||||
let (ts, vs) = collector.collect(&ast)?;
|
||||
|
||||
let module_path = source_rel.parent()
|
||||
.and_then(|p| p.to_str())
|
||||
.unwrap_or("")
|
||||
.unwrap_or(Path::new(""))
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
|
||||
let ms = module_symbols_map.entry(module_path.clone()).or_insert_with(ModuleSymbols::new);
|
||||
@ -150,28 +151,36 @@ pub fn compile_project(
|
||||
for (alias, project_id) in &step.deps {
|
||||
if let Some(compiled) = dep_modules.get(project_id) {
|
||||
for (key, _) in &compiled.exports {
|
||||
let synthetic_module_path = format!("@{}:{}", alias, key.module_path);
|
||||
let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new);
|
||||
// Support syntax: "alias/module" and "@alias:module"
|
||||
let key_module_path = key.module_path.replace("src/main/modules/", "");
|
||||
let synthetic_paths = [
|
||||
format!("{}/{}", alias, key_module_path),
|
||||
format!("@{}:{}", alias, key_module_path),
|
||||
];
|
||||
|
||||
let sym = Symbol {
|
||||
name: key.symbol_name.clone(),
|
||||
kind: key.kind.clone(),
|
||||
namespace: match key.kind {
|
||||
SymbolKind::Function | SymbolKind::Service => Namespace::Value,
|
||||
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type,
|
||||
_ => Namespace::Value,
|
||||
},
|
||||
visibility: Visibility::Pub,
|
||||
ty: None,
|
||||
is_host: false,
|
||||
span: Span::new(0, 0, 0),
|
||||
origin: Some(synthetic_module_path.clone()),
|
||||
};
|
||||
for synthetic_module_path in synthetic_paths {
|
||||
let ms = all_visible_modules.entry(synthetic_module_path.clone()).or_insert_with(ModuleSymbols::new);
|
||||
|
||||
if sym.namespace == Namespace::Type {
|
||||
ms.type_symbols.insert(sym).ok();
|
||||
} else {
|
||||
ms.value_symbols.insert(sym).ok();
|
||||
let sym = Symbol {
|
||||
name: key.symbol_name.clone(),
|
||||
kind: key.kind.clone(),
|
||||
namespace: match key.kind {
|
||||
SymbolKind::Function | SymbolKind::Service => Namespace::Value,
|
||||
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type,
|
||||
_ => Namespace::Value,
|
||||
},
|
||||
visibility: Visibility::Pub,
|
||||
ty: None,
|
||||
is_host: false,
|
||||
span: Span::new(0, 0, 0),
|
||||
origin: Some(synthetic_module_path.clone()),
|
||||
};
|
||||
|
||||
if sym.namespace == Namespace::Type {
|
||||
ms.type_symbols.insert(sym).ok();
|
||||
} else {
|
||||
ms.value_symbols.insert(sym).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,14 +298,16 @@ pub fn compile_project(
|
||||
const_pool: fragments.const_pool,
|
||||
code: fragments.code,
|
||||
function_metas: fragments.functions,
|
||||
debug_info: fragments.debug_info,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_compile_root_only_project() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::deps::resolver::{ProjectId, ResolvedGraph};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::PathBuf;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::deps::resolver::{ProjectId, ResolvedGraph};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@ -131,9 +131,9 @@ impl PartialOrd for ReverseProjectId {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::deps::resolver::{ProjectId, ResolvedNode, ResolvedEdge, ResolvedGraph};
|
||||
use crate::sources::ProjectSources;
|
||||
use crate::deps::resolver::{ProjectId, ResolvedEdge, ResolvedGraph, ResolvedNode};
|
||||
use crate::manifest::Manifest;
|
||||
use crate::sources::ProjectSources;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn mock_node(name: &str, version: &str) -> ResolvedNode {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::manifest::Manifest;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
use crate::manifest::Manifest;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ProjectConfig {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::spans::Span;
|
||||
use serde::{Serialize, Serializer};
|
||||
use crate::common::files::FileManager;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DiagnosticLevel {
|
||||
@ -112,9 +112,9 @@ impl From<Diagnostic> for DiagnosticBundle {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::files::FileManager;
|
||||
use crate::frontends::pbs::PbsFrontend;
|
||||
use crate::frontends::Frontend;
|
||||
use crate::common::files::FileManager;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
||||
@ -5,11 +5,9 @@
|
||||
|
||||
use crate::backend;
|
||||
use crate::common::config::ProjectConfig;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::symbols::Symbol;
|
||||
use crate::frontends::Frontend;
|
||||
use crate::ir_vm;
|
||||
use anyhow::Result;
|
||||
use prometeu_bytecode::v0::BytecodeModule;
|
||||
use std::path::Path;
|
||||
|
||||
/// The result of a successful compilation process.
|
||||
@ -38,77 +36,50 @@ impl CompilationUnit {
|
||||
}
|
||||
}
|
||||
|
||||
/// Orchestrates the compilation of a Prometeu project starting from an entry file.
|
||||
///
|
||||
/// This function executes the full compiler pipeline:
|
||||
/// 1. **Frontend**: Loads and parses the entry file (and its dependencies).
|
||||
/// Currently, it uses the `TypescriptFrontend`.
|
||||
/// 2. **IR Generation**: The frontend produces a high-level Intermediate Representation (IR).
|
||||
/// 3. **Validation**: Checks the IR for consistency and VM compatibility.
|
||||
/// 4. **Backend**: Lowers the IR into final Prometeu ByteCode.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if parsing fails, validation finds issues, or code generation fails.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use std::path::Path;
|
||||
/// let project_dir = Path::new(".");
|
||||
/// let unit = prometeu_compiler::compiler::compile(project_dir).expect("Failed to compile");
|
||||
/// unit.export(Path::new("build/program.pbc"), true, true).unwrap();
|
||||
/// ```
|
||||
|
||||
pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
|
||||
let config = ProjectConfig::load(project_dir)?;
|
||||
|
||||
// 1. Select Frontend
|
||||
// The _frontend is responsible for parsing source code and producing the IR.
|
||||
let _frontend: Box<dyn Frontend> = match config.script_fe.as_str() {
|
||||
"pbs" => Box::new(crate::frontends::pbs::PbsFrontend),
|
||||
_ => anyhow::bail!("Invalid frontend: {}", config.script_fe),
|
||||
};
|
||||
if config.script_fe == "pbs" {
|
||||
let graph = crate::deps::resolver::resolve_graph(project_dir)
|
||||
.map_err(|e| anyhow::anyhow!("Dependency resolution failed: {}", e))?;
|
||||
|
||||
#[allow(unreachable_code, unused_variables, unused_mut)]
|
||||
{
|
||||
let entry = project_dir.join(&config.entry);
|
||||
let mut file_manager = FileManager::new();
|
||||
let program_image = crate::building::orchestrator::build_from_graph(&graph, crate::building::plan::BuildTarget::Main)
|
||||
.map_err(|e| anyhow::anyhow!("Build failed: {}", e))?;
|
||||
|
||||
// 2. Compile to IR (Intermediate Representation)
|
||||
// This step abstracts away source-specific syntax (like TypeScript) into a
|
||||
// generic set of instructions that the backend can understand.
|
||||
let ir_module = _frontend.compile_to_ir(&entry, &mut file_manager)
|
||||
.map_err(|bundle| {
|
||||
if let Some(diag) = bundle.diagnostics.first() {
|
||||
anyhow::anyhow!("{}", diag.message)
|
||||
} else {
|
||||
anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len())
|
||||
}
|
||||
})?;
|
||||
let module = BytecodeModule::from(program_image.clone());
|
||||
let rom = module.serialize();
|
||||
|
||||
// 3. IR Validation
|
||||
// Ensures the generated IR is sound and doesn't violate any VM constraints
|
||||
// before we spend time generating bytecode.
|
||||
ir_vm::validate::validate_module(&ir_module)
|
||||
.map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?;
|
||||
|
||||
// 4. Emit Bytecode
|
||||
// The backend takes the validated IR and produces the final binary executable.
|
||||
let result = backend::emit_module(&ir_module, &file_manager)?;
|
||||
let mut symbols = Vec::new();
|
||||
if let Some(debug) = &program_image.debug_info {
|
||||
for (pc, span) in &debug.pc_to_span {
|
||||
symbols.push(Symbol {
|
||||
pc: *pc,
|
||||
file: format!("file_{}", span.file_id),
|
||||
line: 0,
|
||||
col: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CompilationUnit {
|
||||
rom: result.rom,
|
||||
symbols: result.symbols,
|
||||
rom,
|
||||
symbols,
|
||||
})
|
||||
} else {
|
||||
anyhow::bail!("Invalid frontend: {}", config.script_fe)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
use prometeu_bytecode::v0::BytecodeLoader;
|
||||
use crate::ir_vm;
|
||||
use prometeu_bytecode::disasm::disasm;
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::v0::BytecodeLoader;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_invalid_frontend() {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use anyhow::Result;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CacheManifest {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use crate::deps::cache::{get_cache_root, get_git_worktree_path, CacheManifest, GitCacheEntry};
|
||||
use crate::manifest::DependencySpec;
|
||||
use crate::deps::cache::{CacheManifest, get_cache_root, get_git_worktree_path, GitCacheEntry};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FetchError {
|
||||
@ -141,8 +141,8 @@ pub fn fetch_git(url: &str, version: &str, root_project_dir: &Path) -> Result<Pa
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_fetch_path_resolves_relative() {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::deps::fetch::{fetch_dependency, FetchError};
|
||||
use crate::manifest::{load_manifest, Manifest};
|
||||
use crate::sources::{discover, ProjectSources, SourceError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::manifest::{Manifest, load_manifest};
|
||||
use crate::deps::fetch::{fetch_dependency, FetchError};
|
||||
use crate::sources::{ProjectSources, discover, SourceError};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ProjectId {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::frontends::pbs::types::PbsType;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ContractMethod {
|
||||
pub id: u32,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::common::spans::Span;
|
||||
use super::token::{Token, TokenKind};
|
||||
use std::str::Chars;
|
||||
use crate::common::spans::Span;
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
chars: Peekable<Chars<'a>>,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
||||
use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
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};
|
||||
@ -1088,8 +1088,8 @@ impl<'a> Lowerer<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::frontends::pbs::parser::Parser;
|
||||
use crate::frontends::pbs::collector::SymbolCollector;
|
||||
use crate::frontends::pbs::parser::Parser;
|
||||
use crate::frontends::pbs::symbols::ModuleSymbols;
|
||||
use crate::ir_core;
|
||||
|
||||
|
||||
@ -10,13 +10,13 @@ pub mod typecheck;
|
||||
pub mod lowering;
|
||||
pub mod contracts;
|
||||
|
||||
pub use lexer::Lexer;
|
||||
pub use token::{Token, TokenKind};
|
||||
pub use symbols::{Symbol, SymbolTable, ModuleSymbols, Visibility, SymbolKind, Namespace};
|
||||
pub use collector::SymbolCollector;
|
||||
pub use resolver::{Resolver, ModuleProvider};
|
||||
pub use typecheck::TypeChecker;
|
||||
pub use lexer::Lexer;
|
||||
pub use lowering::Lowerer;
|
||||
pub use resolver::{ModuleProvider, Resolver};
|
||||
pub use symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, SymbolTable, Visibility};
|
||||
pub use token::{Token, TokenKind};
|
||||
pub use typecheck::TypeChecker;
|
||||
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
use crate::common::files::FileManager;
|
||||
|
||||
@ -108,24 +108,47 @@ impl Parser {
|
||||
fn parse_import_spec(&mut self) -> Result<Node, DiagnosticBundle> {
|
||||
let mut path = Vec::new();
|
||||
let start_span = self.peek().span;
|
||||
loop {
|
||||
if let TokenKind::Identifier(ref name) = self.peek().kind {
|
||||
path.push(name.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(self.error("Expected identifier in import spec"));
|
||||
}
|
||||
|
||||
if self.peek().kind == TokenKind::Dot {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
if self.peek().kind == TokenKind::OpenBrace {
|
||||
self.advance(); // {
|
||||
loop {
|
||||
if let TokenKind::Identifier(ref name) = self.peek().kind {
|
||||
path.push(name.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(self.error("Expected identifier in import spec"));
|
||||
}
|
||||
|
||||
if self.peek().kind == TokenKind::Comma {
|
||||
self.advance();
|
||||
} else if self.peek().kind == TokenKind::CloseBrace {
|
||||
break;
|
||||
} else {
|
||||
return Err(self.error("Expected ',' or '}' in import spec"));
|
||||
}
|
||||
}
|
||||
self.consume(TokenKind::CloseBrace)?;
|
||||
} else {
|
||||
loop {
|
||||
if let TokenKind::Identifier(ref name) = self.peek().kind {
|
||||
path.push(name.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(self.error("Expected identifier in import spec"));
|
||||
}
|
||||
|
||||
if self.peek().kind == TokenKind::Dot {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let end_span = self.tokens[self.pos-1].span;
|
||||
|
||||
let end_span = self.tokens[self.pos - 1].span;
|
||||
Ok(Node::ImportSpec(ImportSpecNode {
|
||||
span: Span::new(self.file_id, start_span.start, end_span.end),
|
||||
path
|
||||
path,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -445,9 +445,9 @@ impl<'a> Resolver<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::frontends::pbs::*;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn setup_test(source: &str) -> (ast::FileNode, usize) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::common::spans::Span;
|
||||
use crate::frontends::pbs::types::PbsType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
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::resolver::ModuleProvider;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
use crate::frontends::pbs::types::PbsType;
|
||||
use crate::frontends::pbs::resolver::ModuleProvider;
|
||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct TypeChecker<'a> {
|
||||
@ -835,9 +835,9 @@ impl<'a> TypeChecker<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::files::FileManager;
|
||||
use crate::frontends::pbs::PbsFrontend;
|
||||
use crate::frontends::Frontend;
|
||||
use crate::common::files::FileManager;
|
||||
use std::fs;
|
||||
|
||||
fn check_code(code: &str) -> Result<(), String> {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::instr::Instr;
|
||||
use super::terminator::Terminator;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A basic block in a function's control flow graph.
|
||||
/// Contains a sequence of instructions and ends with a terminator.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::ids::ConstId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents a constant value that can be stored in the constant pool.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::ids::FunctionId;
|
||||
use super::block::Block;
|
||||
use super::ids::FunctionId;
|
||||
use super::types::Type;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Instructions within a basic block.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@ -9,15 +9,15 @@ pub mod instr;
|
||||
pub mod terminator;
|
||||
pub mod validate;
|
||||
|
||||
pub use ids::*;
|
||||
pub use const_pool::*;
|
||||
pub use types::*;
|
||||
pub use program::*;
|
||||
pub use module::*;
|
||||
pub use function::*;
|
||||
pub use block::*;
|
||||
pub use const_pool::*;
|
||||
pub use function::*;
|
||||
pub use ids::*;
|
||||
pub use instr::*;
|
||||
pub use module::*;
|
||||
pub use program::*;
|
||||
pub use terminator::*;
|
||||
pub use types::*;
|
||||
pub use validate::*;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::function::Function;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A module within a program, containing functions and other declarations.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::module::Module;
|
||||
use super::const_pool::ConstPool;
|
||||
use super::ids::FieldId;
|
||||
use super::module::Module;
|
||||
use super::types::Type;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
//! easy to lower into VM-specific bytecode.
|
||||
|
||||
use crate::common::spans::Span;
|
||||
use crate::ir_vm::types::{ConstId, TypeId};
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_vm::types::{ConstId, TypeId};
|
||||
|
||||
/// An `Instruction` combines an instruction's behavior (`kind`) with its
|
||||
/// source code location (`span`) for debugging and error reporting.
|
||||
|
||||
@ -29,15 +29,15 @@ pub mod module;
|
||||
pub mod instr;
|
||||
pub mod validate;
|
||||
|
||||
pub use instr::{Instruction, InstrKind, Label};
|
||||
pub use module::{Module, Function, Global, Param};
|
||||
pub use types::{Type, Value, GateId, ConstId, TypeId};
|
||||
pub use instr::{InstrKind, Instruction, Label};
|
||||
pub use module::{Function, Global, Module, Param};
|
||||
pub use types::{ConstId, GateId, Type, TypeId, Value};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ir_core::ids::{ConstId, FunctionId};
|
||||
use crate::ir_core::const_pool::{ConstPool, ConstantValue};
|
||||
use crate::ir_core::ids::{ConstId, FunctionId};
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
|
||||
@ -4,10 +4,10 @@
|
||||
//! The IR is a higher-level representation of the program than bytecode, but lower
|
||||
//! than the source code AST. It is organized into Modules, Functions, and Globals.
|
||||
|
||||
use crate::ir_vm::instr::Instruction;
|
||||
use crate::ir_vm::types::Type;
|
||||
use crate::ir_core::const_pool::ConstPool;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_vm::instr::Instruction;
|
||||
use crate::ir_vm::types::Type;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A `Module` is the top-level container for a compiled program or library.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::ir_vm;
|
||||
use crate::ir_core;
|
||||
use crate::ir_vm;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -402,8 +402,8 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ir_core;
|
||||
use crate::ir_core::{Block, Instr, Terminator, ConstantValue, Program, ConstPool};
|
||||
use crate::ir_core::ids::{FunctionId, ConstId as CoreConstId};
|
||||
use crate::ir_core::ids::{ConstId as CoreConstId, FunctionId};
|
||||
use crate::ir_core::{Block, ConstPool, ConstantValue, Instr, Program, Terminator};
|
||||
use crate::ir_vm::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@ -188,8 +188,8 @@ fn validate_manifest(manifest: &Manifest, path: &Path) -> Result<(), ManifestErr
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_parse_minimal_manifest() {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::fs;
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::manifest::{load_manifest, ManifestKind};
|
||||
use crate::frontends::pbs::{Symbol, Visibility, parser::Parser, collector::SymbolCollector};
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::diagnostics::DiagnosticBundle;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::frontends::pbs::{collector::SymbolCollector, parser::Parser, Symbol, Visibility};
|
||||
use crate::manifest::{load_manifest, ManifestKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ProjectSources {
|
||||
@ -154,8 +154,8 @@ pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Resul
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::tempdir;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_discover_app_with_main() {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use prometeu_bytecode::disasm::disasm;
|
||||
use prometeu_bytecode::v0::BytecodeLoader;
|
||||
use prometeu_compiler::compiler::compile;
|
||||
use prometeu_compiler::frontends::pbs::ast::Node;
|
||||
use prometeu_compiler::frontends::pbs::parser::Parser;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use prometeu_compiler::compiler::compile;
|
||||
use prometeu_bytecode::v0::BytecodeLoader;
|
||||
use prometeu_bytecode::disasm::disasm;
|
||||
use prometeu_compiler::frontends::pbs::parser::Parser;
|
||||
use prometeu_compiler::frontends::pbs::ast::Node;
|
||||
|
||||
#[test]
|
||||
fn generate_canonical_goldens() {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use prometeu_compiler::ir_core::{self, Program, Block, Instr, Terminator, ConstantValue, ConstPool};
|
||||
use prometeu_compiler::ir_core::ids::{FunctionId, ConstId as CoreConstId, TypeId as CoreTypeId, FieldId, ValueId};
|
||||
use prometeu_compiler::ir_vm::InstrKind;
|
||||
use prometeu_compiler::lowering::lower_program;
|
||||
use prometeu_compiler::backend::emit_bytecode::emit_module;
|
||||
use prometeu_compiler::common::files::FileManager;
|
||||
use prometeu_compiler::ir_core::ids::{ConstId as CoreConstId, FieldId, FunctionId, TypeId as CoreTypeId, ValueId};
|
||||
use prometeu_compiler::ir_core::{self, Block, ConstPool, ConstantValue, Instr, Program, Terminator};
|
||||
use prometeu_compiler::ir_vm::InstrKind;
|
||||
use prometeu_compiler::lowering::lower_program;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
|
||||
82
crates/prometeu-compiler/tests/link_integration.rs
Normal file
82
crates/prometeu-compiler/tests/link_integration.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use prometeu_compiler::compiler::compile;
|
||||
use prometeu_core::hardware::{AssetManager, Audio, Gfx, HardwareBridge, MemoryBanks, Pad, Touch};
|
||||
use prometeu_core::virtual_machine::{HostReturn, LogicalFrameEndingReason, NativeInterface, Value, VirtualMachine, VmFault};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct SimpleNative;
|
||||
impl NativeInterface for SimpleNative {
|
||||
fn syscall(&mut self, _id: u32, _args: &[Value], _ret: &mut HostReturn, _hw: &mut dyn HardwareBridge) -> Result<(), VmFault> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleHardware {
|
||||
gfx: Gfx,
|
||||
audio: Audio,
|
||||
pad: Pad,
|
||||
touch: Touch,
|
||||
assets: AssetManager,
|
||||
}
|
||||
|
||||
impl SimpleHardware {
|
||||
fn new() -> Self {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
Self {
|
||||
gfx: Gfx::new(320, 240, banks.clone()),
|
||||
audio: Audio::new(banks.clone()),
|
||||
pad: Pad::default(),
|
||||
touch: Touch::default(),
|
||||
assets: AssetManager::new(vec![], vec![], banks.clone(), banks.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareBridge for SimpleHardware {
|
||||
fn gfx(&self) -> &Gfx { &self.gfx }
|
||||
fn gfx_mut(&mut self) -> &mut Gfx { &mut self.gfx }
|
||||
fn audio(&self) -> &Audio { &self.audio }
|
||||
fn audio_mut(&mut self) -> &mut Audio { &mut self.audio }
|
||||
fn pad(&self) -> &Pad { &self.pad }
|
||||
fn pad_mut(&mut self) -> &mut Pad { &mut self.pad }
|
||||
fn touch(&self) -> &Touch { &self.touch }
|
||||
fn touch_mut(&mut self) -> &mut Touch { &mut self.touch }
|
||||
fn assets(&self) -> &AssetManager { &self.assets }
|
||||
fn assets_mut(&mut self) -> &mut AssetManager { &mut self.assets }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integration_test01_link() {
|
||||
let project_dir = PathBuf::from("../../test-cartridges/test01");
|
||||
// Since the test runs from crates/prometeu-compiler, we need to adjust path if necessary.
|
||||
// Actually, usually tests run from the workspace root if using cargo test --workspace,
|
||||
// but if running from the crate dir, it's different.
|
||||
|
||||
// Let's try absolute path or relative to project root.
|
||||
let mut root_dir = std::env::current_dir().unwrap();
|
||||
while !root_dir.join("test-cartridges").exists() {
|
||||
if let Some(parent) = root_dir.parent() {
|
||||
root_dir = parent.to_path_buf();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let _project_dir = root_dir.join("test-cartridges/test01");
|
||||
|
||||
let unit = compile(&project_dir).expect("Failed to compile and link");
|
||||
|
||||
let mut vm = VirtualMachine::default();
|
||||
// Use initialize to load the ROM and resolve entrypoint
|
||||
vm.initialize(unit.rom, "src/main/modules:frame").expect("Failed to initialize VM");
|
||||
|
||||
let mut native = SimpleNative;
|
||||
let mut hw = SimpleHardware::new();
|
||||
|
||||
// Run for a bit
|
||||
let report = vm.run_budget(1000, &mut native, &mut hw).expect("VM execution failed");
|
||||
|
||||
// It should not trap. test01 might loop or return.
|
||||
if let LogicalFrameEndingReason::Trap(t) = report.reason {
|
||||
panic!("VM trapped: {:?}", t);
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ use crate::log::{LogLevel, LogService, LogSource};
|
||||
use crate::model::{BankType, Cartridge, Color};
|
||||
use crate::prometeu_os::NativeInterface;
|
||||
use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
|
||||
use crate::virtual_machine::{Value, VirtualMachine, HostReturn, SyscallId, VmFault, expect_int, expect_bool};
|
||||
use crate::virtual_machine::{expect_bool, expect_int, HostReturn, SyscallId, Value, VirtualMachine, VmFault};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::virtual_machine::opcode_spec::{OpCodeSpecExt, OpcodeSpec};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use crate::virtual_machine::opcode_spec::{OpcodeSpec, OpCodeSpecExt};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DecodeError {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::virtual_machine::call_frame::CallFrame;
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_INVALID_LOCAL};
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
|
||||
/// Computes the absolute stack index for the start of the current frame's locals (including args).
|
||||
pub fn local_base(frame: &CallFrame) -> usize {
|
||||
|
||||
@ -10,11 +10,11 @@ pub mod verifier;
|
||||
|
||||
use crate::hardware::HardwareBridge;
|
||||
pub use program::ProgramImage;
|
||||
pub use prometeu_bytecode::abi::TrapInfo;
|
||||
pub use prometeu_bytecode::opcode::OpCode;
|
||||
pub use value::Value;
|
||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||
pub use prometeu_bytecode::abi::TrapInfo;
|
||||
pub use verifier::VerifierError;
|
||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||
|
||||
pub type SyscallId = u32;
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::virtual_machine::Value;
|
||||
use prometeu_bytecode::v0::{FunctionMeta, DebugInfo, BytecodeModule, ConstantPoolEntry};
|
||||
use prometeu_bytecode::abi::TrapInfo;
|
||||
use std::sync::Arc;
|
||||
use prometeu_bytecode::v0::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProgramImage {
|
||||
@ -91,3 +91,33 @@ impl From<BytecodeModule> for ProgramImage {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProgramImage> for BytecodeModule {
|
||||
fn from(program: ProgramImage) -> Self {
|
||||
let const_pool = program.constant_pool.iter().map(|v| match v {
|
||||
Value::Null => ConstantPoolEntry::Null,
|
||||
Value::Int64(v) => ConstantPoolEntry::Int64(*v),
|
||||
Value::Float(v) => ConstantPoolEntry::Float64(*v),
|
||||
Value::Boolean(v) => ConstantPoolEntry::Boolean(*v),
|
||||
Value::String(v) => ConstantPoolEntry::String(v.clone()),
|
||||
Value::Int32(v) => ConstantPoolEntry::Int32(*v),
|
||||
Value::Bounded(v) => ConstantPoolEntry::Int32(*v as i32),
|
||||
Value::Gate(_) => ConstantPoolEntry::Null,
|
||||
}).collect();
|
||||
|
||||
let exports = program.exports.iter().map(|(symbol, &func_idx)| Export {
|
||||
symbol: symbol.clone(),
|
||||
func_idx,
|
||||
}).collect();
|
||||
|
||||
BytecodeModule {
|
||||
version: 0,
|
||||
const_pool,
|
||||
functions: program.functions.as_ref().to_vec(),
|
||||
code: program.rom.as_ref().to_vec(),
|
||||
debug_info: program.debug_info.clone(),
|
||||
exports,
|
||||
imports: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
use crate::virtual_machine::bytecode::decoder::{decode_at, DecodeError};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use std::collections::{HashMap, VecDeque, HashSet};
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VerifierError {
|
||||
|
||||
@ -3,8 +3,8 @@ use crate::virtual_machine::call_frame::CallFrame;
|
||||
use crate::virtual_machine::scope_frame::ScopeFrame;
|
||||
use crate::virtual_machine::value::Value;
|
||||
use crate::virtual_machine::{NativeInterface, ProgramImage, VmInitError};
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_OOB, TRAP_TYPE};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB, TRAP_DIV_ZERO, TRAP_TYPE, TRAP_INVALID_FUNC, TRAP_BAD_RET_SLOTS};
|
||||
|
||||
/// Reason why the Virtual Machine stopped execution during a specific run.
|
||||
/// This allows the system to decide if it should continue execution in the next tick
|
||||
@ -914,10 +914,10 @@ impl VirtualMachine {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
use prometeu_bytecode::abi::SourceSpan;
|
||||
use crate::hardware::HardwareBridge;
|
||||
use crate::virtual_machine::{Value, HostReturn, VmFault, expect_int};
|
||||
use crate::virtual_machine::{expect_int, HostReturn, Value, VmFault};
|
||||
use prometeu_bytecode::abi::SourceSpan;
|
||||
use prometeu_bytecode::v0::FunctionMeta;
|
||||
|
||||
struct MockNative;
|
||||
impl NativeInterface for MockNative {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use prometeu_core::virtual_machine::{VirtualMachine, LogicalFrameEndingReason};
|
||||
use prometeu_core::hardware::HardwareBridge;
|
||||
use prometeu_core::Hardware;
|
||||
use prometeu_core::virtual_machine::HostReturn;
|
||||
use prometeu_core::virtual_machine::NativeInterface;
|
||||
use prometeu_core::virtual_machine::Value;
|
||||
use prometeu_core::virtual_machine::HostReturn;
|
||||
use std::path::Path;
|
||||
use prometeu_core::virtual_machine::{LogicalFrameEndingReason, VirtualMachine};
|
||||
use prometeu_core::Hardware;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
struct MockNative;
|
||||
impl NativeInterface for MockNative {
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user