📊 Phase 25.1l: Region観測レイヤー骨格 + スコープ契約設計理解

**Region Box統一理論の実装開始**

新規追加:
- src/mir/region/mod.rs: Region/RefSlotKind型定義
- src/mir/region/observer.rs: Region観測レイヤー
- docs/development/roadmap/phases/phase-25.1l/: 設計ドキュメント

主要概念:
- Region Box = Function/Loop/If の統一箱
- RefSlotKind = GC管理用スロット種別(Strong/Weak/Borrowed/NonRef)
- 観測専用(NYASH_REGION_TRACE=1で動作、挙動変更なし)

設計理解の深化:
- ValueId(40)問題 = LoopForm v2スコープ契約違反の症状
- 根本解決 = Region観測で無名一時値のスコープまたぎを検出
- 箱理論3原則: 境界明確化/差し替え可能/段階的移行

関連議論:
- ChatGPT提案: Region統一理論でGC/寿命管理の基盤構築
- SlotRegistry: 変数の単一真実源(SSOT)
- 階層構造: FunctionRegion → LoopRegion → IfRegion

次のステップ:
- Phase 1: Region観測(現在)- 非破壊的追加
- Phase 2: メタデータ出力(MIR JSON拡張)
- Phase 3: GC統合(retain/release挿入)

テスト追加:
- lang/src/compiler/tests/stageb_mini_driver.hako
- tools/test_loopssa_breakfinder_slot.sh

Build:  全警告は既存のもの
Tests: 既存テスト全て緑維持
This commit is contained in:
nyash-codex
2025-11-19 02:44:40 +09:00
parent 80f8a7bc8c
commit 39f5256c18
22 changed files with 941 additions and 68 deletions

View File

@ -19,6 +19,13 @@ using selfhost.shared.mir.control_form as ControlFormBox
local trace = trace_flag
if trace == 1 {
print("[break-finder] start")
// Program(JSON v0) の先頭だけ観測用に出力する
local preview = "" + json_str
local max_len = 200
if preview.length() > max_len {
preview = preview.substring(0, max_len)
}
print("[break-finder/json] " + preview)
}
local breaks = new ArrayBox()
@ -80,6 +87,16 @@ using selfhost.shared.mir.control_form as ControlFormBox
local loops = new ArrayBox()
local s = "" + json_str
// trace=1 のときは Program(JSON v0) の先頭だけ観測用に出力する
if trace == 1 {
local preview = s
local max_len = 200
if preview.length() > max_len {
preview = preview.substring(0, max_len)
}
print("[break-finder/json] " + preview)
}
// Simple pattern: find "loop_header":NNN, "loop_exit":MMM
// This is a simplified version - just finds explicit loop markers
local i = 0

View File

@ -9,8 +9,10 @@
// - 出力: exit PHI 相当の命令が入った Program(JSON v0)(文字列)。
// - 解析: BreakFinderBox.find_breaks(json, trace_flag) が JSON を読み取り専用で解析。
// - 変換: PhiInjectorBox.inject_exit_phis(json, breaks, trace_flag) が exit block をテキストベースで書き換える。
// - 環境変数/トレース: HAKO_LOOPSSA_EXIT_PHI / HAKO_COMPILER_BUILDER_TRACE を LoopSSA 側で解釈し、
// 下流には 0/1 の trace_flag だけを渡す(箱ごとに ENV を直読しない)。
// - 環境変数/トレース:
// - HAKO_LOOPSSA_TRACE=1 : LoopSSA/BreakFinder/PhiInjector 専用のトレースON
// - HAKO_COMPILER_BUILDER_TRACE=1 : (後方互換)未設定時のフォールバックとして扱う
// LoopSSA 側で trace_flag を 0/1 に正規化し、下流には数値だけを渡す(箱ごとに ENV を直読しない)。
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox
@ -20,7 +22,11 @@ using lang.compiler.builder.ssa.exit_phi.phi_injector as PhiInjectorBox
// Phase 2-5 implementation: detect breaks and inject exit PHIs
stabilize_merges(stage1_json) {
// Resolve trace flag once at the entry point0/1 に正規化)
local trace_env = env.get("HAKO_COMPILER_BUILDER_TRACE")
// 優先: HAKO_LOOPSSA_TRACE / 後方互換: HAKO_COMPILER_BUILDER_TRACE
local trace_env = env.get("HAKO_LOOPSSA_TRACE")
if trace_env == null {
trace_env = env.get("HAKO_COMPILER_BUILDER_TRACE")
}
local trace_flag = 0
if trace_env != null && ("" + trace_env) == "1" { trace_flag = 1 }

View File

@ -0,0 +1,37 @@
// breakfinder_direct_min.hako — BreakFinderBox 直接呼び出し用の極小ハーネス
//
// 目的:
// - StageB/LoopSSA を経由せずに、BreakFinderBox.find_breaks/2 自体の
// MIR 形状と SSA 性を確認するための最小サンプルだよ。
// - Program(JSON v0) 文字列を 1 本用意して、BreakFinderBox.find_breaks(json, trace)
// を直接呼び出し、VM/Verifier で undefined receiver などのエラーを観測する。
//
// JSON 形状loopssa_breakfinder_min.hako と同じ単純な緑ケース):
// {
// "kind":"Program",
// "functions":[
// {
// "name":"main",
// "blocks":[
// {"id":0,"loop_header":0,"loop_exit":2},
// {"id":1},
// {"id":2}
// ]
// }
// ]
// }
using lang.compiler.builder.ssa.exit_phi.break_finder as BreakFinderBox
static box Main {
method main(args) {
// 単純な Program(JSON v0) を直接埋め込む
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
// trace_flag=1 で BreakFinderBox 内部の挙動を観測する
local breaks = BreakFinderBox.find_breaks(json, 1)
print(breaks)
return 0
}
}

View File

@ -0,0 +1,29 @@
// loopssa_breakfinder_slot.hako — LoopSSA/BreakFinder 用「最小失敗 JSON」スロット
//
// 目的:
// - StageB 最小サンプルstageb_min_sample.hako など)から切り出した
// 「問題のある Program(JSON v0)」を貼り付けて、LoopSSA v2 /
// BreakFinderBox / PhiInjectorBox の挙動を単体で再現するためのスロットだよ。
// - 初期状態では loopssa_breakfinder_min.hako と同じ、単純な緑の JSON を使っておく。
// バグが再現できる JSON v0 が手に入ったら、下の `json` 文字列を書き換えて使う。
//
// 注意:
// - ここは「.hako パーサ → LoopSSA」経路のデバッグ専用。StageB 全体ではなく、
// LoopSSA/BreakFinderBox 周辺の挙動だけを確認したいときに使ってね。
//
using lang.compiler.builder.ssa.loopssa as LoopSSA
static box Main {
method main(args) {
// TODO: StageB Test2 から切り出した Program(JSON v0) で上書きして使う。
// 現在は loopssa_breakfinder_min と同じ単純な緑の JSON だよ。
local json = "{\"kind\":\"Program\",\"functions\":[{\"name\":\"main\",\"blocks\":[{\"id\":0,\"loop_header\":0,\"loop_exit\":2},{\"id\":1},{\"id\":2}]}]}"
// LoopSSA v2 を直接呼び出すExit PHI パスのみ)
local out = LoopSSA.stabilize_merges(json)
print(out)
return 0
}
}

View File

@ -0,0 +1,100 @@
// stageb_mini_driver.hako — StageB 近似の極小ドライバ
//
// 目的:
// - StageBDriverBox.main/1 の経路を「最小限」に真似したテスト用ドライバだよ。
// - ParserBox + CompilerBuilder.apply_all(LoopSSA/BreakFinder/PhiInjector を含む)
// だけを通し、StageB 本体の箱StageBArgsBox / StageBBodyExtractorBox / FuncScannerBox
// を介さずに SSA/ValueId 問題を再現できるか確認する。
//
// 振る舞い:
// - lang/src/compiler/tests/stageb_min_sample.hako と同等のソースコード文字列を
// 内部に埋め込み、ParserBox.parse_program2 → CompilerBuilder.apply_all を実行する。
// - 出力は Stage1 Program(JSON v0)LoopSSA 適用後。VM/Verifier/ログで
// BreakFinderBox/LoopSSA 周辺の挙動を観測する。
using lang.compiler.builder.mod as CompilerBuilder
static box StageBMiniDriverBox {
method run(args) {
// stageb_min_sample.hako 相当のソースをそのまま埋め込む
local src = ""
// static box TestArgs { ... }
src = src + "static box TestArgs {\n"
src = src + " method process(args) {\n"
src = src + " if args != null {\n"
src = src + " local n = args.length()\n"
src = src + " local i = 0\n"
src = src + " loop(i < n) {\n"
src = src + " local item = args.get(i)\n"
src = src + " print(item)\n"
src = src + " i = i + 1\n"
src = src + " }\n"
src = src + " }\n"
src = src + " return 0\n"
src = src + " }\n"
src = src + "}\n\n"
// static box TestSimple { ... }
src = src + "static box TestSimple {\n"
src = src + " method run() {\n"
src = src + " local x = new ArrayBox()\n"
src = src + " local len = x.length()\n"
src = src + " print(len)\n"
src = src + " return 0\n"
src = src + " }\n"
src = src + "}\n\n"
// static box TestNested { ... }
src = src + "static box TestNested {\n"
src = src + " method complex(data) {\n"
src = src + " if data != null {\n"
src = src + " local count = data.length()\n"
src = src + " if count > 0 {\n"
src = src + " local j = 0\n"
src = src + " loop(j < count) {\n"
src = src + " local val = data.get(j)\n"
src = src + " if val != null {\n"
src = src + " local s = \"\" + val\n"
src = src + " print(s)\n"
src = src + " }\n"
src = src + " j = j + 1\n"
src = src + " }\n"
src = src + " }\n"
src = src + " }\n"
src = src + " return 0\n"
src = src + " }\n"
src = src + "}\n\n"
// static box Main { ... }
src = src + "static box Main {\n"
src = src + " method main(args) {\n"
src = src + " local t1 = TestArgs.process(args)\n"
src = src + " local t2 = TestSimple.run()\n"
src = src + " local test_data = new ArrayBox()\n"
src = src + " local t3 = TestNested.complex(test_data)\n"
src = src + " return 0\n"
src = src + " }\n"
src = src + "}\n"
// 1) Stage1 Program(JSON v0) を生成
local p = new ParserBox()
p.stage3_enable(1)
local ast_json = p.parse_program2(src)
// 2) CompilerBuilder パイプライン適用Rewrite/LocalSSA/LoopSSA 等)
ast_json = CompilerBuilder.apply_all(ast_json)
// 3) 出力をそのまま表示StageB 本体の emit に相当)
print(ast_json)
return 0
}
}
// エントリポイント: Main.main → StageBMiniDriverBox.run
static box Main {
method main(args) {
return StageBMiniDriverBox.run(args)
}
}