feat(joinir): Phase 53 - SELFHOST-NORM-DEV-EXPAND implementation
Expanded selfhost dev Normalized target with 2 practical P2/P3 loop variations, strengthened structural signature axis, and implemented two-stage detection. Key Changes: 1. Documentation (phase49-selfhost-joinir-depth2-design.md +128 lines): - Added Phase 53 section with candidate selection rationale - Documented two-stage detector strategy (structural primary + dev-only name guard) - Defined structural axis strengthening (carrier count/type, branch patterns) 2. Fixtures (+210 lines): - selfhost_args_parse_p2.program.json (60 lines): P2 with String carrier + conditional branching - selfhost_stmt_count_p3.program.json (150 lines): P3 with 5 carriers + multi-branch if-else 3. Structured Builders (fixtures.rs +48 lines): - build_selfhost_args_parse_p2_structured_for_normalized_dev() - build_selfhost_stmt_count_p3_structured_for_normalized_dev() 4. ShapeGuard Two-Stage Detection (shape_guard.rs +80 lines): - Added SelfhostArgsParseP2/SelfhostStmtCountP3 to NormalizedDevShape enum - Implemented is_selfhost_args_parse_p2(): P2 core family + name guard - Implemented is_selfhost_stmt_count_p3(): 2-10 carrier check + name guard - Updated capability_for_shape() mappings 5. Bridge Integration (bridge.rs +8 lines, normalized.rs +10 lines): - Added shape handlers delegating to existing normalizers - Added roundtrip reconstruction handlers 6. Entry Point Registration (ast_lowerer/mod.rs +2 lines): - Registered selfhost_args_parse_p2/selfhost_stmt_count_p3 as LoopFrontend routes 7. Dev VM Comparison Tests (normalized_joinir_min.rs +40 lines): - normalized_selfhost_args_parse_p2_vm_bridge_direct_matches_structured() - normalized_selfhost_stmt_count_p3_vm_bridge_direct_matches_structured() 8. Test Context Fix (dev_env.rs): - Added thread-local test context depth counter - Fixed deadlock in nested test_ctx() calls via reentrant with_dev_env_if_unset() Structural Axis Growth: P2 family: - Carrier count: 1-3 (unchanged) - NEW: Type diversity (Integer/String mixed) - NEW: Conditional branching patterns (Eq-heavy comparisons) P3 family: - NEW: Carrier count upper bound: 2-10 (was 2-4) - NEW: Multi-branch if-else (5+ branches with nested structure) - NEW: Complex conditional patterns Test Results: - normalized_dev: 40/40 PASS (including 2 new tests) - lib regression: 939 PASS, 56 ignored - Existing behavior unchanged (normalized_dev feature-gated) Phase 53 Achievements: ✅ P2/P3 each gained 1 practical variation (2 total) ✅ Two-stage detection: structural primary + dev-only name guard ✅ Structural axis expanded: 4 axes (carrier count/type/Compare/branch patterns) ✅ All tests PASS, no regressions ✅ Test context deadlock fixed (0.04s for 29 tests) Files Modified: 14 files Lines Added: ~516 lines (net) Implementation: Pure additive (feature-gated) Next Phase (54+): - Accumulate 6+ loops per P2/P3 family - Achieve 5+ stable structural axes - Target < 5% false positive rate - Then shrink/remove name guard scope
This commit is contained in:
@ -72,6 +72,14 @@ fn normalize_for_shape(
|
||||
| NormalizedDevShape::JsonparserParseNumberReal => {
|
||||
catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module)))
|
||||
}
|
||||
NormalizedDevShape::SelfhostTokenScanP2 => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_selfhost_token_scan_p2(module)
|
||||
.expect("selfhost P2 normalization failed")
|
||||
})),
|
||||
NormalizedDevShape::SelfhostTokenScanP2Accum => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_selfhost_token_scan_p2_accum(module)
|
||||
.expect("selfhost P2 accum normalization failed")
|
||||
})),
|
||||
// Phase 47-A: P3 minimal normalization
|
||||
NormalizedDevShape::Pattern3IfSumMinimal => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal(module)
|
||||
@ -86,11 +94,44 @@ fn normalize_for_shape(
|
||||
crate::mir::join_ir::normalized::normalize_pattern3_if_sum_json_minimal(module)
|
||||
.expect("P3 json normalization failed")
|
||||
})),
|
||||
NormalizedDevShape::SelfhostIfSumP3 => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_selfhost_if_sum_p3(module)
|
||||
.expect("selfhost P3 normalization failed")
|
||||
})),
|
||||
NormalizedDevShape::SelfhostIfSumP3Ext => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_selfhost_if_sum_p3_ext(module)
|
||||
.expect("selfhost P3 ext normalization failed")
|
||||
})),
|
||||
// Phase 53: selfhost P2/P3 practical variations (delegate to existing normalizers)
|
||||
NormalizedDevShape::SelfhostArgsParseP2 => {
|
||||
catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module)))
|
||||
}
|
||||
NormalizedDevShape::SelfhostStmtCountP3 => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_selfhost_if_sum_p3_ext(module)
|
||||
.expect("selfhost stmt_count P3 normalization failed")
|
||||
})),
|
||||
// Phase 48-A: P4 minimal normalization
|
||||
NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| {
|
||||
crate::mir::join_ir::normalized::normalize_pattern4_continue_minimal(module)
|
||||
.expect("P4 normalization failed")
|
||||
})),
|
||||
// Phase 48-B: JsonParser continue skip_ws (array/object)
|
||||
NormalizedDevShape::JsonparserParseArrayContinueSkipWs => catch_unwind(AssertUnwindSafe(
|
||||
|| {
|
||||
crate::mir::join_ir::normalized::normalize_jsonparser_parse_array_continue_skip_ws(
|
||||
module,
|
||||
)
|
||||
.expect("P4 array normalization failed")
|
||||
},
|
||||
)),
|
||||
NormalizedDevShape::JsonparserParseObjectContinueSkipWs => catch_unwind(AssertUnwindSafe(
|
||||
|| {
|
||||
crate::mir::join_ir::normalized::normalize_jsonparser_parse_object_continue_skip_ws(
|
||||
module,
|
||||
)
|
||||
.expect("P4 object normalization failed")
|
||||
},
|
||||
)),
|
||||
};
|
||||
|
||||
match result {
|
||||
@ -195,9 +236,7 @@ pub(crate) fn bridge_joinir_to_mir_with_meta(
|
||||
{
|
||||
let mode = current_joinir_mode();
|
||||
|
||||
// Phase 47-C: Canonical set (P2-Core + P2-Mid + P3 if-sum) always uses Normalized→MIR(direct)
|
||||
// Canonical set: Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real,
|
||||
// P3 if-sum minimal/multi/json
|
||||
// Canonical set (P2/P3/P4): Always uses Normalized→MIR(direct) regardless of mode/env
|
||||
let canonical_shapes = shape_guard::canonical_shapes(module);
|
||||
if !canonical_shapes.is_empty() {
|
||||
match try_normalized_direct_bridge(module, meta, &canonical_shapes, false, false)? {
|
||||
|
||||
@ -11,6 +11,12 @@ use crate::mir::{BasicBlockId, EffectMask, MirFunction, MirInstruction, ValueId}
|
||||
|
||||
use super::{convert_mir_like_inst, join_func_name, JoinIrVmBridgeError};
|
||||
|
||||
fn log_dbg(message: impl AsRef<str>) {
|
||||
if crate::config::env::joinir_test_debug_enabled() {
|
||||
eprintln!("{}", message.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JoinIrBlockConverter {
|
||||
current_block_id: BasicBlockId,
|
||||
current_instructions: Vec<MirInstruction>,
|
||||
@ -58,15 +64,15 @@ impl JoinIrBlockConverter {
|
||||
else_val,
|
||||
} = mir_like
|
||||
{
|
||||
eprintln!(
|
||||
log_dbg(format!(
|
||||
"[joinir_block] ✅ Found Select! dst={:?}, calling handle_select",
|
||||
dst
|
||||
);
|
||||
));
|
||||
self.handle_select(mir_func, dst, cond, then_val, else_val, &None)?;
|
||||
continue;
|
||||
}
|
||||
// Debug: show what instruction we're processing
|
||||
eprintln!("[joinir_block] Compute instruction: {:?}", mir_like);
|
||||
log_dbg(format!("[joinir_block] Compute instruction: {:?}", mir_like));
|
||||
let mir_inst = convert_mir_like_inst(mir_like)?;
|
||||
self.current_instructions.push(mir_inst);
|
||||
}
|
||||
@ -86,6 +92,10 @@ impl JoinIrBlockConverter {
|
||||
method,
|
||||
args,
|
||||
} => {
|
||||
log_dbg(format!(
|
||||
"[joinir_block] Converting ConditionalMethodCall: dst={:?}, cond={:?}",
|
||||
dst, cond
|
||||
));
|
||||
self.handle_conditional_method_call(
|
||||
mir_func, cond, dst, receiver, method, args,
|
||||
)?;
|
||||
@ -481,21 +491,21 @@ impl JoinIrBlockConverter {
|
||||
type_hint: type_hint.clone(),
|
||||
});
|
||||
merge_block_obj.instruction_spans.push(Span::unknown());
|
||||
eprintln!(
|
||||
log_dbg(format!(
|
||||
"[joinir_block/handle_select] Created merge_block {:?} with {} instructions (first={:?})",
|
||||
merge_block,
|
||||
merge_block_obj.instructions.len(),
|
||||
merge_block_obj.instructions.first()
|
||||
);
|
||||
));
|
||||
mir_func.blocks.insert(merge_block, merge_block_obj);
|
||||
|
||||
// Verify PHI was inserted
|
||||
if let Some(inserted) = mir_func.blocks.get(&merge_block) {
|
||||
eprintln!(
|
||||
log_dbg(format!(
|
||||
"[joinir_block/handle_select] After insert: merge_block {:?} has {} instructions",
|
||||
merge_block,
|
||||
inserted.instructions.len()
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
self.current_block_id = merge_block;
|
||||
@ -713,11 +723,11 @@ impl JoinIrBlockConverter {
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
eprintln!(
|
||||
log_dbg(format!(
|
||||
"[joinir_block/finalize_block] block_id={:?}, instructions.len()={}",
|
||||
block_id,
|
||||
instructions.len()
|
||||
);
|
||||
));
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
// Phase 189 FIX: Preserve existing PHI instructions at block start
|
||||
// PHI instructions must remain at the beginning of the block
|
||||
@ -730,10 +740,10 @@ impl JoinIrBlockConverter {
|
||||
let phi_count = existing_phis.len();
|
||||
|
||||
if phi_count > 0 {
|
||||
eprintln!(
|
||||
log_dbg(format!(
|
||||
"[joinir_block/finalize_block] Preserving {} PHI instructions in block {:?}",
|
||||
phi_count, block_id
|
||||
);
|
||||
));
|
||||
// PHI first, then new instructions
|
||||
let mut merged = existing_phis;
|
||||
merged.extend(instructions);
|
||||
|
||||
Reference in New Issue
Block a user