Files
hakorune/docs/private/roadmap/phases/phase-32/PLAN.md

16 KiB
Raw Blame History

Phase 32: Hakorune Static Analyzer - 実装計画(改訂版)

期間: 2025-10-20 → 2025-10-27 (7日間) 配置: selfhost/analyzer/Ring-1: Meta/自己ホスト) 構造: 責務分離driver/ast_scan/mir_scan/report


Day 1: スケルトン作成 + README (2025-10-20)

🎯 目標

  • ディレクトリ構造作成
  • 4つのBoxスケルトン
  • hako_module.toml設定
  • README.md責務/入出力/ENV

タスク

ディレクトリ作成 (10分)

mkdir -p selfhost/analyzer/{boxes,smokes}

hako_module.toml作成 (20分)

# selfhost/analyzer/hako_module.toml

[package]
name = "selfhost.analyzer"
version = "0.1.0"

[exports]
driver = "boxes/driver_box.hako"
ast_scan = "boxes/ast_scan_box.hako"
mir_scan = "boxes/mir_scan_box.hako"
report = "boxes/report_box.hako"

[dependencies]
BoxHelpers = "selfhost.shared.box_helpers"
MirSchema = "selfhost.shared.mir.schema"
JsonEmit = "selfhost.shared.json.emit"

driver_box.hako スケルトン (30分)

# selfhost/analyzer/boxes/driver_box.hako

using selfhost.analyzer.mir_scan as MirScanBox
using selfhost.analyzer.report as ReportBox

static box DriverBox {
  birth() {
    # 空のbirthstatic boxなので状態なし
  }

  # 文字列入力→文字列出力
  run_from_string(input, kind) {
    # TODO: Day 2-3で実装
    return "[DriverBox] Not implemented yet"
  }

  # MapBox入力→MapBox出力
  run_from_box(input, kind) {
    # TODO: Day 2-3で実装
    local result
    result = new MapBox()
    return result
  }
}

mir_scan_box.hako スケルトン (30分)

# selfhost/analyzer/boxes/mir_scan_box.hako

using selfhost.shared.box_helpers as BoxHelpers
using selfhost.shared.mir.schema as MirSchema

static box MirScanBox {
  birth() {
    # 空のbirth
  }

  # MIR JSON → 関数リスト抽出
  extract_functions(mir_json) {
    # TODO: Day 2で実装
    local result
    result = new ArrayBox()
    return result
  }

  # 呼び出しグラフ構築
  build_call_graph(mir_json) {
    # TODO: Day 3で実装
    local result
    result = new MapBox()
    return result
  }

  # BFS到達可能性分析
  find_dead_code(call_graph, entry_points) {
    # TODO: Day 4で実装
    local result
    result = new ArrayBox()
    return result
  }
}

report_box.hako スケルトン (20分)

# selfhost/analyzer/boxes/report_box.hako

static box ReportBox {
  birth() {
    # 空のbirth
  }

  # 診断サマリ文字列生成
  generate_text_report(dead_functions, all_functions) {
    # TODO: Day 5で実装
    return "[ReportBox] Analysis complete"
  }

  # MapBox診断生成
  generate_map_report(dead_functions, reachable_functions) {
    # TODO: Day 5で実装
    local result
    result = new MapBox()
    return result
  }
}

ast_scan_box.hako スケルトン (10分)

# selfhost/analyzer/boxes/ast_scan_box.hako

static box AstScanBox {
  birth() {
    # 空のbirth
  }

  # AST走査Phase 32外、将来実装
  scan(ast_json) {
    return "[AstScanBox] Not implemented in Phase 32"
  }
}

README.md作成 (40分)

# selfhost/analyzer - Hakorune Static Analyzer

## 責務

HakoruneでHakoruneコードを解析する静的解析ツールPhase 32: デッドコード検出MVP

## 入出力

### 入力
- MIR JSON文字列Gate-C Strict前提
- またはMapBoxMIR構造

### 出力
- テキストレポート(診断サマリ)
- またはMapBox構造化診断

## ENV変数

```bash
# 追加チェックON
NYASH_ANALYZER_STRICT=1

# 将来の切替(既定=mir
HAKO_ANALYZER_ENTRY=ast|mir

# Gate-C/AST Strict既定ON
NYASH_GATE_C_STRICT=1
NYASH_AST_STRICT=1

非対象

  • 外部I/Oファイル読み書き
  • プラグイン化当面VM経由のみ
  • CLI将来tools/に追加可能)

使用例

using selfhost.analyzer.driver as DriverBox

local mir_json = "{ ... }"  // MIR JSON文字列
local result = DriverBox.run_from_string(mir_json, "mir")
print(result)

### 成果物
- ✅ selfhost/analyzer/hako_module.toml: 20行
- ✅ driver_box.hako: 30行
- ✅ mir_scan_box.hako: 40行
- ✅ report_box.hako: 25行
- ✅ ast_scan_box.hako: 15行
- ✅ README.md: 50行
- **合計**: 180行

### 完了基準
- [ ] 全ファイル作成完了
- [ ] hako_module.toml が正しく読み込まれる
- [ ] スケルトンBoxがインポート可能

---

## Day 2: MIR走査基盤関数抽出 (2025-10-21)

### 🎯 目標
- extract_functions 実装
- MIR JSON → 関数リスト変換
- 基本的な走査パターン確立

### タスク

#### extract_functions 実装 (2時間)
```hako
# selfhost/analyzer/boxes/mir_scan_box.hako

extract_functions(mir_json) {
  local functions_key
  functions_key = "functions"

  local has_key
  has_key = mir_json.has(functions_key)

  if has_key == 0 {
    print("[MirScanBox] ERROR: Missing 'functions' key")
    return null
  }

  local functions_array
  functions_array = mir_json.get(functions_key)

  local result
  result = new ArrayBox()

  local i
  i = 0

  loop(i < functions_array.size()) {
    local func
    func = functions_array.get(i)

    local name
    name = me.extract_function_name(func)

    if name != null {
      result.push(name)
    }

    i = i + 1
  }

  return result
}

extract_function_name(func_obj) {
  local name_key
  name_key = "name"

  local has_name
  has_name = func_obj.has(name_key)

  if has_name == 0 {
    return null
  }

  local name
  name = func_obj.get(name_key)

  local arity_key
  arity_key = "arity"

  local arity
  arity = func_obj.get(arity_key)

  # "Main.main/0" 形式
  local slash
  slash = "/"

  local arity_str
  arity_str = arity.to_string()

  local qualified
  qualified = name.concat(slash)
  qualified = qualified.concat(arity_str)

  return qualified
}

テストケース作成 (1時間)

  • 簡単なMIR JSON2関数
  • 関数リスト抽出確認

成果物

  • mir_scan_box.hako拡張: 100行
  • テストケース: 50行
  • 合計: 150行

完了基準

  • extract_functions が動作
  • 実際のMIR JSONから関数リスト取得可能

Day 3: 呼び出しグラフ構築 (2025-10-22)

🎯 目標

  • build_call_graph 実装
  • mir_call命令から呼び出し先抽出
  • caller → [callees] マップ生成

タスク

build_call_graph 実装 (3時間)

  • 各関数のinstructionsをスキャン
  • mir_call命令のcalleeを抽出
  • MapBoxcaller → ArrayBox構築

Callee型パース実装 (1時間)

parse_callee(callee_obj) {
  # "function:Main.helper/1"
  # "extern:env.console.log"
  # "constructor:ArrayBox"
  local callee_type
  callee_type = callee_obj.get("type")

  if callee_type == "ModuleFunction" {
    local module
    module = callee_obj.get("module")
    local func
    func = callee_obj.get("function")
    local arity
    arity = callee_obj.get("arity")

    local dot
    dot = "."
    local slash
    slash = "/"

    local result
    result = module.concat(dot)
    result = result.concat(func)
    result = result.concat(slash)

    local arity_str
    arity_str = arity.to_string()

    result = result.concat(arity_str)
    return result
  }

  # TODO: Constructor/Extern対応
  return null
}

成果物

  • mir_scan_box.hako拡張: 150行
  • テストケース: 100行
  • 合計: 250行

完了基準

  • build_call_graph が動作
  • 実際のMIR JSONから呼び出しグラフ生成可能

Day 4: BFS到達可能性分析 (2025-10-23)

🎯 目標

  • find_dead_code 実装
  • BFS幅優先探索アルゴリズム
  • デッドコード検出

タスク

find_dead_code 実装 (2時間)

find_dead_code(call_graph, entry_points) {
  local reachable
  reachable = me.mark_reachable(call_graph, entry_points)

  local all_funcs
  all_funcs = call_graph.keys()

  local dead
  dead = new ArrayBox()

  local i
  i = 0

  loop(i < all_funcs.size()) {
    local func
    func = all_funcs.get(i)

    local is_reachable
    is_reachable = reachable.has(func)

    if is_reachable == 0 {
      dead.push(func)
    }

    i = i + 1
  }

  return dead
}

mark_reachable(graph, entry_points) {
  local visited
  visited = new MapBox()  # Set代わり

  local queue
  queue = new ArrayBox()  # 簡易キュー

  # エントリポイント追加
  local i
  i = 0

  loop(i < entry_points.size()) {
    local entry
    entry = entry_points.get(i)
    queue.push(entry)

    i = i + 1
  }

  # BFS
  loop(queue.size() > 0) {
    local current
    current = queue.get(0)

    # 簡易shift先頭削除の代わりに新しいArrayBoxを作る
    local new_queue
    new_queue = new ArrayBox()

    local j
    j = 1

    loop(j < queue.size()) {
      local item
      item = queue.get(j)
      new_queue.push(item)

      j = j + 1
    }

    queue = new_queue

    local already_visited
    already_visited = visited.has(current)

    if already_visited == 1 {
      continue  # ✅ Phase 12.7で実装済み
    }

    visited.set(current, 1)

    local callees
    callees = graph.get(current)

    if callees != null {
      local ci
      ci = 0

      loop(ci < callees.size()) {
        local callee
        callee = callees.get(ci)

        local callee_visited
        callee_visited = visited.has(callee)

        if callee_visited == 0 {
          queue.push(callee)
        }

        ci = ci + 1
      }
    }
  }

  return visited
}

成果物

  • mir_scan_box.hako完成: 200行
  • テストケース: 100行
  • 合計: 300行

完了基準

  • find_dead_code が動作
  • BFS正確性確認既知デッドコード検出

Day 5: レポート生成 (2025-10-24)

🎯 目標

  • generate_text_report 実装
  • generate_map_report 実装
  • 人間可読・機械可読両対応

タスク

generate_text_report 実装 (1.5時間)

generate_text_report(dead_functions, all_functions) {
  local header
  header = "Hakorune Dead Code Analyzer\n"

  local separator
  separator = "=============================\n"

  local result
  result = header.concat(separator)

  local all_count
  all_count = all_functions.size()

  local all_str
  all_str = all_count.to_string()

  local all_line
  all_line = "Functions found: "
  all_line = all_line.concat(all_str)
  all_line = all_line.concat("\n")

  result = result.concat(all_line)

  local dead_count
  dead_count = dead_functions.size()

  local dead_str
  dead_str = dead_count.to_string()

  local dead_line
  dead_line = "Dead functions: "
  dead_line = dead_line.concat(dead_str)
  dead_line = dead_line.concat("\n")

  result = result.concat(dead_line)

  # 個別リスト表示
  local i
  i = 0

  loop(i < dead_functions.size()) {
    local func
    func = dead_functions.get(i)

    local prefix
    prefix = "  [DEAD] "

    local line
    line = prefix.concat(func)
    line = line.concat("\n")

    result = result.concat(line)

    i = i + 1
  }

  return result
}

generate_map_report 実装 (1時間)

generate_map_report(dead_functions, reachable_functions) {
  local result
  result = new MapBox()

  result.set("dead_functions", dead_functions)
  result.set("reachable_functions", reachable_functions)

  local dead_count
  dead_count = dead_functions.size()

  result.set("dead_count", dead_count)

  local reachable_count
  reachable_count = reachable_functions.size()

  result.set("reachable_count", reachable_count)

  return result
}

成果物

  • report_box.hako完成: 120行
  • 合計: 120行

完了基準

  • レポートが見やすい
  • 統計情報が正確

Day 6: スモークテスト整備 (2025-10-25)

🎯 目標

  • 3本のスモークテスト作成
  • happy path 2本 + 負例1本
  • quick-lite/quick/integrationプロファイル対応

タスク

happy path スモーク1 (1時間)

# selfhost/analyzer/smokes/analyzer_mir_gatec_ok_vm.sh

#!/bin/bash
set -euo pipefail

# Gate-C MIR JSON入力正常系

MIR_JSON='{"functions":[{"name":"Main.main","arity":0,"blocks":[]}]}'

RESULT=$(NYASH_GATE_C_STRICT=1 ./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox

local mir = \"$MIR_JSON\"
local result = DriverBox.run_from_string(mir, \"mir\")
print(result)
")

if echo "$RESULT" | grep -q "Functions found: 1"; then
  echo "✅ PASS: analyzer_mir_gatec_ok_vm"
  exit 0
else
  echo "❌ FAIL: Expected 'Functions found: 1'"
  exit 1
fi

負例スモーク (1時間)

# selfhost/analyzer/smokes/analyzer_bad_mir_fail_vm.sh

#!/bin/bash
set -euo pipefail

# 不正MIR: 余剰キー/型不一致

BAD_MIR='{"functions":[], "extra_key": "should fail"}'

set +e
RESULT=$(NYASH_GATE_C_STRICT=1 ./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox

local mir = \"$BAD_MIR\"
local result = DriverBox.run_from_string(mir, \"mir\")
print(result)
" 2>&1)
EXIT_CODE=$?
set -e

if [ $EXIT_CODE -ne 0 ]; then
  echo "✅ PASS: analyzer_bad_mir_fail_vm (expected failure)"
  exit 0
else
  echo "❌ FAIL: Should have failed on bad MIR"
  exit 1
fi

プロファイル統合 (1時間)

  • quick-lite: 負例のみ
  • quick: 全3本
  • integration: セルフホスト解析追加

成果物

  • スモークテスト3本: 150行
  • プロファイル設定: 30行
  • 合計: 180行

完了基準

  • 全スモークテストPASS
  • quick-liteで負例Fail-Fast確認

Day 7: セルフホスト解析 + ドキュメント (2025-10-26)

🎯 目標

  • セルフホストコードでの実用検証
  • 精度95%以上確認
  • ドキュメント整備

タスク

セルフホスト解析 (2時間)

# selfhost/compiler/builder/rewrite/known.hako を解析
NYASH_GATE_C_STRICT=1 ./target/release/hakorune --emit-mir-json /tmp/known.json \
  apps/selfhost-compiler/builder/rewrite/known.hako

# Analyzerで解析
./target/release/hakorune -c "
using selfhost.analyzer.driver as DriverBox

# /tmp/known.json の内容を読み込み(簡易版)
local result = DriverBox.run_from_string(mir_json, \"mir\")
print(result)
"

ドキュメント整備 (1時間)

  • Phase 32 README.md 更新(実装完了マーク)
  • PLAN.md 実績LOC記録

精度検証 (30分)

  • 手動でデッドコード候補確認
  • 偽陽性/偽陰性チェック

成果物

  • 実用検証レポート: 50行
  • ドキュメント更新
  • 合計: 50行

完了基準

  • セルフホストコードで95%以上の精度
  • ドキュメント完備
  • 1,000行ファイルを5秒以内で解析

📊 進捗トラッキング

Day 計画LOC 実績LOC 状態 備考
Day 1 180 - 🟡 スケルトン + README
Day 2 150 - 🟡 MIR走査基盤
Day 3 250 - 🟡 呼び出しグラフ
Day 4 300 - 🟡 BFS到達可能性
Day 5 120 - 🟡 レポート生成
Day 6 180 - 🟡 スモークテスト
Day 7 50 - 🟡 セルフホスト解析
Total 1,230 -

🎯 成功基準(最終確認)

  • 精度: >95%(セルフホストコードで手動検証)
  • 再現率: >90%(既知デッドコード検出)
  • 処理速度: <5秒1,000行ファイル
  • 使いやすさ: 1コマンドで実行可能
  • テストカバレッジ: 3件のスモークテスト全PASS

🚨 リスク & 対策

リスク1: selfhost/shared依存の複雑さ

  • 対策: Day 1でimport確認、早期検証
  • バックアップ: 簡易JSON解析を自前実装

リスク2: BFS パフォーマンス

  • 対策: ArrayBox簡易キュー小規模グラフで十分
  • バックアップ: 将来QueueBox追加可能

リスク3: セルフホスト解析で想定外の問題

  • 対策: Day 7に余裕を持たせる
  • バックアップ: 簡単な例で先に検証

作成日: 2025-10-19 改訂日: 2025-10-19構造改訂版 開始予定: 2025-10-20 完了予定: 2025-10-27