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