refactor(joinir): Phase 287 P6 - Remove scan stage (2-stage pipeline)

This commit is contained in:
2025-12-27 13:25:15 +09:00
parent 8d4472ddd3
commit f225e27007
8 changed files with 22 additions and 390 deletions

View File

@ -1,8 +1,8 @@
# Phase 287 P6: Scan plan 統合docs-first, 意味論不変の範囲で)
**Date**: 2025-12-27
**Status**: Readynext
**Scope**: `merge/instruction_rewriter.rs` の Stage 1scanで生成する `RewritePlan` を、Stage 2planへ “同じ意味” で反映するか、または Stage 1 を削除して pipeline を単純化するための判断を docs で固定する
**Status**: Completed ✅
**Scope**: `merge/instruction_rewriter.rs` の Stage 1scanを削除して pipeline を 2-stagePlan→Applyへ単純化する意味論不変
**Non-goals**: 挙動変更、ログ恒常増加、silent fallback 追加、テスト専用の暫定コード追加
---
@ -40,6 +40,12 @@
- debug/log の恒常出力差を出さない(既存 `debug` フラグに従う)
- pipeline の見通し(責務)を docs と module 構造で補強する
**Decision2025-12-27**: Option B を採用する。
理由:
- Stage 2Planは boundary/local map/PHI/tailcall/terminator を扱っており、Scan を“本当に使う”には Scan 側に情報を増殖させやすい。
- Scan が未使用のまま残るのは二重修正の温床なので、先に削除して 2-stage へ収束させるのが最も構造的に安全。
---
## 手順docs-first

View File

@ -30,7 +30,7 @@ use super::rewriter::rewrite_context::RewriteContext;
use super::contract_checks;
// Phase 287 P5: Unified import through stages facade (SSOT)
use super::rewriter::stages::{scan_blocks, plan_rewrites, apply_rewrites};
use super::rewriter::stages::{plan_rewrites, apply_rewrites};
/// Phase 4: Merge ALL functions and rewrite instructions
@ -65,10 +65,9 @@ pub(super) fn merge_and_rewrite(
exit_block_id: BasicBlockId,
debug: bool,
) -> Result<MergeResult, String> {
// Phase 286C-4: 3-stage pipeline orchestrator
// 1. scan_blocks() - Read-only analysis (identify what to rewrite)
// 2. plan_rewrites() - Generate rewritten blocks (pure transformation)
// 3. apply_rewrites() - Mutate builder (apply changes)
// Phase 286C-4: pipeline orchestrator
// 1. plan_rewrites() - Generate rewritten blocks (pure transformation)
// 2. apply_rewrites() - Mutate builder (apply changes)
let trace = trace::trace();
// Only verbose if explicitly requested via debug flag (not env var - causes test failures)
@ -138,36 +137,15 @@ pub(super) fn merge_and_rewrite(
);
}
// ===== STAGE 1: SCAN (Read-only analysis) =====
// ===== STAGE 1: PLAN (Generate rewritten blocks) =====
if debug {
log!(
true,
"[merge_and_rewrite] Phase 286C-4 Stage 1: Scanning {} functions",
mir_module.functions.len()
);
}
let scan_plan = scan_blocks(mir_module, remapper, value_to_func_name, &ctx, debug)?;
if debug {
log!(
true,
"[merge_and_rewrite] Phase 286C-4: Scan complete - {} tail calls, {} returns",
scan_plan.tail_calls.len(),
scan_plan.return_conversions.len()
);
}
// ===== STAGE 2: PLAN (Generate rewritten blocks) =====
if debug {
log!(
true,
"[merge_and_rewrite] Phase 286C-4 Stage 2: Planning rewrites"
"[merge_and_rewrite] Phase 286C-4 Stage 1: Planning rewrites"
);
}
let blocks = plan_rewrites(
scan_plan,
mir_module,
remapper,
function_params,
@ -191,11 +169,11 @@ pub(super) fn merge_and_rewrite(
contract_checks::verify_carrier_inputs_complete(b, &blocks.carrier_inputs)?;
}
// ===== STAGE 3: APPLY (Mutate builder) =====
// ===== STAGE 2: APPLY (Mutate builder) =====
if debug {
log!(
true,
"[merge_and_rewrite] Phase 286C-4 Stage 3: Applying rewrites"
"[merge_and_rewrite] Phase 286C-4 Stage 2: Applying rewrites"
);
}

View File

@ -45,8 +45,7 @@ pub(super) mod tail_call_detector_box; // Phase 286C-2 Step 2: Tail call detecti
pub(super) mod terminator; // Phase 260 P0.1 Step 5: Terminator remapping extracted ✅
pub(super) mod type_propagation; // Phase 260 P0.1 Step 4: Type propagation extracted ✅
// Phase 286C-2.1: 3-stage pipeline (Scan → Plan → Apply)
pub(super) mod scan_box; // Stage 1: Data structures
// Phase 286C-2.1: pipeline (Plan → Apply)
pub(super) mod plan_box; // Stage 2: Data structures
pub(super) mod apply_box; // Stage 3: Data structures (stub)
pub(super) mod plan_helpers; // Helper functions for plan_rewrites()

View File

@ -1,13 +1,10 @@
//! PlanBox: Pure transformation (Scan → RewrittenBlocks)
//! RewrittenBlocks: Plan stage output (pure transformation)
//!
//! Phase 286C-2.1: Second stage of 3-stage pipeline (Scan → Plan → Apply)
//! Generates new blocks and mappings WITHOUT touching the builder.
//! Plan stage generates new blocks and mappings WITHOUT touching the builder.
use crate::mir::{BasicBlock, BasicBlockId, ValueId};
use std::collections::BTreeMap;
use super::scan_box::RewritePlan;
/// Rewritten blocks: New blocks, PHI inputs, carrier inputs (ready to apply)
#[derive(Debug, Clone)]
pub struct RewrittenBlocks {
@ -23,48 +20,3 @@ pub struct RewrittenBlocks {
/// Carrier inputs: Map<carrier_name, Vec<(from_block, value)>>
pub carrier_inputs: BTreeMap<String, Vec<(BasicBlockId, ValueId)>>,
}
/// PlanBox: Pure transformation from RewritePlan to RewrittenBlocks
pub struct PlanBox;
impl PlanBox {
/// Transform a RewritePlan into RewrittenBlocks
///
/// This is a PURE transformation - no builder mutation, no side effects.
/// Can update RewriteContext but does NOT touch MirBuilder.
///
/// # Arguments
/// * `plan` - The rewrite plan from ScanBox
///
/// # Returns
/// * RewrittenBlocks ready to be applied to the builder
pub fn transform(_plan: RewritePlan) -> RewrittenBlocks {
// TODO: Implement transformation logic
// For now, return empty result
RewrittenBlocks {
new_blocks: Vec::new(),
block_replacements: BTreeMap::new(),
phi_inputs: Vec::new(),
carrier_inputs: BTreeMap::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plan_box_basic() {
// Basic smoke test
let plan = RewritePlan {
tail_calls: Vec::new(),
return_conversions: Vec::new(),
phi_adjustments: Vec::new(),
parameter_bindings: Vec::new(),
};
let blocks = PlanBox::transform(plan);
assert!(blocks.new_blocks.is_empty());
}
}

View File

@ -1,162 +0,0 @@
//! ScanBox: Read-only scanning for rewrite planning
//!
//! Phase 286C-2.1: First stage of 3-stage pipeline (Scan → Plan → Apply)
//! Identifies what needs to be rewritten WITHOUT mutating any state.
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
use std::collections::BTreeSet;
/// Rewrite plan: Describes WHAT to do (doesn't touch instructions yet)
#[derive(Debug, Clone)]
pub struct RewritePlan {
/// Tail calls to be converted to parameter bindings + Jump
pub tail_calls: Vec<TailCallRewrite>,
/// Return instructions to convert to Jump to exit block
pub return_conversions: Vec<ReturnConversion>,
/// PHI adjustments needed (currently empty, for future use)
pub phi_adjustments: Vec<PhiAdjustment>,
/// Parameter bindings to generate for tail calls
pub parameter_bindings: Vec<ParameterBinding>,
}
/// Information about a tail call to rewrite
#[derive(Debug, Clone)]
pub struct TailCallRewrite {
/// Block containing the tail call
pub block_id: BasicBlockId,
/// Target function name
pub target_func_name: String,
/// Target block (entry block of called function)
pub target_block: BasicBlockId,
/// Remapped arguments to pass
pub args: Vec<ValueId>,
/// Whether this is a recursive call (same function)
pub is_recursive: bool,
/// Whether this is a continuation call (k_exit, etc.)
pub is_continuation: bool,
/// Whether the target is the loop entry function
pub is_loop_entry: bool,
}
/// Information about a Return instruction to convert
#[derive(Debug, Clone)]
pub struct ReturnConversion {
/// Block containing the Return
pub block_id: BasicBlockId,
/// Return value (if any)
pub return_value: Option<ValueId>,
/// Whether this has exit edge args
pub has_exit_edge_args: bool,
/// Whether to keep Return (non-skippable continuation)
pub should_keep_return: bool,
}
/// PHI adjustment (placeholder for future use)
#[derive(Debug, Clone)]
pub struct PhiAdjustment {
/// Block containing the PHI
pub block_id: BasicBlockId,
/// PHI destination
pub phi_dst: ValueId,
}
/// Parameter binding to generate
#[derive(Debug, Clone)]
pub struct ParameterBinding {
/// Block to insert the binding into
pub block_id: BasicBlockId,
/// Destination ValueId (parameter)
pub dst: ValueId,
/// Source ValueId (argument)
pub src: ValueId,
}
/// ScanBox: Read-only scanning for rewrite planning
pub struct ScanBox;
impl ScanBox {
/// Scan a block to identify rewrite operations needed
///
/// This is a READ-ONLY operation - it doesn't mutate any state,
/// just analyzes what needs to be done.
///
/// # Arguments
/// * `block` - The block to scan
/// * `block_id` - The block's ID
/// * `func_name` - The function containing this block
/// * Additional context parameters (TODO: consolidate into ScanContext)
///
/// # Returns
/// * Instructions to skip, tail calls found, returns to convert, etc.
pub fn scan_block(
block: &BasicBlock,
block_id: BasicBlockId,
_func_name: &str,
) -> BlockScanResult {
let skip_instructions = BTreeSet::new();
let mut found_tail_call = false;
// Scan instructions for tail calls, returns, etc.
for (idx, inst) in block.instructions.iter().enumerate() {
match inst {
MirInstruction::Call { .. } => {
// Detect tail calls
// TODO: Implement tail call detection logic
found_tail_call = false; // Placeholder
}
MirInstruction::Return { .. } => {
// Mark return for conversion
// TODO: Implement return conversion detection
}
_ => {}
}
}
BlockScanResult {
skip_instructions,
found_tail_call,
tail_call_target: None,
}
}
}
/// Result of scanning a single block
#[derive(Debug, Clone)]
pub struct BlockScanResult {
/// Instruction indices to skip during rewriting
pub skip_instructions: BTreeSet<usize>,
/// Whether a tail call was found
pub found_tail_call: bool,
/// Tail call target (if found)
pub tail_call_target: Option<(BasicBlockId, Vec<ValueId>)>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scan_box_basic() {
// Basic smoke test - will expand with real logic
let block = BasicBlock::new(BasicBlockId(0));
let result = ScanBox::scan_block(&block, BasicBlockId(0), "test_func");
assert!(!result.found_tail_call);
}
}

View File

@ -3,10 +3,9 @@
//! Phase 287 P3: Modularize instruction_rewriter.rs 3-stage pipeline
//! Extracted from instruction_rewriter.rs to separate physical files.
//!
//! This module contains the three stages of JoinIR instruction rewriting:
//! 1. **Scan**: Read-only analysis to identify what needs rewriting
//! 2. **Plan**: Transform scan plan into concrete rewritten blocks
//! 3. **Apply**: Apply rewritten blocks to MirBuilder
//! This module contains the pipeline stages of JoinIR instruction rewriting:
//! 1. **Plan**: Build rewritten blocks (pure transformation)
//! 2. **Apply**: Apply rewritten blocks to MirBuilder
//!
//! # Phase 287 P5: Facade Pattern (Re-export SSOT)
//!
@ -15,11 +14,9 @@
//! single entry point for the pipeline.
// Module declarations (implementation files)
mod scan;
mod plan;
mod apply;
// Phase 287 P5: Re-export stage functions (facade pattern)
pub(in crate::mir::builder::control_flow::joinir::merge) use scan::scan_blocks;
pub(in crate::mir::builder::control_flow::joinir::merge) use plan::plan_rewrites;
pub(in crate::mir::builder::control_flow::joinir::merge) use apply::apply_rewrites;

View File

@ -15,7 +15,6 @@
use super::super::{
plan_helpers::{build_local_block_map, sync_spans},
rewrite_context::RewriteContext,
scan_box::RewritePlan,
plan_box::RewrittenBlocks,
helpers::is_skippable_continuation,
};
@ -60,7 +59,6 @@ mod terminator_rewrite;
/// # Phase 287 P5: Re-exported through stages/mod.rs
/// Access via stages::{plan_rewrites} for unified API.
pub(in crate::mir::builder::control_flow::joinir::merge) fn plan_rewrites(
_plan: RewritePlan,
mir_module: &MirModule,
remapper: &mut JoinIrIdRemapper,
function_params: &BTreeMap<String, Vec<ValueId>>,

View File

@ -1,136 +0,0 @@
//! Stage 1: Scan - Read-only analysis to identify what needs rewriting
//!
//! Phase 287 P3: Extracted from instruction_rewriter.rs::scan_blocks()
//!
//! Scans all blocks and instructions to identify:
//! - Tail calls to convert
//! - Returns to convert to exit jumps
//! - PHI adjustments needed
//! - Parameter bindings to generate
//!
//! This is a READ-ONLY operation - no mutations, just analysis.
use super::super::{
rewrite_context::RewriteContext,
scan_box::{RewritePlan, ReturnConversion, TailCallRewrite},
};
use super::super::super::trace;
use crate::mir::{MirInstruction, MirModule, ValueId};
use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper;
use std::collections::BTreeMap;
/// Stage 1: Scan - Read-only analysis to identify what needs rewriting
///
/// Scans all blocks and instructions to identify:
/// - Tail calls to convert
/// - Returns to convert to exit jumps
/// - PHI adjustments needed
/// - Parameter bindings to generate
///
/// This is a READ-ONLY operation - no mutations, just analysis.
///
/// # Phase 287 P5: Re-exported through stages/mod.rs
/// Access via stages::{scan_blocks} for unified API.
pub(in crate::mir::builder::control_flow::joinir::merge) fn scan_blocks(
mir_module: &MirModule,
remapper: &JoinIrIdRemapper,
value_to_func_name: &BTreeMap<ValueId, String>,
ctx: &RewriteContext,
debug: bool,
) -> Result<RewritePlan, String> {
let trace = trace::trace();
// Only verbose if explicitly requested via debug flag (not env var - causes test failures)
let verbose = debug;
macro_rules! log {
($enabled:expr, $($arg:tt)*) => {
trace.stderr_if(&format!($($arg)*), $enabled);
};
}
let mut plan = RewritePlan {
tail_calls: Vec::new(),
return_conversions: Vec::new(),
phi_adjustments: Vec::new(),
parameter_bindings: Vec::new(),
};
// Sort functions for deterministic iteration
let mut functions_merge: Vec<_> = mir_module.functions.iter().collect();
functions_merge.sort_by_key(|(name, _)| name.as_str());
for (func_name, func) in functions_merge {
// Sort blocks for deterministic iteration
let mut blocks_merge: Vec<_> = func.blocks.iter().collect();
blocks_merge.sort_by_key(|(id, _)| id.0);
for (old_block_id, old_block) in blocks_merge {
let new_block_id = remapper
.get_block(func_name, *old_block_id)
.ok_or_else(|| format!("Block {:?} not found for {}", old_block_id, func_name))?;
// Scan instructions for tail calls and PHI adjustments
for inst in &old_block.instructions {
// Detect tail calls
if let MirInstruction::Call { func: callee, args, .. } = inst {
if let Some(callee_name) = value_to_func_name.get(callee) {
// Check if this is an intra-module call (tail call candidate)
if let Some(&target_block) = ctx.function_entry_map.get(callee_name) {
let remapped_args: Vec<ValueId> = args
.iter()
.map(|&v| remapper.get_value(v).unwrap_or(v))
.collect();
let is_recursive = callee_name == func_name;
// For now, mark all tail calls as non-continuation (will be refined in plan stage)
let is_continuation = false;
let is_loop_entry = false; // Will be determined in plan stage
plan.tail_calls.push(TailCallRewrite {
block_id: new_block_id,
target_func_name: callee_name.clone(),
target_block,
args: remapped_args,
is_recursive,
is_continuation,
is_loop_entry,
});
log!(
verbose,
"[scan_blocks] Detected tail call: block={:?} target='{}' args={:?}",
new_block_id, callee_name, args
);
}
}
}
}
// Check terminator for Return instructions
if let Some(MirInstruction::Return { value }) = &old_block.terminator {
plan.return_conversions.push(ReturnConversion {
block_id: new_block_id,
return_value: *value,
has_exit_edge_args: old_block.edge_args_from_terminator().is_some(),
should_keep_return: false, // Will be determined in plan stage
});
log!(
verbose,
"[scan_blocks] Detected return: block={:?} value={:?}",
new_block_id, value
);
}
}
}
if debug {
log!(
true,
"[scan_blocks] Plan summary: {} tail calls, {} returns",
plan.tail_calls.len(),
plan.return_conversions.len()
);
}
Ok(plan)
}