//! # 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, } impl Instruction { /// Creates a new instruction with an optional source span. pub fn new(kind: InstrKind, span: Option) -> 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); } } }