refactor(joinir): introduce CaseAContext to consolidate common lowering logic
- Add CaseAContext struct in loop_scope_shape.rs to centralize: - LoopForm validation - intake_loop_form invocation - LoopScopeShape construction - Variable name → ValueId mapping - pinned_ids/carrier_ids/exit_args resolution - Refactor all 4 generic_case_a.rs functions to use CaseAContext: - lower_case_a_loop_to_joinir_for_minimal_skip_ws - lower_case_a_loop_to_joinir_for_trim_minimal - lower_case_a_loop_to_joinir_for_append_defs_minimal - lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal - Remove unused name_to_header_id field from LoopFormIntake (was duplicate of header_snapshot) Code reduction: ~200 lines of duplicated pattern → 4 lines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -6,10 +6,9 @@
|
||||
//! - pinned/carrier/exit は LoopVarClassBox / LoopExitLivenessBox から渡された前提で扱う
|
||||
//! - 解析に失敗したら必ず None を返し、呼び元にフォールバックさせる
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args;
|
||||
use crate::mir::join_ir::lowering::loop_form_intake::{intake_loop_form, LoopFormIntake};
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::CaseAContext;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::skip_ws as vid;
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as stage1_vid;
|
||||
@ -33,13 +32,8 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<JoinModule> {
|
||||
// 1) LoopForm が Case A っぽいか最低限チェック(header→body/exit、latch→header)
|
||||
if loop_form.header == loop_form.exit {
|
||||
eprintln!("[joinir/generic_case_a] loop_form malformed (header == exit), fallback");
|
||||
return None;
|
||||
}
|
||||
// 追加の latch チェック(skip_ws 固有)
|
||||
if loop_form.latch != loop_form.body && loop_form.latch != loop_form.header {
|
||||
// minimal_skip_ws では latch==body or header 想定。違えばフォールバック。
|
||||
eprintln!(
|
||||
"[joinir/generic_case_a] unexpected latch {:?} (body={:?}, header={:?}), fallback",
|
||||
loop_form.latch, loop_form.body, loop_form.header
|
||||
@ -47,53 +41,25 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2) MIR から pinned/carrier/exit 情報を抽出する
|
||||
let LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: _name_to_id,
|
||||
header_snapshot: header_vals_mir,
|
||||
exit_snapshots,
|
||||
exit_preds: _exit_preds,
|
||||
} = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
// CaseAContext で共通ロジックを実行
|
||||
let ctx = CaseAContext::new(
|
||||
loop_form,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
mir_func,
|
||||
"skip_ws",
|
||||
|offset| vid::loop_step(offset),
|
||||
)?;
|
||||
|
||||
// JoinIR 用の ValueId を範囲から割り当て(名前→ID マップ)
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(name.clone(), vid::loop_step(offset));
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(name.clone(), vid::loop_step(offset));
|
||||
offset += 1;
|
||||
}
|
||||
// JoinModule を手書き skip_ws と同じ形で構築(ValueId 範囲も揃える)
|
||||
let string_key = ctx.pinned_name_or_first(0)?;
|
||||
let len_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone());
|
||||
let index_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
// Exit live 集合(LoopExitLivenessBox を利用)
|
||||
let exit_live_set: BTreeSet<String> =
|
||||
exit_live.compute_live_at_exit(query, loop_form.exit, &header_vals_mir, &exit_snapshots);
|
||||
let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
// 3) JoinModule を手書き skip_ws と同じ形で構築(ValueId 範囲も揃える)
|
||||
let string_key = ordered_pinned.get(0).cloned()?;
|
||||
let len_key = ordered_pinned
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| string_key.clone());
|
||||
let index_key = ordered_carriers.get(0).cloned()?;
|
||||
|
||||
let s_loop = *name_to_loop_id.get(&string_key)?;
|
||||
let i_loop = *name_to_loop_id.get(&index_key)?;
|
||||
let n_loop = *name_to_loop_id.get(&len_key)?;
|
||||
let s_loop = ctx.get_loop_id(&string_key)?;
|
||||
let i_loop = ctx.get_loop_id(&index_key)?;
|
||||
let n_loop = ctx.get_loop_id(&len_key)?;
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
@ -123,9 +89,10 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
}));
|
||||
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
let loop_call_args: Vec<ValueId> = ordered_pinned
|
||||
let loop_call_args: Vec<ValueId> = ctx
|
||||
.ordered_pinned
|
||||
.iter()
|
||||
.chain(ordered_carriers.iter())
|
||||
.chain(ctx.ordered_carriers.iter())
|
||||
.map(|name| entry_name_to_id.get(name).copied())
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
@ -140,7 +107,7 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
join_module.add_function(skip_func);
|
||||
|
||||
// loop_step(s, i, n)
|
||||
let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone());
|
||||
let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone());
|
||||
let loop_params = header_shape.to_loop_step_params();
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone());
|
||||
@ -164,10 +131,10 @@ pub fn lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
rhs: n_loop,
|
||||
}));
|
||||
|
||||
let _exit_shape = if exit_args.is_empty() {
|
||||
let _exit_shape = if ctx.exit_args.is_empty() {
|
||||
LoopExitShape::new_manual(vec![i_loop])
|
||||
} else {
|
||||
LoopExitShape::new_manual(exit_args.clone())
|
||||
LoopExitShape::new_manual(ctx.exit_args.clone())
|
||||
}; // exit_args = [i] が期待値
|
||||
|
||||
// if i >= n { return i }
|
||||
@ -279,60 +246,24 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal(
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<JoinModule> {
|
||||
if loop_form.header == loop_form.exit {
|
||||
eprintln!("[joinir/generic_case_a/trim] loop_form malformed (header == exit), fallback");
|
||||
return None;
|
||||
}
|
||||
// CaseAContext で共通ロジックを実行
|
||||
let ctx = CaseAContext::new(
|
||||
loop_form,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
mir_func,
|
||||
"trim",
|
||||
|offset| value_id_ranges::funcscanner_trim::loop_step(offset),
|
||||
)?;
|
||||
|
||||
let LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: _name_to_id,
|
||||
header_snapshot,
|
||||
exit_snapshots,
|
||||
exit_preds: _exit_preds,
|
||||
} = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
let string_key = ctx.pinned_name_or_first(0)?;
|
||||
let base_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| string_key.clone());
|
||||
let carrier_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(
|
||||
name.clone(),
|
||||
value_id_ranges::funcscanner_trim::loop_step(offset),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(
|
||||
name.clone(),
|
||||
value_id_ranges::funcscanner_trim::loop_step(offset),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
let exit_live_set: BTreeSet<String> =
|
||||
exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots);
|
||||
let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
let string_key = ordered_pinned.get(0).cloned()?;
|
||||
let base_key = ordered_pinned
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| string_key.clone());
|
||||
let carrier_key = ordered_carriers.get(0).cloned()?;
|
||||
|
||||
let s_loop = *name_to_loop_id.get(&string_key)?;
|
||||
let b_loop = *name_to_loop_id.get(&base_key)?;
|
||||
let e_loop = *name_to_loop_id.get(&carrier_key)?;
|
||||
let s_loop = ctx.get_loop_id(&string_key)?;
|
||||
let b_loop = ctx.get_loop_id(&base_key)?;
|
||||
let e_loop = ctx.get_loop_id(&carrier_key)?;
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
@ -400,9 +331,10 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal(
|
||||
entry_name_to_id.insert(base_key.clone(), b_val);
|
||||
entry_name_to_id.insert(carrier_key.clone(), e_init);
|
||||
|
||||
let loop_call_args: Vec<ValueId> = ordered_pinned
|
||||
let loop_call_args: Vec<ValueId> = ctx
|
||||
.ordered_pinned
|
||||
.iter()
|
||||
.chain(ordered_carriers.iter())
|
||||
.chain(ctx.ordered_carriers.iter())
|
||||
.map(|name| entry_name_to_id.get(name).copied())
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
@ -418,7 +350,7 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal(
|
||||
join_module.add_function(trim_main_func);
|
||||
|
||||
// loop_step(str, b, e)
|
||||
let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone());
|
||||
let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone());
|
||||
let loop_params = header_shape.to_loop_step_params();
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone());
|
||||
@ -461,10 +393,10 @@ pub fn lower_case_a_loop_to_joinir_for_trim_minimal(
|
||||
op: CompareOp::Eq,
|
||||
}));
|
||||
|
||||
let _exit_shape_trim = if exit_args.is_empty() {
|
||||
let _exit_shape_trim = if ctx.exit_args.is_empty() {
|
||||
LoopExitShape::new_manual(vec![e_loop])
|
||||
} else {
|
||||
LoopExitShape::new_manual(exit_args.clone())
|
||||
LoopExitShape::new_manual(ctx.exit_args.clone())
|
||||
};
|
||||
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
@ -794,67 +726,26 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal(
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<JoinModule> {
|
||||
if loop_form.header == loop_form.exit {
|
||||
eprintln!(
|
||||
"[joinir/generic_case_a/append_defs] loop_form malformed (header == exit), fallback"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
// CaseAContext で共通ロジックを実行
|
||||
let ctx = CaseAContext::new(
|
||||
loop_form,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
mir_func,
|
||||
"append_defs",
|
||||
|offset| value_id_ranges::funcscanner_append_defs::loop_step(offset),
|
||||
)?;
|
||||
|
||||
let LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: _name_to_id,
|
||||
header_snapshot,
|
||||
exit_snapshots,
|
||||
exit_preds: _exit_preds,
|
||||
} = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
let dst_key = ctx.pinned_name_or_first(0)?;
|
||||
let defs_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| dst_key.clone());
|
||||
let n_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| defs_key.clone());
|
||||
let i_key = ctx.carrier_name_or_first(0)?;
|
||||
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(
|
||||
name.clone(),
|
||||
value_id_ranges::funcscanner_append_defs::loop_step(offset),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(
|
||||
name.clone(),
|
||||
value_id_ranges::funcscanner_append_defs::loop_step(offset),
|
||||
);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
let exit_live_set: BTreeSet<String> =
|
||||
exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots);
|
||||
let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
let dst_key = ordered_pinned.get(0).cloned()?;
|
||||
let defs_key = ordered_pinned
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| dst_key.clone());
|
||||
let n_key = ordered_pinned
|
||||
.get(2)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| defs_key.clone());
|
||||
let i_key = ordered_carriers.get(0).cloned()?;
|
||||
|
||||
let dst_loop = *name_to_loop_id.get(&dst_key)?;
|
||||
let defs_loop = *name_to_loop_id.get(&defs_key)?;
|
||||
let n_loop = *name_to_loop_id.get(&n_key)?;
|
||||
let i_loop = *name_to_loop_id.get(&i_key)?;
|
||||
let dst_loop = ctx.get_loop_id(&dst_key)?;
|
||||
let defs_loop = ctx.get_loop_id(&defs_key)?;
|
||||
let n_loop = ctx.get_loop_id(&n_key)?;
|
||||
let i_loop = ctx.get_loop_id(&i_key)?;
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
@ -882,9 +773,10 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal(
|
||||
entry_name_to_id.insert("n".to_string(), n_param);
|
||||
entry_name_to_id.insert("i".to_string(), i_init);
|
||||
|
||||
let loop_call_args: Vec<ValueId> = ordered_pinned
|
||||
let loop_call_args: Vec<ValueId> = ctx
|
||||
.ordered_pinned
|
||||
.iter()
|
||||
.chain(ordered_carriers.iter())
|
||||
.chain(ctx.ordered_carriers.iter())
|
||||
.map(|name| entry_name_to_id.get(name).copied())
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
@ -900,7 +792,7 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal(
|
||||
join_module.add_function(entry_func);
|
||||
|
||||
// loop_step(dst, defs_box, n, i)
|
||||
let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone());
|
||||
let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone());
|
||||
let loop_params = header_shape.to_loop_step_params();
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone());
|
||||
@ -915,11 +807,11 @@ pub fn lower_case_a_loop_to_joinir_for_append_defs_minimal(
|
||||
rhs: n_loop,
|
||||
}));
|
||||
|
||||
let _exit_shape = LoopExitShape::new_manual(exit_args.clone());
|
||||
let _exit_shape = LoopExitShape::new_manual(ctx.exit_args.clone());
|
||||
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId::new(0),
|
||||
args: exit_args.clone(),
|
||||
args: ctx.exit_args.clone(),
|
||||
cond: Some(cmp_result),
|
||||
});
|
||||
|
||||
@ -988,69 +880,30 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<JoinModule> {
|
||||
if loop_form.header == loop_form.exit {
|
||||
eprintln!("[joinir/generic_case_a/stage1] loop_form malformed (header == exit), fallback");
|
||||
return None;
|
||||
}
|
||||
// CaseAContext で共通ロジックを実行
|
||||
let ctx = CaseAContext::new(
|
||||
loop_form,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
mir_func,
|
||||
"stage1",
|
||||
|offset| stage1_vid::loop_step(offset),
|
||||
)?;
|
||||
|
||||
let LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: _name_to_id,
|
||||
header_snapshot,
|
||||
exit_snapshots,
|
||||
exit_preds: _exit_preds,
|
||||
} = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
let entries_key = ctx.pinned_name_or_first(0)?;
|
||||
let n_key = ctx.pinned_name_or_first(1).unwrap_or_else(|| entries_key.clone());
|
||||
let modules_key = ctx.pinned_name_or_first(2).unwrap_or_else(|| entries_key.clone());
|
||||
let seen_key = ctx.pinned_name_or_first(3).unwrap_or_else(|| entries_key.clone());
|
||||
let prefix_key = ctx.carrier_name_or_first(0)?;
|
||||
let i_key = ctx.carrier_name_or_first(1).unwrap_or_else(|| prefix_key.clone());
|
||||
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset));
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(name.clone(), stage1_vid::loop_step(offset));
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
let exit_live_set: BTreeSet<String> =
|
||||
exit_live.compute_live_at_exit(query, loop_form.exit, &header_snapshot, &exit_snapshots);
|
||||
let exit_args = resolve_exit_args(&exit_live_set, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
let entries_key = ordered_pinned.get(0).cloned()?;
|
||||
let n_key = ordered_pinned
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let modules_key = ordered_pinned
|
||||
.get(2)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let seen_key = ordered_pinned
|
||||
.get(3)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| entries_key.clone());
|
||||
let prefix_key = ordered_carriers.get(0).cloned()?;
|
||||
let i_key = ordered_carriers
|
||||
.get(1)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| prefix_key.clone());
|
||||
|
||||
let entries_loop = *name_to_loop_id.get(&entries_key)?;
|
||||
let n_loop = *name_to_loop_id.get(&n_key)?;
|
||||
let modules_loop = *name_to_loop_id.get(&modules_key)?;
|
||||
let seen_loop = *name_to_loop_id.get(&seen_key)?;
|
||||
let prefix_loop = *name_to_loop_id.get(&prefix_key)?;
|
||||
let i_loop = *name_to_loop_id.get(&i_key)?;
|
||||
let entries_loop = ctx.get_loop_id(&entries_key)?;
|
||||
let n_loop = ctx.get_loop_id(&n_key)?;
|
||||
let modules_loop = ctx.get_loop_id(&modules_key)?;
|
||||
let seen_loop = ctx.get_loop_id(&seen_key)?;
|
||||
let prefix_loop = ctx.get_loop_id(&prefix_key)?;
|
||||
let i_loop = ctx.get_loop_id(&i_key)?;
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
@ -1089,9 +942,10 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
|
||||
entry_name_to_id.insert(prefix_key.clone(), prefix_param);
|
||||
entry_name_to_id.insert(i_key.clone(), i_init);
|
||||
|
||||
let loop_call_args: Vec<ValueId> = ordered_pinned
|
||||
let loop_call_args: Vec<ValueId> = ctx
|
||||
.ordered_pinned
|
||||
.iter()
|
||||
.chain(ordered_carriers.iter())
|
||||
.chain(ctx.ordered_carriers.iter())
|
||||
.map(|name| entry_name_to_id.get(name).copied())
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
@ -1107,7 +961,7 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
|
||||
join_module.add_function(resolve_func);
|
||||
|
||||
// loop_step(entries, n, modules, seen, prefix, i)
|
||||
let header_shape = LoopHeaderShape::new_manual(pinned_ids.clone(), carrier_ids.clone());
|
||||
let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone());
|
||||
let loop_params = header_shape.to_loop_step_params();
|
||||
let mut loop_step_func =
|
||||
JoinFunction::new(loop_step_id, "loop_step".to_string(), loop_params.clone());
|
||||
@ -1122,10 +976,10 @@ pub fn lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
|
||||
rhs: n_loop,
|
||||
}));
|
||||
|
||||
let exit_shape = if exit_args.is_empty() {
|
||||
let exit_shape = if ctx.exit_args.is_empty() {
|
||||
LoopExitShape::new_manual(vec![prefix_loop])
|
||||
} else {
|
||||
LoopExitShape::new_manual(exit_args.clone())
|
||||
LoopExitShape::new_manual(ctx.exit_args.clone())
|
||||
};
|
||||
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
|
||||
@ -14,7 +14,6 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
pub(crate) struct LoopFormIntake {
|
||||
pub pinned_ordered: Vec<String>,
|
||||
pub carrier_ordered: Vec<String>,
|
||||
pub name_to_header_id: BTreeMap<String, ValueId>,
|
||||
pub header_snapshot: BTreeMap<String, ValueId>,
|
||||
pub exit_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)>,
|
||||
pub exit_preds: Vec<BasicBlockId>,
|
||||
@ -204,7 +203,6 @@ pub(crate) fn intake_loop_form(
|
||||
Some(LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: header_vals_mir.clone(),
|
||||
header_snapshot: header_vals_mir,
|
||||
exit_snapshots,
|
||||
exit_preds,
|
||||
|
||||
499
src/mir/join_ir/lowering/loop_scope_shape.rs
Normal file
499
src/mir/join_ir/lowering/loop_scope_shape.rs
Normal file
@ -0,0 +1,499 @@
|
||||
//! LoopScopeShape - ループ変数スコープの統合ビュー
|
||||
//!
|
||||
//! # Purpose
|
||||
//!
|
||||
//! 既存の3箱(LoopVarClassBox, LoopExitLivenessBox, LocalScopeInspectorBox)の
|
||||
//! 情報を1つの構造体に統合し、JoinIR lowering が参照する唯一のソースにする。
|
||||
//!
|
||||
//! # Design Philosophy (Box-First)
|
||||
//!
|
||||
//! Phase 29/30 で以下の責務分離を実現:
|
||||
//! - LoopForm: ループの「形」(preheader/header/body/latch/exit)
|
||||
//! - LoopScopeShape: 変数の「役割」(pinned/carrier/body_local/exit_live)
|
||||
//! - JoinIR: 関数と継続で表現された制御構造
|
||||
//!
|
||||
//! # Phase 29 Strategy
|
||||
//!
|
||||
//! 現在は「既存箱を呼ぶだけの薄いラッパー」として実装。
|
||||
//! 将来は既存箱を吸収して LoopScopeShape を唯一のソースにする(Phase 30)。
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let scope = LoopScopeShape::from_existing_boxes(
|
||||
//! &loop_form_intake,
|
||||
//! &var_classes,
|
||||
//! &exit_live,
|
||||
//! query,
|
||||
//! )?;
|
||||
//!
|
||||
//! // JoinIR lowering では scope のフィールドを参照
|
||||
//! for name in &scope.pinned {
|
||||
//! // ...
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::mir::join_ir::lowering::exit_args_resolver::resolve_exit_args;
|
||||
use crate::mir::join_ir::lowering::loop_form_intake::{intake_loop_form, LoopFormIntake};
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirQuery, ValueId};
|
||||
|
||||
/// ループ変数スコープの統合ビュー
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `pinned`: ループ外から来て変わらない変数(header/exit PHI 両方必要)
|
||||
/// - `carriers`: 各イテレーションで更新される変数(header/exit PHI 両方必要)
|
||||
/// - `body_locals`: ループ内だけで完結する変数(JoinIR では参照しない)
|
||||
/// - `exit_live`: ループ後に使われる変数(needs_exit_phi() == true)
|
||||
/// - `progress_carrier`: ループを前に進める変数(将来の Verifier 用)
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LoopScopeShape {
|
||||
/// Loop-crossing parameters (always need header/exit PHI)
|
||||
pub pinned: BTreeSet<String>,
|
||||
|
||||
/// Loop-modified variables (always need header/exit PHI)
|
||||
pub carriers: BTreeSet<String>,
|
||||
|
||||
/// Body-local variables (JoinIR doesn't need these)
|
||||
/// Includes both BodyLocalExit and BodyLocalInternal
|
||||
pub body_locals: BTreeSet<String>,
|
||||
|
||||
/// Variables live at exit (needs_exit_phi() == true)
|
||||
/// = pinned ∪ carriers ∪ BodyLocalExit
|
||||
pub exit_live: BTreeSet<String>,
|
||||
|
||||
/// Progress carrier for loop termination check (future Verifier use)
|
||||
/// Typically the loop index variable (i, pos, etc.)
|
||||
pub progress_carrier: Option<String>,
|
||||
}
|
||||
|
||||
impl LoopScopeShape {
|
||||
/// Create LoopScopeShape from existing boxes (Phase 29: thin wrapper)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `intake`: LoopFormIntake containing classified variable info
|
||||
/// - `var_classes`: LoopVarClassBox for classification
|
||||
/// - `exit_live_box`: LoopExitLivenessBox for exit liveness
|
||||
/// - `query`: MirQuery for liveness computation
|
||||
/// - `exit_block`: Exit block ID
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Some(LoopScopeShape) if successful, None if critical data is missing.
|
||||
///
|
||||
/// # Phase 29 Behavior
|
||||
///
|
||||
/// Currently just reads from existing boxes without modifying behavior.
|
||||
/// Future Phase 30 will absorb the boxes entirely.
|
||||
pub(crate) fn from_existing_boxes(
|
||||
intake: &LoopFormIntake,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live_box: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
exit_block: BasicBlockId,
|
||||
) -> Option<Self> {
|
||||
// Extract pinned and carriers from intake (already classified)
|
||||
let pinned: BTreeSet<String> = intake.pinned_ordered.iter().cloned().collect();
|
||||
let carriers: BTreeSet<String> = intake.carrier_ordered.iter().cloned().collect();
|
||||
|
||||
// Build LocalScopeInspectorBox for body_local classification
|
||||
let mut inspector =
|
||||
crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new();
|
||||
inspector.record_snapshot(exit_block, &intake.header_snapshot);
|
||||
for (bb, snap) in &intake.exit_snapshots {
|
||||
inspector.record_snapshot(*bb, snap);
|
||||
}
|
||||
|
||||
// Classify all variables to find body_locals
|
||||
let all_names: Vec<String> = intake.header_snapshot.keys().cloned().collect();
|
||||
let classified = var_classes.classify_all(
|
||||
&all_names,
|
||||
&intake.pinned_ordered,
|
||||
&intake.carrier_ordered,
|
||||
&inspector,
|
||||
&intake.exit_preds,
|
||||
);
|
||||
|
||||
let mut body_locals: BTreeSet<String> = BTreeSet::new();
|
||||
let mut exit_live: BTreeSet<String> = BTreeSet::new();
|
||||
|
||||
for (name, class) in &classified {
|
||||
match class {
|
||||
LoopVarClass::Pinned => {
|
||||
exit_live.insert(name.clone());
|
||||
}
|
||||
LoopVarClass::Carrier => {
|
||||
exit_live.insert(name.clone());
|
||||
}
|
||||
LoopVarClass::BodyLocalExit => {
|
||||
body_locals.insert(name.clone());
|
||||
exit_live.insert(name.clone());
|
||||
}
|
||||
LoopVarClass::BodyLocalInternal => {
|
||||
body_locals.insert(name.clone());
|
||||
// NOT in exit_live (Option C: skip exit PHI)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute exit_live from LoopExitLivenessBox (Phase 1: usually empty)
|
||||
let exit_live_from_box = exit_live_box.compute_live_at_exit(
|
||||
query,
|
||||
exit_block,
|
||||
&intake.header_snapshot,
|
||||
&intake.exit_snapshots,
|
||||
);
|
||||
|
||||
// Merge: exit_live = (classification-based) ∪ (liveness-box-based)
|
||||
for name in exit_live_from_box {
|
||||
exit_live.insert(name);
|
||||
}
|
||||
|
||||
// Determine progress_carrier (heuristic: first carrier, typically 'i')
|
||||
let progress_carrier = carriers.iter().next().cloned();
|
||||
|
||||
Some(Self {
|
||||
pinned,
|
||||
carriers,
|
||||
body_locals,
|
||||
exit_live,
|
||||
progress_carrier,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a variable needs header PHI
|
||||
pub fn needs_header_phi(&self, var_name: &str) -> bool {
|
||||
self.pinned.contains(var_name) || self.carriers.contains(var_name)
|
||||
}
|
||||
|
||||
/// Check if a variable needs exit PHI
|
||||
pub fn needs_exit_phi(&self, var_name: &str) -> bool {
|
||||
self.exit_live.contains(var_name)
|
||||
}
|
||||
|
||||
/// Get ordered pinned variables (for JoinIR parameter generation)
|
||||
pub fn pinned_ordered(&self) -> Vec<String> {
|
||||
self.pinned.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// Get ordered carrier variables (for JoinIR parameter generation)
|
||||
pub fn carriers_ordered(&self) -> Vec<String> {
|
||||
self.carriers.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// Get all variables that need header PHI (pinned + carriers)
|
||||
pub fn header_phi_vars(&self) -> Vec<String> {
|
||||
let mut result: Vec<String> = self.pinned.iter().cloned().collect();
|
||||
result.extend(self.carriers.iter().cloned());
|
||||
result
|
||||
}
|
||||
|
||||
/// Get all variables that need exit PHI
|
||||
pub fn exit_phi_vars(&self) -> Vec<String> {
|
||||
self.exit_live.iter().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CaseAContext - generic_case_a 共通ロジックの集約
|
||||
// ============================================================================
|
||||
|
||||
/// Case A lowering の共通コンテキスト
|
||||
///
|
||||
/// generic_case_a.rs の4関数に共通するロジックを集約し、
|
||||
/// 約200行の重複コードを削減する。
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```ignore
|
||||
/// let ctx = CaseAContext::new(
|
||||
/// loop_form, var_classes, exit_live, query, mir_func,
|
||||
/// "skip_ws",
|
||||
/// |offset| vid::loop_step(offset),
|
||||
/// )?;
|
||||
///
|
||||
/// // ctx から必要な情報を取り出して JoinModule を構築
|
||||
/// let header_shape = LoopHeaderShape::new_manual(ctx.pinned_ids.clone(), ctx.carrier_ids.clone());
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CaseAContext {
|
||||
/// LoopScopeShape(変数スコープ情報)
|
||||
pub scope: LoopScopeShape,
|
||||
|
||||
/// 順序付き pinned 変数名
|
||||
pub ordered_pinned: Vec<String>,
|
||||
|
||||
/// 順序付き carrier 変数名
|
||||
pub ordered_carriers: Vec<String>,
|
||||
|
||||
/// 変数名 → ループ関数内 ValueId のマッピング
|
||||
pub name_to_loop_id: BTreeMap<String, ValueId>,
|
||||
|
||||
/// pinned 変数の ValueId 列
|
||||
pub pinned_ids: Vec<ValueId>,
|
||||
|
||||
/// carrier 変数の ValueId 列
|
||||
pub carrier_ids: Vec<ValueId>,
|
||||
|
||||
/// exit 時に渡す引数の ValueId 列
|
||||
pub exit_args: Vec<ValueId>,
|
||||
}
|
||||
|
||||
impl CaseAContext {
|
||||
/// CaseAContext を構築する
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `loop_form`: ループ構造情報
|
||||
/// - `var_classes`: 変数分類器
|
||||
/// - `exit_live`: exit liveness 情報
|
||||
/// - `query`: MIR クエリ
|
||||
/// - `mir_func`: MIR 関数
|
||||
/// - `log_tag`: ログ出力用タグ(例: "skip_ws", "trim")
|
||||
/// - `loop_step_id_fn`: offset から ValueId を生成する関数
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Some(CaseAContext) if successful, None if validation fails or data is missing.
|
||||
pub(crate) fn new<F>(
|
||||
loop_form: &LoopForm,
|
||||
var_classes: &LoopVarClassBox,
|
||||
exit_live: &LoopExitLivenessBox,
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
log_tag: &str,
|
||||
loop_step_id_fn: F,
|
||||
) -> Option<Self>
|
||||
where
|
||||
F: Fn(u32) -> ValueId,
|
||||
{
|
||||
// 1) LoopForm validation
|
||||
if loop_form.header == loop_form.exit {
|
||||
eprintln!(
|
||||
"[joinir/generic_case_a/{}] loop_form malformed (header == exit), fallback",
|
||||
log_tag
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
// 2) MIR から pinned/carrier/exit 情報を抽出
|
||||
let intake = intake_loop_form(loop_form, var_classes, query, mir_func)?;
|
||||
|
||||
// 3) LoopScopeShape を構築
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&intake,
|
||||
var_classes,
|
||||
exit_live,
|
||||
query,
|
||||
loop_form.exit,
|
||||
)?;
|
||||
|
||||
let ordered_pinned = scope.pinned_ordered();
|
||||
let ordered_carriers = scope.carriers_ordered();
|
||||
|
||||
// 4) 変数名 → ValueId マッピングを構築
|
||||
let mut name_to_loop_id: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut offset: u32 = 0;
|
||||
for name in &ordered_pinned {
|
||||
name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset));
|
||||
offset += 1;
|
||||
}
|
||||
for name in &ordered_carriers {
|
||||
name_to_loop_id.insert(name.clone(), loop_step_id_fn(offset));
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
// 5) pinned_ids / carrier_ids を構築
|
||||
let pinned_ids: Vec<ValueId> = ordered_pinned
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
let carrier_ids: Vec<ValueId> = ordered_carriers
|
||||
.iter()
|
||||
.filter_map(|k| name_to_loop_id.get(k).copied())
|
||||
.collect();
|
||||
|
||||
// 6) exit_args を解決
|
||||
let exit_args = resolve_exit_args(&scope.exit_live, &name_to_loop_id, &ordered_carriers)?;
|
||||
|
||||
Some(Self {
|
||||
scope,
|
||||
ordered_pinned,
|
||||
ordered_carriers,
|
||||
name_to_loop_id,
|
||||
pinned_ids,
|
||||
carrier_ids,
|
||||
exit_args,
|
||||
})
|
||||
}
|
||||
|
||||
/// 変数名から loop 関数内の ValueId を取得
|
||||
pub fn get_loop_id(&self, name: &str) -> Option<ValueId> {
|
||||
self.name_to_loop_id.get(name).copied()
|
||||
}
|
||||
|
||||
/// pinned 変数の n 番目の名前を取得(なければ 0 番目を使う)
|
||||
pub fn pinned_name_or_first(&self, index: usize) -> Option<String> {
|
||||
self.ordered_pinned
|
||||
.get(index)
|
||||
.cloned()
|
||||
.or_else(|| self.ordered_pinned.first().cloned())
|
||||
}
|
||||
|
||||
/// carrier 変数の n 番目の名前を取得(なければ 0 番目を使う)
|
||||
pub fn carrier_name_or_first(&self, index: usize) -> Option<String> {
|
||||
self.ordered_carriers
|
||||
.get(index)
|
||||
.cloned()
|
||||
.or_else(|| self.ordered_carriers.first().cloned())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn make_dummy_intake() -> LoopFormIntake {
|
||||
let mut header_snapshot = BTreeMap::new();
|
||||
header_snapshot.insert("s".to_string(), ValueId(10));
|
||||
header_snapshot.insert("n".to_string(), ValueId(20));
|
||||
header_snapshot.insert("i".to_string(), ValueId(30));
|
||||
|
||||
LoopFormIntake {
|
||||
pinned_ordered: vec!["s".to_string(), "n".to_string()],
|
||||
carrier_ordered: vec!["i".to_string()],
|
||||
header_snapshot,
|
||||
exit_snapshots: vec![],
|
||||
exit_preds: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyQuery;
|
||||
impl MirQuery for EmptyQuery {
|
||||
fn insts_in_block(&self, _bb: BasicBlockId) -> &[crate::mir::MirInstruction] {
|
||||
&[]
|
||||
}
|
||||
fn succs(&self, _bb: BasicBlockId) -> Vec<BasicBlockId> {
|
||||
Vec::new()
|
||||
}
|
||||
fn reads_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
|
||||
Vec::new()
|
||||
}
|
||||
fn writes_of(&self, _inst: &crate::mir::MirInstruction) -> Vec<ValueId> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_existing_boxes_basic() {
|
||||
let intake = make_dummy_intake();
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
BasicBlockId::new(100),
|
||||
);
|
||||
|
||||
assert!(scope.is_some());
|
||||
let scope = scope.unwrap();
|
||||
|
||||
// pinned should be {s, n}
|
||||
assert!(scope.pinned.contains("s"));
|
||||
assert!(scope.pinned.contains("n"));
|
||||
assert_eq!(scope.pinned.len(), 2);
|
||||
|
||||
// carriers should be {i}
|
||||
assert!(scope.carriers.contains("i"));
|
||||
assert_eq!(scope.carriers.len(), 1);
|
||||
|
||||
// progress_carrier should be "i"
|
||||
assert_eq!(scope.progress_carrier, Some("i".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_needs_header_phi() {
|
||||
let intake = make_dummy_intake();
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(scope.needs_header_phi("s"));
|
||||
assert!(scope.needs_header_phi("n"));
|
||||
assert!(scope.needs_header_phi("i"));
|
||||
assert!(!scope.needs_header_phi("unknown"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_needs_exit_phi() {
|
||||
let intake = make_dummy_intake();
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Pinned and Carrier should need exit PHI
|
||||
assert!(scope.needs_exit_phi("s"));
|
||||
assert!(scope.needs_exit_phi("n"));
|
||||
assert!(scope.needs_exit_phi("i"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ordered_accessors() {
|
||||
let intake = make_dummy_intake();
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live_box = LoopExitLivenessBox::new();
|
||||
let query = EmptyQuery;
|
||||
|
||||
let scope = LoopScopeShape::from_existing_boxes(
|
||||
&intake,
|
||||
&var_classes,
|
||||
&exit_live_box,
|
||||
&query,
|
||||
BasicBlockId::new(100),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let pinned = scope.pinned_ordered();
|
||||
assert_eq!(pinned.len(), 2);
|
||||
assert!(pinned.contains(&"s".to_string()));
|
||||
assert!(pinned.contains(&"n".to_string()));
|
||||
|
||||
let carriers = scope.carriers_ordered();
|
||||
assert_eq!(carriers.len(), 1);
|
||||
assert!(carriers.contains(&"i".to_string()));
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ pub mod funcscanner_append_defs;
|
||||
pub mod funcscanner_trim;
|
||||
pub mod generic_case_a;
|
||||
pub mod loop_form_intake;
|
||||
pub mod loop_scope_shape;
|
||||
pub mod min_loop;
|
||||
pub mod skip_ws;
|
||||
pub mod stage1_using_resolver;
|
||||
|
||||
Reference in New Issue
Block a user