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));
|
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 ---
|
// --- PHASE 1: Lowering IR to Assembly-like structures ---
|
||||||
for function in &module.functions {
|
for function in &module.functions {
|
||||||
// Each function starts with a label for its entry point.
|
// Each function starts with a label for its entry point.
|
||||||
@ -146,7 +152,8 @@ impl<'a> BytecodeEmitter<'a> {
|
|||||||
InstrKind::Label(label) => {
|
InstrKind::Label(label) => {
|
||||||
asm_instrs.push(Asm::Label(label.0.clone()));
|
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)]));
|
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![])),
|
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)]));
|
asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)]));
|
||||||
}
|
}
|
||||||
InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])),
|
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();
|
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 struct Span {
|
||||||
pub file_id: usize,
|
pub file_id: usize,
|
||||||
pub start: u32,
|
pub start: u32,
|
||||||
|
|||||||
@ -5,11 +5,11 @@
|
|||||||
//! easy to lower into VM-specific bytecode.
|
//! easy to lower into VM-specific bytecode.
|
||||||
|
|
||||||
use crate::common::spans::Span;
|
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
|
/// An `Instruction` combines an instruction's behavior (`kind`) with its
|
||||||
/// source code location (`span`) for debugging and error reporting.
|
/// source code location (`span`) for debugging and error reporting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct Instruction {
|
pub struct Instruction {
|
||||||
pub kind: InstrKind,
|
pub kind: InstrKind,
|
||||||
/// The location in the original source code that generated this instruction.
|
/// 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.
|
/// A `Label` represents a destination for a jump instruction.
|
||||||
/// During the assembly phase, labels are resolved into actual memory offsets.
|
/// 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);
|
pub struct Label(pub String);
|
||||||
|
|
||||||
/// The various types of operations that can be performed in the IR.
|
/// 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.
|
/// 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 {
|
pub enum InstrKind {
|
||||||
/// Does nothing.
|
/// Does nothing.
|
||||||
Nop,
|
Nop,
|
||||||
@ -124,9 +124,9 @@ pub enum InstrKind {
|
|||||||
JmpIfFalse(Label),
|
JmpIfFalse(Label),
|
||||||
/// Defines a location that can be jumped to. Does not emit code by itself.
|
/// Defines a location that can be jumped to. Does not emit code by itself.
|
||||||
Label(Label),
|
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.
|
/// 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.
|
/// Returns from the current function. The return value (if any) should be on top of the stack.
|
||||||
Ret,
|
Ret,
|
||||||
|
|
||||||
@ -136,9 +136,4 @@ pub enum InstrKind {
|
|||||||
Syscall(u32),
|
Syscall(u32),
|
||||||
/// Special instruction to synchronize with the hardware frame clock.
|
/// Special instruction to synchronize with the hardware frame clock.
|
||||||
FrameSync,
|
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 instr;
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
|
||||||
pub use instr::Instruction;
|
pub use instr::{Instruction, InstrKind, Label};
|
||||||
pub use module::Module;
|
pub use module::{Module, Function, Global, Param};
|
||||||
pub use types::Type;
|
pub use types::Type;
|
||||||
|
|||||||
@ -7,10 +7,12 @@
|
|||||||
use crate::ir::instr::Instruction;
|
use crate::ir::instr::Instruction;
|
||||||
use crate::ir::types::Type;
|
use crate::ir::types::Type;
|
||||||
use crate::ir_core::const_pool::ConstPool;
|
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.
|
/// 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.
|
/// It contains a collection of global variables, functions, and a constant pool.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
/// The name of the module (usually derived from the project name).
|
/// The name of the module (usually derived from the project name).
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -26,8 +28,10 @@ pub struct Module {
|
|||||||
///
|
///
|
||||||
/// Functions consist of a signature (name, parameters, return type) and a body
|
/// Functions consist of a signature (name, parameters, return type) and a body
|
||||||
/// which is a flat list of IR instructions.
|
/// which is a flat list of IR instructions.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
|
/// The unique identifier of the function.
|
||||||
|
pub id: FunctionId,
|
||||||
/// The unique name of the function.
|
/// The unique name of the function.
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// The list of input parameters.
|
/// The list of input parameters.
|
||||||
@ -39,7 +43,7 @@ pub struct Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A parameter passed to a function.
|
/// A parameter passed to a function.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Param {
|
pub struct Param {
|
||||||
/// The name of the parameter (useful for debugging and symbols).
|
/// The name of the parameter (useful for debugging and symbols).
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -48,7 +52,7 @@ pub struct Param {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A global variable accessible by any function in the module.
|
/// A global variable accessible by any function in the module.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Global {
|
pub struct Global {
|
||||||
/// The name of the global variable.
|
/// The name of the global variable.
|
||||||
pub name: String,
|
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 {
|
pub enum Type {
|
||||||
Any,
|
Any,
|
||||||
Null,
|
Null,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use prometeu_compiler::ir::module::{Module, Function};
|
use prometeu_compiler::ir::module::{Module, Function};
|
||||||
use prometeu_compiler::ir::instr::{Instruction, InstrKind};
|
use prometeu_compiler::ir::instr::{Instruction, InstrKind};
|
||||||
use prometeu_compiler::ir::types::Type;
|
use prometeu_compiler::ir::types::Type;
|
||||||
|
use prometeu_compiler::ir_core::ids::FunctionId;
|
||||||
use prometeu_compiler::ir_core::const_pool::ConstantValue;
|
use prometeu_compiler::ir_core::const_pool::ConstantValue;
|
||||||
use prometeu_compiler::backend::emit_module;
|
use prometeu_compiler::backend::emit_module;
|
||||||
use prometeu_compiler::common::files::FileManager;
|
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 id_str = module.const_pool.insert(ConstantValue::String("hello".to_string()));
|
||||||
|
|
||||||
let function = Function {
|
let function = Function {
|
||||||
|
id: FunctionId(0),
|
||||||
name: "main".to_string(),
|
name: "main".to_string(),
|
||||||
params: vec![],
|
params: vec![],
|
||||||
return_type: Type::Void,
|
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