docs(joinir): Phase 232-239 documentation and ExprLowerer refinements
Documentation: - Move completion reports to docs/archive/reports/ - Add phase232-238 design/inventory documents - Update joinir-architecture-overview.md - Add doc-status-policy.md Code refinements: - ExprLowerer: condition catalog improvements - ScopeManager: boundary clarifications - CarrierInfo: cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
10
src/mir/join_ir/lowering/README.md
Normal file
10
src/mir/join_ir/lowering/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# JoinIR Lowering (ExprLowerer / ScopeManager / Envs)
|
||||
|
||||
このディレクトリは JoinIR lowering の中でも、条件式や環境まわりの箱(ExprLowerer, ScopeManager, ConditionEnv, LoopBodyLocalEnv, UpdateEnv など)を扱う層だよ。コードを触るときは、以下の最小ルールを守ってね。
|
||||
|
||||
- ExprLowerer は **ScopeManager 経由のみ** で名前解決する。ConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo に直接触らない。
|
||||
- 条件式から UpdateEnv を参照しない。UpdateEnv はキャリア更新専用で、header/break/continue 条件は ScopeManager→ConditionEnv で完結させる。
|
||||
- ConditionEnv は「条件で参照する JoinIR ValueId だけ」を持つ。body-local を直接入れず、必要なら昇格+ScopeManager に解決を任せる。
|
||||
- Fail-Fast 原則: Unsupported/NotFound は明示エラーにして、by-name ヒューリスティックや静かなフォールバックは禁止。
|
||||
|
||||
詳しい境界ルールは `docs/development/current/main/phase238-exprlowerer-scope-boundaries.md` を参照してね。***
|
||||
@ -406,6 +406,54 @@ impl CarrierInfo {
|
||||
pub fn trim_helper(&self) -> Option<&crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper> {
|
||||
self.trim_helper.as_ref()
|
||||
}
|
||||
|
||||
/// Phase 229/231: Resolve promoted LoopBodyLocal name to carrier JoinIR ValueId
|
||||
///
|
||||
/// This helper centralizes the naming convention for promoted variables so that
|
||||
/// ScopeManager 実装がそれぞれ命名規約を再実装しなくて済むようにするよ。
|
||||
///
|
||||
/// 命名規約:
|
||||
/// - DigitPos パターン: `"var"` → `"is_var"`(例: "digit_pos" → "is_digit_pos")
|
||||
/// - Trim パターン : `"var"` → `"is_var_match"`(例: "ch" → "is_ch_match")
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `original_name` - 元の LoopBodyLocal 名(例: "digit_pos")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(ValueId)` - 対応する carrier の join_id が見つかった場合
|
||||
/// * `None` - promoted_loopbodylocals に含まれない、または join_id 未設定の場合
|
||||
pub fn resolve_promoted_join_id(&self, original_name: &str) -> Option<ValueId> {
|
||||
if !self.promoted_loopbodylocals.contains(&original_name.to_string()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let candidates = [
|
||||
format!("is_{}", original_name), // DigitPos pattern
|
||||
format!("is_{}_match", original_name), // Trim pattern
|
||||
];
|
||||
|
||||
for carrier_name in &candidates {
|
||||
// loop_var 自身が ConditionOnly carrier として扱われるケースは現状ほぼないが、
|
||||
// 将来の拡張に備えて loop_var_name も一応チェックしておく。
|
||||
if carrier_name == &self.loop_var_name {
|
||||
if let Some(carrier) = self.carriers.iter().find(|c| c.name == self.loop_var_name) {
|
||||
if let Some(join_id) = carrier.join_id {
|
||||
return Some(join_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(carrier) = self.carriers.iter().find(|c| c.name == *carrier_name) {
|
||||
if let Some(join_id) = carrier.join_id {
|
||||
return Some(join_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Exit metadata returned by lowerers
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{JoinInst, MirLikeInst, BinOpKind, UnaryOp as JoinUnaryOp};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use super::scope_manager::ScopeManager;
|
||||
use super::condition_lowerer::lower_condition_to_joinir;
|
||||
@ -108,6 +109,12 @@ pub struct ExprLowerer<'env, 'builder, S: ScopeManager> {
|
||||
|
||||
/// Debug flag (inherited from caller)
|
||||
debug: bool,
|
||||
|
||||
/// Last lowered instruction sequence (for testing/inspection)
|
||||
///
|
||||
/// Phase 235: Tests can inspect this to assert that appropriate Compare / BinOp / Not
|
||||
/// instructions are emitted for supported patterns. Productionコードからは未使用。
|
||||
last_instructions: Vec<JoinInst>,
|
||||
}
|
||||
|
||||
impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
@ -124,6 +131,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
context,
|
||||
builder,
|
||||
debug: false,
|
||||
last_instructions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +141,14 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Take the last lowered instruction sequence (mainly for tests)
|
||||
///
|
||||
/// Phase 235: This allows unit tests to validate that Compare / BinOp / Not
|
||||
/// instructions are present without影響を与えずに ExprLowerer の外から観察できる。
|
||||
pub fn take_last_instructions(&mut self) -> Vec<JoinInst> {
|
||||
std::mem::take(&mut self.last_instructions)
|
||||
}
|
||||
|
||||
/// Lower an expression to JoinIR ValueId
|
||||
///
|
||||
/// Phase 231: This is the main entry point. Currently delegates to
|
||||
@ -184,12 +200,15 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
id
|
||||
};
|
||||
|
||||
let (result_value, _instructions) = lower_condition_to_joinir(
|
||||
let (result_value, instructions) = lower_condition_to_joinir(
|
||||
ast,
|
||||
&mut alloc_value,
|
||||
&condition_env,
|
||||
).map_err(|e| ExprLoweringError::LoweringError(e))?;
|
||||
|
||||
// Phase 235: 保存しておき、テストから観察できるようにする
|
||||
self.last_instructions = instructions;
|
||||
|
||||
if self.debug {
|
||||
eprintln!("[expr_lowerer/phase231] Lowered condition → ValueId({:?})", result_value);
|
||||
}
|
||||
@ -292,7 +311,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Span, LiteralValue};
|
||||
use crate::mir::join_ir::lowering::scope_manager::{Pattern2ScopeManager, VarScopeKind};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
|
||||
@ -301,6 +320,41 @@ mod tests {
|
||||
MirBuilder::new()
|
||||
}
|
||||
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
}
|
||||
|
||||
fn var(name: &str) -> ASTNode {
|
||||
ASTNode::Variable {
|
||||
name: name.to_string(),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lit_i(value: i64) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(value),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: op,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn not(expr: ASTNode) -> ASTNode {
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(expr),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_simple_comparison() {
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
@ -451,4 +505,226 @@ mod tests {
|
||||
};
|
||||
assert!(!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast));
|
||||
}
|
||||
|
||||
// Phase 235: Additional patterns for condition lowering
|
||||
|
||||
fn make_basic_scope() -> Pattern2ScopeManager<'static> {
|
||||
// NOTE: we leak these small envs for the duration of the test to satisfy lifetimes simply.
|
||||
// テスト専用なので許容する。
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(1));
|
||||
condition_env.insert("j".to_string(), ValueId(2));
|
||||
|
||||
let boxed_env: Box<ConditionEnv> = Box::new(condition_env);
|
||||
let condition_env_ref: &'static ConditionEnv = Box::leak(boxed_env);
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
let boxed_carrier: Box<CarrierInfo> = Box::new(carrier_info);
|
||||
let carrier_ref: &'static CarrierInfo = Box::leak(boxed_carrier);
|
||||
|
||||
Pattern2ScopeManager {
|
||||
condition_env: condition_env_ref,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: carrier_ref,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_has_compare(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::Compare { .. }
|
||||
))),
|
||||
"Expected at least one Compare instruction, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_binop(instructions: &[JoinInst], op: BinOpKind) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::BinOp { op: o, .. } ) if *o == op
|
||||
)),
|
||||
"Expected at least one BinOp {:?}, got {:?}",
|
||||
op,
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_has_not(instructions: &[JoinInst]) {
|
||||
assert!(
|
||||
instructions.iter().any(|inst| matches!(
|
||||
inst,
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { op: JoinUnaryOp::Not, .. }
|
||||
))),
|
||||
"Expected at least one UnaryOp::Not, got {:?}",
|
||||
instructions
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_var_less_literal_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i < 10
|
||||
let ast = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i < 10 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_literal_less_var_generates_compare() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// 0 < i
|
||||
let ast = bin(BinaryOperator::Less, lit_i(0), var("i"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "0 < i should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_greater_than_between_vars() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > j
|
||||
let ast = bin(BinaryOperator::Greater, var("i"), var("j"));
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > j should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_and_combination() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// i > 0 && j < 5
|
||||
let left = bin(BinaryOperator::Greater, var("i"), lit_i(0));
|
||||
let right = bin(BinaryOperator::Less, var("j"), lit_i(5));
|
||||
let ast = bin(BinaryOperator::And, left, right);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "i > 0 && j < 5 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_binop(&instructions, BinOpKind::And);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_not_of_comparison() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// !(i < 10)
|
||||
let inner = bin(BinaryOperator::Less, var("i"), lit_i(10));
|
||||
let ast = not(inner);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "!(i < 10) should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(!instructions.is_empty(), "instructions should not be empty");
|
||||
assert_has_compare(&instructions);
|
||||
assert_has_not(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_pattern2_break_digit_pos_less_zero_generates_compare() {
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("digit_pos".to_string(), ValueId(10));
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
condition_env: &condition_env,
|
||||
loop_body_local_env: None,
|
||||
captured_env: None,
|
||||
carrier_info: &carrier_info,
|
||||
};
|
||||
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// digit_pos < 0
|
||||
let ast = bin(BinaryOperator::Less, var("digit_pos"), lit_i(0));
|
||||
assert!(
|
||||
ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"digit_pos < 0 should be supported in Pattern2 break condition"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
assert!(result.is_ok(), "digit_pos < 0 should lower successfully");
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
assert!(
|
||||
!instructions.is_empty(),
|
||||
"instructions for digit_pos < 0 should not be empty"
|
||||
);
|
||||
assert_has_compare(&instructions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expr_lowerer_pattern2_break_methodcall_is_unsupported() {
|
||||
let scope = make_basic_scope();
|
||||
let mut builder = create_test_builder();
|
||||
|
||||
// s.length() (MethodCall) is not supported for Pattern2 break condition
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(var("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert!(
|
||||
!ExprLowerer::<Pattern2ScopeManager>::is_supported_condition(&ast),
|
||||
"MethodCall should be rejected for Pattern2 break condition"
|
||||
);
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope, ExprContext::Condition, &mut builder);
|
||||
let result = expr_lowerer.lower(&ast);
|
||||
|
||||
assert!(
|
||||
matches!(result, Err(ExprLoweringError::UnsupportedNode(_))),
|
||||
"MethodCall should fail-fast with UnsupportedNode"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -379,6 +379,7 @@ pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn test_update_kind_name() {
|
||||
@ -387,91 +388,174 @@ mod tests {
|
||||
assert_eq!(UpdateKind::Other.name(), "Other");
|
||||
}
|
||||
|
||||
// NOTE: Phase 222 - test_typical_index_names commented out
|
||||
// Function is_typical_index_name() was removed in earlier phase
|
||||
// #[test]
|
||||
// fn test_typical_index_names() {
|
||||
// assert!(is_typical_index_name("i"));
|
||||
// assert!(is_typical_index_name("idx"));
|
||||
// assert!(is_typical_index_name("pos"));
|
||||
// assert!(!is_typical_index_name("result"));
|
||||
// assert!(!is_typical_index_name("sum"));
|
||||
// }
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
}
|
||||
|
||||
fn var(name: &str) -> ASTNode {
|
||||
ASTNode::Variable {
|
||||
name: name.to_string(),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lit_i(value: i64) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(value),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(lhs),
|
||||
right: Box::new(rhs),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn assign(name: &str, value: ASTNode) -> ASTNode {
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(var(name)),
|
||||
value: Box::new(value),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn if_with_updates(
|
||||
condition: ASTNode,
|
||||
then_body: Vec<ASTNode>,
|
||||
else_body: Option<Vec<ASTNode>>,
|
||||
) -> ASTNode {
|
||||
ASTNode::If {
|
||||
condition: Box::new(condition),
|
||||
then_body,
|
||||
else_body,
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_single_counter() {
|
||||
fn test_analyze_single_counter_from_ast() {
|
||||
let names = vec!["i".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
|
||||
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(summary.has_single_counter());
|
||||
assert!(!summary.has_accumulation());
|
||||
assert_eq!(summary.counter_count(), 1);
|
||||
assert_eq!(summary.accumulation_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_accumulation() {
|
||||
let names = vec!["result".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
fn test_analyze_accumulation_from_ast() {
|
||||
let names = vec!["sum".to_string()];
|
||||
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
|
||||
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(!summary.has_single_counter());
|
||||
assert!(summary.has_accumulation());
|
||||
assert_eq!(summary.counter_count(), 0);
|
||||
assert_eq!(summary.accumulation_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_analyze_mixed() {
|
||||
fn test_analyze_mixed_from_ast() {
|
||||
let names = vec!["i".to_string(), "sum".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![
|
||||
assign("sum", add(var("sum"), var("i"))),
|
||||
assign("i", add(var("i"), lit_i(1))),
|
||||
];
|
||||
|
||||
assert!(!summary.has_single_counter()); // 2つあるので false
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(!summary.has_single_counter());
|
||||
assert!(summary.has_accumulation());
|
||||
assert_eq!(summary.counter_count(), 1);
|
||||
assert_eq!(summary.accumulation_count(), 1);
|
||||
}
|
||||
|
||||
// Phase 213 tests for is_simple_if_sum_pattern
|
||||
#[test]
|
||||
fn test_is_simple_if_sum_pattern_basic() {
|
||||
// phase212_if_sum_min.hako pattern: i (counter) + sum (accumulator)
|
||||
fn test_is_simple_if_sum_pattern_basic_ast() {
|
||||
let names = vec!["i".to_string(), "sum".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![
|
||||
if_with_updates(
|
||||
var("cond"),
|
||||
vec![assign("sum", add(var("sum"), var("i")))],
|
||||
None,
|
||||
),
|
||||
assign("i", add(var("i"), lit_i(1))),
|
||||
];
|
||||
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(summary.is_simple_if_sum_pattern());
|
||||
assert_eq!(summary.counter_count(), 1);
|
||||
assert_eq!(summary.accumulation_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_simple_if_sum_pattern_with_count() {
|
||||
// Phase 195 pattern: i (counter) + sum + count (2 accumulators)
|
||||
fn test_is_simple_if_sum_pattern_with_count_ast() {
|
||||
let names = vec!["i".to_string(), "sum".to_string(), "count".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![
|
||||
if_with_updates(
|
||||
var("cond"),
|
||||
vec![
|
||||
assign("sum", add(var("sum"), var("i"))),
|
||||
assign("count", add(var("count"), lit_i(1))),
|
||||
],
|
||||
None,
|
||||
),
|
||||
assign("i", add(var("i"), lit_i(1))),
|
||||
];
|
||||
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(summary.is_simple_if_sum_pattern());
|
||||
assert_eq!(summary.counter_count(), 1);
|
||||
assert_eq!(summary.accumulation_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_simple_if_sum_pattern_no_accumulator() {
|
||||
// Only counter, no accumulator
|
||||
fn test_is_simple_if_sum_pattern_no_accumulator_ast() {
|
||||
let names = vec!["i".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![assign("i", add(var("i"), lit_i(1)))];
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern()); // No accumulator
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern());
|
||||
assert_eq!(summary.counter_count(), 1);
|
||||
assert_eq!(summary.accumulation_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_simple_if_sum_pattern_no_counter() {
|
||||
// Only accumulator, no counter
|
||||
fn test_is_simple_if_sum_pattern_no_counter_ast() {
|
||||
let names = vec!["sum".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![assign("sum", add(var("sum"), lit_i(1)))];
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern()); // No counter
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern());
|
||||
assert_eq!(summary.counter_count(), 0);
|
||||
assert_eq!(summary.accumulation_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_simple_if_sum_pattern_multiple_counters() {
|
||||
// Multiple counters (not supported)
|
||||
fn test_is_simple_if_sum_pattern_multiple_counters_ast() {
|
||||
let names = vec!["i".to_string(), "j".to_string(), "sum".to_string()];
|
||||
let summary = analyze_loop_updates(&names);
|
||||
let loop_body = vec![
|
||||
assign("i", add(var("i"), lit_i(1))),
|
||||
assign("j", add(var("j"), lit_i(1))),
|
||||
assign("sum", add(var("sum"), var("i"))),
|
||||
];
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern()); // 2 counters
|
||||
let summary = analyze_loop_updates_from_ast(&names, &loop_body);
|
||||
|
||||
assert!(!summary.is_simple_if_sum_pattern());
|
||||
assert_eq!(summary.counter_count(), 2);
|
||||
assert_eq!(summary.accumulation_count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,12 +251,57 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
// After condition lowering, allocate remaining ValueIds
|
||||
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
|
||||
|
||||
// Phase 170-B: Lower break condition using condition_to_joinir (no hardcoding!)
|
||||
let (break_cond_value, mut break_cond_instructions) = lower_condition_to_joinir(
|
||||
break_condition,
|
||||
&mut alloc_value,
|
||||
env,
|
||||
)?;
|
||||
// Phase 170-B / Phase 236-EX: Lower break condition
|
||||
//
|
||||
// - ループ条件: 既存どおり ConditionEnv + lower_condition_to_joinir 経路を使用
|
||||
// - break 条件: Phase 236 から ExprLowerer/ScopeManager 経由に切り替え
|
||||
//
|
||||
// ExprLowerer 自体は内部で lower_condition_to_joinir を呼び出すため、JoinIR レベルの命令列は
|
||||
// 従来経路と構造的に同等になることを期待している。
|
||||
let (break_cond_value, mut break_cond_instructions) = {
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
// Phase 236-EX: ExprLowerer は MirBuilder 参照を要求するが、
|
||||
// Pattern2 の JoinIR lowering では既存の JoinInst バッファと ValueId allocator を直接使っている。
|
||||
// ここでは MirBuilder は ExprLowerer 内での API 互換のためだけに使い、
|
||||
// 実際の JoinIR 命令は lower_condition_to_joinir が返すものを採用する。
|
||||
let mut dummy_builder = MirBuilder::new();
|
||||
|
||||
// Build minimal ScopeManager view for break condition lowering.
|
||||
let empty_body_env = LoopBodyLocalEnv::new();
|
||||
let empty_captured_env = CapturedEnv::new();
|
||||
|
||||
let scope_manager = Pattern2ScopeManager {
|
||||
condition_env: env,
|
||||
loop_body_local_env: Some(&empty_body_env),
|
||||
captured_env: Some(&empty_captured_env),
|
||||
carrier_info,
|
||||
};
|
||||
|
||||
let mut expr_lowerer = ExprLowerer::new(&scope_manager, ExprContext::Condition, &mut dummy_builder);
|
||||
let value_id = match expr_lowerer.lower(break_condition) {
|
||||
Ok(v) => v,
|
||||
Err(ExprLoweringError::UnsupportedNode(msg)) => {
|
||||
return Err(format!(
|
||||
"[joinir/pattern2] ExprLowerer does not support break condition node: {}",
|
||||
msg
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"[joinir/pattern2] ExprLowerer failed to lower break condition: {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let instructions = expr_lowerer.take_last_instructions();
|
||||
(value_id, instructions)
|
||||
};
|
||||
|
||||
let _const_1 = alloc_value(); // Increment constant
|
||||
let _i_next = alloc_value(); // i + 1
|
||||
|
||||
@ -146,31 +146,9 @@ impl<'a> ScopeManager for Pattern2ScopeManager<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Promoted LoopBodyLocal → Carrier lookup
|
||||
// If this variable was promoted, try to find its carrier equivalent
|
||||
if self.carrier_info.promoted_loopbodylocals.contains(&name.to_string()) {
|
||||
// Try naming conventions
|
||||
for carrier_name in &[
|
||||
format!("is_{}", name), // DigitPos pattern
|
||||
format!("is_{}_match", name), // Trim pattern
|
||||
] {
|
||||
// Check if it's the loop variable (unlikely but possible)
|
||||
if carrier_name == &self.carrier_info.loop_var_name {
|
||||
if let Some(id) = self.condition_env.get(&self.carrier_info.loop_var_name) {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise check carriers
|
||||
if let Some(carrier) = self.carrier_info.carriers.iter().find(|c| c.name == *carrier_name) {
|
||||
if let Some(join_id) = carrier.join_id {
|
||||
return Some(join_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
// 4. Promoted LoopBodyLocal → Carrier lookup(命名規約は CarrierInfo 側に集約)
|
||||
self.carrier_info
|
||||
.resolve_promoted_join_id(name)
|
||||
}
|
||||
|
||||
fn scope_of(&self, name: &str) -> Option<VarScopeKind> {
|
||||
@ -323,4 +301,36 @@ mod tests {
|
||||
assert_eq!(scope.lookup("temp"), Some(ValueId(200)));
|
||||
assert_eq!(scope.scope_of("temp"), Some(VarScopeKind::LoopBodyLocal));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern2_scope_manager_captured() {
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.insert("i".to_string(), ValueId(100));
|
||||
condition_env.insert("len".to_string(), ValueId(201));
|
||||
|
||||
let mut captured_env = crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv::new();
|
||||
captured_env.add_var(CapturedVar {
|
||||
name: "len".to_string(),
|
||||
host_id: ValueId(42),
|
||||
is_immutable: true,
|
||||
});
|
||||
|
||||
let carrier_info = CarrierInfo {
|
||||
loop_var_name: "i".to_string(),
|
||||
loop_var_id: ValueId(1),
|
||||
carriers: vec![],
|
||||
trim_helper: None,
|
||||
promoted_loopbodylocals: vec![],
|
||||
};
|
||||
|
||||
let scope = Pattern2ScopeManager {
|
||||
condition_env: &condition_env,
|
||||
loop_body_local_env: None,
|
||||
captured_env: Some(&captured_env),
|
||||
carrier_info: &carrier_info,
|
||||
};
|
||||
|
||||
assert_eq!(scope.lookup("len"), Some(ValueId(201)));
|
||||
assert_eq!(scope.scope_of("len"), Some(VarScopeKind::Captured));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user