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.
//!
//! 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.
use crate::common::files::FileManager;
use crate::common::symbols::Symbol;
use crate::ir;
use crate::ir::instr::InstrKind;
use crate::ir_vm;
use crate::ir_vm::instr::InstrKind;
use crate::ir_core::ConstantValue;
use anyhow::{anyhow, Result};
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.
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);
emitter.emit(module)
}
@ -69,7 +69,7 @@ impl<'a> BytecodeEmitter<'a> {
}
/// 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 ir_instr_map = Vec::new(); // Maps Asm index to IR instruction (for symbols)
@ -232,9 +232,9 @@ impl<'a> BytecodeEmitter<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::module::{Module, Function};
use crate::ir::instr::{Instruction, InstrKind};
use crate::ir::types::Type;
use crate::ir_vm::module::{Module, Function};
use crate::ir_vm::instr::{Instruction, InstrKind};
use crate::ir_vm::types::Type;
use crate::ir_core::ids::FunctionId;
use crate::ir_core::const_pool::ConstantValue;
use crate::common::files::FileManager;

View File

@ -8,7 +8,7 @@ use crate::common::config::ProjectConfig;
use crate::common::files::FileManager;
use crate::common::symbols::Symbol;
use crate::frontends::Frontend;
use crate::ir;
use crate::ir_vm;
use anyhow::Result;
use std::path::Path;
@ -87,7 +87,7 @@ pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
// 3. IR Validation
// Ensures the generated IR is sound and doesn't violate any VM constraints
// 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))?;
// 4. Emit Bytecode

View File

@ -1,5 +1,5 @@
use crate::common::diagnostics::DiagnosticBundle;
use crate::ir;
use crate::ir_vm;
use std::path::Path;
use crate::common::files::FileManager;
@ -13,5 +13,5 @@ pub trait Frontend {
&self,
entry: &Path,
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::files::FileManager;
use crate::frontends::Frontend;
use crate::ir;
use crate::ir_vm;
use crate::lowering::core_to_vm;
use std::path::Path;
@ -36,7 +36,7 @@ impl Frontend for PbsFrontend {
&self,
entry: &Path,
file_manager: &mut FileManager,
) -> Result<ir::Module, DiagnosticBundle> {
) -> Result<ir_vm::Module, DiagnosticBundle> {
let source = std::fs::read_to_string(entry).map_err(|e| {
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
//! than the source code AST. It is organized into Modules, Functions, and Globals.
use crate::ir::instr::Instruction;
use crate::ir::types::Type;
use crate::ir_vm::instr::Instruction;
use crate::ir_vm::types::Type;
use crate::ir_core::const_pool::ConstPool;
use crate::ir_core::ids::FunctionId;
use serde::{Deserialize, Serialize};

View File

@ -1,5 +1,5 @@
use crate::common::diagnostics::DiagnosticBundle;
use crate::ir::module::Module;
use crate::ir_vm::module::Module;
pub fn validate_module(_module: &Module) -> Result<(), DiagnosticBundle> {
// 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.
pub mod common;
pub mod ir;
pub mod ir_vm;
pub mod ir_core;
pub mod lowering;
pub mod backend;

View File

@ -1,9 +1,9 @@
use crate::ir;
use crate::ir_vm;
use crate::ir_core;
use anyhow::Result;
/// 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.
// In the future, we might want to lower all modules and link them.
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.
pub fn lower_module(core_module: &ir_core::Module, const_pool: &ir_core::ConstPool) -> Result<ir::Module> {
let mut vm_module = ir::Module::new(core_module.name.clone());
pub fn lower_module(core_module: &ir_core::Module, const_pool: &ir_core::ConstPool) -> Result<ir_vm::Module> {
let mut vm_module = ir_vm::Module::new(core_module.name.clone());
vm_module.const_pool = const_pool.clone();
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.
pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
let mut vm_func = ir::Function {
pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function> {
let mut vm_func = ir_vm::Function {
id: core_func.id,
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(),
r#type: lower_type(&p.ty),
}).collect(),
@ -40,62 +40,62 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
for block in &core_func.blocks {
// Core blocks map to labels in the flat VM IR instruction list.
vm_func.body.push(ir::Instruction::new(
ir::InstrKind::Label(ir::Label(format!("block_{}", block.id))),
vm_func.body.push(ir_vm::Instruction::new(
ir_vm::InstrKind::Label(ir_vm::Label(format!("block_{}", block.id))),
None,
));
for instr in &block.instrs {
let kind = match instr {
ir_core::Instr::PushConst(id) => ir::InstrKind::PushConst(*id),
ir_core::Instr::Call(func_id, arg_count) => ir::InstrKind::Call {
ir_core::Instr::PushConst(id) => ir_vm::InstrKind::PushConst(*id),
ir_core::Instr::Call(func_id, arg_count) => ir_vm::InstrKind::Call {
func_id: *func_id,
arg_count: *arg_count
},
ir_core::Instr::Syscall(id) => ir::InstrKind::Syscall(*id),
ir_core::Instr::GetLocal(slot) => ir::InstrKind::GetLocal(*slot),
ir_core::Instr::SetLocal(slot) => ir::InstrKind::SetLocal(*slot),
ir_core::Instr::Pop => ir::InstrKind::Pop,
ir_core::Instr::Dup => ir::InstrKind::Dup,
ir_core::Instr::Add => ir::InstrKind::Add,
ir_core::Instr::Sub => ir::InstrKind::Sub,
ir_core::Instr::Mul => ir::InstrKind::Mul,
ir_core::Instr::Div => ir::InstrKind::Div,
ir_core::Instr::Neg => ir::InstrKind::Neg,
ir_core::Instr::Eq => ir::InstrKind::Eq,
ir_core::Instr::Neq => ir::InstrKind::Neq,
ir_core::Instr::Lt => ir::InstrKind::Lt,
ir_core::Instr::Lte => ir::InstrKind::Lte,
ir_core::Instr::Gt => ir::InstrKind::Gt,
ir_core::Instr::Gte => ir::InstrKind::Gte,
ir_core::Instr::And => ir::InstrKind::And,
ir_core::Instr::Or => ir::InstrKind::Or,
ir_core::Instr::Not => ir::InstrKind::Not,
ir_core::Instr::Alloc => ir::InstrKind::Alloc,
ir_core::Instr::ReadGate => ir::InstrKind::LoadRef(0),
ir_core::Instr::WriteGate => ir::InstrKind::StoreRef(0),
ir_core::Instr::Free => ir::InstrKind::Nop,
ir_core::Instr::Syscall(id) => ir_vm::InstrKind::Syscall(*id),
ir_core::Instr::GetLocal(slot) => ir_vm::InstrKind::GetLocal(*slot),
ir_core::Instr::SetLocal(slot) => ir_vm::InstrKind::SetLocal(*slot),
ir_core::Instr::Pop => ir_vm::InstrKind::Pop,
ir_core::Instr::Dup => ir_vm::InstrKind::Dup,
ir_core::Instr::Add => ir_vm::InstrKind::Add,
ir_core::Instr::Sub => ir_vm::InstrKind::Sub,
ir_core::Instr::Mul => ir_vm::InstrKind::Mul,
ir_core::Instr::Div => ir_vm::InstrKind::Div,
ir_core::Instr::Neg => ir_vm::InstrKind::Neg,
ir_core::Instr::Eq => ir_vm::InstrKind::Eq,
ir_core::Instr::Neq => ir_vm::InstrKind::Neq,
ir_core::Instr::Lt => ir_vm::InstrKind::Lt,
ir_core::Instr::Lte => ir_vm::InstrKind::Lte,
ir_core::Instr::Gt => ir_vm::InstrKind::Gt,
ir_core::Instr::Gte => ir_vm::InstrKind::Gte,
ir_core::Instr::And => ir_vm::InstrKind::And,
ir_core::Instr::Or => ir_vm::InstrKind::Or,
ir_core::Instr::Not => ir_vm::InstrKind::Not,
ir_core::Instr::Alloc => ir_vm::InstrKind::Alloc,
ir_core::Instr::ReadGate => ir_vm::InstrKind::LoadRef(0),
ir_core::Instr::WriteGate => ir_vm::InstrKind::StoreRef(0),
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 {
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) => {
vm_func.body.push(ir::Instruction::new(
ir::InstrKind::Jmp(ir::Label(format!("block_{}", target))),
vm_func.body.push(ir_vm::Instruction::new(
ir_vm::InstrKind::Jmp(ir_vm::Label(format!("block_{}", target))),
None,
));
}
ir_core::Terminator::JumpIfFalse { target, else_target } => {
vm_func.body.push(ir::Instruction::new(
ir::InstrKind::JmpIfFalse(ir::Label(format!("block_{}", target))),
vm_func.body.push(ir_vm::Instruction::new(
ir_vm::InstrKind::JmpIfFalse(ir_vm::Label(format!("block_{}", target))),
None,
));
vm_func.body.push(ir::Instruction::new(
ir::InstrKind::Jmp(ir::Label(format!("block_{}", else_target))),
vm_func.body.push(ir_vm::Instruction::new(
ir_vm::InstrKind::Jmp(ir_vm::Label(format!("block_{}", else_target))),
None,
));
}
@ -105,20 +105,20 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir::Function> {
Ok(vm_func)
}
fn lower_type(ty: &ir_core::Type) -> ir::Type {
fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
match ty {
ir_core::Type::Void => ir::Type::Void,
ir_core::Type::Int => ir::Type::Int,
ir_core::Type::Float => ir::Type::Float,
ir_core::Type::Bool => ir::Type::Bool,
ir_core::Type::String => ir::Type::String,
ir_core::Type::Optional(inner) => ir::Type::Array(Box::new(lower_type(inner))), // Approximation
ir_core::Type::Void => ir_vm::Type::Void,
ir_core::Type::Int => ir_vm::Type::Int,
ir_core::Type::Float => ir_vm::Type::Float,
ir_core::Type::Bool => ir_vm::Type::Bool,
ir_core::Type::String => ir_vm::Type::String,
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::Struct(_) => ir::Type::Object,
ir_core::Type::Service(_) => ir::Type::Object,
ir_core::Type::Contract(_) => ir::Type::Object,
ir_core::Type::ErrorType(_) => ir::Type::Object,
ir_core::Type::Function { .. } => ir::Type::Function,
ir_core::Type::Struct(_) => ir_vm::Type::Object,
ir_core::Type::Service(_) => ir_vm::Type::Object,
ir_core::Type::Contract(_) => ir_vm::Type::Object,
ir_core::Type::ErrorType(_) => ir_vm::Type::Object,
ir_core::Type::Function { .. } => ir_vm::Type::Function,
}
}
@ -127,7 +127,7 @@ mod tests {
use super::*;
use crate::ir_core;
use crate::ir_core::*;
use crate::ir::*;
use crate::ir_vm::*;
#[test]
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**.
>
> **Hard rules:**
>
> * Each PR must compile and pass tests.
> * Each PR must include tests.
> * No speculative features.
> * Follow the `Prometeu Base Script (PBS) - Implementation Spec`.
> * 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.
> * PBS frontend is implemented and produces Core IR.
> * Bytecode stability is a hard requirement.
> * VM currently has stack + const pool; heap exists but is unused.
> * HIP semantics (gates/storage) are currently **incorrectly lowered**.
> * `ir_vm` is feature-frozen at the moment. we are going to validate only `ir_core`
> * Lowering is the only place `ir_core` and `ir_vm` touch each other.
> - VM IR is never imported from Core IR.
> - Core IR never imports VM IR.
---
## Global Architectural Direction (Non-negotiable)
## Global Rules (Read Before Any PR)
* PBS is the **primary language**.
* Frontend is implemented **before** runtime integration.
* Architecture uses **two IR layers**:
* **Core IR** (PBS-semantic, typed, resolved)
* **VM IR** (stack-based, backend-friendly)
* VM IR remains simple and stable.
* Lowering is explicit and testable.
1. **No new features.** Only semantic correction.
2. **No new VM opcodes yet.** VM changes come later.
3. **No fallback values** (e.g. `FunctionId(0)`). Fail with diagnostics.
4. **Every PR must include tests** (golden or unit).
5. **Core IR is the source of semantic truth.**
---
# PR-01 — ProjectConfig and Frontend Selection
# PR-20 — Core IR: Make HIP Semantics Explicit (No Handle Loss)
### 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`
* Fields (v0):
### Required Changes
* `script_fe: "pbs"`
* `entry: "main.pbs"`
* Refactor compiler entry point to:
#### 1. Extend Core IR instructions
* load config
* select frontend by `script_fe`
* resolve entry path relative to project root
Add explicit HIP instructions:
### Files Likely Touched
```rust
enum CoreInstr {
// existing …
* `compiler/mod.rs`
* `compiler/driver.rs`
* `common/config.rs` (new)
Alloc { ty: TypeId, slots: u32 },
### Tests (mandatory)
BeginPeek { gate: ValueId },
BeginBorrow { gate: ValueId },
BeginMutate { gate: ValueId },
* unit test: load valid `prometeu.json`
* unit test: invalid frontend → diagnostic
* integration test: project root + entry resolution
EndPeek,
EndBorrow,
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
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`
* Define minimal structures:
* Lower PBS `peek``BeginPeek` / `EndPeek`
* Lower PBS `borrow``BeginBorrow` / `EndBorrow`
* Lower PBS `mutate``BeginMutate` / `EndMutate`
* `Program`
* `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
These **must not** share the same lowering path.
### Tests
* construct Core IR manually in tests
* snapshot test (JSON) for deterministic shape
* PBS snippet with all three operations
* Assert distinct Core IR instruction sequences
---
# PR-03 — Constant Pool and IDs
# PR-22 — Make Allocation Shape Explicit in Core IR
### 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
* numbers
* Replace inline literals in VM IR with `ConstId`
* Update existing VM IR to accept `PushConst(ConstId)`
```rust
Alloc { ty: TypeId, slots: u32 }
```
Rules:
* `TypeId` comes from frontend type checking
* `slots` is derived deterministically (struct fields / array size)
### Tests
* const pool deduplication
* deterministic ConstId assignment
* IR snapshot stability
* Allocating storage struct emits correct `slots`
* Allocating array emits correct `slots`
---
# PR-04 — VM IR Cleanup (Stabilization)
# PR-23 — Eliminate Invalid Call Fallbacks
### 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`
* Ensure locals are accessed via slots
* Remove or internalize `PushScope` / `PopScope`
* Remove **all** fallbacks to `FunctionId(0)` or equivalent
* On unresolved symbols during lowering:
* Emit canonical diagnostic (`E_RESOLVE_UNDEFINED` or `E_LOWER_UNSUPPORTED`)
* Abort lowering
### Tests
* golden VM IR tests
* lowering smoke test (Core IR → VM IR)
* PBS program calling missing function → compile error
* 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
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`
* Lowering rules:
* During PBS type checking:
* Core blocks → labels
* Core calls → VM calls
* Host calls preserved
* No PBS frontend yet
* Validate argument count against contract signature
* Validate argument types
* Lower only validated calls to `HostCall`
### Tests
* lowering correctness
* instruction ordering
* label resolution
* Wrong arity → `E_TYPE_MISMATCH`
* Correct call lowers to Core IR `HostCall`
---
# PR-06 — PBS Frontend: Lexer
# PR-25 — Core IR Invariants Test Suite
### Goal
Implement PBS lexer according to the spec.
Lock in correct semantics before touching the VM.
### Scope
### Required Invariants
* Token kinds
* Keyword table
* Span tracking
* Every `Begin*` has a matching `End*`
* Gate passed to `Begin*` is available at `End*`
* No storage writes without `BeginMutate`
* No silent fallbacks
### Tests
* tokenization tests
* keyword vs identifier tests
* bounded literals
* Property-style tests or golden IR assertions
---
# 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
* Top-level declarations
* Blocks
* Expressions (calls, literals, control flow)
### Tests
* valid programs
* syntax error recovery
Any VM work before this is a hard rejection.
---
# 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
* 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.
This document is binding.