pr 20
This commit is contained in:
parent
c34166435f
commit
fe00eda925
@ -159,7 +159,9 @@ mod tests {
|
||||
|
||||
assert!(opcodes.contains(&OpCode::Alloc));
|
||||
assert!(opcodes.contains(&OpCode::LoadRef));
|
||||
assert!(opcodes.contains(&OpCode::StoreRef));
|
||||
// After PR-20, BeginMutate/EndMutate map to LoadRef/Nop for now
|
||||
// because VM is feature-frozen. StoreRef is removed from lowering.
|
||||
assert!(opcodes.contains(&OpCode::Nop));
|
||||
assert!(opcodes.contains(&OpCode::Add));
|
||||
assert!(opcodes.contains(&OpCode::Ret));
|
||||
}
|
||||
@ -238,15 +240,15 @@ mod tests {
|
||||
0064 Alloc
|
||||
0066 SetLocal U32(1)
|
||||
006C GetLocal U32(1)
|
||||
0072 LoadRef U32(0)
|
||||
0078 SetLocal U32(2)
|
||||
007E GetLocal U32(2)
|
||||
0084 PushConst U32(5)
|
||||
008A Add
|
||||
008C SetLocal U32(3)
|
||||
0092 GetLocal U32(2)
|
||||
0098 StoreRef U32(0)
|
||||
009E Ret
|
||||
0072 SetLocal U32(2)
|
||||
0078 LoadRef U32(0)
|
||||
007E SetLocal U32(3)
|
||||
0084 GetLocal U32(3)
|
||||
008A PushConst U32(5)
|
||||
0090 Add
|
||||
0092 SetLocal U32(4)
|
||||
0098 Nop
|
||||
009A Ret
|
||||
"#;
|
||||
|
||||
assert_eq!(disasm_text, expected_disasm);
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::frontends::pbs::ast::*;
|
||||
use crate::frontends::pbs::symbols::*;
|
||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||
use crate::ir_core;
|
||||
use crate::ir_core::ids::FunctionId;
|
||||
use crate::ir_core::ids::{FunctionId, TypeId, ValueId};
|
||||
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -135,40 +135,58 @@ impl<'a> Lowerer<'a> {
|
||||
Node::Unary(n) => self.lower_unary(n),
|
||||
Node::IfExpr(n) => self.lower_if_expr(n),
|
||||
Node::Alloc(n) => self.lower_alloc(n),
|
||||
Node::Mutate(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, true),
|
||||
Node::Borrow(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
Node::Peek(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, false),
|
||||
Node::Mutate(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, "mutate"),
|
||||
Node::Borrow(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, "borrow"),
|
||||
Node::Peek(n) => self.lower_hip(n.span, &n.target, &n.binding, &n.body, "peek"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_alloc(&mut self, _n: &AllocNode) {
|
||||
// Allocation: Push type descriptor? For v0 just emit Alloc
|
||||
self.emit(Instr::Alloc);
|
||||
// Allocation: Now requires explicit TypeId and slots.
|
||||
// v0 approximation: TypeId(0), slots: 1.
|
||||
// Proper derivation will be added in PR-22.
|
||||
self.emit(Instr::Alloc { ty: TypeId(0), slots: 1 });
|
||||
}
|
||||
|
||||
fn lower_hip(&mut self, _span: crate::common::spans::Span, target: &Node, binding: &str, body: &Node, is_mutate: bool) {
|
||||
// HIP Access Pattern:
|
||||
fn lower_hip(&mut self, _span: crate::common::spans::Span, target: &Node, binding: &str, body: &Node, op: &str) {
|
||||
// HIP Access Pattern (Explicit Semantics):
|
||||
// 1. Evaluate target (gate)
|
||||
self.lower_node(target);
|
||||
// 2. ReadGate (pops gate, pushes reference/value)
|
||||
self.emit(Instr::ReadGate);
|
||||
// 3. Bind to local
|
||||
let slot = self.get_next_local_slot();
|
||||
self.local_vars.push(HashMap::new());
|
||||
self.local_vars.last_mut().unwrap().insert(binding.to_string(), slot);
|
||||
self.emit(Instr::SetLocal(slot));
|
||||
|
||||
// 4. Body
|
||||
// 2. Preserve gate identity.
|
||||
// We MUST NOT lose the gate, as storage access must be mediated by it.
|
||||
let gate_slot = self.get_next_local_slot();
|
||||
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
||||
self.emit(Instr::SetLocal(gate_slot));
|
||||
|
||||
// 3. Begin Operation.
|
||||
// This instruction reads the gate from the local and pushes a view.
|
||||
let instr = match op {
|
||||
"peek" => Instr::BeginPeek { gate: ValueId(gate_slot) },
|
||||
"borrow" => Instr::BeginBorrow { gate: ValueId(gate_slot) },
|
||||
"mutate" => Instr::BeginMutate { gate: ValueId(gate_slot) },
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.emit(instr);
|
||||
|
||||
// 4. Bind view to local
|
||||
self.local_vars.push(HashMap::new());
|
||||
let view_slot = self.get_next_local_slot();
|
||||
self.local_vars.last_mut().unwrap().insert(binding.to_string(), view_slot);
|
||||
self.emit(Instr::SetLocal(view_slot));
|
||||
|
||||
// 5. Body
|
||||
self.lower_node(body);
|
||||
|
||||
// 5. Cleanup / WriteBack
|
||||
if is_mutate {
|
||||
// Need the gate again? This is IR-design dependent.
|
||||
// Let's assume WriteGate pops value and use some internal mechanism for gate.
|
||||
self.emit(Instr::GetLocal(slot));
|
||||
self.emit(Instr::WriteGate);
|
||||
}
|
||||
// 6. End Operation
|
||||
let end_instr = match op {
|
||||
"peek" => Instr::EndPeek,
|
||||
"borrow" => Instr::EndBorrow,
|
||||
"mutate" => Instr::EndMutate,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.emit(end_instr);
|
||||
|
||||
self.local_vars.pop();
|
||||
}
|
||||
@ -466,9 +484,45 @@ mod tests {
|
||||
let func = &program.modules[0].functions[0];
|
||||
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc)));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::ReadGate)));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::WriteGate)));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc { .. })));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::BeginMutate { .. })));
|
||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::EndMutate)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hip_lowering_golden() {
|
||||
let code = "
|
||||
fn test_hip() {
|
||||
let g = alloc int;
|
||||
mutate g as x {
|
||||
let y = x + 1;
|
||||
}
|
||||
}
|
||||
";
|
||||
let mut parser = Parser::new(code, 0);
|
||||
let ast = parser.parse_file().expect("Failed to parse");
|
||||
|
||||
let mut collector = SymbolCollector::new();
|
||||
let (type_symbols, value_symbols) = collector.collect(&ast).unwrap();
|
||||
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||
|
||||
let lowerer = Lowerer::new(&module_symbols);
|
||||
let program = lowerer.lower_file(&ast, "test");
|
||||
|
||||
let json = serde_json::to_string_pretty(&program).unwrap();
|
||||
|
||||
// Assertions for PR-20 HIP Semantics:
|
||||
// 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0))
|
||||
// 2. BeginMutate uses that local (BeginMutate { gate: 1 })
|
||||
// 3. EndMutate exists
|
||||
// 4. No ReadGate/WriteGate (they were removed from Instr)
|
||||
|
||||
assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local");
|
||||
assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate");
|
||||
assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local");
|
||||
assert!(json.contains("\"EndMutate\""), "Should have EndMutate");
|
||||
assert!(!json.contains("ReadGate"), "ReadGate should be gone");
|
||||
assert!(!json.contains("WriteGate"), "WriteGate should be gone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -14,3 +14,8 @@ pub struct ConstId(pub u32);
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TypeId(pub u32);
|
||||
|
||||
/// Unique identifier for a value (usually a local slot).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct ValueId(pub u32);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use super::ids::{ConstId, FunctionId};
|
||||
use super::ids::{ConstId, FunctionId, TypeId, ValueId};
|
||||
|
||||
/// Instructions within a basic block.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
@ -33,8 +33,12 @@ pub enum Instr {
|
||||
Or,
|
||||
Not,
|
||||
/// HIP operations.
|
||||
Alloc,
|
||||
Free, // Not used in v0 but good to have in Core IR
|
||||
ReadGate,
|
||||
WriteGate,
|
||||
Alloc { ty: TypeId, slots: u32 },
|
||||
BeginPeek { gate: ValueId },
|
||||
BeginBorrow { gate: ValueId },
|
||||
BeginMutate { gate: ValueId },
|
||||
EndPeek,
|
||||
EndBorrow,
|
||||
EndMutate,
|
||||
Free,
|
||||
}
|
||||
|
||||
@ -71,9 +71,13 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function>
|
||||
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::Alloc { .. } => ir_vm::InstrKind::Alloc,
|
||||
ir_core::Instr::BeginPeek { .. } |
|
||||
ir_core::Instr::BeginBorrow { .. } |
|
||||
ir_core::Instr::BeginMutate { .. } => ir_vm::InstrKind::LoadRef(0),
|
||||
ir_core::Instr::EndPeek |
|
||||
ir_core::Instr::EndBorrow |
|
||||
ir_core::Instr::EndMutate => ir_vm::InstrKind::Nop,
|
||||
ir_core::Instr::Free => ir_vm::InstrKind::Nop,
|
||||
};
|
||||
vm_func.body.push(ir_vm::Instruction::new(kind, None));
|
||||
|
||||
27
docs/specs/pbs/files/PRs para Junie Global.md
Normal file
27
docs/specs/pbs/files/PRs para Junie Global.md
Normal file
@ -0,0 +1,27 @@
|
||||
# PBS ⇄ VM Alignment — Junie PRs (HIP Semantics Hardening)
|
||||
|
||||
> **Purpose:** fix semantic mismatches between the PBS frontend (Core IR) and the VM **before** any VM heap/gate implementation.
|
||||
>
|
||||
> These PRs are **surgical**, **mandatory**, and **non-creative**.
|
||||
> Junie must follow them **exactly**.
|
||||
|
||||
> **Context:**
|
||||
>
|
||||
> * 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 Rules (Read Before Any PR)
|
||||
|
||||
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.**
|
||||
@ -1,84 +1,3 @@
|
||||
# PBS ⇄ VM Alignment — Junie PRs (HIP Semantics Hardening)
|
||||
|
||||
> **Purpose:** fix semantic mismatches between the PBS frontend (Core IR) and the VM **before** any VM heap/gate implementation.
|
||||
>
|
||||
> These PRs are **surgical**, **mandatory**, and **non-creative**.
|
||||
> Junie must follow them **exactly**.
|
||||
|
||||
> **Context:**
|
||||
>
|
||||
> * 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 Rules (Read Before Any PR)
|
||||
|
||||
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-20 — Core IR: Make HIP Semantics Explicit (No Handle Loss)
|
||||
|
||||
### Goal
|
||||
|
||||
Fix the Core IR so HIP operations never lose the destination gate.
|
||||
|
||||
### Problem (Current Bug)
|
||||
|
||||
Current lowering evaluates a gate, reads storage, stores the result in a local, and later attempts to write back **without having the gate anymore**.
|
||||
|
||||
This violates PBS semantics: storage access must always be mediated by the **original gate**.
|
||||
|
||||
### Required Changes
|
||||
|
||||
#### 1. Extend Core IR instructions
|
||||
|
||||
Add explicit HIP instructions:
|
||||
|
||||
```rust
|
||||
enum CoreInstr {
|
||||
// existing …
|
||||
|
||||
Alloc { ty: TypeId, slots: u32 },
|
||||
|
||||
BeginPeek { gate: ValueId },
|
||||
BeginBorrow { gate: ValueId },
|
||||
BeginMutate { gate: ValueId },
|
||||
|
||||
EndPeek,
|
||||
EndBorrow,
|
||||
EndMutate,
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* `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-21 — Distinguish `peek`, `borrow`, and `mutate` in Core IR
|
||||
|
||||
### Goal
|
||||
Loading…
x
Reference in New Issue
Block a user