Stabilize joinir tests and env guards
This commit is contained in:
@ -88,27 +88,27 @@ mod tests {
|
||||
#[test]
|
||||
fn test_binding_context_basic() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(ctx.is_empty());
|
||||
assert_eq!(ctx.len(), 0);
|
||||
assert!(ctx.binding_map.is_empty());
|
||||
assert_eq!(ctx.binding_map.len(), 0);
|
||||
|
||||
let bid = BindingId::new(0);
|
||||
ctx.insert("x".to_string(), bid);
|
||||
assert_eq!(ctx.lookup("x"), Some(bid));
|
||||
assert_eq!(ctx.len(), 1);
|
||||
assert!(!ctx.is_empty());
|
||||
assert_eq!(ctx.binding_map.len(), 1);
|
||||
assert!(!ctx.binding_map.is_empty());
|
||||
|
||||
ctx.remove("x");
|
||||
assert_eq!(ctx.lookup("x"), None);
|
||||
assert!(ctx.is_empty());
|
||||
assert!(ctx.binding_map.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binding_context_contains() {
|
||||
let mut ctx = BindingContext::new();
|
||||
assert!(!ctx.contains("x"));
|
||||
assert!(!ctx.binding_map.contains_key("x"));
|
||||
|
||||
ctx.insert("x".to_string(), BindingId::new(0));
|
||||
assert!(ctx.contains("x"));
|
||||
assert!(ctx.binding_map.contains_key("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -117,9 +117,8 @@ mod tests {
|
||||
ctx.insert("a".to_string(), BindingId::new(1));
|
||||
ctx.insert("b".to_string(), BindingId::new(2));
|
||||
|
||||
let map = ctx.binding_map();
|
||||
assert_eq!(map.len(), 2);
|
||||
assert_eq!(map.get("a"), Some(&BindingId::new(1)));
|
||||
assert_eq!(map.get("b"), Some(&BindingId::new(2)));
|
||||
assert_eq!(ctx.binding_map.len(), 2);
|
||||
assert_eq!(ctx.binding_map.get("a"), Some(&BindingId::new(1)));
|
||||
assert_eq!(ctx.binding_map.get("b"), Some(&BindingId::new(2)));
|
||||
}
|
||||
}
|
||||
|
||||
41
src/mir/builder/builder_test_api.rs
Normal file
41
src/mir/builder/builder_test_api.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use super::MirBuilder;
|
||||
use crate::mir::{BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirType, ValueId};
|
||||
|
||||
impl MirBuilder {
|
||||
pub fn enter_function_for_test(&mut self, name: String) {
|
||||
let entry_block = self.core_ctx.next_block();
|
||||
let signature = FunctionSignature {
|
||||
name,
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let function = self.new_function_with_metadata(signature, entry_block);
|
||||
self.scope_ctx.current_function = Some(function);
|
||||
self.current_block = Some(entry_block);
|
||||
}
|
||||
|
||||
pub fn exit_function_for_test(&mut self) {
|
||||
self.scope_ctx.current_function = None;
|
||||
self.current_block = None;
|
||||
}
|
||||
|
||||
pub fn push_block_for_test(&mut self) -> Result<BasicBlockId, String> {
|
||||
let block_id = self.core_ctx.next_block();
|
||||
self.start_new_block(block_id)?;
|
||||
Ok(block_id)
|
||||
}
|
||||
|
||||
pub fn current_block_for_test(&self) -> Result<BasicBlockId, String> {
|
||||
self.current_block
|
||||
.ok_or_else(|| "No current block".to_string())
|
||||
}
|
||||
|
||||
pub fn alloc_value_for_test(&mut self) -> ValueId {
|
||||
self.next_value_id()
|
||||
}
|
||||
|
||||
pub fn emit_for_test(&mut self, inst: MirInstruction) -> Result<(), String> {
|
||||
self.emit_instruction(inst)
|
||||
}
|
||||
}
|
||||
@ -62,4 +62,19 @@ impl EdgeStub {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 既に配線先が確定している EdgeStub を生成(テスト/配線済み用途)
|
||||
pub fn with_target(
|
||||
from: BasicBlockId,
|
||||
kind: ExitKind,
|
||||
target: BasicBlockId,
|
||||
args: EdgeArgs,
|
||||
) -> Self {
|
||||
Self {
|
||||
from,
|
||||
kind,
|
||||
target: Some(target),
|
||||
args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ mod tests {
|
||||
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("[joinir/contract/B1]"),
|
||||
err.contains("contract/B1"),
|
||||
"Error should have B1 tag: {}",
|
||||
err
|
||||
);
|
||||
@ -147,7 +147,7 @@ mod tests {
|
||||
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.contains("[joinir/contract/B1]"),
|
||||
err.contains("contract/B1"),
|
||||
"Error should have B1 tag: {}",
|
||||
err
|
||||
);
|
||||
|
||||
@ -272,6 +272,7 @@ impl ExitLineReconnector {
|
||||
variable_map: &BTreeMap<String, ValueId>,
|
||||
) {
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
use crate::mir::ValueId;
|
||||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||||
|
||||
for binding in &boundary.exit_bindings {
|
||||
@ -286,6 +287,17 @@ impl ExitLineReconnector {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// Phase 247-EX: Skip carriers without host slots (loop-local or FromHost placeholders).
|
||||
if binding.host_slot == ValueId(0) {
|
||||
trace.stderr_if(
|
||||
&format!(
|
||||
"[JoinIR/ExitLine/Contract] Phase 247-EX: Skipping carrier '{}' (no host slot)",
|
||||
binding.carrier_name
|
||||
),
|
||||
true,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Contract 1: carrier_phis must contain this carrier
|
||||
let phi_dst = carrier_phis.get(&binding.carrier_name);
|
||||
|
||||
@ -932,7 +932,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
|
||||
if let Some(ref func) = builder.scope_ctx.current_function {
|
||||
debug_assertions::verify_joinir_contracts(
|
||||
func,
|
||||
entry_block,
|
||||
loop_header_phi_info.header_block,
|
||||
exit_block_id,
|
||||
&loop_header_phi_info,
|
||||
boundary,
|
||||
|
||||
@ -74,6 +74,7 @@ pub fn sync_spans(
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Span;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
#[test]
|
||||
fn test_sync_spans_exact_match() {
|
||||
|
||||
@ -49,11 +49,12 @@ pub(super) fn resolve_target_func_name<'a>(
|
||||
.find_map(|(fname, &entry_block)| (entry_block == target_block).then(|| fname.as_str()))
|
||||
}
|
||||
|
||||
/// Check if block is MAIN's entry block (pure lexical check)
|
||||
pub(super) fn is_joinir_main_entry_block(
|
||||
/// Check if the source function is the host (not the loop header function).
|
||||
///
|
||||
/// Used to classify LoopEntry for tail-call handling.
|
||||
pub(super) fn is_joinir_loop_entry_source(
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
entry_func_name: Option<&str>,
|
||||
) -> bool {
|
||||
func_name == canonical_names::MAIN && old_block_id == func.entry_block
|
||||
entry_func_name.map(|name| name != func_name).unwrap_or(false)
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
//! - Record latch incoming for loop header PHI
|
||||
|
||||
// Import helpers from entry_resolver
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block};
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_loop_entry_source};
|
||||
|
||||
// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/)
|
||||
use super::super::super::{
|
||||
@ -43,8 +43,8 @@ pub(super) fn process_tail_call_params(
|
||||
new_block: &mut BasicBlock,
|
||||
tail_call_target: (BasicBlockId, &[ValueId]),
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
_func: &MirFunction,
|
||||
_old_block_id: BasicBlockId,
|
||||
function_params: &BTreeMap<String, Vec<ValueId>>,
|
||||
entry_func_name: Option<&str>,
|
||||
continuation_candidates: &BTreeSet<String>,
|
||||
@ -83,7 +83,7 @@ pub(super) fn process_tail_call_params(
|
||||
// Phase 287 P2: Calculate tail_call_kind early for latch incoming logic
|
||||
// Only treat MAIN's entry block as entry-like (not loop_step's entry block)
|
||||
let is_entry_like_block_for_latch =
|
||||
is_joinir_main_entry_block(func_name, func, old_block_id);
|
||||
is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
//! - Collect carrier inputs for exit jumps
|
||||
|
||||
// Import helpers from entry_resolver
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_main_entry_block};
|
||||
use super::entry_resolver::{resolve_target_func_name, is_joinir_loop_entry_source};
|
||||
|
||||
// Rewriter siblings (2 super:: up from plan/ to stages/, then 1 more to rewriter/)
|
||||
use super::super::super::{
|
||||
@ -51,8 +51,8 @@ pub(super) fn process_block_terminator(
|
||||
found_tail_call: bool,
|
||||
tail_call_target: Option<(BasicBlockId, &[ValueId])>,
|
||||
func_name: &str,
|
||||
func: &MirFunction,
|
||||
old_block_id: BasicBlockId,
|
||||
_func: &MirFunction,
|
||||
_old_block_id: BasicBlockId,
|
||||
new_block_id: BasicBlockId,
|
||||
remapper: &JoinIrIdRemapper,
|
||||
local_block_map: &BTreeMap<BasicBlockId, BasicBlockId>,
|
||||
@ -257,9 +257,9 @@ pub(super) fn process_block_terminator(
|
||||
.map(|name| entry_func_name == Some(name))
|
||||
.unwrap_or(false);
|
||||
|
||||
// Phase 287 P2: main の entry block からの呼び出しを LoopEntry 扱いにする
|
||||
// Only treat MAIN's entry block as entry-like (not loop_step's entry block)
|
||||
let is_entry_like_block = is_joinir_main_entry_block(func_name, func, old_block_id);
|
||||
// Phase 287 P2: host entry block からの呼び出しを LoopEntry 扱いにする
|
||||
// (loop header func の entry block は含めない)
|
||||
let is_entry_like_block = is_joinir_loop_entry_source(func_name, entry_func_name);
|
||||
|
||||
// CRITICAL: Argument order must match merge level classify_tail_call()
|
||||
let tail_call_kind = classify_tail_call(
|
||||
|
||||
105
src/mir/builder/phi_observation_tests.rs
Normal file
105
src/mir/builder/phi_observation_tests.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::verify_phi_reserved::{
|
||||
analyze_distribution, disable_observation, enable_observation, get_observations,
|
||||
};
|
||||
use crate::mir::types::ConstValue;
|
||||
use crate::mir::MirInstruction;
|
||||
|
||||
/// Phase 72-1: Observe PHI dst distribution from MirBuilder usage
|
||||
///
|
||||
/// This test manually creates MIR scenarios to observe PHI dst allocation.
|
||||
#[test]
|
||||
fn test_phase72_observe_phi_dst_via_builder() {
|
||||
enable_observation();
|
||||
|
||||
// Create multiple builders to simulate different compilation contexts
|
||||
for scenario in 0..10 {
|
||||
let mut builder = MirBuilder::new();
|
||||
builder.enter_function_for_test(format!("test_func_{}", scenario));
|
||||
|
||||
// Allocate some values (simulating loop setup)
|
||||
let _entry_block = builder.current_block_for_test().expect("entry block");
|
||||
|
||||
// Simulate loop header block
|
||||
builder.push_block_for_test().expect("push block");
|
||||
let _header_block = builder.current_block_for_test().expect("header block");
|
||||
|
||||
// Allocate initial values
|
||||
let v1 = builder.alloc_value_for_test();
|
||||
let v2 = builder.alloc_value_for_test();
|
||||
let v3 = builder.alloc_value_for_test();
|
||||
|
||||
// Emit some instructions
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v1,
|
||||
value: ConstValue::Integer(0),
|
||||
})
|
||||
.expect("emit const v1");
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v2,
|
||||
value: ConstValue::Integer(100),
|
||||
})
|
||||
.expect("emit const v2");
|
||||
builder
|
||||
.emit_for_test(MirInstruction::Const {
|
||||
dst: v3,
|
||||
value: ConstValue::Integer(1),
|
||||
})
|
||||
.expect("emit const v3");
|
||||
|
||||
// Now allocate PHI dst candidates (what would be PHI dsts)
|
||||
// These come from builder.next_value_id() in loop_header_phi_builder
|
||||
let phi_dst_1 = builder.alloc_value_for_test();
|
||||
let phi_dst_2 = builder.alloc_value_for_test();
|
||||
let phi_dst_3 = builder.alloc_value_for_test();
|
||||
|
||||
// Manually observe these as if they were PHI dsts
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_1);
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_2);
|
||||
crate::mir::join_ir::verify_phi_reserved::observe_phi_dst(phi_dst_3);
|
||||
}
|
||||
|
||||
builder.exit_function_for_test();
|
||||
}
|
||||
|
||||
// Collect observations
|
||||
let observations = get_observations();
|
||||
let report = analyze_distribution(&observations);
|
||||
|
||||
eprintln!("\n========== Phase 72: PHI dst Distribution Analysis ==========");
|
||||
eprintln!("{}", report.summary());
|
||||
eprintln!();
|
||||
eprintln!("Detailed breakdown:");
|
||||
eprintln!(
|
||||
" - Reserved region (0-99): {} PHI dsts",
|
||||
report.in_reserved
|
||||
);
|
||||
eprintln!(" - Param region (100-999): {} PHI dsts", report.in_param);
|
||||
eprintln!(" - Local region (1000+): {} PHI dsts", report.in_local);
|
||||
eprintln!();
|
||||
|
||||
if report.is_all_reserved() {
|
||||
eprintln!("OK: CONCLUSION: All PHI dsts are in reserved region (0-99)");
|
||||
eprintln!(" -> Safe to strengthen verifier with reserved region check");
|
||||
} else {
|
||||
eprintln!("WARN: CONCLUSION: Some PHI dsts are OUTSIDE reserved region");
|
||||
eprintln!(" -> PHI dst allocation does NOT respect reserved boundary");
|
||||
eprintln!(" -> Document this finding and skip verifier strengthening");
|
||||
|
||||
if let (Some(min), Some(max)) = (report.min_val, report.max_val) {
|
||||
eprintln!();
|
||||
eprintln!("Observed range: [{}, {}]", min, max);
|
||||
eprintln!("Expected range: [0, 99]");
|
||||
}
|
||||
}
|
||||
eprintln!("==============================================================\n");
|
||||
|
||||
disable_observation();
|
||||
|
||||
// This test always passes - it's for observation and decision-making
|
||||
assert!(true, "Observation complete - see output above for analysis");
|
||||
}
|
||||
Reference in New Issue
Block a user