Newer
Older
SmTIAS-LightSim / src / sm_tias_lightsim / runtime / llvm_init.py
@rintoHasegawa rintoHasegawa on 28 May 6 KB [add] Speed-1 LLVM JIT 自動検出と診断ログ強化 — runtime/llvm_init.py を新規実装.ensure_drjit_libllvm_path() で DRJIT_LIBLLVM_PATH 未設定時に Windows 標準 4 候補 (C:\Program Files\LLVM 等) + POSIX glob + LLVM_DIR + extra_candidates を順次探索して os.environ 設定(既設定なら上書きしない).log_variant_status() で llvm_/cuda_/scalar_ 経路を INFO 明示,要求 llvm_* で実 scalar_* なら WARNING 追加.render/runner.py で variant=llvm_* 時に自動検出を呼び ImportError 時は原因明示ログ→ re-raise,mitsuba を関数内遅延 import 化.visualize/baker.py の _set_variant_once に LLVM 自動探索分岐を追加し direct/path 両方で log_variant_status を呼ぶ.cli/render.py で import 順を制御し sensors.py/lights.py のモジュールレベル mitsuba import 前に検出を確実に通す.docs/02_ENV/ENV_02 に LLVM インストール手順・DRJIT_LIBLLVM_PATH 説明・動作確認コマンドを追記.新規テスト 51 件 (runtime/test_llvm_init.py 25, render/test_runner_jit_init.py 14, visualize/test_baker_jit_init.py 8, cli/test_render_jit_init.py 4) で仕様 A1-E1 完全カバー,DRJIT_LIBLLVM_PATH 設定下で 606 件全通過.速度は不変(目的は再発防止+診断容易化).数値同等性は MC 内部シードで SHA256 異なるが平均相対誤差 7.23e-05 で統計同等性パス.これで環境変数消失によるサイレントクラッシュが再発しない土台が整備された.TEST_04 Speed-1 セクションを実施結果で更新.CLAUDE.md 開発進捗に Speed-1 完了追記+実装済みモジュール一覧に runtime/llvm_init.py 追加
"""LLVM JIT 自動検出と診断ログモジュール.

Mitsuba 3 の llvm_* variant を使う際に必要な DRJIT_LIBLLVM_PATH 環境変数を
自動設定する機能と,variant 設定後の状態を診断ログに出力する機能を提供する.

mitsuba は import しない(テストの軽量化・副作用なし設計).
os.environ への書き込みのみが副作用.
"""

from __future__ import annotations

import glob
import logging
import os
from pathlib import Path

_logger = logging.getLogger(__name__)


def _iter_default_candidates() -> list[str]:
    """LLVM-C.dll / libLLVM-C.so の標準インストール先パス候補を返す.

    OS に応じて Windows / POSIX を分岐する.
    テスト容易化のため独立関数として定義する.

    Returns:
        パス候補文字列のリスト(優先度順).
    """
    candidates: list[str] = []

    # 環境変数 LLVM_DIR が設定されていれば bin/ 配下を優先候補に追加する
    llvm_dir = os.environ.get("LLVM_DIR")
    if llvm_dir:
        if os.name == "nt":
            candidates.append(str(Path(llvm_dir) / "bin" / "LLVM-C.dll"))
        else:
            candidates.append(str(Path(llvm_dir) / "lib" / "libLLVM-C.so"))
            candidates.append(str(Path(llvm_dir) / "lib" / "libLLVM-C.dylib"))

    if os.name == "nt":
        # Windows 標準インストール先
        candidates += [
            r"C:\Program Files\LLVM\bin\LLVM-C.dll",
            r"C:\Program Files (x86)\LLVM\bin\LLVM-C.dll",
            r"C:\ProgramData\chocolatey\lib\llvm\tools\LLVM\bin\LLVM-C.dll",
            r"C:\msys64\mingw64\bin\LLVM-C.dll",
            r"C:\msys64\clang64\bin\LLVM-C.dll",
        ]
    else:
        # POSIX 系(Linux / macOS)
        # glob で llvm-* の複数バージョンを一括検索
        candidates += glob.glob("/usr/lib/llvm-*/lib/libLLVM-C.so")
        candidates += [
            "/usr/local/lib/libLLVM-C.so",
            "/opt/homebrew/opt/llvm/lib/libLLVM-C.dylib",
            "/usr/local/opt/llvm/lib/libLLVM-C.dylib",
        ]

    return candidates


def ensure_drjit_libllvm_path(extra_candidates: list[str] | None = None) -> str | None:
    """DRJIT_LIBLLVM_PATH が未設定なら標準パスを探索して os.environ に設定する.

    優先順位:
      1. 既に os.environ['DRJIT_LIBLLVM_PATH'] が設定されている場合はそれを尊重して
         何もしない(戻り値はその既存値).
      2. 環境変数 LLVM_DIR が設定されていれば `$LLVM_DIR/bin/LLVM-C.dll`(Windows)
         または `$LLVM_DIR/lib/libLLVM-C.so` 等(POSIX)を試す.
      3. Windows の標準インストール先:
         - C:\\Program Files\\LLVM\\bin\\LLVM-C.dll
         - C:\\Program Files (x86)\\LLVM\\bin\\LLVM-C.dll
         - C:\\ProgramData\\chocolatey\\lib\\llvm\\tools\\LLVM\\bin\\LLVM-C.dll
         - C:\\msys64\\mingw64\\bin\\LLVM-C.dll
         - C:\\msys64\\clang64\\bin\\LLVM-C.dll
      4. POSIX 系:
         - /usr/lib/llvm-*/lib/libLLVM-C.so(glob)
         - /usr/local/lib/libLLVM-C.so
         - /opt/homebrew/opt/llvm/lib/libLLVM-C.dylib
         - /usr/local/opt/llvm/lib/libLLVM-C.dylib
      5. extra_candidates が指定されていればそれを最後に試す.

    Args:
        extra_candidates: 追加で試すパス候補のリスト(任意).
            標準候補で見つからない場合に最後に試す.

    Returns:
        発見・設定したパス文字列.既設定だった場合はその値.
        何も見つからなければ None.

    Side effect:
        os.environ['DRJIT_LIBLLVM_PATH'] を設定する(未設定かつ発見時のみ).
    """
    # 1. 既に設定されている場合はそのまま返す
    existing = os.environ.get("DRJIT_LIBLLVM_PATH")
    if existing:
        _logger.debug("DRJIT_LIBLLVM_PATH は既に設定されています: %s", existing)
        return existing

    # 標準候補 + 追加候補を順番に探索する
    all_candidates = _iter_default_candidates()
    if extra_candidates:
        all_candidates += extra_candidates

    for candidate in all_candidates:
        if Path(candidate).is_file():
            os.environ["DRJIT_LIBLLVM_PATH"] = candidate
            _logger.info(
                "DRJIT_LIBLLVM_PATH を自動設定しました: %s",
                candidate,
            )
            return candidate

    _logger.warning(
        "LLVM-C.dll / libLLVM-C.so が標準パスに見つかりませんでした."
        "LLVM をインストールするか,DRJIT_LIBLLVM_PATH 環境変数を手動で設定してください."
        "詳細は docs/02_ENV/ENV_02_環境構築手順.md を参照してください."
    )
    return None


def log_variant_status(logger: logging.Logger, requested_variant: str, actual_variant: str) -> None:
    """variant 設定後の状態を INFO / WARNING でログ出力する.

    actual_variant が requested_variant と異なる(scalar fallback 等)場合は
    WARNING で明示する.

    Args:
        logger: 出力先の Logger オブジェクト.
        requested_variant: 要求した variant 文字列(YAML の variant フィールド等).
        actual_variant: `mi.variant()` で確認した実際の variant 文字列.
    """
    if actual_variant.startswith("llvm_"):
        llvm_path = os.environ.get("DRJIT_LIBLLVM_PATH", "(未設定)")
        logger.info(
            "Mitsuba variant=%s, LLVM JIT 経路で実行(DRJIT_LIBLLVM_PATH=%s)",
            actual_variant,
            llvm_path,
        )
    elif actual_variant.startswith("cuda_"):
        logger.info("Mitsuba variant=%s, CUDA GPU 経路で実行", actual_variant)
    elif actual_variant.startswith("scalar_"):
        logger.info("Mitsuba variant=%s, scalar 経路で実行(単スレッド)", actual_variant)
    else:
        logger.info("Mitsuba variant=%s", actual_variant)

    # 要求が llvm_* なのに scalar_* に落ちている場合は WARNING を追加する
    if requested_variant.startswith("llvm_") and actual_variant.startswith("scalar_"):
        logger.warning(
            "要求 variant=%s に対し実 variant=%s"
            "(LLVM 経路に乗れていません."
            "LLVM-C.dll の場所と DRJIT_LIBLLVM_PATH を確認してください)",
            requested_variant,
            actual_variant,
        )