## Task B: External env input bug fix (Priority 1) Fix: Suffix normalization couldn't access prefix-built local variables **Problem**: `s.length()` failed because 's' (from prefix `s = "abc"`) was not in available_inputs during suffix normalization. **Root cause**: `AvailableInputsCollectorBox::collect()` only collected function params and CapturedEnv, missing `builder.variable_map`. **Solution**: Add `prefix_variables` parameter with 3-source merge: 1. Function params (highest priority) 2. Prefix variables (medium priority - NEW) 3. CapturedEnv (lowest priority) **Changed files**: - src/mir/control_tree/normalized_shadow/available_inputs_collector.rs - src/mir/builder/control_flow/normalization/execute_box.rs - src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs - src/mir/builder/control_flow/joinir/routing.rs - src/mir/builder/stmts.rs - src/mir/control_tree/normalized_shadow/dev_pipeline.rs - docs/development/current/main/design/normalized-expr-lowering.md (Available Inputs SSOT section) **Tests**: 3 new unit tests (prefix merge, priority order) ## Task A: KnownIntrinsic SSOT化 (Priority 2) Eliminate string literal scattered matching by centralizing to registry. **Problem**: Adding new intrinsics required editing if/match chains with hard-coded string literals (`if method == KnownIntrinsic::Length0.method_name()`). **Solution**: Create `KnownIntrinsicRegistryBox` as SSOT: - `lookup(method, arity) -> Option<KnownIntrinsic>` - `get_spec(intrinsic) -> KnownIntrinsicSpec` - Adding new intrinsics now requires: (1) enum variant, (2) registry entry only **Changed files**: - src/mir/control_tree/normalized_shadow/common/known_intrinsics.rs (NEW) - src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs - src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs (deprecated methods removed) - src/mir/control_tree/normalized_shadow/common/mod.rs - docs/development/current/main/design/normalized-expr-lowering.md (Known Intrinsic SSOT section) **Impact**: ~30% code reduction in intrinsic matching logic ## Task C: Better diagnostics (Priority 3) Add `OutOfScopeReason::IntrinsicNotWhitelisted` for precise diagnostics. **Changed files**: - src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs (enum variant) - src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs (diagnostic logic) ## Verification ✅ Build: `cargo build --release` - PASS ✅ Phase 97 regression: next_non_ws LLVM EXE - PASS ✅ Phase 131: loop(true) break-once VM - PASS ✅ Phase 136: return literal VM - PASS ✅ Phase 137: return x+2 VM - PASS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7.8 KiB
Normalized Expression Lowering (ExprLowererBox)
Status: SSOT
Scope: Normalized shadow での expression lowering(pure のみ)を、パターン総当たりではなく AST walker で一般化する。
Related:
docs/development/current/main/30-Backlog.mddocs/development/current/main/phases/phase-138/README.mddocs/development/current/main/phases/phase-140/README.mddocs/development/current/main/phases/phase-141/README.mdsrc/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rssrc/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rssrc/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rssrc/mir/builder/control_flow/normalization/README.md
目的(なぜ必要か)
Phase 131–138 で loop(true) 系の Normalized shadow を段階的に拡張したが、return/式の表現力を「形ごとのパターン追加」で増やし続けると、パターン爆発で持続不可能になる。
ここでの収束方針は次の通り:
- 制御フローの骨格(loop/if/post_k/continuation)は正規化(段階投入)で固める
- 式(return value を含む)は一般化(AST walker)で受ける
非目標
- Call/MethodCall の一般化(effects + typing を含む)を Phase 140 でやらない
→ Phase 141+ に分離して段階投入する。 - PHI 生成を Normalized 側に戻すこと(PHI-free 維持。必要なら後段に押し出す)
SSOT: NormalizedExprLowererBox
役割
NormalizedExprLowererBox は、pure expression を JoinIR(Normalized dialect)へ lowering する SSOT になる。
- 入力: AST(
AstNodeHandle/ASTNode)+ env(BTreeMap<String, ValueId>)+ 出力先Vec<JoinInst>+next_value_id - 出力:
Result<Option<ValueId>, String>Ok(Some(vid)): lowering 成功(vidが式の値)Ok(None): out-of-scope(既存経路へフォールバック、既定挙動不変)Err(...): 内部不整合のみ(strict では fail-fast)
Pure Expression の定義(Phase 140 の範囲)
副作用がなく、Control-flow を内包しない式のみ。
VariableLiteral(Integer/Bool を主対象。String/Float は必要になったら追加)UnaryOp(not,-)BinaryOp(+ - * /)Compare(== < <= > >=等の比較系)
Call/MethodCall は対象外(Phase 141+)。
ReturnValueLowererBox の縮退方針
Phase 138 時点では ReturnValueLowererBox が return の形(var/int/add)を直接扱っている。
Phase 140 以降は、ReturnValueLowererBox を「return 構文(None/Some)を処理する薄い箱」に縮退し、実体は NormalizedExprLowererBox に委譲する方針とする。
実装 SSOT:
src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rssrc/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs
収束ロードマップ
- Phase 139: if-only
post_if_post_k.rsの return をReturnValueLowererBoxへ統一(出口 SSOT 完成) - Phase 140:
NormalizedExprLowererBox初版(pure のみ)導入、return は ExprLowerer 経由へ寄せる - Phase 141 P0: impure 拡張点(contract)を SSOT 化(Call/MethodCall はまだ out-of-scope)
- Phase 141 P1: “既知 intrinsic だけ” を許可して段階投入(それ以外は out-of-scope のまま)
- Phase 141 P2+: Call/MethodCall を effects + typing で分離しつつ段階投入
パターン爆発の分解(実務)
パターン爆発には “別種” が混ざるため、対策も分ける。
- 式(return 値 / 右辺)の爆発:
return 7,return x+2,return x+y,return (x+2)+3…- 対策: ExprLowerer(AST walker)の対応ノードを増やす 1 箇所に収束させる。
- 制御(loop/if/break/continue)の爆発:
loop(true){break}とloop(true){if(...) break}等- 対策: ControlTree/StepTree の語彙(If/Loop/Exit)を増やし、同じ lowering に通す。
- “新パターン” ではなく “語彙追加” にするのが収束の鍵。
正規化単位(NormalizationPlan の粒度)
現状は NormalizationPlanBox が block suffix を単位として正規化(loop + post assigns + return など)を扱う。
将来の収束案として、正規化単位を statement(loop 1 個) に寄せ、post/return は通常の lowering に任せる選択肢がある。
- 利点: suffix パターン増殖の圧力が下がる
- 注意: 既存の Phase 132–138 の成果(post_k/DirectValue/exit reconnect)と整合する形で段階移行すること
- 既定方針: Phase 140(pure expr)で ExprLowerer を確立した後に再検討する(Phase 141+ の設計判断)
受け入れ基準(設計)
- "形ごとの if 分岐追加" ではなく、AST walker の対応ノード追加が主たる拡張点になっている
- out-of-scope は
Ok(None)でフォールバックし、既定挙動不変を維持する - JoinIR merge は by-name 推測をせず、boundary/contract を SSOT として従う
Available Inputs SSOT (Phase 141 P1.5)
問題(Problem)
Suffix normalization couldn't access prefix-built local variables (e.g., s = "abc").
Bug scenario:
s = "abc" // Prefix: builder.variable_map["s"] = ValueId(42)
flag = 1
if flag == 1 { // Suffix normalization starts
s = s
}
return s.length() // ERROR: 's' not in available_inputs!
Root cause: AvailableInputsCollectorBox::collect(builder, None) only collected function params and CapturedEnv, missing prefix-built local variables.
解決策(Solution)
3-source merge with priority order:
- Function params (highest priority - from
scope_ctx.function_param_names) - Prefix variables (medium priority - from
builder.variable_map) - CapturedEnv (lowest priority - from closure captures)
Contract
AvailableInputsCollectorBox::collect(
builder: &MirBuilder,
captured_env: Option<&CapturedEnv>,
prefix_variables: Option<&BTreeMap<String, ValueId>>, // NEW: Phase 141 P1.5
) -> BTreeMap<String, ValueId>
Usage
// At call site (e.g., stmts.rs)
let prefix_variables = Some(&self.variable_ctx.variable_map);
// Execute normalization
NormalizationExecuteBox::execute(
builder,
&plan,
remaining,
func_name,
debug,
prefix_variables, // Pass through
)?;
// Inside execute_loop_only/execute_loop_with_post
let available_inputs = AvailableInputsCollectorBox::collect(
builder,
None,
prefix_variables, // Merge prefix variables
);
Edge Cases
- Variable shadowing: Function params override prefix variables
- Empty prefix:
prefix_variables = None(e.g., suffix with no prefix execution) - Mutation:
variable_mapis cloned/borrowed, not moved
SSOT Location
- Implementation:
src/mir/control_tree/normalized_shadow/available_inputs_collector.rs - Call sites:
src/mir/builder/control_flow/normalization/execute_box.rs(execute_loop_only, execute_loop_with_post)src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rssrc/mir/builder/stmts.rs(build_block suffix router call)
Known Intrinsic SSOT (Phase 141 P1.5)
目的
“既知 intrinsic だけ” の段階投入を、by-name の直書き増殖ではなく SSOT registry に収束させる。
SSOT
- Registry:
src/mir/control_tree/normalized_shadow/common/known_intrinsics.rsKnownIntrinsicRegistryBox::{lookup,get_spec}
- Marker enum:
src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rsKnownIntrinsicは “識別子” のみ(metadata は registry に集約)
Diagnostics
OutOfScopeReason::IntrinsicNotWhitelistedを追加し、MethodCallの out-of-scope 理由を精密化する。