Files
hakorune/docs/private/roadmap/phases/phase-21-optimization/llvm-optimization-strategy.md

9.6 KiB
Raw Blame History

LLVM最適化戦略Gemini提案統合版

出典: Gemini AI提案2025-10-02 統合者: Claude (Sonnet 4.5)


🎯 基本方針

nyashの14個の命令 → LLVMの何百もの低レベル命令

この「言葉のレベルの違い」をうまく利用するのが最適化の鍵!


📚 最適化手法4つの柱

1. LLVM Intrinsics魔法の関数を使う 🪄

LLVMには @llvm. で始まる特別な関数Intrinsicsがある。 これらはLLVMのオプティマイザが特別な意味を知っている魔法の関数!

主要Intrinsics

Intrinsic 用途 効果
@llvm.memcpy メモリ一括コピー CPUの最速命令に変換
@llvm.memmove オーバーラップ対応コピー 安全な一括コピー
@llvm.memset メモリ初期化 一括ゼロクリア
@llvm.sqrt.f64 平方根計算 CPU命令1個に変換
@llvm.abs.i64 絶対値計算 分岐なし高速化
@llvm.bswap.i64 バイトスワップ エンディアン変換

実装例: メモリコピー最適化

# ❌ Before: 自前ループ(遅い)
def copy_array_slow(dst, src, length):
    for i in range(length):
        dst_ptr = builder.gep(dst, [ir.Constant(i64, i)])
        src_ptr = builder.gep(src, [ir.Constant(i64, i)])
        val = builder.load(src_ptr)
        builder.store(val, dst_ptr)

# ✅ After: LLVM Intrinsic速い
def copy_array_fast(dst, src, length):
    memcpy_func = declare_llvm_memcpy(module)
    builder.call(memcpy_func, [
        dst,      # destination
        src,      # source
        length,   # byte count
        ir.Constant(i1, 0)  # is_volatile
    ])

効果:

  • ループオーバーヘッド削減
  • SIMD命令への自動変換
  • CPUのDMA機能活用

2. Box構造をLLVMに教える 📦

nyashでは「Everything is Box」だが、LLVMには具体的な構造体として伝える必要がある。

Box構造の明示

# StringBox の構造定義
StringBox = ir.LiteralStructType([
    ir.IntType(32),               # 0: length
    ir.IntType(8).as_pointer()    # 1: data*
])

# IntegerBox の構造定義
IntegerBox = ir.LiteralStructType([
    ir.IntType(64)  # 0: value
])

extractvalue命令への置き換え

# ❌ Before: 関数呼び出し(遅い)
length_func = module.get_function("StringBox.length")
length = builder.call(length_func, [string_box])

# ✅ After: 構造体アクセス(速い)
length = builder.extract_value(string_box, 0, name="length")
# → たった1命令関数呼び出しオーバーヘッドなし

効果:

  • 関数呼び出しコスト削減10-100倍高速化
  • インライン展開促進
  • レジスタ割り当て最適化

3. TBAAType-Based Alias Analysisヒント 🎯

「このポインタとあのポインタは別物だよ」とLLVMに教えてあげる。

TBAAメタデータの設定

class TBAABuilder:
    """TBAA型ベースエイリアス解析メタデータ生成"""

    def __init__(self, module):
        self.module = module
        self.root = self._create_tbaa_root()
        self.box_types = {}

    def _create_tbaa_root(self):
        """TBAA階層のルート"""
        return self.module.add_metadata([
            ir.MetaDataString(self.module, "nyash-tbaa")
        ])

    def create_box_type(self, box_name):
        """Box型ごとのTBAAード"""
        if box_name not in self.box_types:
            self.box_types[box_name] = self.module.add_metadata([
                ir.MetaDataString(self.module, box_name),
                self.root,
                ir.IntType(64)(0)  # offset
            ])
        return self.box_types[box_name]

    def annotate_load(self, load_inst, box_name):
        """load命令にTBAAメタデータ付与"""
        tbaa_node = self.create_box_type(box_name)
        load_inst.set_metadata("tbaa", tbaa_node)

# 使用例
tbaa = TBAABuilder(module)

# StringBoxへのアクセス
str_load = builder.load(string_ptr)
tbaa.annotate_load(str_load, "StringBox")

# IntegerBoxへのアクセス
int_load = builder.load(integer_ptr)
tbaa.annotate_load(int_load, "IntegerBox")

効果:

# TBAA情報があると、LLVMはこう最適化できる:

# ① 整数を読む
int_val = load(integer_box)

# ② 文字列を書き込むIntegerBoxとは無関係
store(string_val, string_box)

# ③ もう一度同じ整数を読む
# → LLVMは「②の書き込みは①の整数に影響しない」と判断
# → ③の再読み込みを省略! int_val を再利用
int_val_reused = int_val  # load削減

4. 関数属性Function Attributes 🏷️

関数の「性格」をLLVMに伝える。

主要属性

属性 意味 対象関数例
readnone 副作用なし・メモリ読まない Math.add, Math.sqrt
readonly 読み込みのみ・書き込みなし Array.length, String.length
nounwind 例外を投げない すべてのnyash関数
alwaysinline 常にインライン展開 小さいヘルパー関数
noinline インライン展開禁止 デバッグ用関数
cold めったに実行されない エラーハンドラ

実装例

def annotate_pure_function(func):
    """純粋関数の最適化"""
    # 副作用なし → LLVMが同じ引数の呼び出しを1回に最適化
    func.attributes.add("readnone")
    func.attributes.add("nounwind")

    # 小さい関数は積極的にインライン化
    if count_instructions(func) < 10:
        func.attributes.add("alwaysinline")

# 使用例
math_add_func = module.get_function("Math.add")
annotate_pure_function(math_add_func)

# ✅ 最適化結果:
# result1 = Math.add(1, 2)  # → 実行される
# result2 = Math.add(1, 2)  # → LLVMが省略result1を再利用

🚨 注意点(絶対守るべきルール)

1. 未定義動作UBを絶対に避ける ⚠️

LLVMは未定義動作があるととんでもないコードを生成することがある!

主要UB一覧

UB 対策
nullポインタ参照 *null_ptr null check挿入
符号付きオーバーフロー INT_MAX + 1 チェック付き演算
未初期化変数使用 int x; return x; 明示的初期化
配列境界外アクセス arr[length] 境界チェック

実装例: null check

def safe_load(ptr, name="load"):
    """null check付きload"""
    # null check
    is_null = builder.icmp_unsigned('==', ptr, ir.Constant(ptr.type, None))

    with builder.if_then(is_null):
        # nullならエラー
        builder.call(panic_func, [
            ir.Constant.literal_struct([
                ir.Constant(i8p, "Null pointer dereference")
            ])
        ])

    # 安全にload
    return builder.load(ptr, name=name)

2. データレイアウト・呼び出し規約の統一 📐

プラットフォーム別の標準ルールに従う。

# ターゲット別のデータレイアウト
TARGET_LAYOUTS = {
    "x86_64-unknown-linux-gnu":
        "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
    "wasm32-unknown-unknown":
        "e-m:e-p:32:32-i64:64-n32:64-S128",
    "aarch64-unknown-linux-gnu":
        "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
}

# モジュールに設定
module.triple = target_triple
module.data_layout = TARGET_LAYOUTS[target_triple]

3. PHI命令は慎重に 🔀

全ての前任ブロックからの値を漏れなく指定!

# ✅ 正しいPHI
phi = builder.phi(i64, name="phi_result")
phi.add_incoming(value_from_if, if_block)
phi.add_incoming(value_from_else, else_block)

# ❌ 間違い: else_blockからの値を忘れた
phi = builder.phi(i64, name="phi_result")
phi.add_incoming(value_from_if, if_block)
# → Verifierエラー

4. Verifierを毎回実行

開発中は関数を1つ生成するたびに検証

def build_function(func_name, ...):
    # 関数生成
    func = ir.Function(module, func_type, name=func_name)
    # ... IRを生成 ...

    # 必ず検証!
    if not func.verify():
        raise CompilerError(f"Invalid IR in {func_name}")

    return func

📊 期待される効果(ベンチマーク予測)

最適化手法 期待される効果
LLVM Intrinsics メモリ操作: 5-10倍高速化
Box構造明示 メソッド呼び出し: 10-100倍高速化
TBAA ループ内load削減: 20-50%
関数属性 関数呼び出しオーバーヘッド: 50-90%削減

総合効果: 現在比で 2-5倍の性能向上を予測


🎯 実装優先順位

  1. Verifier統合 🔴 最優先
  2. UBチェック 🔴 最優先
  3. データレイアウト標準化 🟡 重要
  4. Box構造明示 🟡 重要
  5. LLVM Intrinsics 🟢
  6. 関数属性 🟢
  7. TBAA 🔵 低(効果大だが実装複雑)

📚 参考資料


まとめ: Geminiの提案は実用的で効果的Phase 21で段階的に実装していく価値がある 🎊