change ir to ir_vm and make it feature frozen

This commit is contained in:
Nilton Constantino 2026-01-29 16:45:02 +00:00
parent 4174d01a43
commit c34166435f
No known key found for this signature in database
12 changed files with 197 additions and 331 deletions

View File

@ -4,13 +4,13 @@
//! converting the Intermediate Representation (IR) into the binary Prometeu ByteCode (PBC) format. //! converting the Intermediate Representation (IR) into the binary Prometeu ByteCode (PBC) format.
//! //!
//! It performs two main tasks: //! It performs two main tasks:
//! 1. **Instruction Lowering**: Translates `ir::Instruction` into `prometeu_bytecode::asm::Asm` ops. //! 1. **Instruction Lowering**: Translates `ir_vm::Instruction` into `prometeu_bytecode::asm::Asm` ops.
//! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations. //! 2. **Symbol Mapping**: Associates bytecode offsets (Program Counter) with source code locations.
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::ir; use crate::ir_vm;
use crate::ir::instr::InstrKind; use crate::ir_vm::instr::InstrKind;
use crate::ir_core::ConstantValue; use crate::ir_core::ConstantValue;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use prometeu_bytecode::asm::{assemble, update_pc_by_operand, Asm, Operand}; use prometeu_bytecode::asm::{assemble, update_pc_by_operand, Asm, Operand};
@ -26,7 +26,7 @@ pub struct EmitResult {
} }
/// Entry point for emitting a bytecode module from the IR. /// Entry point for emitting a bytecode module from the IR.
pub fn emit_module(module: &ir::Module, file_manager: &FileManager) -> Result<EmitResult> { pub fn emit_module(module: &ir_vm::Module, file_manager: &FileManager) -> Result<EmitResult> {
let mut emitter = BytecodeEmitter::new(file_manager); let mut emitter = BytecodeEmitter::new(file_manager);
emitter.emit(module) emitter.emit(module)
} }
@ -69,7 +69,7 @@ impl<'a> BytecodeEmitter<'a> {
} }
/// Transforms an IR module into a binary PBC file. /// Transforms an IR module into a binary PBC file.
fn emit(&mut self, module: &ir::Module) -> Result<EmitResult> { fn emit(&mut self, module: &ir_vm::Module) -> Result<EmitResult> {
let mut asm_instrs = Vec::new(); let mut asm_instrs = Vec::new();
let mut ir_instr_map = Vec::new(); // Maps Asm index to IR instruction (for symbols) let mut ir_instr_map = Vec::new(); // Maps Asm index to IR instruction (for symbols)
@ -232,9 +232,9 @@ impl<'a> BytecodeEmitter<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ir::module::{Module, Function}; use crate::ir_vm::module::{Module, Function};
use crate::ir::instr::{Instruction, InstrKind}; use crate::ir_vm::instr::{Instruction, InstrKind};
use crate::ir::types::Type; use crate::ir_vm::types::Type;
use crate::ir_core::ids::FunctionId; use crate::ir_core::ids::FunctionId;
use crate::ir_core::const_pool::ConstantValue; use crate::ir_core::const_pool::ConstantValue;
use crate::common::files::FileManager; use crate::common::files::FileManager;

View File

@ -8,7 +8,7 @@ use crate::common::config::ProjectConfig;
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::frontends::Frontend; use crate::frontends::Frontend;
use crate::ir; use crate::ir_vm;
use anyhow::Result; use anyhow::Result;
use std::path::Path; use std::path::Path;
@ -87,7 +87,7 @@ pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
// 3. IR Validation // 3. IR Validation
// Ensures the generated IR is sound and doesn't violate any VM constraints // Ensures the generated IR is sound and doesn't violate any VM constraints
// before we spend time generating bytecode. // before we spend time generating bytecode.
ir::validate::validate_module(&ir_module) ir_vm::validate::validate_module(&ir_module)
.map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?; .map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?;
// 4. Emit Bytecode // 4. Emit Bytecode

View File

@ -1,5 +1,5 @@
use crate::common::diagnostics::DiagnosticBundle; use crate::common::diagnostics::DiagnosticBundle;
use crate::ir; use crate::ir_vm;
use std::path::Path; use std::path::Path;
use crate::common::files::FileManager; use crate::common::files::FileManager;
@ -13,5 +13,5 @@ pub trait Frontend {
&self, &self,
entry: &Path, entry: &Path,
file_manager: &mut FileManager, file_manager: &mut FileManager,
) -> Result<ir::Module, DiagnosticBundle>; ) -> Result<ir_vm::Module, DiagnosticBundle>;
} }

View File

@ -21,7 +21,7 @@ pub use lowering::Lowerer;
use crate::common::diagnostics::DiagnosticBundle; use crate::common::diagnostics::DiagnosticBundle;
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::frontends::Frontend; use crate::frontends::Frontend;
use crate::ir; use crate::ir_vm;
use crate::lowering::core_to_vm; use crate::lowering::core_to_vm;
use std::path::Path; use std::path::Path;
@ -36,7 +36,7 @@ impl Frontend for PbsFrontend {
&self, &self,
entry: &Path, entry: &Path,
file_manager: &mut FileManager, file_manager: &mut FileManager,
) -> Result<ir::Module, DiagnosticBundle> { ) -> Result<ir_vm::Module, DiagnosticBundle> {
let source = std::fs::read_to_string(entry).map_err(|e| { let source = std::fs::read_to_string(entry).map_err(|e| {
DiagnosticBundle::error(format!("Failed to read file: {}", e), None) DiagnosticBundle::error(format!("Failed to read file: {}", e), None)
})?; })?;

View File

@ -4,8 +4,8 @@
//! The IR is a higher-level representation of the program than bytecode, but lower //! 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. //! than the source code AST. It is organized into Modules, Functions, and Globals.
use crate::ir::instr::Instruction; use crate::ir_vm::instr::Instruction;
use crate::ir::types::Type; use crate::ir_vm::types::Type;
use crate::ir_core::const_pool::ConstPool; use crate::ir_core::const_pool::ConstPool;
use crate::ir_core::ids::FunctionId; use crate::ir_core::ids::FunctionId;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,5 +1,5 @@
use crate::common::diagnostics::DiagnosticBundle; use crate::common::diagnostics::DiagnosticBundle;
use crate::ir::module::Module; use crate::ir_vm::module::Module;
pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> { pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> {
// TODO: Implement common IR validations: // TODO: Implement common IR validations:

View File

@ -38,7 +38,7 @@
//! See the [`compiler`] module for the main entry point to trigger a compilation programmatically. //! See the [`compiler`] module for the main entry point to trigger a compilation programmatically.
pub mod common; pub mod common;
pub mod ir; pub mod ir_vm;
pub mod ir_core; pub mod ir_core;
pub mod lowering; pub mod lowering;
pub mod backend; pub mod backend;

View File

@ -1,9 +1,9 @@
use crate::ir; use crate::ir_vm;
use crate::ir_core; use crate::ir_core;
use anyhow::Result; use anyhow::Result;
/// Lowers a Core IR program into a VM IR module. /// Lowers a Core IR program into a VM IR module.
pub fn lower_program(program: &ir_core::Program) -> Result<ir::Module> { pub fn lower_program(program: &ir_core::Program) -> Result<ir_vm::Module> {
// For now, we assume a single module program or lower the first one. // For now, we assume a single module program or lower the first one.
// In the future, we might want to lower all modules and link them. // In the future, we might want to lower all modules and link them.
if let Some(core_module) = program.modules.first() { if let Some(core_module) = program.modules.first() {
@ -14,8 +14,8 @@ pub fn lower_program(program: &ir_core::Program) -> Result<ir::Module> {
} }
/// Lowers a single Core IR module into a VM IR module. /// Lowers a single Core IR module into a VM IR module.
pub fn lower_module(core_module: &ir_core::Module, const_pool: &ir_core::ConstPool) -> Result<ir::Module> { pub fn lower_module(core_module: &ir_core::Module, const_pool: &ir_core::ConstPool) -> Result<ir_vm::Module> {
let mut vm_module = ir::Module::new(core_module.name.clone()); let mut vm_module = ir_vm::Module::new(core_module.name.clone());
vm_module.const_pool = const_pool.clone(); vm_module.const_pool = const_pool.clone();
for core_func in &core_module.functions { for core_func in &core_module.functions {
@ -26,11 +26,11 @@ pub fn lower_module(core_module: &ir_core::Module, const_pool: &ir_core::ConstPo
} }
/// Lowers a Core IR function into a VM IR function. /// Lowers a Core IR function into a VM IR function.
pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> { pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function> {
let mut vm_func = ir::Function { let mut vm_func = ir_vm::Function {
id: core_func.id, id: core_func.id,
name: core_func.name.clone(), name: core_func.name.clone(),
params: core_func.params.iter().map(|p| ir::Param { params: core_func.params.iter().map(|p| ir_vm::Param {
name: p.name.clone(), name: p.name.clone(),
r#type: lower_type(&p.ty), r#type: lower_type(&p.ty),
}).collect(), }).collect(),
@ -40,62 +40,62 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
for block in &core_func.blocks { for block in &core_func.blocks {
// Core blocks map to labels in the flat VM IR instruction list. // Core blocks map to labels in the flat VM IR instruction list.
vm_func.body.push(ir::Instruction::new( vm_func.body.push(ir_vm::Instruction::new(
ir::InstrKind::Label(ir::Label(format!("block_{}", block.id))), ir_vm::InstrKind::Label(ir_vm::Label(format!("block_{}", block.id))),
None, None,
)); ));
for instr in &block.instrs { for instr in &block.instrs {
let kind = match instr { let kind = match instr {
ir_core::Instr::PushConst(id) => ir::InstrKind::PushConst(*id), ir_core::Instr::PushConst(id) => ir_vm::InstrKind::PushConst(*id),
ir_core::Instr::Call(func_id, arg_count) => ir::InstrKind::Call { ir_core::Instr::Call(func_id, arg_count) => ir_vm::InstrKind::Call {
func_id: *func_id, func_id: *func_id,
arg_count: *arg_count arg_count: *arg_count
}, },
ir_core::Instr::Syscall(id) => ir::InstrKind::Syscall(*id), ir_core::Instr::Syscall(id) => ir_vm::InstrKind::Syscall(*id),
ir_core::Instr::GetLocal(slot) => ir::InstrKind::GetLocal(*slot), ir_core::Instr::GetLocal(slot) => ir_vm::InstrKind::GetLocal(*slot),
ir_core::Instr::SetLocal(slot) => ir::InstrKind::SetLocal(*slot), ir_core::Instr::SetLocal(slot) => ir_vm::InstrKind::SetLocal(*slot),
ir_core::Instr::Pop => ir::InstrKind::Pop, ir_core::Instr::Pop => ir_vm::InstrKind::Pop,
ir_core::Instr::Dup => ir::InstrKind::Dup, ir_core::Instr::Dup => ir_vm::InstrKind::Dup,
ir_core::Instr::Add => ir::InstrKind::Add, ir_core::Instr::Add => ir_vm::InstrKind::Add,
ir_core::Instr::Sub => ir::InstrKind::Sub, ir_core::Instr::Sub => ir_vm::InstrKind::Sub,
ir_core::Instr::Mul => ir::InstrKind::Mul, ir_core::Instr::Mul => ir_vm::InstrKind::Mul,
ir_core::Instr::Div => ir::InstrKind::Div, ir_core::Instr::Div => ir_vm::InstrKind::Div,
ir_core::Instr::Neg => ir::InstrKind::Neg, ir_core::Instr::Neg => ir_vm::InstrKind::Neg,
ir_core::Instr::Eq => ir::InstrKind::Eq, ir_core::Instr::Eq => ir_vm::InstrKind::Eq,
ir_core::Instr::Neq => ir::InstrKind::Neq, ir_core::Instr::Neq => ir_vm::InstrKind::Neq,
ir_core::Instr::Lt => ir::InstrKind::Lt, ir_core::Instr::Lt => ir_vm::InstrKind::Lt,
ir_core::Instr::Lte => ir::InstrKind::Lte, ir_core::Instr::Lte => ir_vm::InstrKind::Lte,
ir_core::Instr::Gt => ir::InstrKind::Gt, ir_core::Instr::Gt => ir_vm::InstrKind::Gt,
ir_core::Instr::Gte => ir::InstrKind::Gte, ir_core::Instr::Gte => ir_vm::InstrKind::Gte,
ir_core::Instr::And => ir::InstrKind::And, ir_core::Instr::And => ir_vm::InstrKind::And,
ir_core::Instr::Or => ir::InstrKind::Or, ir_core::Instr::Or => ir_vm::InstrKind::Or,
ir_core::Instr::Not => ir::InstrKind::Not, ir_core::Instr::Not => ir_vm::InstrKind::Not,
ir_core::Instr::Alloc => ir::InstrKind::Alloc, ir_core::Instr::Alloc => ir_vm::InstrKind::Alloc,
ir_core::Instr::ReadGate => ir::InstrKind::LoadRef(0), ir_core::Instr::ReadGate => ir_vm::InstrKind::LoadRef(0),
ir_core::Instr::WriteGate => ir::InstrKind::StoreRef(0), ir_core::Instr::WriteGate => ir_vm::InstrKind::StoreRef(0),
ir_core::Instr::Free => ir::InstrKind::Nop, ir_core::Instr::Free => ir_vm::InstrKind::Nop,
}; };
vm_func.body.push(ir::Instruction::new(kind, None)); vm_func.body.push(ir_vm::Instruction::new(kind, None));
} }
match &block.terminator { match &block.terminator {
ir_core::Terminator::Return => { ir_core::Terminator::Return => {
vm_func.body.push(ir::Instruction::new(ir::InstrKind::Ret, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Ret, None));
} }
ir_core::Terminator::Jump(target) => { ir_core::Terminator::Jump(target) => {
vm_func.body.push(ir::Instruction::new( vm_func.body.push(ir_vm::Instruction::new(
ir::InstrKind::Jmp(ir::Label(format!("block_{}", target))), ir_vm::InstrKind::Jmp(ir_vm::Label(format!("block_{}", target))),
None, None,
)); ));
} }
ir_core::Terminator::JumpIfFalse { target, else_target } => { ir_core::Terminator::JumpIfFalse { target, else_target } => {
vm_func.body.push(ir::Instruction::new( vm_func.body.push(ir_vm::Instruction::new(
ir::InstrKind::JmpIfFalse(ir::Label(format!("block_{}", target))), ir_vm::InstrKind::JmpIfFalse(ir_vm::Label(format!("block_{}", target))),
None, None,
)); ));
vm_func.body.push(ir::Instruction::new( vm_func.body.push(ir_vm::Instruction::new(
ir::InstrKind::Jmp(ir::Label(format!("block_{}", else_target))), ir_vm::InstrKind::Jmp(ir_vm::Label(format!("block_{}", else_target))),
None, None,
)); ));
} }
@ -105,20 +105,20 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
Ok(vm_func) Ok(vm_func)
} }
fn lower_type(ty: &ir_core::Type) -> ir::Type { fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
match ty { match ty {
ir_core::Type::Void => ir::Type::Void, ir_core::Type::Void => ir_vm::Type::Void,
ir_core::Type::Int => ir::Type::Int, ir_core::Type::Int => ir_vm::Type::Int,
ir_core::Type::Float => ir::Type::Float, ir_core::Type::Float => ir_vm::Type::Float,
ir_core::Type::Bool => ir::Type::Bool, ir_core::Type::Bool => ir_vm::Type::Bool,
ir_core::Type::String => ir::Type::String, ir_core::Type::String => ir_vm::Type::String,
ir_core::Type::Optional(inner) => ir::Type::Array(Box::new(lower_type(inner))), // Approximation ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))), // Approximation
ir_core::Type::Result(ok, _) => lower_type(ok), // Approximation ir_core::Type::Result(ok, _) => lower_type(ok), // Approximation
ir_core::Type::Struct(_) => ir::Type::Object, ir_core::Type::Struct(_) => ir_vm::Type::Object,
ir_core::Type::Service(_) => ir::Type::Object, ir_core::Type::Service(_) => ir_vm::Type::Object,
ir_core::Type::Contract(_) => ir::Type::Object, ir_core::Type::Contract(_) => ir_vm::Type::Object,
ir_core::Type::ErrorType(_) => ir::Type::Object, ir_core::Type::ErrorType(_) => ir_vm::Type::Object,
ir_core::Type::Function { .. } => ir::Type::Function, ir_core::Type::Function { .. } => ir_vm::Type::Function,
} }
} }
@ -127,7 +127,7 @@ mod tests {
use super::*; use super::*;
use crate::ir_core; use crate::ir_core;
use crate::ir_core::*; use crate::ir_core::*;
use crate::ir::*; use crate::ir_vm::*;
#[test] #[test]
fn test_full_lowering() { fn test_full_lowering() {

View File

@ -1,357 +1,223 @@
# PBS Compiler — Junie PR Plan # PBS ⇄ VM Alignment — Junie PRs (HIP Semantics Hardening)
> **Purpose:** this document defines a sequence of small, focused Pull Requests to be implemented by *Junie*, one at a time. > **Purpose:** fix semantic mismatches between the PBS frontend (Core IR) and the VM **before** any VM heap/gate implementation.
> >
> **Audience:** compiler implementer (AI or human). > These PRs are **surgical**, **mandatory**, and **non-creative**.
> Junie must follow them **exactly**.
> **Context:**
> >
> **Scope:** PBS-first compiler architecture. TS and Lua frontends are assumed **removed**. > * PBS frontend is implemented and produces Core IR.
> > * Bytecode stability is a hard requirement.
> **Hard rules:** > * VM currently has stack + const pool; heap exists but is unused.
> > * HIP semantics (gates/storage) are currently **incorrectly lowered**.
> * Each PR must compile and pass tests. > * `ir_vm` is feature-frozen at the moment. we are going to validate only `ir_core`
> * Each PR must include tests. > * Lowering is the only place `ir_core` and `ir_vm` touch each other.
> * No speculative features. > - VM IR is never imported from Core IR.
> * Follow the `Prometeu Base Script (PBS) - Implementation Spec`. > - Core IR never imports VM IR.
> * VM IR is frozen: new opcodes are forbidden unless explicitly planned in a PR titled “VM Instruction Set Change” with a full rationale + golden bytecode tests.
--- ---
## Global Architectural Direction (Non-negotiable) ## Global Rules (Read Before Any PR)
* PBS is the **primary language**. 1. **No new features.** Only semantic correction.
* Frontend is implemented **before** runtime integration. 2. **No new VM opcodes yet.** VM changes come later.
* Architecture uses **two IR layers**: 3. **No fallback values** (e.g. `FunctionId(0)`). Fail with diagnostics.
* **Core IR** (PBS-semantic, typed, resolved) 4. **Every PR must include tests** (golden or unit).
* **VM IR** (stack-based, backend-friendly) 5. **Core IR is the source of semantic truth.**
* VM IR remains simple and stable.
* Lowering is explicit and testable.
--- ---
# PR-01 — ProjectConfig and Frontend Selection # PR-20 — Core IR: Make HIP Semantics Explicit (No Handle Loss)
### Goal ### Goal
Introduce a project-level configuration that selects the frontend and entry file explicitly. Fix the Core IR so HIP operations never lose the destination gate.
### Motivation ### Problem (Current Bug)
The compiler must not hardcode entry points or languages. PBS will be the first frontend, others may return later. Current lowering evaluates a gate, reads storage, stores the result in a local, and later attempts to write back **without having the gate anymore**.
### Scope This violates PBS semantics: storage access must always be mediated by the **original gate**.
* Add `ProjectConfig` (serde-deserializable) loaded from `prometeu.json` ### Required Changes
* Fields (v0):
* `script_fe: "pbs"` #### 1. Extend Core IR instructions
* `entry: "main.pbs"`
* Refactor compiler entry point to:
* load config Add explicit HIP instructions:
* select frontend by `script_fe`
* resolve entry path relative to project root
### Files Likely Touched ```rust
enum CoreInstr {
// existing …
* `compiler/mod.rs` Alloc { ty: TypeId, slots: u32 },
* `compiler/driver.rs`
* `common/config.rs` (new)
### Tests (mandatory) BeginPeek { gate: ValueId },
BeginBorrow { gate: ValueId },
BeginMutate { gate: ValueId },
* unit test: load valid `prometeu.json` EndPeek,
* unit test: invalid frontend → diagnostic EndBorrow,
* integration test: project root + entry resolution EndMutate,
}
```
### Notes to Junie Rules:
Do **not** add PBS parsing yet. This PR is infrastructure only. * `Begin*` instructions **do not consume** the gate.
* Gate identity must remain available until the matching `End*`.
#### 2. Remove any lowering that copies HIP storage into locals
* No `ReadGate → SetLocal` pattern.
* Storage views are **not locals**.
### Tests (Mandatory)
* Golden Core IR test showing `BeginMutate(gate)``EndMutate` wrapping body
* Test asserting gate is still live at `EndMutate`
--- ---
# PR-02 — Core IR Skeleton (PBS-first) # PR-21 — Distinguish `peek`, `borrow`, and `mutate` in Core IR
### Goal ### Goal
Introduce a **Core IR** layer independent from the VM IR. Restore the semantic distinction mandated by PBS.
### Motivation ### Required Semantics
PBS semantics must be represented before lowering to VM instructions. | Operation | Effect |
| --------- | ------------------------------- |
| `peek` | Copy storage → stack value |
| `borrow` | Temporary read-only view |
| `mutate` | Temporary mutable view + commit |
### Scope ### Required Changes
* Add new module: `ir_core` * Lower PBS `peek``BeginPeek` / `EndPeek`
* Define minimal structures: * Lower PBS `borrow``BeginBorrow` / `EndBorrow`
* Lower PBS `mutate``BeginMutate` / `EndMutate`
* `Program` These **must not** share the same lowering path.
* `Module`
* `Function`
* `Block`
* `Instr`
* `Terminator`
* IDs only (no string-based calls):
* `FunctionId`
* `ConstId`
* `TypeId`
### Constraints
* Core IR must NOT reference VM opcodes
* No lowering yet
### Tests ### Tests
* construct Core IR manually in tests * PBS snippet with all three operations
* snapshot test (JSON) for deterministic shape * Assert distinct Core IR instruction sequences
--- ---
# PR-03 — Constant Pool and IDs # PR-22 — Make Allocation Shape Explicit in Core IR
### Goal ### Goal
Introduce a stable constant pool shared by Core IR and VM IR. Stop implicit / guessed heap layouts.
### Scope ### Required Changes
* Add `ConstPool`: * Replace any shape-less `Alloc` with:
* strings ```rust
* numbers Alloc { ty: TypeId, slots: u32 }
* Replace inline literals in VM IR with `ConstId` ```
* Update existing VM IR to accept `PushConst(ConstId)`
Rules:
* `TypeId` comes from frontend type checking
* `slots` is derived deterministically (struct fields / array size)
### Tests ### Tests
* const pool deduplication * Allocating storage struct emits correct `slots`
* deterministic ConstId assignment * Allocating array emits correct `slots`
* IR snapshot stability
--- ---
# PR-04 — VM IR Cleanup (Stabilization) # PR-23 — Eliminate Invalid Call Fallbacks
### Goal ### Goal
Stabilize VM IR as a **lowering target**, not a language IR. Prevent invalid bytecode generation.
### Scope ### Required Changes
* Replace string-based calls with `FunctionId` * Remove **all** fallbacks to `FunctionId(0)` or equivalent
* Ensure locals are accessed via slots * On unresolved symbols during lowering:
* Remove or internalize `PushScope` / `PopScope`
* Emit canonical diagnostic (`E_RESOLVE_UNDEFINED` or `E_LOWER_UNSUPPORTED`)
* Abort lowering
### Tests ### Tests
* golden VM IR tests * PBS program calling missing function → compile error
* lowering smoke test (Core IR → VM IR) * No Core IR or VM IR emitted
--- ---
# PR-05 — Core IR → VM IR Lowering Pass # PR-24 — Validate Contract Calls in Frontend (Arity + Types)
### Goal ### Goal
Implement the lowering pass from Core IR to VM IR. Move contract validation to compile time.
### Scope ### Required Changes
* New module: `lowering/core_to_vm.rs` * During PBS type checking:
* Lowering rules:
* Core blocks → labels * Validate argument count against contract signature
* Core calls → VM calls * Validate argument types
* Host calls preserved
* No PBS frontend yet * Lower only validated calls to `HostCall`
### Tests ### Tests
* lowering correctness * Wrong arity → `E_TYPE_MISMATCH`
* instruction ordering * Correct call lowers to Core IR `HostCall`
* label resolution
--- ---
# PR-06 — PBS Frontend: Lexer # PR-25 — Core IR Invariants Test Suite
### Goal ### Goal
Implement PBS lexer according to the spec. Lock in correct semantics before touching the VM.
### Scope ### Required Invariants
* Token kinds * Every `Begin*` has a matching `End*`
* Keyword table * Gate passed to `Begin*` is available at `End*`
* Span tracking * No storage writes without `BeginMutate`
* No silent fallbacks
### Tests ### Tests
* tokenization tests * Property-style tests or golden IR assertions
* keyword vs identifier tests
* bounded literals
--- ---
# PR-07 — PBS Frontend: Parser (Raw AST) ## STOP POINT
### Goal After PR-25:
Parse PBS source into a raw AST. * Core IR correctly represents PBS HIP semantics
* Lowering is deterministic and safe
* VM is still unchanged
### Scope **Only after this point may VM PRs begin.**
* Imports Any VM work before this is a hard rejection.
* Top-level declarations
* Blocks
* Expressions (calls, literals, control flow)
### Tests
* valid programs
* syntax error recovery
--- ---
# PR-08 — PBS Frontend: Symbol Collection and Resolver ## Instruction to Junie
### Goal If any rule in this document is unclear:
Resolve names, modules, and visibility. * Stop
* Add a failing test
* Document the ambiguity
### Scope Do not invent behavior.
* Type namespace vs value namespace This document is binding.
* Visibility rules
* Import resolution
### Tests
* duplicate symbols
* invalid imports
* visibility errors
---
# PR-09 — PBS Frontend: Type Checking
### Goal
Validate PBS semantics.
### Scope
* Primitive types
* Structs
* `optional<T>` and `result<T, E>`
* Mutability rules
* Return path validation
### Tests
* type mismatch
* mutability violations
* implicit `none` behavior
---
# PR-10 — PBS Frontend: Semantic Lowering to Core IR
### Goal
Lower typed PBS AST into Core IR.
### Scope
* ID-based calls
* ConstPool usage
* Control flow lowering
* SAFE vs HIP effects represented explicitly
### Tests
* PBS → Core IR snapshots
* semantic correctness
---
# PR-11 — Host-bound Contracts and Syscall Mapping
### Goal
Connect PBS host-bound contracts to runtime syscalls (without executing them).
### Scope
* Contract registry
* Mapping: contract.method → syscall id
* Core IR host call nodes
### Tests
* invalid contract calls
* correct syscall mapping
---
# PR-12 — Diagnostics Canonicalization
### Goal
Standardize diagnostics output.
### Scope
* Error codes (`E_*`, `W_*`)
* Stable messages
* Span accuracy
### Tests
* golden diagnostics
---
# PR-13 — Backend Integration (VM IR → Bytecode)
### Goal
Reconnect the pipeline to the Prometeu runtime backend.
### Scope
* VM IR → bytecode emission
* No PBS semantics here
### Tests
* bytecode emission smoke test
---
# PR-14 — End-to-End PBS Compile Test
### Goal
Prove the full pipeline works.
### Scope
* Sample PBS project
* Compile → bytecode
* Diagnostics only (no execution)
### Tests
* golden bytecode snapshot
---
## Final Note to Junie
Do **not** skip PRs.
Do **not** merge multiple PRs together.
If the spec is unclear, create a failing test and document the ambiguity.
This plan is the authoritative roadmap for PBS frontend implementation.