Files
hakorune/docs/private/papers/paper-d-ssa-construction/technical-details.md
Selfhosting Dev 4c0e6726e3 🔧 refactor(llvm-py): Fix resolver PHI handling and add trace improvements
Changes to resolver.py:
- Improved PHI value tracking in _value_at_end_i64() (lines 268-285)
- Added trace logging for snap hits with PHI detection
- Fixed PHI placeholder reuse logic to preserve dominance
- PHI values now returned directly from snapshots when valid

Changes to llvm_builder.py:
- Fixed externcall instruction parsing (line 522: 'func' instead of 'name')
- Improved block snapshot tracing (line 439)
- Added PHI incoming metadata tracking (lines 316-376)
- Enhanced definition tracking for lifetime hints

This should help debug the string carry=0 issue in esc_dirname_smoke where
PHI values were being incorrectly coerced instead of preserved.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-14 16:25:21 +09:00

7.4 KiB
Raw Blame History

SSA構築の技術詳細

1. Nyash特有のSSA課題

1.1 Box型システムとSSA

// Nyashコード
local str = "hello"
local num = 42
local result = str + num  // 動的な型
; LLVM IRでの課題
%str = call i64 @nyash_string_new(i8* @.str.hello)  ; handle
%num = i64 42
%result = ?  ; concat_si? concat_ii? 実行時まで不明

1.2 PHI型の決定問題

; 複雑な合流での型推論
bb1:
  %val1 = i64 123          ; integer handle
  br label %merge
bb2:
  %val2 = i8* @string      ; string pointer
  %handle = ptrtoint i8* %val2 to i64
  br label %merge
merge:
  %phi = phi ??? [ %val1, %bb1 ], [ %handle, %bb2 ]
  ; i64? i8*? 文脈依存で決定が必要

2. BuilderCursor設計の詳細

2.1 問題:位置管理の複雑さ

// 悪い例グローバルなbuilder状態
builder.position_at_end(bb1);
emit_instructions();
builder.position_at_end(bb2);  // 位置が変わる!
// bb1の続きを書きたいが...

2.2 解決BuilderCursor

pub struct BuilderCursor<'ctx, 'b> {
    builder: &'b Builder<'ctx>,
    closed_by_bid: HashMap<BasicBlockId, bool>,
    cur_bid: Option<BasicBlockId>,
    cur_llbb: Option<BasicBlock<'ctx>>,
}

impl BuilderCursor {
    pub fn with_block<R>(&mut self, bid, bb, f: impl FnOnce(&mut Self) -> R) -> R {
        // 状態を保存
        let prev = (self.cur_bid, self.cur_llbb);
        self.at_end(bid, bb);
        let result = f(self);
        // 状態を復元
        (self.cur_bid, self.cur_llbb) = prev;
        result
    }
}

2.3 終端管理

pub fn emit_term(&mut self, bid: BasicBlockId, f: impl FnOnce(&Builder)) {
    self.assert_open(bid);  // 閉じたブロックへの挿入を防止
    f(self.builder);
    self.closed_by_bid.insert(bid, true);  // 明示的に閉じる
}

3. Sealed SSAの実装

3.1 従来のアプローチ(問題あり)

// emit_jump/branchで即座にPHI配線
if let Some(phis) = phis_by_block.get(target) {
    for (dst, phi, inputs) in phis {
        // predからの値をその場で配線
        let val = vmap.get(vid)?;  // でも値がまだない場合も...
        phi.add_incoming(&[(val, pred_bb)]);
    }
}

3.2 Sealed SSAアプローチ

// ブロック終了時にスナップショット
let mut block_end_values: HashMap<BlockId, HashMap<ValueId, Value>> = HashMap::new();

// 各ブロック降下後
let snapshot = vmap.iter()
    .filter(|(vid, _)| defined_in_block.contains(vid))
    .map(|(k, v)| (*k, *v))
    .collect();
block_end_values.insert(bid, snapshot);

// seal時にスナップショットから配線
fn seal_block(...) {
    let val = block_end_values[&pred_bid].get(&vid)
        .or_else(|| /* フォールバック */);
}

3.3 PHI正規化の課題

// 理想pred数 = incoming数
assert_eq!(phi.count_incoming(), preds.get(&bb).len());

// 現実MIR PHIとCFG predsの不一致
// - MIRは静的に決定
// - CFGは動的に変化最適化、終端追加など

4. 型変換の統一戦略

4.1 基本方針

; すべてのBox値はi64 handleとして統一
; 必要な箇所でのみptr変換

; 原則
%handle = i64 ...
%ptr = inttoptr i64 %handle to i8*  ; 必要時のみ

; PHIも原則i64
%phi = phi i64 [...], [...]

4.2 文字列処理の特殊性

; 文字列リテラル
%str_ptr = getelementptr [6 x i8], [6 x i8]* @.str.hello, i32 0, i32 0
%handle = call i64 @nyash_string_new(i8* %str_ptr)

; 文字列操作handleベース
%len = call i64 @nyash.string.len_h(i64 %handle)
%sub = call i64 @nyash.string.substring_hii(i64 %handle, i64 %start, i64 %end)

5. デバッグとトレース

5.1 環境変数による制御

NYASH_CLI_VERBOSE=1          # 基本ログ
NYASH_LLVM_TRACE_PHI=1       # PHI配線の詳細
NYASH_LLVM_PHI_SEALED=1      # Sealed SSAモード
NYASH_ENABLE_LOOPFORM=1      # LoopForm実験

5.2 診断出力の例

[PHI:new] fn=Main_esc_json_1 bb=30 dst=30 ty=i64 inputs=(23->7),(27->30)
[PHI] sealed add pred_bb=27 val=30 ty=i64 (snapshot)
[PHI] sealed add (synth) pred_bb=23 zero-ty=i64
[LLVM] terminator present for bb=27
[LoopForm] detect while-pattern: header=15 body=16 other=17

6. 未解決の技術課題

6.1 完全なDominance保証

  • 現状hoistingとentry block配置で部分対応
  • 課題:ループ内での循環参照
  • 将来LoopFormでの構造化解決

6.2 最適PHI配置

  • 現状MIR指定の場所に素直に配置
  • 課題冗長なPHIの削減
  • 将来PHI最小化アルゴリズム

6.3 例外安全性

  • 現状:ゼロ値合成でクラッシュ回避
  • 課題:意味的正確性の保証
  • 将来Box型システムでのnull安全性

7. 箱理論による革命的簡略化

7.1 実装アーキテクチャ

class BoxBasedSSA:
    def __init__(self):
        self.boxes = {}      # block_id -> {var: value}
        self.current_box = {}
        self.deferred_phis = []  # 後処理用

7.2 PHI処理の簡略化

# 従来複雑なdominance計算とキャッシュ
def resolve_phi_complex(self, phi_info):
    # 300行のResolver処理...
    # dominance確認、型変換、キャッシュ管理

# 箱理論:単純な値選択
def resolve_phi_simple(self, var, predecessors):
    for pred_id, _ in predecessors:
        if self.came_from(pred_id):
            return self.boxes[pred_id].get(var, 0)
    return 0

7.3 alloca/load/store方式への転換

# SSA形式を諦めて、メモリベースの実装
def emit_variable_access(self, var):
    if var not in self.allocas:
        # 変数用のメモリ確保
        self.allocas[var] = self.builder.alloca(self.i64, name=var)
    
    # 読み込み
    def load_var():
        return self.builder.load(self.allocas[var])
    
    # 書き込み
    def store_var(value):
        self.builder.store(value, self.allocas[var])

7.4 型システムの単純化

# すべてをi64として扱う
def to_i64(self, value):
    if is_pointer(value):
        # ポインタ→ハンドル変換
        return self.call_from_i8_string(value)
    elif is_integer(value):
        return value
    else:
        return 0  # デフォルト

# 必要時のみポインタ変換
def to_ptr_if_needed(self, value, context):
    if context == "console_log":
        return self.call_to_i8p_h(value)
    return value

7.5 パフォーマンス特性

従来のSSA実装:
- コンパイル時間: 遅いPHI配線で50分
- 実行時性能: 最適
- メモリ使用: 少ない

箱理論実装:
- コンパイル時間: 高速5分以内
- 実行時性能: やや遅いalloca/load/storeのオーバーヘッド
- メモリ使用: やや多い変数ごとにalloca

トレードオフ: "動かないより100倍マシ"

7.6 実装の段階的移行

# Phase 1: 最小動作確認(現在)
- allocaベースで全変数管理
- PHI完全スキップ
- 動作優先

# Phase 2: 部分的最適化(将来)
- 読み取り専用変数はSSA
- ループ変数のみalloca
- 段階的性能改善

# Phase 3: 完全最適化(長期)
- 箱理論の知見を活かしたSSA再実装
- 100行のシンプルさを維持

これらの技術詳細は、論文の Technical Section の基礎となる。箱理論により、理論的な美しさより実装の実用性を優先した新しいアプローチを示している。