pr 04
This commit is contained in:
parent
845fc36bd2
commit
8240785160
@ -79,6 +79,12 @@ impl<'a> BytecodeEmitter<'a> {
|
||||
mapped_const_ids.push(self.add_ir_constant(val));
|
||||
}
|
||||
|
||||
// Map FunctionIds to names for call resolution
|
||||
let mut func_names = std::collections::HashMap::new();
|
||||
for func in &module.functions {
|
||||
func_names.insert(func.id, func.name.clone());
|
||||
}
|
||||
|
||||
// --- PHASE 1: Lowering IR to Assembly-like structures ---
|
||||
for function in &module.functions {
|
||||
// Each function starts with a label for its entry point.
|
||||
@ -146,7 +152,8 @@ impl<'a> BytecodeEmitter<'a> {
|
||||
InstrKind::Label(label) => {
|
||||
asm_instrs.push(Asm::Label(label.0.clone()));
|
||||
}
|
||||
InstrKind::Call { name, arg_count } => {
|
||||
InstrKind::Call { func_id, arg_count } => {
|
||||
let name = func_names.get(func_id).ok_or_else(|| anyhow!("Undefined function ID: {:?}", func_id))?;
|
||||
asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(name.clone()), Operand::U32(*arg_count)]));
|
||||
}
|
||||
InstrKind::Ret => asm_instrs.push(Asm::Op(OpCode::Ret, vec![])),
|
||||
@ -154,8 +161,6 @@ impl<'a> BytecodeEmitter<'a> {
|
||||
asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)]));
|
||||
}
|
||||
InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])),
|
||||
InstrKind::PushScope => asm_instrs.push(Asm::Op(OpCode::PushScope, vec![])),
|
||||
InstrKind::PopScope => asm_instrs.push(Asm::Op(OpCode::PopScope, vec![])),
|
||||
}
|
||||
|
||||
let end_idx = asm_instrs.len();
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
use crate::ir;
|
||||
use crate::ir_core;
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn lower_program(program: &ir_core::Program) -> Result<ir::Module> {
|
||||
// For now, we just lower the first module as a smoke test
|
||||
if let Some(core_module) = program.modules.first() {
|
||||
let mut vm_module = ir::Module::new(core_module.name.clone());
|
||||
vm_module.const_pool = program.const_pool.clone();
|
||||
|
||||
for core_func in &core_module.functions {
|
||||
let mut vm_func = ir::Function {
|
||||
id: core_func.id,
|
||||
name: core_func.name.clone(),
|
||||
params: vec![], // TODO: lower params
|
||||
return_type: ir::Type::Null, // TODO: lower return type
|
||||
body: vec![],
|
||||
};
|
||||
|
||||
for block in &core_func.blocks {
|
||||
// Label for the block
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Label(ir::Label(format!("block_{}", block.id))),
|
||||
None,
|
||||
));
|
||||
|
||||
for instr in &block.instrs {
|
||||
match instr {
|
||||
ir_core::Instr::PushConst(id) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::PushConst(*id),
|
||||
None,
|
||||
));
|
||||
}
|
||||
ir_core::Instr::Call(id) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Call { func_id: *id, arg_count: 0 },
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &block.terminator {
|
||||
ir_core::Terminator::Return => {
|
||||
vm_func.body.push(ir::Instruction::new(ir::InstrKind::Ret, None));
|
||||
}
|
||||
ir_core::Terminator::Jump(target) => {
|
||||
vm_func.body.push(ir::Instruction::new(
|
||||
ir::InstrKind::Jmp(ir::Label(format!("block_{}", target))),
|
||||
None,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
vm_module.functions.push(vm_func);
|
||||
}
|
||||
Ok(vm_module)
|
||||
} else {
|
||||
anyhow::bail!("No modules in core program")
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Span {
|
||||
pub file_id: usize,
|
||||
pub start: u32,
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
//! easy to lower into VM-specific bytecode.
|
||||
|
||||
use crate::common::spans::Span;
|
||||
use crate::ir_core::ids::ConstId;
|
||||
use crate::ir_core::ids::{ConstId, FunctionId};
|
||||
|
||||
/// An `Instruction` combines an instruction's behavior (`kind`) with its
|
||||
/// source code location (`span`) for debugging and error reporting.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Instruction {
|
||||
pub kind: InstrKind,
|
||||
/// The location in the original source code that generated this instruction.
|
||||
@ -25,13 +25,13 @@ impl Instruction {
|
||||
|
||||
/// A `Label` represents a destination for a jump instruction.
|
||||
/// During the assembly phase, labels are resolved into actual memory offsets.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Label(pub String);
|
||||
|
||||
/// The various types of operations that can be performed in the IR.
|
||||
///
|
||||
/// The IR uses a stack-based model, similar to the final Prometeu ByteCode.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum InstrKind {
|
||||
/// Does nothing.
|
||||
Nop,
|
||||
@ -124,9 +124,9 @@ pub enum InstrKind {
|
||||
JmpIfFalse(Label),
|
||||
/// Defines a location that can be jumped to. Does not emit code by itself.
|
||||
Label(Label),
|
||||
/// Calls a function by name with the specified number of arguments.
|
||||
/// Calls a function by ID with the specified number of arguments.
|
||||
/// Arguments should be pushed onto the stack before calling.
|
||||
Call { name: String, arg_count: u32 },
|
||||
Call { func_id: FunctionId, arg_count: u32 },
|
||||
/// Returns from the current function. The return value (if any) should be on top of the stack.
|
||||
Ret,
|
||||
|
||||
@ -136,9 +136,4 @@ pub enum InstrKind {
|
||||
Syscall(u32),
|
||||
/// Special instruction to synchronize with the hardware frame clock.
|
||||
FrameSync,
|
||||
|
||||
/// Internal: Pushes a new lexical scope (used for variable resolution).
|
||||
PushScope,
|
||||
/// Internal: Pops the current lexical scope.
|
||||
PopScope,
|
||||
}
|
||||
|
||||
@ -3,6 +3,6 @@ pub mod module;
|
||||
pub mod instr;
|
||||
pub mod validate;
|
||||
|
||||
pub use instr::Instruction;
|
||||
pub use module::Module;
|
||||
pub use instr::{Instruction, InstrKind, Label};
|
||||
pub use module::{Module, Function, Global, Param};
|
||||
pub use types::Type;
|
||||
|
||||
@ -7,10 +7,12 @@
|
||||
use crate::ir::instr::Instruction;
|
||||
use crate::ir::types::Type;
|
||||
use crate::ir_core::const_pool::ConstPool;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A `Module` is the top-level container for a compiled program or library.
|
||||
/// It contains a collection of global variables, functions, and a constant pool.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Module {
|
||||
/// The name of the module (usually derived from the project name).
|
||||
pub name: String,
|
||||
@ -26,8 +28,10 @@ pub struct Module {
|
||||
///
|
||||
/// Functions consist of a signature (name, parameters, return type) and a body
|
||||
/// which is a flat list of IR instructions.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Function {
|
||||
/// The unique identifier of the function.
|
||||
pub id: FunctionId,
|
||||
/// The unique name of the function.
|
||||
pub name: String,
|
||||
/// The list of input parameters.
|
||||
@ -39,7 +43,7 @@ pub struct Function {
|
||||
}
|
||||
|
||||
/// A parameter passed to a function.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Param {
|
||||
/// The name of the parameter (useful for debugging and symbols).
|
||||
pub name: String,
|
||||
@ -48,7 +52,7 @@ pub struct Param {
|
||||
}
|
||||
|
||||
/// A global variable accessible by any function in the module.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Global {
|
||||
/// The name of the global variable.
|
||||
pub name: String,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
Null,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use prometeu_compiler::ir::module::{Module, Function};
|
||||
use prometeu_compiler::ir::instr::{Instruction, InstrKind};
|
||||
use prometeu_compiler::ir::types::Type;
|
||||
use prometeu_compiler::ir_core::ids::FunctionId;
|
||||
use prometeu_compiler::ir_core::const_pool::ConstantValue;
|
||||
use prometeu_compiler::backend::emit_module;
|
||||
use prometeu_compiler::common::files::FileManager;
|
||||
@ -16,6 +17,7 @@ fn test_emit_module_with_const_pool() {
|
||||
let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string()));
|
||||
|
||||
let function = Function {
|
||||
id: FunctionId(0),
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: Type::Void,
|
||||
|
||||
122
crates/prometeu-compiler/tests/vm_ir_tests.rs
Normal file
122
crates/prometeu-compiler/tests/vm_ir_tests.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use prometeu_compiler::ir::*;
|
||||
use prometeu_compiler::ir_core::ids::{ConstId, FunctionId};
|
||||
use prometeu_compiler::ir_core::const_pool::{ConstPool, ConstantValue};
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
fn test_vm_ir_serialization() {
|
||||
let mut const_pool = ConstPool::new();
|
||||
const_pool.insert(ConstantValue::String("Hello VM".to_string()));
|
||||
|
||||
let module = Module {
|
||||
name: "test_module".to_string(),
|
||||
const_pool,
|
||||
functions: vec![Function {
|
||||
id: FunctionId(1),
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
return_type: Type::Null,
|
||||
body: vec![
|
||||
Instruction::new(InstrKind::PushConst(ConstId(0)), None),
|
||||
Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None),
|
||||
Instruction::new(InstrKind::Ret, None),
|
||||
],
|
||||
}],
|
||||
globals: vec![],
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&module).unwrap();
|
||||
|
||||
// Snapshot check
|
||||
let expected = r#"{
|
||||
"name": "test_module",
|
||||
"const_pool": {
|
||||
"constants": [
|
||||
{
|
||||
"String": "Hello VM"
|
||||
}
|
||||
]
|
||||
},
|
||||
"functions": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "main",
|
||||
"params": [],
|
||||
"return_type": "Null",
|
||||
"body": [
|
||||
{
|
||||
"kind": {
|
||||
"PushConst": 0
|
||||
},
|
||||
"span": null
|
||||
},
|
||||
{
|
||||
"kind": {
|
||||
"Call": {
|
||||
"func_id": 2,
|
||||
"arg_count": 1
|
||||
}
|
||||
},
|
||||
"span": null
|
||||
},
|
||||
{
|
||||
"kind": "Ret",
|
||||
"span": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"globals": []
|
||||
}"#;
|
||||
assert_eq!(json, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lowering_smoke() {
|
||||
use prometeu_compiler::ir_core;
|
||||
use prometeu_compiler::backend::lowering::lower_program;
|
||||
|
||||
let mut const_pool = ir_core::ConstPool::new();
|
||||
const_pool.insert(ir_core::ConstantValue::Int(42));
|
||||
|
||||
let program = ir_core::Program {
|
||||
const_pool,
|
||||
modules: vec![ir_core::Module {
|
||||
name: "test_core".to_string(),
|
||||
functions: vec![ir_core::Function {
|
||||
id: FunctionId(10),
|
||||
name: "start".to_string(),
|
||||
blocks: vec![ir_core::Block {
|
||||
id: 0,
|
||||
instrs: vec![
|
||||
ir_core::Instr::PushConst(ConstId(0)),
|
||||
],
|
||||
terminator: ir_core::Terminator::Return,
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
let vm_module = lower_program(&program).expect("Lowering failed");
|
||||
|
||||
assert_eq!(vm_module.name, "test_core");
|
||||
assert_eq!(vm_module.functions.len(), 1);
|
||||
let func = &vm_module.functions[0];
|
||||
assert_eq!(func.name, "start");
|
||||
assert_eq!(func.id, FunctionId(10));
|
||||
|
||||
// Check if instructions were lowered (label + pushconst + ret)
|
||||
assert_eq!(func.body.len(), 3);
|
||||
match &func.body[0].kind {
|
||||
InstrKind::Label(Label(l)) => assert!(l.contains("block_0")),
|
||||
_ => panic!("Expected label"),
|
||||
}
|
||||
match &func.body[1].kind {
|
||||
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
|
||||
_ => panic!("Expected PushConst"),
|
||||
}
|
||||
match &func.body[2].kind {
|
||||
InstrKind::Ret => (),
|
||||
_ => panic!("Expected Ret"),
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user