Files
hakorune/docs/development/current/llvm/PHASE_97_INTEGRATION_GUIDE.md
nyash-codex 6d73fc3404 feat(llvm): Phase 97 Box/Policy refactoring complete
Box化完了:
- CallRoutePolicyBox: Call routing SSoT
- PrintArgMarshallerBox: Print marshalling SSoT
- TypeFactsBox: Type propagation SSoT
- PhiSnapshotPolicyBox: PHI contract SSoT
- PluginErrorContext: Structured error reporting

📋 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-17 04:14:26 +09:00

15 KiB
Raw Permalink Blame History

Phase 97 Integration Guide - 箱化モジュールの統合手順

概要

Phase 97で作成した5つのBox/Policyを既存コードに統合する際の詳細手順です。

前提条件

  • Phase 97で作成した全Boxが正常にビルド完了していること
  • 既存テストが全てPASSしていること

統合順序(推奨)

依存関係を考慮した統合順序:

  1. TypeFactsBox基盤
  2. PhiSnapshotPolicyBoxPHI処理
  3. PrintArgMarshallerBoxprint処理
  4. CallRoutePolicyBoxCall処理

PluginErrorContextは既に統合済みのため不要。


統合1: TypeFactsBox

目的

型情報伝播ロジックを一箇所に集約し、stringish taggingの一貫性を保証。

変更ファイル

1. src/llvm_py/resolver.py

変更箇所: LINE 98-119 (mark_string メソッド)

変更前:

def mark_string(self, value_id: int) -> None:
    try:
        vid = int(value_id)
        self.string_ids.add(vid)
        # TypeFacts SSOT: keep value_types in sync
        try:
            if not hasattr(self, 'value_types') or self.value_types is None:
                self.value_types = {}
            cur = self.value_types.get(vid) if isinstance(self.value_types, dict) else None
            is_already_string = False
            if isinstance(cur, dict):
                if cur.get('kind') == 'string':
                    is_already_string = True
                if cur.get('kind') == 'handle' and cur.get('box_type') == 'StringBox':
                    is_already_string = True
            if not is_already_string and isinstance(self.value_types, dict):
                self.value_types[vid] = {'kind': 'handle', 'box_type': 'StringBox'}
        except Exception:
            pass
    except Exception:
        pass

変更後:

def mark_string(self, value_id: int) -> None:
    # Phase 97: Use TypeFactsBox
    from type_facts import TypeFactsBox
    try:
        vid = int(value_id)
        # Delegate to TypeFactsBox
        if not hasattr(self, '_type_facts'):
            self._type_facts = TypeFactsBox()
        self._type_facts.mark_string(vid, reason="resolver.mark_string")

        # Backward compatibility: keep string_ids in sync
        self.string_ids.add(vid)

        # Keep value_types in sync for downstream code
        try:
            if not hasattr(self, 'value_types') or self.value_types is None:
                self.value_types = {}
            if isinstance(self.value_types, dict):
                self.value_types[vid] = {'kind': 'handle', 'box_type': 'StringBox'}
        except Exception:
            pass
    except Exception:
        pass

変更箇所: LINE 121-125 (is_stringish メソッド)

変更後:

def is_stringish(self, value_id: int) -> bool:
    # Phase 97: Use TypeFactsBox
    try:
        if hasattr(self, '_type_facts'):
            return self._type_facts.is_stringish(int(value_id))
        # Fallback to legacy path
        return int(value_id) in self.string_ids
    except Exception:
        return False

2. src/llvm_py/instructions/copy.py

変更箇所: LINE 52-60 (型伝播処理)

変更前:

    # TypeFacts propagation (SSOT): preserve "stringish" tagging across Copy.
    try:
        if resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(src):
            if hasattr(resolver, "mark_string"):
                resolver.mark_string(dst)
    except Exception:
        pass

変更後:

    # Phase 97: Use TypeFactsBox for propagation
    from type_facts import TypeFactsBox
    try:
        if resolver is not None and hasattr(resolver, '_type_facts'):
            resolver._type_facts.propagate_copy(dst, src)
        # Fallback to legacy path for backward compatibility
        elif resolver is not None and hasattr(resolver, "is_stringish") and resolver.is_stringish(src):
            if hasattr(resolver, "mark_string"):
                resolver.mark_string(dst)
    except Exception:
        pass

3. src/llvm_py/phi_wiring/wiring.py

変更箇所: LINE 270-286 (PHI型伝播)

変更前:

            # TypeFacts propagation (SSOT): if any incoming source is stringish, mark dst stringish.
            try:
                if (
                    hasattr(builder, "resolver")
                    and hasattr(builder.resolver, "is_stringish")
                    and hasattr(builder.resolver, "mark_string")
                ):
                    for (_decl_b, v_src) in (incoming or []):
                        try:
                            if builder.resolver.is_stringish(int(v_src)):
                                builder.resolver.mark_string(int(dst_vid))
                                break
                        except Exception:
                            continue
            except Exception:
                pass

変更後:

            # Phase 97: Use TypeFactsBox for PHI propagation
            from type_facts import TypeFactsBox
            try:
                if hasattr(builder, "resolver") and hasattr(builder.resolver, '_type_facts'):
                    # Extract incoming value IDs
                    incoming_ids = [int(v_src) for (_decl_b, v_src) in (incoming or [])]
                    builder.resolver._type_facts.propagate_phi(int(dst_vid), incoming_ids)
                # Fallback to legacy path
                elif (
                    hasattr(builder, "resolver")
                    and hasattr(builder.resolver, "is_stringish")
                    and hasattr(builder.resolver, "mark_string")
                ):
                    for (_decl_b, v_src) in (incoming or []):
                        try:
                            if builder.resolver.is_stringish(int(v_src)):
                                builder.resolver.mark_string(int(dst_vid))
                                break
                        except Exception:
                            continue
            except Exception:
                pass

テスト

# 型伝播のテスト
NYASH_LLVM_TRACE_CALLS=1 ./target/release/hakorune --backend llvm apps/tests/string_ops_basic.hako

# PHI型伝播のテスト
./target/release/hakorune --backend llvm apps/tests/loop_with_string_concat.hako

統合2: PhiSnapshotPolicyBox

目的

PHI値のSSA有効性契約を明示化し、snapshot miss時の適切な処理を保証。

変更ファイル

src/llvm_py/resolver.py

新規メソッド追加: _value_at_end_i64 の前に以下を追加

def is_phi(self, value_id: int) -> bool:
    """Check if value_id is a PHI value

    Phase 97: Helper for PhiSnapshotPolicyBox
    """
    try:
        # Check if value is in block_phi_incomings
        for block_id, dst_map in (self.block_phi_incomings or {}).items():
            if int(value_id) in dst_map:
                return True
        return False
    except Exception:
        return False

def get_phi_definition(self, value_id: int):
    """Get PHI definition value

    Phase 97: Helper for PhiSnapshotPolicyBox
    """
    try:
        # Try to get from vmap first
        if hasattr(self, 'global_vmap') and self.global_vmap:
            return self.global_vmap.get(int(value_id))
        # Try cache
        for cache in [self.i64_cache, self.ptr_cache, self.f64_cache]:
            for (_, vid), val in cache.items():
                if vid == int(value_id):
                    return val
        return None
    except Exception:
        return None

変更箇所: _value_at_end_i64 メソッド(存在する場合)

変更後:

def _value_at_end_i64(self, value_id, block_id):
    """Resolve value at end of block

    Phase 97: Use PhiSnapshotPolicyBox for PHI handling
    """
    from phi_snapshot_policy import PhiSnapshotPolicyBox

    snapshot = self.block_end_values.get(block_id, {})

    # Phase 97: Check if this is a PHI value
    if PhiSnapshotPolicyBox.is_phi(value_id, self):
        return PhiSnapshotPolicyBox.resolve_phi_at_snapshot(
            value_id, snapshot, self
        )

    # Regular value resolution
    return snapshot.get(value_id)

テスト

# PHI処理のテスト
NYASH_PHI_ORDERING_DEBUG=1 ./target/release/hakorune --backend llvm apps/tests/loop_min_while.hako

# PHI snapshotのテスト
./target/release/hakorune --backend llvm apps/tests/if_phi_sum.hako

統合3: PrintArgMarshallerBox

目的

print引数のmarshal処理を統一し、FFI境界の契約を明示化。

変更ファイル

src/llvm_py/instructions/mir_call/global_call.py

変更箇所: LINE 84-120 (print引数の型変換)

変更前:

        # Type conversion for function signature matching
        if i < len(func.args):
            expected_type = func.args[i].type
            if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
                # Convert i64 to i8* for C ABI-style functions (print/panic/error).
                # ... (長い処理)

変更後:

        # Type conversion for function signature matching
        if i < len(func.args):
            expected_type = func.args[i].type
            if expected_type.is_pointer and isinstance(arg_val.type, ir.IntType):
                # Phase 97: Use PrintArgMarshallerBox for print marshal
                if func_name == "print":
                    from instructions.mir_call.print_marshal import PrintArgMarshallerBox
                    try:
                        is_stringish = False
                        if resolver is not None and hasattr(resolver, "is_stringish"):
                            is_stringish = resolver.is_stringish(int(arg_id))

                        type_info = {"stringish": is_stringish}
                        arg_val = PrintArgMarshallerBox.marshal(
                            arg_id, type_info, builder, resolver, module
                        )
                    except Exception as e:
                        # Fallback to legacy path
                        pass
                else:
                    # Non-print functions: legacy path
                    if arg_val.type.width == 64:
                        # ... (既存の処理)

テスト

# print処理のテスト
./target/release/hakorune --backend llvm apps/tests/peek_expr_block.hako

# 型変換のテスト
./target/release/hakorune --backend llvm apps/tests/print_integer.hako

統合4: CallRoutePolicyBox

目的

Call種別判定を統一し、ルーティングロジックを一箇所に集約。

変更ファイル

src/llvm_py/instructions/mir_call/__init__.py

変更箇所: LINE 115-134 (Method call routing)

変更前:

    elif callee_type == "Method":
        # Box method call
        method = callee.get("name")
        box_name = callee.get("box_name")
        receiver = callee.get("receiver")
        certainty = callee.get("certainty")

        # SSOT: Method calls split into two routes:
        # - Static method (receiver=null, certainty=Known): lower as direct function call
        # - Instance method (receiver omitted in v1 JSON): receiver is implicit as first arg
        if receiver is None:
            if certainty == "Known" and box_name and method:
                func_name = f"{box_name}.{method}/{len(args)}"
                lower_global_call(builder, owner.module, func_name, args, dst_vid, vmap, resolver, owner)
                return
            if args:
                receiver = args[0]
                args = args[1:]  # Remove receiver from args

変更後:

    elif callee_type == "Method":
        # Phase 97: Use CallRoutePolicyBox for routing
        from instructions.mir_call.route_policy import CallRoutePolicyBox, CallKind

        method = callee.get("name")
        box_name = callee.get("box_name")
        receiver = callee.get("receiver")
        certainty = callee.get("certainty")

        # Construct callee string for routing decision
        if receiver is None and certainty == "Known" and box_name and method:
            callee_str = f"{box_name}.{method}"
            ctx = {"builtin_boxes": ["StringBox", "IntegerBox", "BoolBox", "ArrayBox", "MapBox"]}

            try:
                decision = CallRoutePolicyBox.decide(callee_str, ctx)

                if decision.kind == CallKind.STATIC_METHOD and decision.is_direct_call:
                    # Direct static method call
                    func_name = f"{box_name}.{method}/{len(args)}"
                    lower_global_call(builder, owner.module, func_name, args, dst_vid, vmap, resolver, owner)
                    return
            except ValueError:
                # Fallback to instance method
                pass

        # Instance method path
        if receiver is None and args:
            receiver = args[0]
            args = args[1:]  # Remove receiver from args

テスト

# Call routingのテスト
NYASH_LLVM_TRACE_CALLS=1 ./target/release/hakorune --backend llvm apps/tests/string_ops_basic.hako

# Static method callのテスト
./target/release/hakorune --backend llvm apps/tests/static_method_call.hako

回帰テスト

必須テスト

すべての統合後、以下のテストを実施:

# 1. Python module compilation
python3 -m py_compile src/llvm_py/**/*.py

# 2. Rust build
cargo build --release

# 3. Smoke tests
tools/smokes/v2/run.sh --profile integration

# 4. 個別機能テスト
./target/release/hakorune --backend llvm apps/tests/string_ops_basic.hako
./target/release/hakorune --backend llvm apps/tests/loop_min_while.hako
./target/release/hakorune --backend llvm apps/tests/if_phi_sum.hako
./target/release/hakorune --backend llvm apps/tests/peek_expr_block.hako

# 5. Phase 97 specific tests
NYASH_LLVM_TRACE_CALLS=1 ./target/release/hakorune --backend llvm apps/tests/call_routing_test.hako
NYASH_PHI_ORDERING_DEBUG=1 ./target/release/hakorune --backend llvm apps/tests/phi_snapshot_test.hako

トラブルシューティング

問題: import error

症状: ImportError: No module named 'type_facts'

解決:

# 相対importに変更
from ..type_facts import TypeFactsBox

問題: PHI値が未定義

症状: AssertionError: Cannot resolve PHI value

解決:

  • is_phi()get_phi_definition() の実装を確認
  • PHI値が正しく block_phi_incomings に登録されているか確認

問題: 型伝播が動作しない

症状: stringish tagが伝播しない

解決:

  • _type_facts が正しく初期化されているか確認
  • propagate_copy() / propagate_phi() が呼ばれているか確認
  • デバッグログで追跡: reason フィールドを確認

まとめ

Phase 97の統合は、以下の手順で段階的に実施します

  1. TypeFactsBox統合基盤
  2. PhiSnapshotPolicyBox統合PHI処理
  3. PrintArgMarshallerBox統合print処理
  4. CallRoutePolicyBox統合Call処理

各統合後は必ず回帰テストを実施し、挙動不変を確認してから次の統合に進んでください。