PC パソコン

Xcode「PHINode should have one entry for each predecessor of its parent basic block!」の原因と回避方法【Swift 6.4】

Xcode 27.0/Swift 6.4で、nonisolated(nonsending)な非同期関数の中から#isolationをnilと直接比較すると、SwiftコンパイラがPHINode should have one entry for each predecessor of its parent basic block!を出して停止することがあります。これはアプリの実行時エラーではなく、Debugビルドで不正なLLVM IRが生成され、検証段階でコンパイルが中断されるSwiftコンパイラの不具合です。本記事では再現条件、原因、推奨回避策、確認方法を解説します。

表示されるエラー

PHINode should have one entry for each predecessor of its parent basic block!
  %13 = phi ptr [ %10, %9 ], [ %11, %9 ]
<unknown>:0: error: fatal error encountered during compilation;
please submit a bug report
<unknown>:0: note: Broken module found, compilation aborted!

スタックダンプには、SwiftフロントエンドがLLVMの検証パスで異常終了したことも記録されます。アサーションを有効にした開発版コンパイラでは、より早いSILGen段階で次の診断になることがあります。

TYPE MISMATCH IN ARGUMENT 0 OF APPLY
argument type: $Optional<any Actor>
parameter type: $Optional<@thick any (~Copyable & ~Escapable).Type>

最小の再現コード

nonisolated(nonsending) func hasCallerIsolation() async -> Bool {
    #isolation != nil
}

Swift 6モードで通常のDebug相当(-Onone)としてコンパイルします。

xcrun swiftc -swift-version 6 -Onone -c test.swift

関数を呼び出すコードは必要ありません。==、左右を入れ替えたnil != #isolation、括弧付き、if条件、メソッド内でも同じ問題が報告されています。

原因

#isolationは、現在の呼び出し元の分離領域を(any Actor)?として表すマクロです。しかし、nonisolated(nonsending)関数内で#isolation != nilを直接書くと、Swift 6.4の型推論が比較演算子の制約されていないジェネリック引数から誤った型を選ぶ場合があります。

その結果、生成されたSILではOptionalの.some側がany Actorではなくメタタイプとして扱われます。Release版コンパイラはその不整合を通過させ、不正なLLVMモジュールを生成するため、最終的にPHIノードの検証で停止します。

ポイント: 問題はnil比較そのものではなく、#isolationを比較演算子へ直接渡した際の型推論です。一度ローカル変数へ束縛するか、型を明示すれば正しいOptional<any Actor>として処理されます。

発生条件と対象バージョン

  • Xcode 27.0(27A266a)/Apple Swift 6.4、macOS 27.0.1、Apple M4で再現
  • Swift 6.2.4、6.3.3、6.4のReleaseコンパイラでも-Ononeで同じLLVM検証エラーを確認
  • -swift-version 5でも再現
  • iOS 26 Simulatorターゲットでも再現
  • NonisolatedNonsendingByDefaultを有効にした通常のnonisolated関数でも発生

Release版コンパイラでは-Oまたは-Osizeで通る場合がありますが、アサーション付きの6.4ブランチやmain nightlyでは最適化時にも型不一致が検出されています。したがって、最適化レベルの変更だけを恒久対策にはできません。

2026年10月4日時点でSwift Issue #92798はOpenです。公式に修正済みと確認できるXcode/Swiftバージョンはありません。

推奨する解決手順

1. #isolationをローカル変数へ束縛する

Issueで期待どおりに動作すると確認されている、最も読みやすい回避策です。

nonisolated(nonsending) func hasCallerIsolation() async -> Bool {
    let isolation = #isolation
    return isolation != nil
}

ローカル変数の型が(any Actor)?として確定するため、誤った比較演算子の型推論を避けられます。

2. 比較前に型を明示する

nonisolated(nonsending) func hasCallerIsolation() async -> Bool {
    (#isolation as (any Actor)?) != nil
}

1行で書く必要がある場合は、#isolationの本来の型を明示する方法が使えます。

3. Optional bindingまたはswitchを使う

nonisolated(nonsending) func hasCallerIsolation() async -> Bool {
    if let _ = (#isolation) {
        return true
    }
    return false
}

switch (#isolation)で.someと.noneを分岐する形も、Issueで正常動作が確認されています。

代替策

同一性比較演算子を使った#isolation === nilも報告上はコンパイルできます。ただし、存在確認を表現するコードとしてはローカル変数への束縛やOptional bindingの方が意図を読み取りやすいため、チームコードではそちらを優先するとよいでしょう。

nonisolated(nonsending) func hasNoCallerIsolation() async -> Bool {
    #isolation === nil
}

一時的にRelease最適化へ切り替えると公式Releaseコンパイラで通る場合がありますが、デバッグ性が変わり、開発版では最適化時にも失敗します。ソースコード側で型推論を安定させる回避策を推奨します。

NonisolatedNonsendingByDefault使用時の修正

次のようにupcoming featureで暗黙にnonisolated(nonsending)となるコードも対象です。

nonisolated func hasCallerIsolation() async -> Bool {
    let isolation = #isolation
    return isolation != nil
}
xcrun swiftc -swift-version 6 \
  -enable-upcoming-feature NonisolatedNonsendingByDefault \
  -Onone -c test.swift

確認方法

  1. xcrun swiftc -versionでXcodeが選択しているSwiftバージョンを記録します。
  2. 元の直接比較を含む最小コードでエラーを再現します。
  3. ローカル変数への束縛または明示的キャストへ変更します。
  4. -Ononeと-Oの両方でコンパイルします。
  5. 通常のXcode Debug/ReleaseビルドとCIで、PHINodeエラーやBroken module foundが消えたことを確認します。
xcrun swiftc -version
xcrun swiftc -swift-version 6 -Onone -c test.swift
xcrun swiftc -swift-version 6 -O -c test.swift

注意点

  • DerivedData削除やClean Build Folderでは、誤った型推論そのものは修正されません。
  • #isolationを使用するすべてのコードが壊れるわけではありません。直接の== nil/!= nil比較が中心です。
  • 同じPHINodeエラーでも別の不正IRが原因の場合があります。スタックだけでなく、#isolationとnonisolated(nonsending)の組み合わせを確認してください。
  • 将来のXcodeで修正を確認するまでは、回避コードを残し、Issueの状態と修正版を照合してください。

まとめ

PHINode should have one entry for each predecessor of its parent basic block!が#isolation != nilで発生する場合、Swiftが#isolationを誤ったメタタイプとして推論し、不正なLLVM IRを生成している可能性があります。#isolationをローカル変数へ束縛する、(any Actor)?へ明示的にキャストする、またはOptional bindingを使うことで回避できます。Issueは未解決のため、修正済みXcodeの断定は避けてください。

参照した公式情報