diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 6623d885..b1300ea6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -31,32 +31,34 @@ Update (2025-11-18 — Phase 25.1k: LoopSSA v2 (.hako) & Stage‑B harness 追 - K‑C: PhiInjectorBox に Carrier/Pinned ベースの v2 入口を追加(既存 `_collect_phi_vars` は当面維持)。 - K‑D: Stage‑B Test3 の `%0` SSA 問題が悪化していないことを確認しつつ、LoopSSA 有効/無効の切り分けを残す。 -Update (2025-11-18 — Phase 25.1l: Region/GC 観測レイヤー導入(Rust 側のみ)) +Update (2025-11-18 — Phase 25.1l: Region/GC 観測レイヤー導入(Rust 側のみ) — completed) - Context: - LoopForm v2 / ControlForm / Conservative PHI Box により、If/Loop の SSA/PHI は Rust 側で安定しているが、 GC や Stage‑B 由来の寿命問題を構造的に見るための「Region/スコープ」ビューが不足していた。 - 25.1l では GC 挙動を変えずに、Rust MIR 側に観測専用の Region/RefSlotKind レイヤーを導入する。 - Done: - `src/mir/region/mod.rs`: - - `RefSlotKind`(`StrongRoot/WeakRoot/Borrowed/NonRef`)、`SlotMetadata { name, ref_kind }`、`RegionId`、`RegionKind(Loop/If)`、`Region { id, kind, entry_block, exit_blocks, slots }` を定義。 + - `RefSlotKind`(`StrongRoot/WeakRoot/Borrowed/NonRef`)、`SlotMetadata { name, ref_kind }`、`RegionId`、`RegionKind(Function/Loop/If)`、`Region { id, kind, parent, entry_block, exit_blocks, slots }` を定義。 - `Region::classify_ref_kind(MirType)` で Box/Array/Future を StrongRoot、それ以外のプリミティブを NonRef として暫定分類。 + - `src/mir/region/function_slot_registry.rs`: + - 関数スコープ単位の `FunctionSlotRegistry` を導入し、`SlotId -> SlotInfo { name, ty, ref_kind }` と `name -> SlotId` を SSOT として管理(現時点では観測専用)。 + - `src/mir/builder.rs`: + - `current_slot_registry: Option` と `current_region_stack: Vec` を追加し、`current_function` 単位でスロットと Region の親子関係を追跡できるようにした(NYASH_REGION_TRACE=0 のときは zero-cost)。 + - `src/mir/builder/lifecycle.rs` / `src/mir/builder/calls/lowering.rs`: + - main 関数および static 関数/instance method lowering で、関数開始時に `FunctionSlotRegistry::new()` をセットし、終了時に `saved_slot_registry` を元に戻すようにした。 + - 同時に `observe_function_region(self)` / `pop_function_region(self)` を呼び出し、Stage‑B 系関数(`\"StageB\"` を含む名前)に対して FunctionRegion→Loop/IfRegion の木構造をログ上で観測できるようにした。 + - 変数生成タイミングでの SlotRegistry 更新(挙動不変・観測専用): + - `calls/lowering.rs::setup_function_params` / `setup_method_params` で、パラメータ名と型(わかる範囲)を `ensure_slot(name, ty)` で登録。 + - `builder/decls.rs::build_static_main_box` で、`self.variable_map.insert(p.clone(), pid)` の直後に `value_types.get(&pid)` を使って `ensure_slot(p, ty)`。 + - `builder/stmts.rs` で `build_local_statement` / `build_nowait_statement` / `build_me_expression` がローカル変数や `me` を SlotRegistry に登録するようにした。 - `src/mir/region/observer.rs`: - - `observe_control_form(builder: &MirBuilder, form: &ControlForm)` を実装し、`NYASH_REGION_TRACE=1` のときだけ Region 情報をログ出力。 - - 現時点では `builder.variable_map` と `builder.value_types` から live スロットと RefKind を推定し、`[region/observe] fn=... id=RegionId(..) kind=Loop/If entry=bb.. exits=[..] slots=[..]` の形で eprintln する(メモリには保存しない)。 - - dev 用フィルタとして `func_name.contains("StageB")` の関数のみを観測対象にし、Stage‑B BodyExtractor まわりの Region/Slot 状況が見えるようにした。 + - `observe_function_region(builder: &mut MirBuilder)` を追加し、`NYASH_REGION_TRACE=1` かつ 関数名に `\"StageB\"` を含む場合だけ `RegionKind::Function` を 1 つ作成して `current_region_stack` に積むようにした。 + - `observe_control_form(builder: &mut MirBuilder, form: &ControlForm)` を SlotRegistry ベースに書き換え、`current_slot_registry` があればそちらから SlotInfo を列挙して RefKind を判定し、なければ従来通り `variable_map` + `value_types` から推定するようにした。 + - Loop/IfRegion は `parent = current_region_stack.last()` を使って FunctionRegion 配下にぶら下がるようにし、`[region/observe] fn=... id=RegionId(..) kind=Loop/If entry=bb.. exits=[..] slots=[..]` をログに出す。 - `src/mir/loop_builder.rs`: - - ループ構築部と `lower_if_in_loop` の If 降下部で、`ControlForm::from_loop` / `ControlForm::from_if` 生成直後に `observe_control_form` を呼び、Loop/If ごとに Region ログを出せるようにした(NYASH_REGION_TRACE=0 のときは完全に無効)。 + - ループ構築部と `lower_if_in_loop` の If 降下部で、`ControlForm::from_loop` / `ControlForm::from_if` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼び出すようにし、Loop/IfRegion を FunctionRegion の子として観測できるようにした。 - テスト: - - `cargo test -q mir_loopform_exit_phi -- --nocapture` は NYASH_REGION_TRACE=0/1 の両方で緑(Loop/If SSA/PHI 挙動に変化なし)。 -- Plan(25.1l 後半〜次フェーズへのブリッジ): - - Region 木と FunctionRegion: - - 次フェーズで `RegionKind::Function` と FunctionRegion (#0) を導入し、関数スコープを含む Region 木を構築する(entry=関数 bb0, exits=ret ブロック群)。 - - MirBuilder に「現在属している RegionId スタック」を追加し、Loop/If 降下時に parent/children 関係を記録できるようにする(観測専用)。 - - SlotRegistry(関数ごと 1 箱)の設計: - - 長期的には `FunctionSlotRegistry` のような箱を導入し、`slot_id -> { name, MirType, RefSlotKind }` / `name -> slot_id` を SSOT として管理する。 - - 25.1l ではまだ `variable_map` ベースの暫定観測に留め、SlotId ベースへの移行は Region 木導入後に小さく刻んで進める。 - - GC 統合: - - Region/Slot 観測結果をもとに、どの Region 境界で StrongRoot スロットを retain/release すべきかの設計を 25.1m 以降で行う(Rust の既存 GC/Barrier 実装を壊さない範囲に限定)。 + - `cargo test -q mir_breakfinder_ssa -- --nocapture` / `cargo test -q mir_loopform_exit_phi -- --nocapture` は NYASH_REGION_TRACE=0/1 の両方で緑(Loop/If SSA/PHI 挙動に変化なし)。 Update (2025-11-18 — Phase 25.1g: Conservative PHI ↔ ControlForm 統合の準備) - Context: diff --git a/docs/development/roadmap/phases/phase-25.1l/README.md b/docs/development/roadmap/phases/phase-25.1l/README.md index e64a7f70..728852f7 100644 --- a/docs/development/roadmap/phases/phase-25.1l/README.md +++ b/docs/development/roadmap/phases/phase-25.1l/README.md @@ -1,6 +1,6 @@ # Phase 25.1l — Region/GC 観測レイヤー(LoopForm v2 × RefSlotKind) -Status: in progress(Rust 側観測レイヤーの最小実装まで完了/挙動変更なし) +Status: completed(Rust 側観測レイヤーの最小実装まで完了/挙動変更なし) ## ゴール @@ -38,10 +38,14 @@ Status: in progress(Rust 側観測レイヤーの最小実装まで完了/ - `SlotMetadata`: - `name: String` — 変数スロット名(`i`, `body_src`, `args` など)。 - `ref_kind: RefSlotKind` — 上記の種別。 + - `RegionKind`: + - `Function` — 関数スコープ。 + - `Loop` — ループ構造(LoopForm v2 由来)。 + - `If` — if/else 構造。 - `Region`: - `id: RegionId`(`u32` ラッパ)。 - - `kind: RegionKind` — `Loop` / `If`(25.1l 時点では Function は未導入)。 - - `parent: Option` / `children: Vec` — 制御構造の親子関係(将来用。25.1l では未設定)。 + - `kind: RegionKind` — 上記 3 種。 + - `parent: Option` — 親 RegionId(FunctionRegion がルート、Loop/If はその子)。 - `entry_block: BasicBlockId` / `exit_blocks: Vec` — ControlForm から引き継ぐ。 - `slots: Vec` — その Region で live とみなすスロット一覧(観測用)。 - RefKind 判定は 25.1l では **簡易ヒューリスティック** に留める: @@ -49,40 +53,71 @@ Status: in progress(Rust 側観測レイヤーの最小実装まで完了/ - 明らかなプリミティブ(整数/bool/文字列)→ `NonRef` - Weak 系/借用系の精密な分類は後続フェーズで詰める。 -### L‑B: RegionObserver の実装と ControlForm からの接続(実装済み) +### L‑B: RegionObserver の実装と ControlForm/Function からの接続(実装済み) - 新規モジュール: `src/mir/region/observer.rs` - 現実装の責務: - - `observe_control_form(builder: &MirBuilder, form: &ControlForm)` という関数型 API で: + - `observe_function_region(builder: &mut MirBuilder)`: + - `NYASH_REGION_TRACE=1` かつ 関数名に `"StageB"` を含む場合のみ、`RegionKind::Function` を 1 つ作成。 + - `RegionId` を `MirBuilder.current_region_stack` に push し、ルート Region としてログ出力。 + - `observe_control_form(builder: &mut MirBuilder, form: &ControlForm)`: - `ControlForm` から `entry`/`exits`/Loop/If の形を読む。 - - 該当スコープ時点の `builder.variable_map` に載っている「名前付きスロット」を走査して `SlotMetadata` を構築。 - - `Region` を作成し、`NYASH_REGION_TRACE=1` のときだけ: + - `builder.current_region_stack.last()` を `parent: Option` として保持し、FunctionRegion の子として Loop/IfRegion をぶら下げる。 + - スロット列挙は FunctionSlotRegistry(後述)を優先し、なければ `variable_map` + `value_types` で暫定推定。 + - `NYASH_REGION_TRACE=1` のときだけ: - `[region/observe] fn=StageBBodyExtractorBox.build_body_src/2 id=RegionId(..) kind=Loop entry=bb.. exits=[..] slots=[..]` - という形でログ出力する(メモリには保持しない)。 -- Hook の置き場所(いずれも読み取り専用): + のようなログを eprintln する(メモリ蓄積はしない)。 +- Hook の置き場所(いずれも観測専用): + - `src/mir/builder/lifecycle.rs`: + - main 関数生成後に `observe_function_region(self)` を呼び出し(関数名フィルタにより、多くはスキップされる)。 + - `src/mir/builder/calls/lowering.rs`: + - static 関数用: `create_function_skeleton` で新関数を作成後、`observe_function_region(self)` を呼び出す。 + - instance method 用: `create_method_skeleton` 後に同様に `observe_function_region(self)` を呼び出す。 + - finalize (`lower_static_method_as_function` / `lower_method_as_function`) の最後で `pop_function_region(self)` を呼び、Region スタックを 1 段戻す。 - `src/mir/loop_builder.rs`: - ループ構築完了後、`LoopShape`→`ControlForm::from_loop` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼ぶ。 - - `src/mir/loop_builder.rs`(if 降下部): - - `IfShape`→`ControlForm::from_if` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼ぶ。 - - `.hako` / GC / SSA ロジックには一切影響しない。 -- dev フィルタ: - - 現時点では Stage‑B 周辺の観測に絞るため、`func_name.contains("StageB")` の関数のみログ対象にしている(ログ爆発防止)。 + - `lower_if_in_loop` でも `IfShape`→`ControlForm::from_if` 生成直後に `observe_control_form(self.parent_builder, &form)` を呼ぶ。 + - dev フィルタ: + - 現時点では Stage‑B 周辺の観測に絞るため、`func_name.contains("StageB")` の関数のみログ対象にしている(ログ爆発防止)。 -### L‑C: 関数スコープ Slot 管理箱(SlotRegistry)との関係(設計メモ) +### L‑C: 関数スコープ Slot 管理箱(FunctionSlotRegistry)との関係(実装済み・観測専用) -- 将来像: - - 各関数ごとに「SlotRegistry(仮称)」箱を 1 つだけ持ち、 - - `slot_id -> { name, MirType, RefSlotKind }` - - `name -> slot_id` - を管理する SSOT として扱う。 - - RegionBox(本 README での Region)は、この SlotRegistry 上で - 「どの SlotId がこの Region で live か」を指すだけにする。 -- 25.1l の暫定実装: - - まだ明示的な SlotRegistry 型は導入せず、 - - `MirBuilder.variable_map.keys()` を「その地点で live なスロット一覧」とみなす。 - - `value_types` から MirType を拾って RefSlotKind を判定する。 - - これは SlotRegistry 導入までのリーズナブルな暫定策として位置付け、 - 将来は `variable_map`→SlotRegistry/SlotId ベースに移行する。 +- 新規モジュール: `src/mir/region/function_slot_registry.rs` + - `SlotId(u32)` / `SlotInfo { name, ty: Option, ref_kind: Option }`。 + - `FunctionSlotRegistry { slots: Vec, name_to_slot: HashMap }`。 + - API: + - `new()` — 空レジストリ。 + - `ensure_slot(name, ty)` — スロットが存在しなければ作成し、`SlotId` を返す。 + - `set_ref_kind(slot, RefSlotKind)` — 後から RefKind を埋める。 + - `iter_slots()` / `get_slot(name)` / `get_slot_info(slot)` — 観測用読み出し。 +- `MirBuilder` への統合: + - フィールド: + - `current_slot_registry: Option` — `current_function` と同じライフサイクルで生存。 + - `current_region_stack: Vec` — FunctionRegion/Loop/IfRegion の親子関係を維持する dev 用スタック。 + - 関数開始・終了での管理: + - main 関数生成時(`prepare_module`)で `FunctionSlotRegistry::new()` を作成。 + - static 関数/instance method lowering 時(`calls/lowering.rs`)で: + - `create_function_skeleton` / `create_method_skeleton` 内で新しいレジストリをセット。 + - `LoweringContext` に `saved_slot_registry` を追加し、呼び出し元のレジストリを退避・復元。 + - `finalize_module` / `lower_static_method_as_function` / `lower_method_as_function` の終了時に `current_slot_registry = None`(または `saved_slot_registry` を復元)。 +- SlotRegistry の更新ポイント(挙動不変・観測のみ): + - パラメータ(static 関数): + - `setup_function_params` で `params` とシグネチャの `MirType` をローカルベクタに集約し、ループ後に `reg.ensure_slot(name, ty)` を呼ぶ。 + - パラメータ(instance method): + - `setup_method_params` で `me` と通常パラメータを `(name, None)` として集約し、同様に `ensure_slot`。 + - static Main ラッパー: + - `build_static_main_box` で `self.variable_map.insert(p.clone(), pid)` の直後に `value_types.get(&pid)` を使って `ensure_slot(p, ty)`。 + - ローカル変数/nowait/me: + - `build_local_statement` で `variable_map` 登録後に `ensure_slot(&var_name, value_types.get(&var_id))`。 + - `build_nowait_statement` で Future を束ねる `variable` 名を `ensure_slot(&variable, None)`。 + - `build_me_expression` で `me` を初回生成したときに `ensure_slot("me", None)`。 +- RegionObserver との接続: + - `observe_control_form` では、`current_slot_registry` が存在する場合はそれを優先: + - `classify_slots_from_registry(reg)` で: + - SlotInfo.ty から `Region::classify_ref_kind` を使って RefKind を決定。 + - ty が無い場合は `classify_slot_name_only`(`args/src/body_src/...` 系を StrongRoot とみなす簡易ヒューリスティック)。 + - 各 SlotInfo に `set_ref_kind` で RefKind を埋めてから `Region.slots` を構築。 + - これにより、RefKind 判定は SlotRegistry 側に一元化され、Region 側は `SlotMetadata { name, ref_kind }` を参照するだけになる。 ### L‑D: Region メタデータの足場(将来の JSON 拡張のための入口だけ) diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 20e62285..f1261869 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -9,6 +9,8 @@ use super::{ BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator, }; +use crate::mir::region::function_slot_registry::FunctionSlotRegistry; +use crate::mir::region::RegionId; use crate::ast::{ASTNode, LiteralValue}; use crate::mir::builder::builder_calls::CallTarget; use std::collections::HashMap; @@ -113,6 +115,11 @@ pub struct MirBuilder { /// 注意: compilation_contextがSomeの場合は使用されません pub(super) value_types: HashMap, + /// 関数スコープの SlotRegistry(観測専用) + /// - current_function と同じライフサイクルを持つよ。 + /// - 既存の variable_map/SSA には影響しない(メタデータのみ)。 + pub(super) current_slot_registry: Option, + /// 🎯 箱理論: 型情報管理の一元化(TypeRegistryBox) /// NYASH_USE_TYPE_REGISTRY=1 で有効化(段階的移行用) pub(super) type_registry: type_registry::TypeRegistry, @@ -133,6 +140,10 @@ pub struct MirBuilder { /// Source size snapshot to detect when to rebuild the tail index pub(super) method_tail_index_source_len: usize, + /// Region 観測用のスタックだよ(FunctionRegion がルート)。 + /// - NYASH_REGION_TRACE=1 のときだけ使われる開発用メタデータだよ。 + pub(super) current_region_stack: Vec, + // include guards removed /// Loop context stacks for lowering break/continue inside nested control flow @@ -229,6 +240,7 @@ impl MirBuilder { field_origin_class: HashMap::new(), field_origin_by_box: HashMap::new(), value_types: HashMap::new(), + current_slot_registry: None, type_registry: type_registry::TypeRegistry::new(), plugin_method_sigs, current_static_box: None, @@ -237,6 +249,8 @@ impl MirBuilder { method_tail_index: std::collections::HashMap::new(), method_tail_index_source_len: 0, + current_region_stack: Vec::new(), + loop_header_stack: Vec::new(), loop_exit_stack: Vec::new(), if_merge_stack: Vec::new(), diff --git a/src/mir/builder/calls/lowering.rs b/src/mir/builder/calls/lowering.rs index c0f4cdd7..e7c4612c 100644 --- a/src/mir/builder/calls/lowering.rs +++ b/src/mir/builder/calls/lowering.rs @@ -7,6 +7,7 @@ use crate::ast::ASTNode; use crate::mir::builder::{MirBuilder, MirType, MirInstruction}; +use crate::mir::region::function_slot_registry::FunctionSlotRegistry; use super::function_lowering; use std::collections::HashMap; @@ -17,6 +18,7 @@ struct LoweringContext { saved_static_ctx: Option, saved_function: Option, saved_block: Option, + saved_slot_registry: Option, } impl MirBuilder { @@ -42,6 +44,9 @@ impl MirBuilder { None }; + // 関数スコープ SlotRegistry は元の関数側から退避しておくよ。 + let saved_slot_registry = self.current_slot_registry.take(); + // BoxCompilationContext mode: clear()で完全独立化 if context_active { self.variable_map.clear(); @@ -59,6 +64,7 @@ impl MirBuilder { saved_static_ctx, saved_function: None, saved_block: None, + saved_slot_registry, } } @@ -77,15 +83,20 @@ impl MirBuilder { ); let entry = self.block_gen.next(); let function = super::super::MirFunction::new(signature, entry); - + // 現在の関数・ブロックを保存 ctx.saved_function = self.current_function.take(); ctx.saved_block = self.current_block.take(); - + // 新しい関数に切り替え self.current_function = Some(function); self.current_block = Some(entry); + // 新しい関数スコープ用の SlotRegistry を準備するよ(観測専用) + self.current_slot_registry = Some(FunctionSlotRegistry::new()); self.ensure_block_exists(entry)?; + + // Region 観測レイヤ: static 関数用の FunctionRegion を積むよ。 + crate::mir::region::observer::observe_function_region(self); Ok(()) } @@ -93,6 +104,9 @@ impl MirBuilder { /// 🎯 箱理論: Step 3 - パラメータ設定 fn setup_function_params(&mut self, params: &[String]) { self.function_param_names.clear(); + // SlotRegistry 更新は borrow 競合を避けるため、まずローカルに集約してから反映するよ。 + let mut slot_regs: Vec<(String, Option)> = Vec::new(); + if let Some(ref mut f) = self.current_function { // 📦 Hotfix 5: Use pre-populated params from MirFunction::new() // Static methods have implicit receiver at params[0], so actual parameters start at offset @@ -101,6 +115,8 @@ impl MirBuilder { if f.params.len() > params.len() { 1 } else { 0 } }; + let param_types = f.signature.params.clone(); + for (idx, p) in params.iter().enumerate() { let param_idx = receiver_offset + idx; let pid = if param_idx < f.params.len() { @@ -114,6 +130,14 @@ impl MirBuilder { }; self.variable_map.insert(p.clone(), pid); self.function_param_names.insert(p.clone()); + let ty = param_types.get(param_idx).cloned(); + slot_regs.push((p.clone(), ty)); + } + } + + if let Some(reg) = self.current_slot_registry.as_mut() { + for (name, ty) in slot_regs { + reg.ensure_slot(&name, ty); } } } @@ -198,6 +222,8 @@ impl MirBuilder { // Static box context復元 self.current_static_box = ctx.saved_static_ctx; + // 関数スコープ SlotRegistry も元の関数に戻すよ。 + self.current_slot_registry = ctx.saved_slot_registry; } /// 🎯 箱理論: Step 2b - 関数スケルトン作成(instance method版) @@ -225,25 +251,41 @@ impl MirBuilder { // 新しい関数に切り替え self.current_function = Some(function); self.current_block = Some(entry); + // instance method 用の関数スコープ SlotRegistry もここで用意するよ。 + self.current_slot_registry = Some(FunctionSlotRegistry::new()); self.ensure_block_exists(entry)?; + // Region 観測レイヤ: instance method 用の FunctionRegion も積んでおくよ。 + crate::mir::region::observer::observe_function_region(self); + Ok(()) } /// 🎯 箱理論: Step 3b - パラメータ設定(instance method版: me + params) fn setup_method_params(&mut self, box_name: &str, params: &[String]) { + // SlotRegistry 更新はローカルバッファに集約してから反映するよ。 + let mut slot_regs: Vec<(String, Option)> = Vec::new(); + if let Some(ref mut f) = self.current_function { // First parameter is always 'me' let me_id = f.next_value_id(); f.params.push(me_id); self.variable_map.insert("me".to_string(), me_id); self.value_origin_newbox.insert(me_id, box_name.to_string()); + slot_regs.push(("me".to_string(), None)); // Then regular parameters for p in params { let pid = f.next_value_id(); f.params.push(pid); self.variable_map.insert(p.clone(), pid); + slot_regs.push((p.clone(), None)); + } + } + + if let Some(reg) = self.current_slot_registry.as_mut() { + for (name, ty) in slot_regs { + reg.ensure_slot(&name, ty); } } } @@ -281,6 +323,9 @@ impl MirBuilder { }; self.finalize_function(returns_value)?; + // FunctionRegion を 1 段ポップして元の関数コンテキストに戻るよ。 + crate::mir::region::observer::pop_function_region(self); + // Step 6: Context復元 self.restore_lowering_context(ctx); @@ -302,6 +347,7 @@ impl MirBuilder { saved_static_ctx: None, saved_function: None, saved_block: None, + saved_slot_registry: self.current_slot_registry.take(), }; // Step 2b: 関数スケルトン作成(method版) @@ -362,12 +408,16 @@ impl MirBuilder { module.add_function(finalized_function); } + // FunctionRegion を 1 段ポップして元の関数コンテキストに戻るよ。 + crate::mir::region::observer::pop_function_region(self); + // Step 6: Context復元(simple version) self.current_function = ctx.saved_function; self.current_block = ctx.saved_block; if let Some(saved) = ctx.saved_var_map { self.variable_map = saved; } + self.current_slot_registry = ctx.saved_slot_registry; Ok(()) } diff --git a/src/mir/builder/decls.rs b/src/mir/builder/decls.rs index b7f49069..e73192bd 100644 --- a/src/mir/builder/decls.rs +++ b/src/mir/builder/decls.rs @@ -87,6 +87,11 @@ impl super::MirBuilder { crate::mir::builder::metadata::propagate::propagate(self, v, pid); } self.variable_map.insert(p.clone(), pid); + // 関数スコープ SlotRegistry にも登録しておくよ(観測専用) + if let Some(reg) = self.current_slot_registry.as_mut() { + let ty = self.value_types.get(&pid).cloned(); + reg.ensure_slot(p, ty); + } } // Lower statements in order to preserve def→use let lowered = self.cf_block(body.clone()); diff --git a/src/mir/builder/lifecycle.rs b/src/mir/builder/lifecycle.rs index d2f3cacd..8d258df7 100644 --- a/src/mir/builder/lifecycle.rs +++ b/src/mir/builder/lifecycle.rs @@ -65,6 +65,14 @@ impl super::MirBuilder { self.current_function = Some(main_function); self.current_block = Some(entry_block); + // 関数スコープの SlotRegistry を初期化するよ(観測専用)。 + // main 関数用のスロット登録箱として使う想定だよ。 + self.current_slot_registry = + Some(crate::mir::region::function_slot_registry::FunctionSlotRegistry::new()); + + // Region 観測レイヤ: main 関数の FunctionRegion を 1 つ作っておくよ。 + crate::mir::region::observer::observe_function_region(self); + // Hint: scope enter at function entry (id=0 for main) self.hint_scope_enter(0); @@ -335,6 +343,12 @@ impl super::MirBuilder { module.add_function(f); } + // main 関数スコープの Region スタックをポップするよ。 + crate::mir::region::observer::pop_function_region(self); + + // main 関数スコープの SlotRegistry を解放するよ。 + self.current_slot_registry = None; + Ok(module) } } diff --git a/src/mir/builder/stmts.rs b/src/mir/builder/stmts.rs index cb6dc980..a0f2702f 100644 --- a/src/mir/builder/stmts.rs +++ b/src/mir/builder/stmts.rs @@ -230,6 +230,11 @@ impl super::MirBuilder { eprintln!("[build_local_statement] Inserting '{}' -> {:?} into variable_map", var_name, var_id); } self.variable_map.insert(var_name.clone(), var_id); + // SlotRegistry にもローカル変数スロットを登録しておくよ(観測専用) + if let Some(reg) = self.current_slot_registry.as_mut() { + let ty = self.value_types.get(&var_id).cloned(); + reg.ensure_slot(&var_name, ty); + } last_value = Some(var_id); } Ok(last_value.unwrap_or_else(|| self.value_gen.next())) @@ -303,6 +308,9 @@ impl super::MirBuilder { effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io), })?; self.variable_map.insert(variable.clone(), future_id); + if let Some(reg) = self.current_slot_registry.as_mut() { + reg.ensure_slot(&variable, None); + } return Ok(future_id); } let expression_value = self.build_expression(expression)?; @@ -312,6 +320,9 @@ impl super::MirBuilder { value: expression_value, })?; self.variable_map.insert(variable.clone(), future_id); + if let Some(reg) = self.current_slot_registry.as_mut() { + reg.ensure_slot(&variable, None); + } Ok(future_id) } @@ -343,6 +354,9 @@ impl super::MirBuilder { }; let me_value = crate::mir::builder::emission::constant::emit_string(self, me_tag); self.variable_map.insert("me".to_string(), me_value); + if let Some(reg) = self.current_slot_registry.as_mut() { + reg.ensure_slot("me", None); + } // P0: Known 化 — 分かる範囲で me の起源クラスを付与(挙動不変)。 super::origin::infer::annotate_me_origin(self, me_value); Ok(me_value) diff --git a/src/mir/region/function_slot_registry.rs b/src/mir/region/function_slot_registry.rs new file mode 100644 index 00000000..19f7d8ae --- /dev/null +++ b/src/mir/region/function_slot_registry.rs @@ -0,0 +1,98 @@ +/*! + * FunctionSlotRegistry – 関数スコープのスロット情報を管理する箱だよ。 + * + * 目的: + * - 変数名ごとの「スロット」を 1 箇所に集約して管理すること。 + * - 各スロットに型情報や RefSlotKind をひも付けられる足場を用意すること。 + * + * このフェーズでは観測専用: + * - MIR/SSA の挙動は一切変えないよ。 + * - MirBuilder.variable_map や PHI 生成ロジックには影響を与えないよ。 + */ + +use crate::mir::MirType; +use super::RefSlotKind; +use std::collections::HashMap; + +/// 1 関数内でのスロット ID だよ(添字ベースの薄いラッパー)。 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SlotId(pub u32); + +/// 1 スロットに対応するメタデータだよ。 +#[derive(Debug, Clone)] +pub struct SlotInfo { + /// スロット名(変数名)だよ。 + pub name: String, + /// MIR 型情報(分かっていれば Some)だよ。 + pub ty: Option, + /// GC/寿命管理の観点から見た種別(まだ観測専用)だよ。 + pub ref_kind: Option, +} + +/// 関数スコープごとのスロットレジストリだよ。 +/// +/// - `slots`: SlotId → SlotInfo の順序付き配列 +/// - `name_to_slot`: 変数名 → SlotId の逆引き +#[derive(Debug, Default, Clone)] +pub struct FunctionSlotRegistry { + slots: Vec, + name_to_slot: HashMap, +} + +impl FunctionSlotRegistry { + /// 空のレジストリを作るよ。 + pub fn new() -> Self { + Self::default() + } + + /// スロットを「なければ作る・あれば返す」で確保するよ。 + /// + /// - name: スロット名(変数名) + /// - ty: 初期の型情報(後から埋めても OK) + pub fn ensure_slot(&mut self, name: &str, ty: Option) -> SlotId { + if let Some(slot) = self.name_to_slot.get(name).copied() { + // 既存スロットに対しては、型がまだ None で新しい情報があれば埋める程度に留める + if let (Some(new_ty), Some(info)) = ( + ty, + self.slots.get_mut(slot.0 as usize), + ) { + if info.ty.is_none() { + info.ty = Some(new_ty); + } + } + return slot; + } + + let id = SlotId(self.slots.len() as u32); + self.slots.push(SlotInfo { + name: name.to_string(), + ty, + ref_kind: None, + }); + self.name_to_slot.insert(name.to_string(), id); + id + } + + /// RefSlotKind を後から埋めるためのヘルパーだよ。 + pub fn set_ref_kind(&mut self, slot: SlotId, kind: RefSlotKind) { + if let Some(info) = self.slots.get_mut(slot.0 as usize) { + info.ref_kind = Some(kind); + } + } + + /// 全スロットを列挙するイテレータだよ(観測専用)。 + pub fn iter_slots(&self) -> impl Iterator { + self.slots.iter() + } + + /// 名前から SlotId を引くよ。 + pub fn get_slot(&self, name: &str) -> Option { + self.name_to_slot.get(name).copied() + } + + /// SlotId から SlotInfo を引くよ。 + pub fn get_slot_info(&self, slot: SlotId) -> Option<&SlotInfo> { + self.slots.get(slot.0 as usize) + } +} + diff --git a/src/mir/region/mod.rs b/src/mir/region/mod.rs index 0085de80..f65d1535 100644 --- a/src/mir/region/mod.rs +++ b/src/mir/region/mod.rs @@ -38,9 +38,10 @@ pub struct SlotMetadata { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct RegionId(pub u32); -/// Region の種別(Loop / If など)だよ。 +/// Region の種別(Function / Loop / If など)だよ。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RegionKind { + Function, Loop, If, } @@ -70,4 +71,4 @@ impl Region { } pub mod observer; - +pub mod function_slot_registry; diff --git a/src/mir/region/observer.rs b/src/mir/region/observer.rs index 957907d3..9aede37b 100644 --- a/src/mir/region/observer.rs +++ b/src/mir/region/observer.rs @@ -7,7 +7,10 @@ use crate::mir::builder::MirBuilder; use crate::mir::control_form::{ControlForm, ControlKind}; -use crate::mir::region::{RefSlotKind, Region, RegionId, RegionKind, SlotMetadata}; +use crate::mir::region::{ + function_slot_registry::FunctionSlotRegistry, RefSlotKind, Region, RegionId, RegionKind, + SlotMetadata, +}; use crate::mir::ValueId; use std::sync::atomic::{AtomicU32, Ordering}; @@ -24,7 +27,7 @@ fn is_region_trace_on() -> bool { /// /// - 25.1l では Stage‑B 周辺のデバッグが主目的なので、 /// まずは `StageBBodyExtractorBox.*` などに絞って使う想定だよ。 -pub fn observe_control_form(builder: &MirBuilder, form: &ControlForm) { +pub fn observe_control_form(builder: &mut MirBuilder, form: &ControlForm) { if !is_region_trace_on() { return; } @@ -57,21 +60,20 @@ pub fn observe_control_form(builder: &MirBuilder, form: &ControlForm) { let entry_block = form.entry; let exit_blocks = form.exits.clone(); - // 変数スロットは variable_map と value_types から best-effort で推定するよ。 - let mut slots: Vec = Vec::new(); + // 変数スロットは SlotRegistry があればそれを優先し、なければ + // variable_map と value_types から best-effort で推定するよ。 + let slots: Vec = if let Some(reg) = builder.current_slot_registry.as_mut() { + classify_slots_from_registry(reg) + } else { + classify_slots_from_variable_map(builder) + }; - for (name, &vid) in builder.variable_map.iter() { - let ref_kind = classify_slot(builder, vid, name.as_str()); - slots.push(SlotMetadata { - name: name.clone(), - ref_kind, - }); - } + let parent = builder.current_region_stack.last().copied(); let region = Region { id, kind, - parent: None, + parent, entry_block, exit_blocks, slots, @@ -83,12 +85,110 @@ pub fn observe_control_form(builder: &MirBuilder, form: &ControlForm) { ); } +/// 関数エントリ時の Region 観測だよ(FunctionRegion を 1 つ作ってスタックに積む)。 +pub fn observe_function_region(builder: &mut MirBuilder) { + if !is_region_trace_on() { + return; + } + + if builder.compilation_context.is_some() { + return; + } + + let func_name = builder + .current_function + .as_ref() + .map(|f| f.signature.name.as_str()) + .unwrap_or(""); + + // まずは Stage‑B 系だけを対象にしてログ量を抑えるよ。 + if !func_name.contains("StageB") { + return; + } + + let id = RegionId(NEXT_REGION_ID.fetch_add(1, Ordering::Relaxed)); + + let entry_block = builder + .current_function + .as_ref() + .map(|f| f.entry_block) + .unwrap_or_else(|| crate::mir::BasicBlockId::new(0)); + + let region = Region { + id, + kind: RegionKind::Function, + parent: None, + entry_block, + exit_blocks: Vec::new(), + slots: Vec::new(), + }; + + builder.current_region_stack.push(id); + + eprintln!( + "[region/observe] fn={} id={:?} kind={:?} entry={:?} exits={:?} slots={:?}", + func_name, region.id, region.kind, region.entry_block, region.exit_blocks, region.slots + ); +} + +/// 関数終了時に Region スタックを 1 段ポップするよ。 +pub fn pop_function_region(builder: &mut MirBuilder) { + if !is_region_trace_on() { + return; + } + let _ = builder.current_region_stack.pop(); +} + +fn classify_slots_from_registry(reg: &mut FunctionSlotRegistry) -> Vec { + // まず SlotRegistry 側に RefKind を埋めてもらうよ(型情報+名前ヒューリスティック)。 + for info in reg.iter_slots().cloned().collect::>() { + if info.ref_kind.is_none() { + let kind = info + .ty + .as_ref() + .map(Region::classify_ref_kind) + .unwrap_or_else(|| classify_slot_name_only(info.name.as_str())); + if let Some(id) = reg.get_slot(info.name.as_str()) { + reg.set_ref_kind(id, kind); + } + } + } + + let mut out = Vec::new(); + for info in reg.iter_slots() { + let ref_kind = info + .ref_kind + .unwrap_or_else(|| classify_slot_name_only(info.name.as_str())); + out.push(SlotMetadata { + name: info.name.clone(), + ref_kind, + }); + } + out +} + +fn classify_slots_from_variable_map(builder: &MirBuilder) -> Vec { + let mut slots = Vec::new(); + for (name, &vid) in builder.variable_map.iter() { + let ref_kind = classify_slot(builder, vid, name.as_str()); + slots.push(SlotMetadata { + name: name.clone(), + ref_kind, + }); + } + slots +} + fn classify_slot(builder: &MirBuilder, v: ValueId, name: &str) -> RefSlotKind { if let Some(ty) = builder.value_types.get(&v) { return Region::classify_ref_kind(ty); } // 型情報が無い場合は名前ヒューリスティックで軽く分類する(観測専用)。 + classify_slot_name_only(name) +} + +fn classify_slot_name_only(name: &str) -> RefSlotKind { if matches!( name, "args" @@ -104,4 +204,3 @@ fn classify_slot(builder: &MirBuilder, v: ValueId, name: &str) -> RefSlotKind { RefSlotKind::NonRef } } -