feat: MIR TypeOp/WeakRef/Barrier PoC implementation
- Add TypeOpKind, WeakRefOp, BarrierOp enums for unified instructions - Implement TypeOp instruction combining TypeCheck/Cast - Implement WeakRef instruction combining WeakNew/WeakLoad - Implement Barrier instruction combining BarrierRead/BarrierWrite - Update VM to handle new unified instructions - Update MIR printer for new instruction formats - Add feature flags mir_typeop_poc and mir_refbarrier_unify_poc - Maintain backward compatibility with legacy instructions This is Phase 8.5 MIR instruction diet PoC implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -456,6 +456,22 @@ impl VM {
|
||||
println!("{}", val.to_string());
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
|
||||
MirInstruction::TypeOp { dst, op, value, ty: _ } => {
|
||||
// PoC: mirror current semantics
|
||||
match op {
|
||||
crate::mir::TypeOpKind::Check => {
|
||||
// Current TypeCheck is a no-op that returns true
|
||||
self.set_value(*dst, VMValue::Bool(true));
|
||||
}
|
||||
crate::mir::TypeOpKind::Cast => {
|
||||
// Current Cast is a copy/no-op
|
||||
let v = self.get_value(*value)?;
|
||||
self.set_value(*dst, v);
|
||||
}
|
||||
}
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
|
||||
MirInstruction::Return { value } => {
|
||||
let return_value = if let Some(val_id) = value {
|
||||
@ -831,6 +847,25 @@ impl VM {
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
|
||||
// Unified PoC ops mapped to legacy behavior
|
||||
MirInstruction::WeakRef { dst, op, value } => {
|
||||
match op {
|
||||
crate::mir::WeakRefOp::New => {
|
||||
let v = self.get_value(*value)?;
|
||||
self.set_value(*dst, v);
|
||||
}
|
||||
crate::mir::WeakRefOp::Load => {
|
||||
let v = self.get_value(*value)?;
|
||||
self.set_value(*dst, v);
|
||||
}
|
||||
}
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
MirInstruction::Barrier { .. } => {
|
||||
// No-op
|
||||
Ok(ControlFlow::Continue)
|
||||
},
|
||||
|
||||
MirInstruction::BarrierRead { ptr: _ } => {
|
||||
// Memory barrier read is a no-op for now
|
||||
// In a real implementation, this would ensure memory ordering
|
||||
@ -1085,6 +1120,7 @@ impl VM {
|
||||
MirInstruction::NewBox { .. } => "NewBox",
|
||||
MirInstruction::TypeCheck { .. } => "TypeCheck",
|
||||
MirInstruction::Cast { .. } => "Cast",
|
||||
MirInstruction::TypeOp { .. } => "TypeOp",
|
||||
MirInstruction::ArrayGet { .. } => "ArrayGet",
|
||||
MirInstruction::ArraySet { .. } => "ArraySet",
|
||||
MirInstruction::Copy { .. } => "Copy",
|
||||
@ -1101,6 +1137,8 @@ impl VM {
|
||||
MirInstruction::WeakLoad { .. } => "WeakLoad",
|
||||
MirInstruction::BarrierRead { .. } => "BarrierRead",
|
||||
MirInstruction::BarrierWrite { .. } => "BarrierWrite",
|
||||
MirInstruction::WeakRef { .. } => "WeakRef",
|
||||
MirInstruction::Barrier { .. } => "Barrier",
|
||||
MirInstruction::FutureNew { .. } => "FutureNew",
|
||||
MirInstruction::FutureSet { .. } => "FutureSet",
|
||||
MirInstruction::Await { .. } => "Await",
|
||||
|
||||
@ -136,6 +136,16 @@ pub enum MirInstruction {
|
||||
value: ValueId,
|
||||
target_type: MirType,
|
||||
},
|
||||
|
||||
// === Type Operations (Unified PoC) ===
|
||||
/// Unified type operation (PoC): Check or Cast
|
||||
/// `%dst = typeop(check|cast, %value, Type)`
|
||||
TypeOp {
|
||||
dst: ValueId,
|
||||
op: TypeOpKind,
|
||||
value: ValueId,
|
||||
ty: MirType,
|
||||
},
|
||||
|
||||
// === Array Operations ===
|
||||
/// Get array element
|
||||
@ -250,6 +260,22 @@ pub enum MirInstruction {
|
||||
BarrierWrite {
|
||||
ptr: ValueId,
|
||||
},
|
||||
|
||||
// === Unified PoC: WeakRef/Barrier (flags-only scaffolding) ===
|
||||
/// Unified weak reference op (PoC)
|
||||
/// `%dst = weakref new %box` or `%dst = weakref load %weak`
|
||||
WeakRef {
|
||||
dst: ValueId,
|
||||
op: WeakRefOp,
|
||||
value: ValueId,
|
||||
},
|
||||
|
||||
/// Unified barrier op (PoC)
|
||||
/// `barrier read %ptr` or `barrier write %ptr`
|
||||
Barrier {
|
||||
op: BarrierOp,
|
||||
ptr: ValueId,
|
||||
},
|
||||
|
||||
// === Phase 7: Async/Future Operations ===
|
||||
|
||||
@ -354,6 +380,7 @@ impl MirInstruction {
|
||||
MirInstruction::UnaryOp { .. } |
|
||||
MirInstruction::Compare { .. } |
|
||||
MirInstruction::Cast { .. } |
|
||||
MirInstruction::TypeOp { .. } |
|
||||
MirInstruction::Copy { .. } |
|
||||
MirInstruction::Phi { .. } |
|
||||
MirInstruction::TypeCheck { .. } |
|
||||
@ -396,6 +423,15 @@ impl MirInstruction {
|
||||
MirInstruction::WeakLoad { .. } => EffectMask::READ, // Loading weak ref has read effects
|
||||
MirInstruction::BarrierRead { .. } => EffectMask::READ.add(Effect::Barrier), // Memory barrier with read
|
||||
MirInstruction::BarrierWrite { .. } => EffectMask::WRITE.add(Effect::Barrier), // Memory barrier with write
|
||||
// PoC unified ops mirror legacy effects
|
||||
MirInstruction::WeakRef { op, .. } => match op {
|
||||
WeakRefOp::New => EffectMask::PURE,
|
||||
WeakRefOp::Load => EffectMask::READ,
|
||||
},
|
||||
MirInstruction::Barrier { op, .. } => match op {
|
||||
BarrierOp::Read => EffectMask::READ.add(Effect::Barrier),
|
||||
BarrierOp::Write => EffectMask::WRITE.add(Effect::Barrier),
|
||||
},
|
||||
|
||||
// Phase 7: Async/Future Operations
|
||||
MirInstruction::FutureNew { .. } => EffectMask::PURE.add(Effect::Alloc), // Creating future may allocate
|
||||
@ -419,12 +455,14 @@ impl MirInstruction {
|
||||
MirInstruction::NewBox { dst, .. } |
|
||||
MirInstruction::TypeCheck { dst, .. } |
|
||||
MirInstruction::Cast { dst, .. } |
|
||||
MirInstruction::TypeOp { dst, .. } |
|
||||
MirInstruction::ArrayGet { dst, .. } |
|
||||
MirInstruction::Copy { dst, .. } |
|
||||
MirInstruction::RefNew { dst, .. } |
|
||||
MirInstruction::RefGet { dst, .. } |
|
||||
MirInstruction::WeakNew { dst, .. } |
|
||||
MirInstruction::WeakLoad { dst, .. } |
|
||||
MirInstruction::WeakRef { dst, .. } |
|
||||
MirInstruction::FutureNew { dst, .. } |
|
||||
MirInstruction::Await { dst, .. } => Some(*dst),
|
||||
|
||||
@ -443,6 +481,7 @@ impl MirInstruction {
|
||||
MirInstruction::RefSet { .. } |
|
||||
MirInstruction::BarrierRead { .. } |
|
||||
MirInstruction::BarrierWrite { .. } |
|
||||
MirInstruction::Barrier { .. } |
|
||||
MirInstruction::FutureSet { .. } |
|
||||
MirInstruction::Safepoint |
|
||||
MirInstruction::Nop => None,
|
||||
@ -462,6 +501,7 @@ impl MirInstruction {
|
||||
MirInstruction::Load { ptr: operand, .. } |
|
||||
MirInstruction::TypeCheck { value: operand, .. } |
|
||||
MirInstruction::Cast { value: operand, .. } |
|
||||
MirInstruction::TypeOp { value: operand, .. } |
|
||||
MirInstruction::Copy { src: operand, .. } |
|
||||
MirInstruction::Debug { value: operand, .. } |
|
||||
MirInstruction::Print { value: operand, .. } => vec![*operand],
|
||||
@ -511,6 +551,8 @@ impl MirInstruction {
|
||||
MirInstruction::WeakLoad { weak_ref, .. } => vec![*weak_ref],
|
||||
MirInstruction::BarrierRead { ptr } => vec![*ptr],
|
||||
MirInstruction::BarrierWrite { ptr } => vec![*ptr],
|
||||
MirInstruction::WeakRef { value, .. } => vec![*value],
|
||||
MirInstruction::Barrier { ptr, .. } => vec![*ptr],
|
||||
|
||||
// Phase 7: Async/Future Operations
|
||||
MirInstruction::FutureNew { value, .. } => vec![*value],
|
||||
@ -523,6 +565,21 @@ impl MirInstruction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of unified type operation (PoC)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TypeOpKind {
|
||||
Check,
|
||||
Cast,
|
||||
}
|
||||
|
||||
/// Kind of unified weak reference operation (PoC)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WeakRefOp { New, Load }
|
||||
|
||||
/// Kind of unified barrier operation (PoC)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BarrierOp { Read, Write }
|
||||
|
||||
impl ConstValue {
|
||||
/*
|
||||
/// Convert to NyashValue
|
||||
@ -790,4 +847,4 @@ mod tests {
|
||||
assert_eq!(void_inst.dst_value(), None);
|
||||
assert_eq!(void_inst.used_values(), vec![arg1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ pub mod value_id;
|
||||
pub mod effect;
|
||||
|
||||
// Re-export main types for easy access
|
||||
pub use instruction::{MirInstruction, BinaryOp, CompareOp, UnaryOp, ConstValue, MirType};
|
||||
pub use instruction::{MirInstruction, BinaryOp, CompareOp, UnaryOp, ConstValue, MirType, TypeOpKind, WeakRefOp, BarrierOp};
|
||||
pub use instruction_v2::{MirInstructionV2, AtomicOrdering}; // New 25-instruction set
|
||||
pub use basic_block::{BasicBlock, BasicBlockId, BasicBlockIdGenerator};
|
||||
pub use function::{MirFunction, MirModule, FunctionSignature};
|
||||
|
||||
@ -279,6 +279,11 @@ impl MirPrinter {
|
||||
format!("{} = cast {} to {:?}", dst, value, target_type)
|
||||
},
|
||||
|
||||
MirInstruction::TypeOp { dst, op, value, ty } => {
|
||||
let op_str = match op { super::TypeOpKind::Check => "check", super::TypeOpKind::Cast => "cast" };
|
||||
format!("{} = typeop {} {} {:?}", dst, op_str, value, ty)
|
||||
},
|
||||
|
||||
MirInstruction::ArrayGet { dst, array, index } => {
|
||||
format!("{} = {}[{}]", dst, array, index)
|
||||
},
|
||||
@ -349,6 +354,16 @@ impl MirPrinter {
|
||||
format!("barrier_write {}", ptr)
|
||||
},
|
||||
|
||||
MirInstruction::WeakRef { dst, op, value } => {
|
||||
let op_str = match op { super::WeakRefOp::New => "new", super::WeakRefOp::Load => "load" };
|
||||
format!("{} = weakref {} {}", dst, op_str, value)
|
||||
},
|
||||
|
||||
MirInstruction::Barrier { op, ptr } => {
|
||||
let op_str = match op { super::BarrierOp::Read => "read", super::BarrierOp::Write => "write" };
|
||||
format!("barrier {} {}", op_str, ptr)
|
||||
},
|
||||
|
||||
// Phase 7: Async/Future Operations
|
||||
MirInstruction::FutureNew { dst, value } => {
|
||||
format!("{} = future_new {}", dst, value)
|
||||
@ -439,4 +454,4 @@ mod tests {
|
||||
|
||||
assert!(output.contains("Module Statistics"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,38 +163,34 @@ impl MirVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify dominance relations
|
||||
/// Verify dominance relations (def must dominate use across blocks)
|
||||
fn verify_dominance(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
// This is a simplified dominance check
|
||||
// In a full implementation, we would compute the dominator tree
|
||||
let errors = Vec::new();
|
||||
|
||||
// For now, just check that values are defined before use in the same block
|
||||
for (_block_id, block) in &function.blocks {
|
||||
let mut defined_in_block = HashSet::new();
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Build def -> block map and dominators
|
||||
let def_block = self.compute_def_blocks(function);
|
||||
let dominators = self.compute_dominators(function);
|
||||
|
||||
for (use_block_id, block) in &function.blocks {
|
||||
for instruction in block.all_instructions() {
|
||||
// Check uses
|
||||
for used_value in instruction.used_values() {
|
||||
if !defined_in_block.contains(&used_value) {
|
||||
// Value used before definition in this block
|
||||
// This is okay if it's defined in a dominating block
|
||||
// For simplicity, we'll skip this check for now
|
||||
if let Some(&def_bb) = def_block.get(&used_value) {
|
||||
if def_bb != *use_block_id {
|
||||
let doms = dominators.get(use_block_id).unwrap();
|
||||
if !doms.contains(&def_bb) {
|
||||
errors.push(VerificationError::DominatorViolation {
|
||||
value: used_value,
|
||||
use_block: *use_block_id,
|
||||
def_block: def_bb,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Record definition
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
defined_in_block.insert(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
|
||||
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||
}
|
||||
|
||||
/// Verify control flow graph integrity
|
||||
@ -234,22 +230,9 @@ impl MirVerifier {
|
||||
/// In merge blocks, values coming from predecessors must be routed through Phi.
|
||||
fn verify_merge_uses(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||
let mut errors = Vec::new();
|
||||
// Build predecessor map
|
||||
let mut preds: std::collections::HashMap<BasicBlockId, Vec<BasicBlockId>> = std::collections::HashMap::new();
|
||||
for (bid, block) in &function.blocks {
|
||||
for succ in &block.successors {
|
||||
preds.entry(*succ).or_default().push(*bid);
|
||||
}
|
||||
}
|
||||
// Build definition map (value -> def block)
|
||||
let mut def_block: std::collections::HashMap<ValueId, BasicBlockId> = std::collections::HashMap::new();
|
||||
for (bid, block) in &function.blocks {
|
||||
for inst in block.all_instructions() {
|
||||
if let Some(dst) = inst.dst_value() {
|
||||
def_block.insert(dst, *bid);
|
||||
}
|
||||
}
|
||||
}
|
||||
let preds = self.compute_predecessors(function);
|
||||
let def_block = self.compute_def_blocks(function);
|
||||
let dominators = self.compute_dominators(function);
|
||||
// Helper: collect phi dsts in a block
|
||||
let mut phi_dsts_in_block: std::collections::HashMap<BasicBlockId, std::collections::HashSet<ValueId>> = std::collections::HashMap::new();
|
||||
for (bid, block) in &function.blocks {
|
||||
@ -263,12 +246,13 @@ impl MirVerifier {
|
||||
let Some(pred_list) = preds.get(bid) else { continue };
|
||||
if pred_list.len() < 2 { continue; }
|
||||
let phi_dsts = phi_dsts_in_block.get(bid);
|
||||
let doms_of_block = dominators.get(bid).unwrap();
|
||||
// check instructions including terminator
|
||||
for inst in block.all_instructions() {
|
||||
for used in inst.used_values() {
|
||||
if let Some(&db) = def_block.get(&used) {
|
||||
if pred_list.contains(&db) {
|
||||
// used value defined in a predecessor; must be routed via phi (i.e., used should be phi dst)
|
||||
// If def doesn't dominate merge block, it must be routed via phi
|
||||
if !doms_of_block.contains(&db) {
|
||||
let is_phi_dst = phi_dsts.map(|s| s.contains(&used)).unwrap_or(false);
|
||||
if !is_phi_dst {
|
||||
errors.push(VerificationError::MergeUsesPredecessorValue {
|
||||
@ -334,6 +318,70 @@ impl MirVerifier {
|
||||
pub fn clear_errors(&mut self) {
|
||||
self.errors.clear();
|
||||
}
|
||||
|
||||
/// Build predecessor map for all blocks
|
||||
fn compute_predecessors(&self, function: &MirFunction) -> HashMap<BasicBlockId, Vec<BasicBlockId>> {
|
||||
let mut preds: HashMap<BasicBlockId, Vec<BasicBlockId>> = HashMap::new();
|
||||
for (bid, block) in &function.blocks {
|
||||
for succ in &block.successors {
|
||||
preds.entry(*succ).or_default().push(*bid);
|
||||
}
|
||||
}
|
||||
preds
|
||||
}
|
||||
|
||||
/// Build a map from ValueId to its defining block
|
||||
fn compute_def_blocks(&self, function: &MirFunction) -> HashMap<ValueId, BasicBlockId> {
|
||||
let mut def_block: HashMap<ValueId, BasicBlockId> = HashMap::new();
|
||||
for (bid, block) in &function.blocks {
|
||||
for inst in block.all_instructions() {
|
||||
if let Some(dst) = inst.dst_value() { def_block.insert(dst, *bid); }
|
||||
}
|
||||
}
|
||||
def_block
|
||||
}
|
||||
|
||||
/// Compute dominator sets per block using standard iterative algorithm
|
||||
fn compute_dominators(&self, function: &MirFunction) -> HashMap<BasicBlockId, HashSet<BasicBlockId>> {
|
||||
let all_blocks: HashSet<BasicBlockId> = function.blocks.keys().copied().collect();
|
||||
let preds = self.compute_predecessors(function);
|
||||
let mut dom: HashMap<BasicBlockId, HashSet<BasicBlockId>> = HashMap::new();
|
||||
|
||||
for &b in function.blocks.keys() {
|
||||
if b == function.entry_block {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(b);
|
||||
dom.insert(b, set);
|
||||
} else {
|
||||
dom.insert(b, all_blocks.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
for &b in function.blocks.keys() {
|
||||
if b == function.entry_block { continue; }
|
||||
let mut new_set: HashSet<BasicBlockId> = all_blocks.clone();
|
||||
if let Some(ps) = preds.get(&b) {
|
||||
if !ps.is_empty() {
|
||||
for (i, p) in ps.iter().enumerate() {
|
||||
if let Some(p_set) = dom.get(p) {
|
||||
if i == 0 { new_set = p_set.clone(); }
|
||||
else { new_set = new_set.intersection(p_set).copied().collect(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
new_set.insert(b);
|
||||
if let Some(old) = dom.get(&b) {
|
||||
if &new_set != old { dom.insert(b, new_set); changed = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dom
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MirVerifier {
|
||||
@ -446,4 +494,57 @@ mod tests {
|
||||
let mir_text = printer.print_module(&module);
|
||||
assert!(mir_text.contains("phi"), "Printed MIR should contain a phi in merge block\n{}", mir_text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_use_before_phi_detected() {
|
||||
use crate::mir::{MirInstruction, ConstValue};
|
||||
|
||||
// Construct a function with a bad merge use (no phi)
|
||||
let signature = FunctionSignature {
|
||||
name: "merge_bad".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::String,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
|
||||
let entry = BasicBlockId::new(0);
|
||||
let mut f = MirFunction::new(signature, entry);
|
||||
|
||||
let then_bb = BasicBlockId::new(1);
|
||||
let else_bb = BasicBlockId::new(2);
|
||||
let merge_bb = BasicBlockId::new(3);
|
||||
|
||||
let cond = f.next_value_id(); // %0
|
||||
{
|
||||
let b0 = f.get_block_mut(entry).unwrap();
|
||||
b0.add_instruction(MirInstruction::Const { dst: cond, value: ConstValue::Bool(true) });
|
||||
b0.add_instruction(MirInstruction::Branch { condition: cond, then_bb, else_bb });
|
||||
}
|
||||
|
||||
let v1 = f.next_value_id(); // %1
|
||||
let mut b1 = BasicBlock::new(then_bb);
|
||||
b1.add_instruction(MirInstruction::Const { dst: v1, value: ConstValue::String("A".to_string()) });
|
||||
b1.add_instruction(MirInstruction::Jump { target: merge_bb });
|
||||
f.add_block(b1);
|
||||
|
||||
let v2 = f.next_value_id(); // %2
|
||||
let mut b2 = BasicBlock::new(else_bb);
|
||||
b2.add_instruction(MirInstruction::Const { dst: v2, value: ConstValue::String("B".to_string()) });
|
||||
b2.add_instruction(MirInstruction::Jump { target: merge_bb });
|
||||
f.add_block(b2);
|
||||
|
||||
let mut b3 = BasicBlock::new(merge_bb);
|
||||
// Illegal: directly use v1 from predecessor
|
||||
b3.add_instruction(MirInstruction::Return { value: Some(v1) });
|
||||
f.add_block(b3);
|
||||
|
||||
f.update_cfg();
|
||||
|
||||
let mut verifier = MirVerifier::new();
|
||||
let res = verifier.verify_function(&f);
|
||||
assert!(res.is_err(), "Verifier should error on merge use without phi");
|
||||
let errs = res.err().unwrap();
|
||||
assert!(errs.iter().any(|e| matches!(e, VerificationError::MergeUsesPredecessorValue{..} | VerificationError::DominatorViolation{..})),
|
||||
"Expected merge/dominator error, got: {:?}", errs);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user