feat(joinir): Phase 171-172 Issue 4 - LoopScopeShapeBuilder unified initialization

Eliminates 50-60 lines of duplicated LoopScopeShape initialization across all 4 patterns.

**Changes**:
- New: `loop_scope_shape_builder.rs` with factory methods
- `empty_body_locals()`: For Pattern 1, 3 (condition-only analysis)
- `with_body_locals()`: For Pattern 2, 4 (AST-based body variable extraction)
- Updated all 4 patterns to use unified builder
- Includes unit tests for both factory methods

**Impact**:
- Pattern 1: 11 lines → 6 lines (5-line reduction)
- Pattern 2: 18 lines → 8 lines (10-line reduction)
- Pattern 3: 11 lines → 6 lines (5-line reduction)
- Pattern 4: 18 lines → 8 lines (10-line reduction)
- Total: 58 lines → 28 lines (30-line reduction, ~52%)

**Test**:
- cargo build --release:  PASS
- cargo test --release:  720/800 PASS (same as before)
- Unit tests:  3/3 PASS

Phase 171-172 Stage 1: 25-30% deletion target (30 lines achieved)
This commit is contained in:
nyash-codex
2025-12-08 03:39:46 +09:00
parent 5ba11468e4
commit 5e3dec99e3
6 changed files with 255 additions and 80 deletions

View File

@ -0,0 +1,211 @@
//! LoopScopeShapeBuilder - Unified LoopScopeShape initialization
//!
//! Phase 171-172: Issue 4
//!
//! Provides unified construction methods for LoopScopeShape across all 4 patterns.
//! This eliminates the 50-60 lines of duplicated initialization code in each pattern.
//!
//! # Responsibility
//!
//! - Provide factory methods for creating LoopScopeShape with common configurations
//! - Extract body_locals from loop body AST when needed
//! - Maintain consistent initialization defaults across patterns
//!
//! # Usage
//!
//! ```rust
//! // Pattern 1, 3: Empty body_locals (condition-only analysis)
//! let scope = LoopScopeShapeBuilder::empty_body_locals(
//! header, body, latch, exit, pinned
//! );
//!
//! // Pattern 2, 4: Extract body_locals from AST
//! let scope = LoopScopeShapeBuilder::with_body_locals(
//! header, body, latch, exit, pinned, loop_body
//! );
//! ```
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::BasicBlockId;
use std::collections::{BTreeMap, BTreeSet};
pub struct LoopScopeShapeBuilder;
impl LoopScopeShapeBuilder {
/// Create LoopScopeShape with empty body_locals
///
/// Used by Pattern 1 and Pattern 3 which don't require body-local variable analysis.
///
/// # Arguments
///
/// * `header` - Header block ID
/// * `body` - Body block ID
/// * `latch` - Latch block ID
/// * `exit` - Exit block ID
/// * `pinned` - Pinned variables (typically empty for current patterns)
pub fn empty_body_locals(
header: BasicBlockId,
body: BasicBlockId,
latch: BasicBlockId,
exit: BasicBlockId,
pinned: BTreeSet<String>,
) -> LoopScopeShape {
LoopScopeShape {
header,
body,
latch,
exit,
pinned,
carriers: BTreeSet::new(),
body_locals: BTreeSet::new(),
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
}
}
/// Create LoopScopeShape with body_locals extracted from AST
///
/// Used by Pattern 2 and Pattern 4 which require body-local variable classification.
/// This is critical for Trim pattern support and carrier promotion analysis.
///
/// # Arguments
///
/// * `header` - Header block ID
/// * `body` - Body block ID
/// * `latch` - Latch block ID
/// * `exit` - Exit block ID
/// * `pinned` - Pinned variables (typically empty for current patterns)
/// * `loop_body` - AST nodes of the loop body for local variable extraction
pub fn with_body_locals(
header: BasicBlockId,
body: BasicBlockId,
latch: BasicBlockId,
exit: BasicBlockId,
pinned: BTreeSet<String>,
loop_body: &[ASTNode],
) -> LoopScopeShape {
let body_locals = Self::extract_body_locals(loop_body);
LoopScopeShape {
header,
body,
latch,
exit,
pinned,
carriers: BTreeSet::new(),
body_locals,
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
}
}
/// Extract local variables defined in loop body
///
/// Scans the loop body AST for `local` declarations and collects variable names.
/// This is used for proper variable classification in LoopConditionScopeBox analysis.
///
/// # Arguments
///
/// * `body` - AST nodes of the loop body
///
/// # Returns
///
/// Set of variable names declared with `local` keyword in the loop body
fn extract_body_locals(body: &[ASTNode]) -> BTreeSet<String> {
let mut locals = BTreeSet::new();
for stmt in body {
if let ASTNode::Local { variables, .. } = stmt {
for var_name in variables {
locals.insert(var_name.clone());
}
}
}
locals
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_body_locals() {
let scope = LoopScopeShapeBuilder::empty_body_locals(
BasicBlockId(1),
BasicBlockId(2),
BasicBlockId(3),
BasicBlockId(4),
BTreeSet::new(),
);
assert_eq!(scope.header, BasicBlockId(1));
assert_eq!(scope.body, BasicBlockId(2));
assert_eq!(scope.latch, BasicBlockId(3));
assert_eq!(scope.exit, BasicBlockId(4));
assert!(scope.body_locals.is_empty());
assert!(scope.carriers.is_empty());
assert!(scope.pinned.is_empty());
}
#[test]
fn test_with_body_locals_extracts_local_variables() {
use crate::ast::Span;
let body = vec![
ASTNode::Local {
variables: vec!["x".to_string(), "y".to_string()],
initial_values: vec![],
span: Span::unknown(),
},
ASTNode::Local {
variables: vec!["z".to_string()],
initial_values: vec![],
span: Span::unknown(),
},
];
let scope = LoopScopeShapeBuilder::with_body_locals(
BasicBlockId(1),
BasicBlockId(2),
BasicBlockId(3),
BasicBlockId(4),
BTreeSet::new(),
&body,
);
assert_eq!(scope.body_locals.len(), 3);
assert!(scope.body_locals.contains("x"));
assert!(scope.body_locals.contains("y"));
assert!(scope.body_locals.contains("z"));
}
#[test]
fn test_extract_body_locals_ignores_non_local_nodes() {
use crate::ast::Span;
let body = vec![
ASTNode::Local {
variables: vec!["a".to_string()],
initial_values: vec![],
span: Span::unknown(),
},
// Use a different AST node type that doesn't declare locals
ASTNode::Literal {
value: crate::ast::LiteralValue::Integer(42),
span: Span::unknown(),
},
];
let scope = LoopScopeShapeBuilder::with_body_locals(
BasicBlockId(0),
BasicBlockId(0),
BasicBlockId(0),
BasicBlockId(0),
BTreeSet::new(),
&body,
);
assert_eq!(scope.body_locals.len(), 1);
assert!(scope.body_locals.contains("a"));
}
}

View File

@ -23,11 +23,15 @@
//! Phase 33-22: Common Pattern Infrastructure //! Phase 33-22: Common Pattern Infrastructure
//! - common_init.rs: CommonPatternInitializer for unified initialization //! - common_init.rs: CommonPatternInitializer for unified initialization
//! - conversion_pipeline.rs: JoinIRConversionPipeline for unified conversion flow //! - conversion_pipeline.rs: JoinIRConversionPipeline for unified conversion flow
//!
//! Phase 171-172: Refactoring Infrastructure
//! - loop_scope_shape_builder.rs: Unified LoopScopeShape initialization (Issue 4)
pub(in crate::mir::builder) mod ast_feature_extractor; pub(in crate::mir::builder) mod ast_feature_extractor;
pub(in crate::mir::builder) mod common_init; pub(in crate::mir::builder) mod common_init;
pub(in crate::mir::builder) mod conversion_pipeline; pub(in crate::mir::builder) mod conversion_pipeline;
pub(in crate::mir::builder) mod exit_binding; pub(in crate::mir::builder) mod exit_binding;
pub(in crate::mir::builder) mod loop_scope_shape_builder;
pub(in crate::mir::builder) mod pattern1_minimal; pub(in crate::mir::builder) mod pattern1_minimal;
pub(in crate::mir::builder) mod pattern2_with_break; pub(in crate::mir::builder) mod pattern2_with_break;
pub(in crate::mir::builder) mod pattern3_with_if_phi; pub(in crate::mir::builder) mod pattern3_with_if_phi;

View File

@ -70,21 +70,16 @@ impl MirBuilder {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().varmap("pattern1_start", &self.variable_map); trace::trace().varmap("pattern1_start", &self.variable_map);
// Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_min_while.hako) // Phase 171-172: Use LoopScopeShapeBuilder for unified initialization (Issue 4)
// Pattern 1 lowerer ignores the scope anyway, so this is just a placeholder // Pattern 1 lowerer ignores the scope anyway, so this is just a placeholder
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use super::loop_scope_shape_builder::LoopScopeShapeBuilder;
let scope = LoopScopeShape { let scope = LoopScopeShapeBuilder::empty_body_locals(
header: BasicBlockId(0), BasicBlockId(0),
body: BasicBlockId(0), BasicBlockId(0),
latch: BasicBlockId(0), BasicBlockId(0),
exit: BasicBlockId(0), BasicBlockId(0),
pinned: BTreeSet::new(), BTreeSet::new(),
carriers: BTreeSet::new(), );
body_locals: BTreeSet::new(),
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
};
// Call Pattern 1 lowerer // Call Pattern 1 lowerer
let join_module = match lower_simple_while_minimal(scope) { let join_module = match lower_simple_while_minimal(scope) {

View File

@ -215,32 +215,17 @@ impl MirBuilder {
eprintln!(" No condition-only variables"); eprintln!(" No condition-only variables");
} }
// Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako) // Phase 171-172: Use LoopScopeShapeBuilder for unified initialization (Issue 4)
// Phase 171-impl-Trim: Extract body_locals from loop body AST for proper variable classification // Extract body_locals from loop body AST for proper variable classification (Trim support)
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use super::loop_scope_shape_builder::LoopScopeShapeBuilder;
let mut body_locals = BTreeSet::new(); let scope = LoopScopeShapeBuilder::with_body_locals(
BasicBlockId(0),
// Extract local variable declarations from loop body BasicBlockId(0),
for stmt in _body { BasicBlockId(0),
if let ASTNode::Local { variables, .. } = stmt { BasicBlockId(0),
for var_name in variables { BTreeSet::new(),
body_locals.insert(var_name.clone()); _body,
} );
}
}
let scope = LoopScopeShape {
header: BasicBlockId(0),
body: BasicBlockId(0),
latch: BasicBlockId(0),
exit: BasicBlockId(0),
pinned: BTreeSet::new(),
carriers: BTreeSet::new(),
body_locals, // Phase 171-impl-Trim: Now populated from AST
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
};
// Phase 170-B: Extract break condition from loop body // Phase 170-B: Extract break condition from loop body
use super::ast_feature_extractor; use super::ast_feature_extractor;

View File

@ -78,21 +78,16 @@ impl MirBuilder {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().varmap("pattern3_start", &self.variable_map); trace::trace().varmap("pattern3_start", &self.variable_map);
// Create a minimal LoopScopeShape (Phase 188: hardcoded for loop_if_phi.hako) // Phase 171-172: Use LoopScopeShapeBuilder for unified initialization (Issue 4)
// Pattern 3 lowerer ignores the scope anyway, so this is just a placeholder // Pattern 3 lowerer ignores the scope anyway, so this is just a placeholder
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use super::loop_scope_shape_builder::LoopScopeShapeBuilder;
let scope = LoopScopeShape { let scope = LoopScopeShapeBuilder::empty_body_locals(
header: BasicBlockId(0), BasicBlockId(0),
body: BasicBlockId(0), BasicBlockId(0),
latch: BasicBlockId(0), BasicBlockId(0),
exit: BasicBlockId(0), BasicBlockId(0),
pinned: BTreeSet::new(), BTreeSet::new(),
carriers: BTreeSet::new(), );
body_locals: BTreeSet::new(),
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
};
// Call Pattern 3 lowerer // Call Pattern 3 lowerer
let join_module = match lower_loop_with_if_phi_pattern(scope) { let join_module = match lower_loop_with_if_phi_pattern(scope) {

View File

@ -196,32 +196,17 @@ impl MirBuilder {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().varmap("pattern4_start", &self.variable_map); trace::trace().varmap("pattern4_start", &self.variable_map);
// Create a minimal LoopScopeShape (Phase 195: hardcoded for loop_continue_pattern4.hako) // Phase 171-172: Use LoopScopeShapeBuilder for unified initialization (Issue 4)
// Phase 171-impl-Trim: Extract body_locals from loop body AST for proper variable classification // Extract body_locals from loop body AST for proper variable classification
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use super::loop_scope_shape_builder::LoopScopeShapeBuilder;
let mut body_locals = BTreeSet::new(); let scope = LoopScopeShapeBuilder::with_body_locals(
BasicBlockId(0),
// Extract local variable declarations from loop body BasicBlockId(0),
for stmt in body_to_analyze { BasicBlockId(0),
if let ASTNode::Local { variables, .. } = stmt { BasicBlockId(0),
for var_name in variables { BTreeSet::new(),
body_locals.insert(var_name.clone()); body_to_analyze,
} );
}
}
let scope = LoopScopeShape {
header: BasicBlockId(0),
body: BasicBlockId(0),
latch: BasicBlockId(0),
exit: BasicBlockId(0),
pinned: BTreeSet::new(),
carriers: BTreeSet::new(),
body_locals, // Phase 171-impl-Trim: Now populated from AST
exit_live: BTreeSet::new(),
progress_carrier: None,
variable_definitions: BTreeMap::new(),
};
// Phase 171-C-3: LoopBodyCarrierPromoter integration // Phase 171-C-3: LoopBodyCarrierPromoter integration
// Check if LoopConditionScopeBox detects LoopBodyLocal variables, and attempt promotion // Check if LoopConditionScopeBox detects LoopBodyLocal variables, and attempt promotion