Compiling Swift Generics を読む
Slavaの書が更新されたので読んでいく
感想やメモを Zenn のスクラップで付けてみる
書がアナウンスされている投稿
PDFの直リンク
1-1 Functions
generic signature, interface type, substitution mapなどが説明された。
archetypeも少し出てきた。
1-2 Nominal Types
ジェネリック型が出てきた
レイアウトやtype metadata, metadata access functionなどが説明された。
1-3 Protocols
これがあることでジェネリックな型に対してメソッドを呼ぶなどができるようになる
conformanceのためにwitness tableが渡される。
ここまでの内容でジェネリクスの実行時の仕組みの概要はかなり説明されている。
1-4 Associated Types
dependent member typeが出てきた。
また、I.[IteratorProtocol]Element
形式の記述が出てきた。
bound/unboundを明示的に表現する事が後の議論で生きてくる。
archetypeの説明が追加された。
archetypeは仮想的な型で、generic signatureと紐づいている。
type parameterはただの名前で、generic signatureが外から紐づくのとは違う。
ジェネリック関数の中でジェネリック型を使うようなことをすると、type parameterをarchetypeで置換するようなことが起きる。
1-5 Associated Requirements
associated typeにもrequirementが課せるという話。
これはランタイムではwitness tableの中でwitness table access functionをもたらす。
1-6 Related Work
Haskellにはassociated typeはあるけどassociated requirementはないという話が書いてあった。GPTによるとそうらしくて、似たことをやるには構造的に別の方法で記述するらしいが、知識不足で理解できなかった。
C++ Conceptsもルーツの時点ではassociated requirementについてはフワッとしていたらしい。でもGPTによると今はほぼ同じ形で書けるっぽいな。
RustはSwiftにない表現ができるが、same type requirementは制限があるらしい。whereもやや弱くて、再記述が生じるとか。
2 Compilation Model
swiftcのドライバ・フロントエンドや、バッチモード、ビルドパイプラインなどのツーリングのアウトラインが説明された。
このあたりはよく知ってるけど、 -###
でジョブダンプされるのは知らなかった。
最初からあったっぽい。
2-1 Name Lookup
Swiftコンパイラにはname bindingフェーズがなくて、オンデマンドでname lookupが行われる。特に意識していなくて普通だと思っていたけど確かにそうだ。また、Swiftのスコープの親子関係はソース範囲の包含関係を保っているらしい。本当?
Unqualified lookupはスコープベースの上方向への探索。ソースレベルまで到達したら、次はモジュールレベルを調べて、最後にインポートしているモジュールを調べる。Qualified lookupはメンバの探索。単体のエンティティについては Direct lookupで、その先で継承しているプロトコルと親クラスも調べる。プロトコルと親クラスではsubstitutionが起きる。親クラスにも Self シンボルがあるからか。モジュール名にメンバアクセスする場合は Module lookup という。objCのDynamic lookupと演算子のOperator lookupについても説明された。
2-2 Delayed Parsing
コンパイルにおいて、プライマリファイルは完全にパースされるが、セカンダリファイルは高速モードでトップレベルのシンボルだけ収集される。
プライマリファイルのタイプチェックの過程で必要に応じてセカンダリファイルの一部をちゃんとパースする。
Dynamic Lookup や Operator Lookup については、それが生じると、影響しうる領域を全てパースする。
つまり、AnyObjectに対するメソッド呼び出しは、全てのclass定義のパースを生じさせてしまう。
2-3 Request Evaluator
コンパイラのさまざまな処理はリクエスト化されていて、共通のevaluatorを通して処理される。これはパラメータによるメモ化と、サイクルの検出を行う。例えばクラスの親が相互に継承されている場合など、この枠組みで対処できる。トップレベルコードがそれより下に書かれた関数を呼び出す場合、その関数のinterface type requestは呼び出し時に解決され、その後で宣言を処理するときにはキャッシュが使われる。