Nilton Constantino adcb34826c
pr 40
2026-01-31 01:33:19 +00:00

389 lines
10 KiB
Rust

//! # IR Instructions
//!
//! This module defines the set of instructions used in the Intermediate Representation (IR).
//! These instructions are designed to be easy to generate from a high-level AST and
//! 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;
/// An `Instruction` combines an instruction's behavior (`kind`) with its
/// source code location (`span`) for debugging and error reporting.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Instruction {
pub kind: InstrKind,
/// The location in the original source code that generated this instruction.
pub span: Option<Span>,
}
impl Instruction {
/// Creates a new instruction with an optional source span.
pub fn new(kind: InstrKind, span: Option<Span>) -> Self {
Self { kind, span }
}
}
/// 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, 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, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum InstrKind {
/// Does nothing.
Nop,
/// Terminates program execution.
Halt,
// --- Literals ---
// These instructions push a constant value from the pool onto the stack.
/// Pushes a constant from the pool onto the stack.
PushConst(ConstId),
/// Pushes a bounded value (0..0xFFFF) onto the stack.
PushBounded(u32),
/// Pushes a boolean onto the stack.
PushBool(bool),
/// Pushes a `null` value onto the stack.
PushNull,
// --- Stack Operations ---
/// Removes the top value from the stack.
Pop,
/// Duplicates the top value on the stack.
Dup,
/// Swaps the top two values on the stack.
Swap,
// --- Arithmetic ---
// These take two values from the stack and push the result.
/// Addition: `a + b`
Add,
/// Subtraction: `a - b`
Sub,
/// Multiplication: `a * b`
Mul,
/// Division: `a / b`
Div,
/// Negation: `-a` (takes one value)
Neg,
// --- Logical/Comparison ---
/// Equality: `a == b`
Eq,
/// Inequality: `a != b`
Neq,
/// Less than: `a < b`
Lt,
/// Greater than: `a > b`
Gt,
/// Less than or equal: `a <= b`
Lte,
/// Greater than or equal: `a >= b`
Gte,
/// Logical AND: `a && b`
And,
/// Logical OR: `a || b`
Or,
/// Logical NOT: `!a`
Not,
// --- Bitwise Operations ---
/// Bitwise AND: `a & b`
BitAnd,
/// Bitwise OR: `a | b`
BitOr,
/// Bitwise XOR: `a ^ b`
BitXor,
/// Shift Left: `a << b`
Shl,
/// Shift Right: `a >> b`
Shr,
// --- Variable Access ---
/// Retrieves a value from a local variable slot and pushes it onto the stack.
LocalLoad { slot: u32 },
/// Pops a value from the stack and stores it in a local variable slot.
LocalStore { slot: u32 },
/// Retrieves a value from a global variable slot and pushes it onto the stack.
GetGlobal(u32),
/// Pops a value from the stack and stores it in a global variable slot.
SetGlobal(u32),
// --- Control Flow ---
/// Unconditionally jumps to the specified label.
Jmp(Label),
/// Pops a boolean from the stack. If false, jumps to the specified label.
JmpIfFalse(Label),
/// Defines a location that can be jumped to. Does not emit code by itself.
Label(Label),
/// Calls a function by ID with the specified number of arguments.
/// Arguments should be pushed onto the stack before calling.
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,
// --- OS / System ---
/// Triggers a system call (e.g., drawing to the screen, reading input).
Syscall(u32),
/// Special instruction to synchronize with the hardware frame clock.
FrameSync,
// --- HIP / Memory ---
/// Allocates memory on the heap.
Alloc { type_id: TypeId, slots: u32 },
/// Reads from heap at gate + offset. Pops gate, pushes value.
GateLoad { offset: u32 },
/// Writes to heap at gate + offset. Pops gate and value.
GateStore { offset: u32 },
// --- Scope Markers ---
GateBeginPeek,
GateEndPeek,
GateBeginBorrow,
GateEndBorrow,
GateBeginMutate,
GateEndMutate,
// --- Reference Counting ---
/// Increments the reference count of a gate handle on the stack.
/// Stack: [..., Gate(g)] -> [..., Gate(g)]
GateRetain,
/// Decrements the reference count of a gate handle and pops it from the stack.
/// Stack: [..., Gate(g)] -> [...]
GateRelease,
}
/// List of instructions that are sensitive to Reference Counting (RC).
/// These instructions must trigger retain/release operations on gate handles.
pub const RC_SENSITIVE_OPS: &[&str] = &[
"LocalStore",
"GateStore",
"GateLoad",
"Pop",
"Ret",
"FrameSync",
];
#[cfg(test)]
mod tests {
use super::*;
use crate::ir_vm::types::{ConstId, TypeId};
#[test]
fn test_instr_kind_is_cloneable() {
let instr = InstrKind::Alloc { type_id: TypeId(1), slots: 2 };
let cloned = instr.clone();
match cloned {
InstrKind::Alloc { type_id, slots } => {
assert_eq!(type_id, TypeId(1));
assert_eq!(slots, 2);
}
_ => panic!("Clone failed"),
}
}
#[test]
fn test_isa_surface_snapshot() {
// This test ensures that the instruction set surface remains stable.
// If you add/remove/change instructions, this test will fail,
// prompting an explicit review of the ISA change.
let instructions = vec![
InstrKind::Nop,
InstrKind::Halt,
InstrKind::PushConst(ConstId(0)),
InstrKind::PushBounded(0),
InstrKind::PushBool(true),
InstrKind::PushNull,
InstrKind::Pop,
InstrKind::Dup,
InstrKind::Swap,
InstrKind::Add,
InstrKind::Sub,
InstrKind::Mul,
InstrKind::Div,
InstrKind::Neg,
InstrKind::Eq,
InstrKind::Neq,
InstrKind::Lt,
InstrKind::Gt,
InstrKind::Lte,
InstrKind::Gte,
InstrKind::And,
InstrKind::Or,
InstrKind::Not,
InstrKind::BitAnd,
InstrKind::BitOr,
InstrKind::BitXor,
InstrKind::Shl,
InstrKind::Shr,
InstrKind::LocalLoad { slot: 0 },
InstrKind::LocalStore { slot: 0 },
InstrKind::GetGlobal(0),
InstrKind::SetGlobal(0),
InstrKind::Jmp(Label("target".to_string())),
InstrKind::JmpIfFalse(Label("target".to_string())),
InstrKind::Label(Label("target".to_string())),
InstrKind::Call { func_id: FunctionId(0), arg_count: 0 },
InstrKind::Ret,
InstrKind::Syscall(0),
InstrKind::FrameSync,
InstrKind::Alloc { type_id: TypeId(0), slots: 0 },
InstrKind::GateLoad { offset: 0 },
InstrKind::GateStore { offset: 0 },
InstrKind::GateBeginPeek,
InstrKind::GateEndPeek,
InstrKind::GateBeginBorrow,
InstrKind::GateEndBorrow,
InstrKind::GateBeginMutate,
InstrKind::GateEndMutate,
InstrKind::GateRetain,
InstrKind::GateRelease,
];
let serialized = serde_json::to_string_pretty(&instructions).unwrap();
// This is a "lock" on the ISA surface.
// If the structure of InstrKind changes, the serialization will change.
let expected_json = r#"[
"Nop",
"Halt",
{
"PushConst": 0
},
{
"PushBounded": 0
},
{
"PushBool": true
},
"PushNull",
"Pop",
"Dup",
"Swap",
"Add",
"Sub",
"Mul",
"Div",
"Neg",
"Eq",
"Neq",
"Lt",
"Gt",
"Lte",
"Gte",
"And",
"Or",
"Not",
"BitAnd",
"BitOr",
"BitXor",
"Shl",
"Shr",
{
"LocalLoad": {
"slot": 0
}
},
{
"LocalStore": {
"slot": 0
}
},
{
"GetGlobal": 0
},
{
"SetGlobal": 0
},
{
"Jmp": "target"
},
{
"JmpIfFalse": "target"
},
{
"Label": "target"
},
{
"Call": {
"func_id": 0,
"arg_count": 0
}
},
"Ret",
{
"Syscall": 0
},
"FrameSync",
{
"Alloc": {
"type_id": 0,
"slots": 0
}
},
{
"GateLoad": {
"offset": 0
}
},
{
"GateStore": {
"offset": 0
}
},
"GateBeginPeek",
"GateEndPeek",
"GateBeginBorrow",
"GateEndBorrow",
"GateBeginMutate",
"GateEndMutate",
"GateRetain",
"GateRelease"
]"#;
assert_eq!(serialized, expected_json);
}
#[test]
fn test_no_ref_leakage_in_instr_names() {
// Enforce the rule that "Ref" must never refer to HIP memory in ir_vm.
// The snapshot test above already locks the names, but this test
// explicitly asserts the absence of the "Ref" substring in HIP-related instructions.
let instructions = [
"GateLoad", "GateStore", "Alloc",
"GateBeginPeek", "GateEndPeek",
"GateBeginBorrow", "GateEndBorrow",
"GateBeginMutate", "GateEndMutate",
"GateRetain", "GateRelease"
];
for name in instructions {
assert!(!name.contains("Ref"), "Instruction {} contains forbidden 'Ref' terminology", name);
}
}
#[test]
fn test_rc_sensitive_list_exists() {
// Required by PR-06: Documentation test or unit assertion that the RC-sensitive list exists
assert!(!RC_SENSITIVE_OPS.is_empty(), "RC-sensitive instructions list must not be empty");
let expected = ["LocalStore", "GateStore", "GateLoad", "Pop", "Ret", "FrameSync"];
for op in expected {
assert!(RC_SENSITIVE_OPS.contains(&op), "RC-sensitive list must contain {}", op);
}
}
}