Swift中間言語の、ひとまず入り口手前まで

  • 30
    いいね
  • 0
    コメント

今年の WWDC を訪れたときに、Swift Lab で Sean Callanan さんが Swift の小さな挙動を探るのに SIL を活用している姿を見せてくれて、それ以来、何かのときに自分も真似して SIL を眺めてみる機会が増えてきました。

SIL というのは Swift Intermediate Language の略、すなわち Swift中間言語 です。

それからというもの Swift の不可思議動作に出逢うたびに SIL を眺めてみてたんですけど、なんとなく読めるだけでも想像以上に助けになってくれました。それでもやっぱり読めないところも多かったりして、もしかしてもっと読めるようになれば SIL がいろんなことを教えてくれるかもしれない。

そう思ったのが、今回の記事を書いてみようと思ったきっかけでした。

🎄 目標のところまで調べきれず

そこで、右も左も分からない Swift中間言語の世界において、せめて右か左かくらいはわかるくらいの知識を身につけることを目標に SIL について調べながら綴ってみることにしたのですけど想像以上に世界が深くて、自分の基礎知識不足も手伝って、入り口になんとかたどり着くのやっとな感じ。できれば Swift中間言語の構文や命令付近まで辿り着きたかったのですけど、今回はその手前で時間ぎれでした。

なんとも半端な内容になってしまいましたけど、自分と同じように SIL に興味が沸きつつ、そもそも SIL ってなんだろうみたいなところから始めようという人の、とっかかりくらいにはなってくれたらいいなと願いながら。

🎄 SIL とは

そもそも SIL とは Swift Intermediate Language の略で、Swift のソースコードを実行可能なバイナリーコードに落とし込むときに、いったん別の、コンパイラーが扱いやすい中間表現1 に表現し直したものになります。

Swift のコンパイラーは LLVM というコンパイラー基盤を通してバイナリーコードを生成するらしいので、バイナリーコードを生成する過程で LLVM 用の中間表現である LLVM IR を生成することになりますが、SIL はこの中間、Swift コードと LLVM IR との中間に位置するものになるようです。

狙い

SIL の狙いとしては、プログラマーが入力した Swift ソースコードと、そこからバイナリコードを作るために生成する LLVM IR との 表現の差異 を埋めるためにあるようです。また、LLVM IR が苦手とする Swift ソースコードレベルでの静的解析も視野に入れた作りになっているようです。

静的単一代入方式

SIL は 静的単一代入方式2 の中間言語になるそうです。

これは、変数を 1 度きりの書き込みを許す方式だそうで いったん設定した変数の値が書き換えられることはない という意味らしいです。これを聞いて Swift の let を想像して、それならもしかして var を使ったときに let での表現に書き換えるのかな? みたいに思ったのですけど、それとは特に関係なくて、あくまでも SIL 言語はそういう仕様になっている ということのようでした。

🎄 SIL の立ち位置を知る

SIL が Swift ソースコードをバイナリーコードにビルドする経過の中のどの辺りに位置するかを知っておくと想像しやすそうだったので、とりあえず Swift コンパイラーの処理の流れについて調べてみることにしました。

Swift のソースコードから実行可能なバイナリーコードを生成するとき、Swift コンパイラーは次の過程を踏んでいくそうです。

構文解析意味解析モジュールのインポートSIL の生成SIL の正規化SIL の最適化LLVM IR の生成 → ...

1. 構文解析

まず、Swift ソースコードをパーサーで処理して、型情報なしの 抽象構文木3 を生成します。この段階で、ソースコードから文法上の不整合が排除されます。

ちなみにパーサーは 再帰下降構文解析4 という手法で作られていて、その実装は lib/Parse にあるそうです。

2. 意味解析

構文解析で生成した抽象構文木は、意味解析器を通して、型推論 なども実施して、型情報も含めた完全な形の抽象構文木に変換されます。この段階で、ソースコードから意味上の不整合が排除されます。

ちなみにこれらの仕様については docs/TypeChecker.rst で言及されているようで、実装は lib/Sema にあるそうです。

3. モジュールのインポート

抽象構文木が完成したら、次に Clang Importer というものによって Clang モジュール が取り込まれ、ここで Objective-C や C API が Swift の文化に調和した形で使えるようになるらしいです。

ただ、これより前の意味解析の段階で、外部モジュールで規定されているメソッドなども踏まえて型チェックが行われる様子なので、必ずしもここで初めてモジュールが関与しだす訳ではなさそうに見えました。この辺りは、まだぜんぜん理解できていません。

ちなみにこの実装は lib/ClangImporter にあるそうです。

4. SIL の生成

ここまできて、今回のテーマである SIL が生成される最初の段階になるようです。SIL ジェネレーターによって、抽象構文木から Raw SIL が生成されます。SIL では、Swift の var 変数は、厳格な静的単一方式ではなく、読み書き可能なメモリ領域として表現されるらしいです。

ちなみに実装は lib/SILGen にあるそうです。

5. SIL の正規化

SIL は作って終わりではなく、続いて正当性の検証が行われて、最終的な Canonical SIL が生成されるそうです。これによって、たとえば "変数に値を入れないまま使おうとした" みたいな精細な間違いを訂正して、Swift コードとしての厳格さが保証されることになるそうです。

ちなみに実装は lib/SILOptimizer/Mandatory で行われているそうです。

6. SIL の最適化

そして、正規化された Canonical SIL を使って、SIL コードの最適化が図られるそうです。具体的には、たとえば ARC や仮想メソッドの呼び出し、ジェネリックまわりの最適化が図られるらしいです。

ちなみに、このあたりの実装は lib/SILOptimizer/Analysislib/SILOptimizer/ARClib/SILOptimizer/LoopTransformslib/SILOptimizer/Transforms で行われるそうです。

7. LLVM IR の生成

こうして調整尽くされた Swift コードが、IR ジェネレーターを通して LLVM IR に変換されて、LLVM の世界への橋渡しが完了するそうです。

ちなみに実装は lib/IRGen で行われているそうです。

🎄 コンパイルの経過を調べる

なお、これらの処理経過は幾らかの段階ごとに見られるようになっていて、Swift コンパイラー swiftc に次のオプションをつけることで、指定した段階まで処理した結果を、テキストで確認できます。

段階 オプション
1. 構文解析 -dump-parse
2. 意味解析 -dump-ast
4. SIL の生成 -emit-silgen
5. SIL の正規化 -emit-sil
7. LLVM IR の生成 -emit-ir

🎄 SIL を出力してみる

それでは試しに、次の Swift コードの処理の経過を出力してみます。

import Foundation

let value: UInt32 = arc4random_uniform(100)
let title = "Random" as NSString

print("\(title) : \(value)")

たとえば構文解析した結果を見たいときには、このコードをたとえば test.swift という名前で保存してから、次のように swiftc コマンドを実行することで確認できます。

swiftc -dump-parse test.swift

型情報なしの抽象構文木

まずは -dump-parse オプションをつけて、型情報がない抽象構文木を出力すると、次のようになりました。

知らないキーワードがたくさん出てきて、読むのもなかなか躊躇いますけど、それでもめげずに眺めていると、なんとなく見覚えのある言葉がいろんなところに出てくることがわかります。ざっくり眺めてみると、構造ごとに丸括弧で括られて、子要素は括弧内にインデントされて含められている様子です。型情報と思われる type のところが <null type> になっている辺りが見どころな気がします。

Swift ソースコードを、構文の仕様に従って、意味のあるところだけを切り出し、それらが構文的にどの役割に当たるのかをたとえば var_decl みたいな識別子でタグ付けをして、その他の必要そうな情報と一緒に、丸括弧で束ねている、みたいに捉えてみれば、それなりに読めるようになってくると思います。

(source_file
  (import_decl 'Foundation')
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_typed
          (pattern_named 'value')
          (type_ident
            (component id='UInt32' bind=none)))
        (call_expr type='<null>'  arg_labels=_:
          (unresolved_decl_ref_expr type='<null>' name=arc4random_uniform specialized=no) function_ref=unapplied
          (paren_expr type='<null>'
            (integer_literal_expr type='<null>' value=100))))
))
  (var_decl "value" type='<null type>' let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named 'title')
        (sequence_expr type='<null>'
          (string_literal_expr type='<null>' encoding=utf8 value="Random" builtin_initializer=**NULL** initializer=**NULL**)
          (coerce_expr type='<null>' writtenType='<null>'
            (**NULL EXPRESSION**))
          (coerce_expr type='<null>' writtenType='<null>'
            (**NULL EXPRESSION**))))
))
  (var_decl "title" type='<null type>' let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (call_expr type='<null>'  arg_labels=_:
        (unresolved_decl_ref_expr type='<null>' name=print specialized=no) function_ref=unapplied
        (paren_expr type='<null>'
          (interpolated_string_literal_expr type='<null>'
            (string_literal_expr type='<null>' encoding=utf8 value="" builtin_initializer=**NULL** initializer=**NULL**)
            (paren_expr type='<null>'
              (unresolved_decl_ref_expr type='<null>' name=title specialized=no) function_ref=unapplied)
            (string_literal_expr type='<null>' encoding=utf8 value=" : " builtin_initializer=**NULL** initializer=**NULL**)
            (paren_expr type='<null>'
              (unresolved_decl_ref_expr type='<null>' name=value specialized=no) function_ref=unapplied)
            (string_literal_expr type='<null>' encoding=utf8 value="" builtin_initializer=**NULL** initializer=**NULL**)))))))

パーサーの着眼点は "構文仕様的に正しいか" だけ

ちなみにこの段階では、構文上の間違いだけが検出される様子です。

構文上の間違いの中には、たとえば privatepublic の両方を指定したみたいなものも含まれますが、これはきっと意味を解釈したというよりは、何の後に何がくるかというルールの規定と違ったことが検出された感じになりそうです。

関数はまだ "わからない"

このとき、関数の呼び出しのところは (unresolved_decl_ref_expr type='<null>' name=arc4random_uniform specialized=no) という表記になっています。

細かい意味まではわかりませんけど、この段階ではまだ 未解決 (unresolved) な状態、関数名があっているか、そもそも存在しているかも含めて "わからない" ということがわかります。実際のところ、まったくテキトウに関数名を指定しても、この段階ではエラーが検出されることはありません。

型情報は、あくまでも "識別子" くらいの扱い

この段階の出力に型情報が登場したとしても、あくまでも記号的なものとして扱われている様子です。

たとえば、変数宣言で型を指定した場合は、その型情報が (type_ident(component id='UInt32' bind=none)) として現れてきますが、これはあくまでも "型識別子が登場するべきところに UInt32 と書いてあった" くらいの重みのように見えます。さらには、型の推論を助ける型明記だと、今回の例では as NSString ですけれど、この段階では不要なのでしょう、そもそも情報として埋め込まれない様子でした。

型情報ありの抽象構文木

続いて -dump-ast オプションを指定して、型情報がついて完成された抽象構文木まで進めてみます。

この段階で急にコードが膨大になって、思わず見るのをやめてしまいたくなりますけど、構造的には先ほどの -dump-parse のときと大きくは変わってないので、そう思って気を落ち着かせればきっと目を背けないで済むはずです。

なぜここまで膨らむかといえば、ひとつ前の段階では <null type> になっていた type のところに具体的な型情報が埋め込まれたのと、ソースコードの該当箇所に関する情報が追加されたのが大きな要因です。これによって、ぱっと見はわかりにくくなりましたけど、むしろどこがどう解釈されたかが、かなりわかりやすくなっています。

(source_file
  (import_decl 'Darwin.C')
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='UInt32' 'value')
        (call_expr type='UInt32' location=/tmp/test.swift:8:13 range=[/tmp/test.swift:8:13 - line:8:35] nothrow  arg_labels=_:
          (declref_expr type='(UInt32) -> UInt32' location=/tmp/test.swift:8:13 range=[/tmp/test.swift:8:13 - line:8:13] decl=Darwin.(file).arc4random_uniform function_ref=single specialized=no)
          (paren_expr type='(UInt32)' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:31 - line:8:35]
            (call_expr implicit type='UInt32' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] nothrow  arg_labels=_builtinIntegerLiteral:
              (constructor_ref_call_expr implicit type='(Int2048) -> UInt32' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] nothrow
                (declref_expr implicit type='(UInt32.Type) -> (Int2048) -> UInt32' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] decl=Swift.(file).UInt32.init(_builtinIntegerLiteral:) function_ref=single specialized=no)
                (type_expr implicit type='UInt32.Type' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] typerepr='UInt32'))
              (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] names=_builtinIntegerLiteral
                (integer_literal_expr type='Int2048' location=/tmp/test.swift:8:32 range=[/tmp/test.swift:8:32 - line:8:32] value=100))))))
))
  (var_decl "value" type='UInt32' access=internal let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (pattern_binding_decl
        (pattern_named type='String' 'title')
        (string_literal_expr type='String' location=/tmp/test.swift:9:13 range=[/tmp/test.swift:9:13 - line:9:13] encoding=utf8 value="Random" builtin_initializer=Swift.(file).String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**))
))
  (var_decl "title" type='String' access=internal let storage_kind=stored)
  (top_level_code_decl
    (brace_stmt
      (call_expr type='()' location=/tmp/test.swift:11:1 range=[/tmp/test.swift:11:1 - line:11:28] nothrow  arg_labels=_:
        (declref_expr type='(Any..., String, String) -> ()' location=/tmp/test.swift:11:1 range=[/tmp/test.swift:11:1 - line:11:1] decl=Swift.(file).print(_:separator:terminator:) function_ref=single specialized=no)
        (tuple_shuffle_expr implicit type='(Any..., separator: String, terminator: String)' location=/tmp/test.swift:11:7 range=[/tmp/test.swift:11:6 - line:11:28] sourceIsScalar elements=[-2, -1, -1] variadic_sources=[0]
          (paren_expr type='Any' location=/tmp/test.swift:11:7 range=[/tmp/test.swift:11:6 - line:11:28]
            (erasure_expr implicit type='Any' location=/tmp/test.swift:11:7 range=[/tmp/test.swift:11:7 - line:11:7]
              (interpolated_string_literal_expr type='String' location=/tmp/test.swift:11:7 range=[/tmp/test.swift:11:7 - line:11:7]
                (string_literal_expr type='String' location=/tmp/test.swift:11:7 range=[/tmp/test.swift:11:7 - line:11:7] encoding=utf8 value="" builtin_initializer=Swift.(file).String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
                (paren_expr type='(String)' location=/tmp/test.swift:11:10 range=[/tmp/test.swift:11:9 - line:11:15]
                  (declref_expr type='String' location=/tmp/test.swift:11:10 range=[/tmp/test.swift:11:10 - line:11:10] decl=test.(file).title@/tmp/test.swift:9:5 direct_to_storage function_ref=unapplied specialized=no))
                (string_literal_expr type='String' location=/tmp/test.swift:11:16 range=[/tmp/test.swift:11:16 - line:11:16] encoding=utf8 value=" : " builtin_initializer=Swift.(file).String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)
                (paren_expr type='(UInt32)' location=/tmp/test.swift:11:21 range=[/tmp/test.swift:11:20 - line:11:26]
                  (declref_expr type='UInt32' location=/tmp/test.swift:11:21 range=[/tmp/test.swift:11:21 - line:11:21] decl=test.(file).value@/tmp/test.swift:8:5 direct_to_storage function_ref=unapplied specialized=no))
                (string_literal_expr type='String' location=/tmp/test.swift:11:27 range=[/tmp/test.swift:11:27 - line:11:27] encoding=utf8 value="" builtin_initializer=Swift.(file).String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**)))))))))

型情報は、完成

この段階で、書かれていない型は推論されて、前の段階までは <null_type> 指定になっていた type の全ての箇所で型が明確に決めうちされます。関数やメソッドなどについても、それがどんな型の引数を取ってどんな型の値を返すかを考慮して型情報が確定されます。併せて、その関数がどの名前空間に所属しているかについても decl から確認できるようになっていました。

関数や型の情報が精査されるため、関数が定義されているかとか、代入や引数に渡すなどのときにちゃんと適切な型を使っているかといったチェックがされます。ここで不整合が起こったときには、エラーとしてそれが通知されます。

ソースコードでの登場位置がわかる

さらにこの段階では、抽象構文木のどの部分が、どのソースファイルのどこに該当するかや、関数などがどの名前空間で定義されているかといった情報が locationrange で添えられるようです。

その情報が長すぎて、コードの量が増しすぎたように見えますけど、むしろファイル名と行番号で目的のものを探すことができるので、探しやすくなっていそうです。もちろんコード量が多い分、全体構造を捉えるという観点での可読性は下がりそうなので、そちらに注目したいときには、ひとつ前の段階で眺めると良いかもしれません。

変数の再代入を検出

この段階で、変数 let に値を 2 回代入したみたいな問題も検出する様子でした。

型のチェックがそこまでの判断に関与するのも不思議なように思いますけど、"値を入れる場面" みたいな認識の仕方はしていそうな感じなので、もしかするとその一環で、完全な抽象構文木を作れなかったという結論に至るのかもしれません。

Raw SIL

次に SIL への変換を見てみます。ここからガラッと印象が変わるのは、いよいよ Swift中間言語の世界に入ったためで、これまでの Swift ソースコードとは違う世界観でコードが描かれています。この辺りから、なんとなくでは読めなくなってきます。

コードの量も 2435 行と大きく増えて、ここに全てを抜粋するのは難しいので、間をごっそり省いて記載しておきます。

sil_stage raw

import Builtin
import Swift
import SwiftShims

// value
sil_global hidden [let] @_Tv4test5valueVs6UInt32 : $UInt32

// title
sil_global hidden [let] @_Tv4test5titleCSo8NSString : $NSString

sil_scope 1 {  parent @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 }

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
// %0                                             // user: %3
// %1                                             // user: %3
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  // function_ref _stdlib_didEnterMain(argc : Int32, argv : UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>) -> ()
  %2 = function_ref @_TFs20_stdlib_didEnterMainFT4argcVs5Int324argvGSpGSqGSpVs4Int8____T_ : $@convention(thin) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> (), scope 1 // user: %3
  %3 = apply %2(%0, %1) : $@convention(thin) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> (), scope 1
  alloc_global @_Tv4test5valueVs6UInt32, loc "/tmp/test.swift":8:5, scope 1 // id: %4

 :

  dealloc_stack %57 : $*NSString, loc "/tmp/test.swift":11:28, scope 1 // id: %99
  %100 = integer_literal $Builtin.Int32, 0, scope 1 // user: %101
  %101 = struct $Int32 (%100 : $Builtin.Int32), scope 1 // user: %102
  return %101 : $Int32, scope 1                   // id: %102
}

:

// arc4random_uniform
sil [clang arc4random_uniform] @arc4random_uniform : $@convention(c) (UInt32) -> UInt32


// UInt32.init(_builtinIntegerLiteral : Builtin.Int2048) -> UInt32
sil [transparent] [fragile] @_TFVs6UInt32CfT22_builtinIntegerLiteralBi2048__S_ : $@convention(method) (Builtin.Int2048, @thin UInt32.Type) -> UInt32

:

とりあえず、そもそもの構文が変わった印象ですけど、Swift 言語と Swift中間言語は別の言語なので、新しい言語をもうひとつ覚えるくらいの気持ちで眺めた方が、もしかすると向き合いやすいかもしれません。もっとも、両方とも Swift の世界観を描いていることには変わりないので、Swift 自体の知識が深まるにつれて、なんとなくでもだんだんと、言いたそうにしていることはわかってくるような気がします。

ただ、なんとなく止まりで理解にはとても至らないので、これを少しでも読めるようになろうというのが、今回の自分の目標です。

読み取れそうな情報

ここには、単純な Swift の型情報だけではなく、それをどんな方式でメモリーに配置されているかや、メモリー管理の操作とか、そういった精密なところもコードに現れてきていそうな印象です。

また、この Swift中間言語の段階でも、元のソースコードのどの場所に書かれている部分であるかを loc で埋め込まれているので、元のコードのどの辺りに対応しているかが判りそうな気がします。ただ、かなりコードが精密に記載されているのか、Swift の厳密な動きを知っていないと、たとえソースコードの場所が記載されているとはいっても、"なぜそこにその中間コードがあるのか" みたいな感じになりそうです。

逆に言えば、精細な挙動が読み解けないときに SIL に頼ってみると、見えなかった小さな動きが感じられそうです。

この段階に入って登場し得るエラーはなんだろう

ところで、思いつかなかったのですけど、型チェック済みの抽象構文木から Raw SIL を作る段階で新たに発生するエラーって何があるのでしょう。たしか何かあったような気がするのですけど、忘れてしまいました。

具体的に何でエラーになるかがわかれば、何をしているかを具体的に想像しやすくなるはずなので、もし何かご存知の人がいらしたら教えてくださいね。

Canonical SIL

もうひとつ、正規化済みの SIL があります。いくら 2 種類あるといっても "どちらも SIL 言語だし、正規化そんなに変わりないだろう" と思っていたのですけど、いざ眺めてみると、コードの量が 4188 行まで膨れ上がる様子でした。

何やらスコープらしきものが形成されていたり、仮想テーブルらしきものが定義されていたりと、想像以上に大きくなっていました。何やら、インポートした機能に関する情報も含まれていそうに見えて、思ったよりもいろんなことをしているみたいです。

ちなみにこのコードが Raw SIL なのか Canonical SIL なのかは、いちばん最初の行にある sil_stage を確認するとわかります。

sil_stage canonical

import Builtin
import Swift
import SwiftShims

// value
sil_global hidden [let] @_Tv4test5valueVs6UInt32 : $UInt32

// title
sil_global hidden [let] @_Tv4test5titleCSo8NSString : $NSString

// static CommandLine._argc
sil_global [fragile] @_TZvOs11CommandLine5_argcVs5Int32 : $Int32

:

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
// %0                                             // user: %3
// %1                                             // user: %9
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = global_addr @_TZvOs11CommandLine5_argcVs5Int32 : $*Int32, scope 1 // user: %3
  store %0 to %2 : $*Int32, scope 1               // id: %3

  :

  dealloc_stack %64 : $*NSString, loc "/tmp/test.swift":11:28, scope 1 // id: %106
  %107 = integer_literal $Builtin.Int32, 0, scope 1 // user: %108
  %108 = struct $Int32 (%107 : $Builtin.Int32), scope 1 // user: %109
  return %108 : $Int32, scope 1                   // id: %109
}

:

// _ContiguousArrayBuffer.init(_ContiguousArrayStorageBase) -> _ContiguousArrayBuffer<A>
sil hidden_external [fragile] @_TFVs22_ContiguousArrayBufferCfCs27_ContiguousArrayStorageBaseGS_x_ : $@convention(method) <Element> (@owned _ContiguousArrayStorageBase, @thin _ContiguousArrayBuffer<Element>.Type) -> @owned _ContiguousArrayBuffer<Element>

sil_scope 31 {  parent @_TTSgq5Vs10_ArrayBody___TFSp10initializefT2tox5countSi_T_ : $@convention(method) (_ArrayBody, Int, UnsafeMutablePointer<_ArrayBody>) -> () }

// specialized UnsafeMutablePointer.initialize(to : A, count : Int) -> ()
sil shared_external [fragile] @_TTSgq5Vs10_ArrayBody___TFSp10initializefT2tox5countSi_T_ : $@convention(method) (_ArrayBody, Int, UnsafeMutablePointer<_ArrayBody>) -> () {
// %0                                             // users: %118, %3
// %1                                             // users: %44, %9, %4
// %2                                             // users: %113, %5
bb0(%0 : $_ArrayBody, %1 : $Int, %2 : $UnsafeMutablePointer<_ArrayBody>):
  debug_value %0 : $_ArrayBody, scope 31          // id: %3
  debug_value %1 : $Int, scope 31                 // id: %4
  debug_value %2 : $UnsafeMutablePointer<_ArrayBody>, scope 31 // id: %5
  %6 = integer_literal $Builtin.Int8, 2, scope 31 // users: %86, %72, %58, %37, %30, %21, %17
  br bb1, scope 31                                // id: %7

:

sil_vtable _SwiftNativeNSArray {
  #_SwiftNativeNSArray.deinit!deallocator: _TFCs19_SwiftNativeNSArrayD  // _SwiftNativeNSArray.__deallocating_deinit
}

sil_witness_table _SwiftNativeNSArray: AnyObject module Swift

sil_witness_table <Value, Element> _HeapBufferStorage<Value, Element>: AnyObject module Swift

何をしているかまでは理解が及ばず

この段階で行われることについては Guaranteed Optimization and Diagnostic Passes に記されているのですけど、知識が足りず、何をしているかまでは理解できませんでした。

とりあえず、関数のインライン化みたいなものもこの段階で行われたりするようなので、元の Raw SIL にそれなりに重い加工が施されそうな印象でした。

未初期化の解消

他にも、この段階になって、変数 letvar を初期化せずに使った場合をエラーとして検出するようになるようでした。

リテラル値のオーバーフロー

たとえば 128 を超える整数リテラルを Int8 にキャストしたときにオーバーフローを検出するのもこの段階に入ってからでした。

🎄 ここで、時間切れ

これでひとまず漠然とながら SIL の入り口付近まで眺められたので、できればそのまま SIL の言語構文や命令セットなどを眺めて見たかったのですけど、残念ながら時間が足りなくなってしまいました。

命令セットもかなりの数があるみたいで、これはひとまず、必要になったところから触れていく感じにしないとなかなか先に進めないかもしれませんね。そんな感じで地道に進めてみることにします。



  1. 中間表現: IR (Intermediate Representation) 

  2. 静的単一代入方式: SSA (Static Single Assignment Form) 

  3. 抽象構文木: AST (Abstract Syntax Tree) 

  4. Recursive Descent Parsing