kateinoigakukunのブログ

思考垂れ流しObserver

IndexStoreを使ってSwiftコードを静的解析する

Swiftで静的解析ツールを作るとなると、基本的にSwiftSyntaxを使うことが多いと思います。 ただ、SwiftSyntaxで得られる情報はコンパイラ内部のパイプラインの序盤で生成されるため、「ある変数がどこから参照されているか」といった意味解析後に解る情報は使えません。

そこで、Xcodeがコードジャンプやリネームなどに使っている(だろう)Index-While-Buildingという仕組みを紹介します。

Index-While-Buildingとはその名の通りコンパイラコンパイル中に生成する中間情報をIDEのコードインデックスに利用する、という仕組みです。 swiftcclangには-index-store-pathというオプションがありディレクトリを指定することで、そこにインデックスデータが出力されます。

この仕組で生成されたデータは、libIndexStoreを使って読み取ることができます。 libIndexStoreAppleがフォークするLLVMプロジェクトの一部で、Xcodeのツールチェーンに同梱されています。

詳しい話はこちらのドキュメントを参照してください。

docs.google.com

libIndexStoreの仕組みを使えば、SwiftSyntaxの構文レベルでの解析では実現できなかった、よりリッチな解析が可能になります。

libIndexStoreを使った例には以下のようなものがあります。

しかし、いざlibIndexStoreを使ってツールを作ろうとすると、Swift向けlibIndexStoreクライアントが無いことに気が付きます。上で紹介したツール達はそれぞれ内部でlibIndexStoreとのバインディングを自前実装しています。 indexstore-dbは一応IndexStoreクライアントとして使うこともできますが、単にIndexStoreを使うためにはtoo muchかつsourcekit-lspから使われる前提でAPIが公開されているため制約が多いです。

そこで、(比較的)libIndexStoreのインターフェースをそのままSwiftから使うためのクライアントライブラリを書きました。

github.com

そして、そのライブラリを使って実装したのがswift-renamerです。雑に紹介するとXcodeのリネーム機能をプログラマブルに制御するようなライブラリです。

github.com

こんな感じのコードを書くと

import SwiftRenamer

let renamer = SwiftRenamer(storePath: indexStorePath)
let replacements = try system.replacements(where: { (occ) -> String? in
    occ.symbol.usr == "s:16IntegrationTests9ViewModelC4nameSSSgvp" ? "nickname" : nil
})

for (filePath, replacements) in replacements {
    let rewriter = try SourceRewriter(content: String(contentsOfFile: filePath))
    replacements.forEach(rewriter.replace)
    let newContent = rewriter.apply()
    newContent.write(toFile: filePath, atomically: true, encoding: .utf8)
}

こんな感じでリネームできます。これは「ある変数がどこから参照されているか」という情報をIndexStoreが持っているのでサクッと実装できます。

 class User {
-    var name: String?
+    var nickname: String?
 }

 class ViewController: UIViewController {

    let user = User()
    override func viewDidLoad() {
        super.viewDidLoad()

-        user.name = "Initial Name"
+        user.nickname = "Initial Name"
    }
}

IndexStoreにどんな情報が入っているのか気になった方は、swift-indexstoreをクローンしてindex-dump-tool コマンドにXcodeが生成するIndexStoreディレクトリを渡すと中身が見えて楽しいと思います。

$ git clone https://github.com/kateinoigakukun/swift-indexstore
$ cd swift-indexstore
$ swift run index-dump-tool print-record --index-store-path ~/Library/Developer/Xcode/DerivedData/YourProject-XXXX/Index/DataStore 

これを使って便利なツールを作ってくれ〜

WebAssembly向けのデバッガを開発しました

こんにちは。私はSwift言語のWebAssembly向けコンパイルをサポートするプロジェクトを進めている一人です。

その開発を効率的に進めていくためにWebAssembly向けのデバッガwasminspectを開発しました。

Swift for WebAssemblyの進捗

この間フォーラムに詳しい進捗報告をしました。 概要としては

  • 約90%のテストケースが通るようになった
  • まだ一部の実行時の言語仕様が動かない
    • 実行時キャストなど

という感じです。

これをデバッグする必要が出てきたこと、既存のデバッグ手法では太刀打ちできないことから今回のデバッガを開発することにしました。

既存のデバッグ手法

既存のデバッグ手法としては以下のようなものが挙げられます。

  1. WebAssembly以外のアーキテクチャデバッグ
  2. printデバッグ
  3. Chrome DevTools
  4. LLDB + wasmtime

WebAssembly以外のアーキテクチャデバッグ

大抵の場合、自分が元の言語で書いたロジックが間違っているので、わざわざWebAssembly向けバイナリでデバッグする必要はありません。RustでWebAssembly開発をしている場合でもRustをx86_64向けにコンパイルしてLLDBでデバッグすればよいのです。

これは手慣れた手法でデバッグでき、LLDBやGDBなどデバッグする環境が十分に整っていることが大きなメリットです。

一方、実際に動かすバイナリをデバッグできるわけではないので、コンパイラが吐くコードが信用できないときには使えません。あくまでデバッグできるのは手元のマシンのアーキテクチャ向けに吐かれたバイナリであり、WebAssemblyバイナリではありません。今回はWebAssembly向けコンパイルのパイプラインをデバッグしたかったため、この手法は不採用でした。

printデバッグ

古典的なデバッグ手法ですが、デバッガが無い環境では非常に有用な手法です。printデバッグの最大のメリットはWebAssemblyバイナリ自体をデバッグできることです。

しかし、printのためのコードが入り込むため、結局デバッグ対象のバイナリ自身をデバッグできるわけではないです。デバッグ用のコードを入れたらスタックが破壊される、ということがザラに起きるのでこれは致命的です。

また、あくまで中間状態をロギングできるだけなので、プロセスの制御ができません。例えばブレークポイントを貼って任意のタイミングでプロセスを止めたり、ステップ実行しながら条件分岐の進み方を確認することはできません。

Chrome DevTools

最新のChromeではDevToolsのWebAssembly対応が進んできています。最近はDWARFの解釈もできるようになったようです。実際にブラウザ上でデバッグできるのは非常に大きなメリットです。また将来的にWebAssemblyをWeb開発で普及させていくためには必須の機能だと思います。

ただ、現状フレーム上の変数やメモリ空間をダンプできなかったり、機能不足感が否めないです。

OSSとして開発されているので開発に参加することも考えましたが、プロジェクトが非常に大きいこと、v8とのインテグレーション作業に時間がかかりそうなことから、見送りました。

developers.google.com

LLDB + wasmtime

wasmtimeはMozillaが開発しているWebAssemblyのJITコンパイラです。

github.com

LLDBやGDBにはJITコンパイルされたネイティブコードとデバッグ情報からデバッグする機能があります。

Debugging JIT-ed Code With GDB — LLVM 10 documentation

wasmtimeはWASMに埋め込まれたDWARFをJITコンパイル後のアーキテクチャ向けに変換して、LLDBと通信することでソースレベルデバッグを実現しています。

実際に自分でデバッガを作り始めるまではこの手法でデバッグしていました。 ソースレベルのステップ実行やフレームの変数ダンプなどはそこそこ動いていました。 何よりもLLDBの基盤に乗ることで、既存のデバッガ資産をそのまま使えるというのが大きいです。

しかし、この手法でデバッグできるのはあくまでJITコンパイルされたネイティブコードであり、WebAssemblyではありません。なのでWebAssembly命令レベルでのステップ実行などはできないです。また、WebAssemblyをネイティブコードに変換する際に一部の情報が落ちてしまう問題もありました。

例えばWebAssemblyには関数呼び出し時にシグネチャの一致をチェックして、一致しない場合trapする仕様があります。デバッグの必要がない場合、不一致の時abortするだけで十分ですが、デバッグ時には実際にどの型を比較したのか、などの追加の情報が欲しいです。これを実現するためにはデバッグ時のみネイティブコードにデバッグ情報を乗せる、などの作業が必要になります。しかし、それぞれの例外をハンドリングするためにデバッグ情報を伝播するのは非常に大変でした。

hacks.mozilla.org

本当に欲しかったデバッガの要件

コンパイラ開発において本当に欲しかったデバッガの要件は以下のとおりです。

  • 実行時にどのwasm命令を実行しているか把握できる
  • プロセスの制御ができる
  • メモリダンプ
  • DWARFを解釈できる

しかしこれを満たすようなデバッガが見つからなかったため、自作する結論に至りました。

wasminspect

https://raw.githubusercontent.com/kateinoigakukun/wasminspect/master/assets/demo.gif

github.com

GIFでだいたいの雰囲気は伝わると思います。

冬休みの一週間強でデバッガ用のWebAssemblyランタイムを書いて、春休みの最初の一週間でデバッガフロントエンドを書いて、やっとまともに使えるようになりました。今ではバリバリコンパイラ開発で活躍しています。

実装

基本的にLLDBのコードを参考にしながら実装しました。大きな違いはLLDBがOSのシステムコールを使ってプロセスを制御するのに対して、wasminspectでは内蔵したランタイムを直接制御する部分です。

ランタイムはJITコンパイルもAOTコンパイルもせず、愚直にインタプリタとして実装しました。これはWebAssembly命令レベルでのステップ実行を実現するためです。また、wasmtimeとLLDBの組み合わせのときに発生したデバッグ情報の伝播も必要なくなりました。

ランタイムの開発は公式のドキュメントテストケースが充実していて非常にスムーズに進みました。

DWARF for WebAssembly

正直DWARF対応だけで何本かブログが書けそうなくらいしんどかったです。基本的に仕様を読みながらLLDBのコードを参考にして実装するだけでしたが、単純に量が多かったです。

dwarfstd.org

WebAssemblyに固有の話としてはDWARF上のスタックとLLVMが吐くWebAssemblyのスタックが一致しない、ということがありました。 LLVMが吐くWebAssemblyでは線形メモリ上に擬似スタックのような空間を確保してそこを伸び縮みさせてフレーム変数を管理しています。(WebAssemblyの仕様で定義されているスタックとは別) rspに対応する値がグローバルに保持されていて、x86_64向けの命令のようにプロローグとエピローグが擬似スタック上で実行されます。

DWARFが示すスタックはこの擬似スタックにほぼ対応しているので、LLVMの実装に依存することでローカル変数のダンプができるようになりました。

github.com

将来的にはWebAssembly向けの位置情報がDWARFの仕様に追加されるんじゃないかなぁと予想しています。既にドラフトの文書があったりします。

yurydelendik.github.io

自作デバッガの功績

今回作ったデバッガは既にいくつかのSwiftのバグを修正するのに大きく貢献しています。特にメモリダンプ機能は非常に便利で、正常に動くx86_64向けバイナリと比較することで破壊されたメモリの位置を特定できます。

まとめ

一応一通り動くデバッガができましたが、基本的に僕自身がSwift for WASMの開発をするための道具として開発しているだけなので、あまり他の環境で使われることを考慮していません。試していないですが、多分EmscriptenではDWARFの解釈がうまく行かないと思います。

ドキュメントを軽く書いたのでもし興味があれば触ってみてください。

0からデバッガを作る機会はなかなか無いので楽しかったです。これからも「無いなら作ればいい」の精神でやっていきたいと思います。

XcodeGenとCocoaPodsをいい感じに使う

背景

XcodeGenはXcodeのプロジェクトファイルをYAMLから生成するためのツールです。生のプロジェクトファイルは記述が冗長であったり、とても人間が手で管理できるものではないため、YAMLで管理しようというのがモチベーションです。 github.com

iOS/macOSアプリの開発においてライブラリ管理のデファクトスタンダードとなっているCocoaPodsというソフトウェアがあります。これはライブラリをビルドするためのプロジェクトファイル Pods.xcodeproj を生成し、メインのプロジェクトファイルと統合することでアプリにライブラリをインストールします。

さて、この2つのツールを組み合わせて使おうとすると、

  1. XcodeGenでプロジェクトファイルを生成
  2. pod install でプロジェクトファイルと Pods.xcpdeprojを統合

という2ステップの流れになります。しかし、pod install はキャッシュ機構があるとはいえ、ブランチをチェックアウトする度に実行するのはヘビーです。

そこでCocoaPodsとプロジェクトファイルの統合を事前に手動で行う方法を紹介します。この方法では Podfile.lockPods/Manifest.lockに差分がない場合においてはpod installを実装せずに済むため、プロジェクトファイルの生成時間を大幅に削減できます。

CocoaPodsの内部実装に依存する形になるのでCocoaPodsの変更に追従していく必要がありますが、やることは単純なので大まかな統合の処理は変わらないはずです。

詳細

実はCocoaPodsがプロジェクトファイルに行う処理はそこまで多くありません。

実際にpod install を行ったときに発生するxcodeprojへのdiffがこちらです。

gist.github.com

  1. ビルドをトリガーするためのダミーターゲット(Pods_XXX.framework)Dependenciesに追加
  2. CocoaPodsが生成したxcconfigをプロジェクトのベースconfigに設定(他のxcconfigが設定されていない場合のみ)
  3. EmbedするDynamic FrameworkをアプリにコピーするBuild Scriptを注入( [CP] Embed Pods Frameworks
  4. ライブラリが使うリソースをアプリにコピーするBuild Scriptを注入 ( [CP] Copy Pods Resources
  5. pod install が実行されていない場合にビルドをfailさせる Build Scriptを注入( [CP] Check Pods Manifest.lock

だけです。ライブラリのビルドをトリガーする部分はXcodeのビルドシステムに乗っているため、特に考えることはないです。 それぞれ順番に何をするか説明します。

0. (下準備)

CocoaPodsがプロジェクトファイルに手を加えないようにPodfileで integrate_targets: falseを指定します。

install! 'cocoapods', integrate_targets: false
target 'XcodeGenWithPods' do
  use_frameworks!
  pod "Alamofire"
end

あと、生成されたPods.xcodeprojとメインのプロジェクトファイルを束ねるxcworkspaceを作っておきます。通常はCocoaPodsが生成しますが自動統合を切ったため自分で用意する必要があります。

1. ビルドをトリガーするためのダミーターゲット(Pods_XXX.framework)Dependenciesに追加

CocoaPodsのビルドシステムは自前で色々やっているので基本的にXcodeのビルドシステムに依存していません。ただ、そのままではXcodeのビルドボタンを押した時に何もビルドされないので、最初のビルドを発火させるためダミーターゲットを用意しています。 これを依存ターゲットとしてプロジェクトファイルに追加する必要があります。プロジェクトファイルを跨いだフレームワークの参照なので、project.ymlにimplicitオプションを付ける必要があります。

    dependencies:
      - framework: "Pods-${target_name}.framework"
        implicit: true
        embed: false

2. CocoaPodsが生成したxcconfigをプロジェクトのベースconfigに設定

これはCocoaPodsを使いつつxcconfigを使うときと同じテクニックです。CocoaPodsはアプリのビルド時に正しくヘッダを探したり、ライブラリをリンクするために様々なフラグをxcconfig経由で渡しています。そのため、XcodeGenからプロジェクトファイルを生成する際にはそれを指定する必要があります。

targets:
  "XcodeGenWithPods":
    configFiles:
      Debug: Pods/Target Support Files/Pods-${target_name}/Pods-${target_name}.debug.xcconfig
      Release: Pods/Target Support Files/Pods-${target_name}/Pods-${target_name}.release.xcconfig

3, 4, 5. Build Scriptを注入

Dynamicなフレームワークとしてライブラリをビルドした場合、実行時に.frameworkファイルを参照するので成果物をアプリにコピーします。 また、Staticなlibraryとしてビルドするとリソースをアプリに直接コピーする必要があるので、その処理を発火させます。

これら2つのシェルスクリプトはCocoaPodsが必要に応じて生成するので、存在しなければ実行する必要はありません。

    preBuildScripts:
      - name: "[CP] Check Pods Manifest.lock"
        path: /bin/sh
        script: |
          diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
          if [ $? != 0 ] ; then
              # print error to STDERR
              echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." >&2
              exit 1
          fi
          # This output is used by Xcode 'outputs' to avoid re-running this script phase.
          echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
        inputFiles:
          - "${PODS_PODFILE_DIR_PATH}/Podfile.lock"
          - "${PODS_ROOT}/Manifest.lock"
        outputFiles:
          - "$(DERIVED_FILE_DIR)/Pods-${target_name}-checkManifestLockResult.txt"
    postCompileScripts:
      - name: "[CP] Embed Pods Frameworks"
        path: /bin/sh
        script: '"${PODS_ROOT}/Target Support Files/Pods-${target_name}/Pods-${target_name}-frameworks.sh"'
      - name: "[CP] Copy Pods Resources"
        path: /bin/sh
        script: '"${PODS_ROOT}/Target Support Files/Pods-${target_name}/Pods-${target_name}-resources.sh"'

サンプルプロジェクト

github.com

targetTemplatesというXcodeGenの素晴らしい機能を使って汎用的なproject.ymlにしているので、これをコピペするだけで大抵の環境では動くはずです。

Staticライブラリにしてる場合やabstract targetを使っている場合、多分動かないので適宜変更してください。

Effective Techniques in Swift Runtime Library

en

Swift言語のランタイムライブラリで使われているテクニックについて解説します。

ランタイムライブラリと実行可能ファイル

Swiftを実行するためにはswiftCoreというランタイムライブラリが必要です。意識することはあまりありませんが、静的もしくは動的に実行可能ファイルにリンクされています。

Swift 5.0からABI安定が達成されたことで、macOS 10.14.4以降は/usr/lib/swift/libswiftCore.dylibにランタイムライブラリがインストールされています。

SwiftのランタイムライブラリにはSwiftの動的な言語仕様をサポートするための関数が含まれています。

言語仕様の2つの側面

プログラミング言語の言語仕様を考えるとき、以下のような2つに分類できます。

  1. 静的な言語仕様
    • 文法
    • 型システム
  2. 動的な言語仕様
    • 実行時型システム
    • 例外ハンドリング
    • バイナリ互換性

ここでは、動的な言語仕様というのは実行時に使われる言語仕様のことを指します。Swiftの言語機能の中では以下のようなものが例として挙げられます。

  • メモリ確保
  • ARC
  • 実行時の型システム
  • 動的キャスト

これらの言語機能は素朴なコンパイル時のコード生成ではコードサイズが肥大化してしまうため、ランタイムライブラリとして切り出されています。

メモリ確保

ランタイムライブラリの提供する言語機能の例としてメモリ確保の動きを見てみましょう。

Swiftの構造体のように、静的に型のサイズが決定できる場合、コンパイラはそのサイズを確保するようなコードを出力します。

この場合、pet変数はコンパイル時にPet型からインスタンスサイズを24byteに決定できます。

// 24 byte
struct Pet {
  let name: String // 16 byte
  let age: Int     // 8 byte
}

let pet = Pet(name: ..., age: ...) // malloc 24 byte

一方で型から静的にサイズを決定できないケースもあります。例えばSwiftのクラス継承による動的な型の決定を想像してみましょう。

// 8 byte
class View {
  var point: (x: Float, y: Float) // 8 byte
  required init(...) { }

  func copy() -> Self {
    return Self(...) // malloc n byte
  } 
}

// 24 byte
class TextView: View {
  var text: String // 16 byte
  required init(...) { }
}

let view: View = TextView(...)

// この時点でviewがTextViewであることを保証できない。
let anotherView: View = view.copy()

この例ではView型はcopyメソッドを実装しており、 Self型からインスタンスを生成します。 Self型はView型またはTextView型になり得ますが、コンパイラはどちらの型が実行時に使われるか静的に決定できません。

このような Selfを使ったコードを正しく実行するためには、実行時にインスタンスの本来の型情報をインスタンス自身が取り回さなければなりません。

Metadata

実行時に取り回される型情報をSwiftではType Metadataと呼びます。基本的にSwiftユーザーからは見えないようになっており、直接使うこともありません。

Type Metadataは型のサイズや動的なメソッド呼び出しのための関数テーブル、ジェネリック型パラメータのメタデータ、後述するType Descriptorへのポインタなどを保持しています。

ランタイムライブラリは主にMetadataを操作することで動的な言語機能を実装しています。メモリ確保の例では、インスタンスに埋め込まれたType Metadataを取り出してランタイム関数に引数として渡すことで、動的にインスタンスのサイズを決定できるようになっています。

Type Descriptorはジェネリックな型パラメータなどに影響されない、特定の型インスタンスから独立した情報のみを保持しています。また、リフレクションのための情報もここに含まれています。

ジェネリックな型のType Metadataは実際にその型が使われた実行時のタイミングで、型パラメータ毎それぞれ動的に作られるケースがあります。 しかしType MetadataとType Descriptorの二段の構造を取ることで、常に同一のType Descriptorデータを使い回すことができ、メモリを節約しています。

テクニックの背景

データ構造について見ていく前に前提となる背景を軽く話します。

TEXTセグメント

まず、メタデータがどのようにバイナリに格納されているかについてです。

ここではmacOSで使われているMach-O形式について書きます。他のバイナリファイルフォーマットでもさほど変わりません。

基本的に実行可能ファイルは機械語を含むTEXTセグメントとグローバル変数を保持するDATAセグメントに分かれます。

これらのセグメントの大きな違いとして書き込み権限の有無が挙げられます。TEXTセグメントは実行中読み出し専用で書き込み不可ですが、DATAセグメントは実行中に書き込みが可能です。

TEXTセグメントの読み出し専用の性質は動的ライブラリを使う際やプロセスをforkする際に大きく役立ちます。同じ動的ライブラリを2つのプロセスでロードする時、素朴な発想では同じ動的ライブラリを2つメモリ空間に展開します。が、TEXTセグメントが読み出し専用であれば読み書き可能なDATAセグメントのみを重複して展開し、TEXTセグメントは同一のメモリ空間を共有できます。

つまり、ライブラリの占めるTEXTセグメントの割合が多いほどメモリ空間の利用効率が向上します。

SwiftはTEXTセグメントにできるだけメタデータを配置するために一工夫しています。

再配置

再配置は主にリンカがオブジェクトファイルをリンクするタイミングと、プログラムローダが実行可能ファイルをロードするタイミングの2回行われます。

リンク時の再配置ではシンボル同士のアドレスの差や、セクションの先頭からのオフセットなど、実行前にわかる情報を反映します。

また、ロード時の再配置ではプログラムの各部分で使われている絶対アドレスを実際のメモリ空間のアドレスに置換します。基本的に、プログラムが展開されるメモリ空間の先頭アドレスを各アドレスに足し合わせるだけです。

データ構造のテクニック

Relative Pointer

Relative Pointerはポインタ自身のアドレスから対象のアドレスまでのオフセットを保持するポインタ形式です。Swiftのメタデータに含まれるポインタは全てこのRelative Pointer形式になっています。

struct RelativePointer<Pointee> {
    var offset: Int32

    mutating func pointee() -> Pointee {
        return withUnsafePointer(to: &self) { [offset] pointer -> UnsafePointer<Pointee> in
            let rawPointer = UnsafeRawPointer(pointer)
            let advanced = rawPointer.advanced(by: Int(offset))
            return advanced.assumingMemoryBound(to: Pointee.self)
        }.pointee
    }
}

通常のポインタの代わりにRelativePointerを使うメリットはいくつかあります。

  1. バイナリサイズの節約
  2. ロード時再配置を減らせる
  3. メタデータをTEXTセグメントに含められるようにできる

上から1つづつ見ていきます。

まず、Relative Pointerは二点間のアドレスの差を計算するリンカの再配置の仕組みを使っています。この再配置の結果は符号付き32bit整数におさまる、という前提で作られているため、通常のポインタが64bit消費するのに対し、Relative Pointerはその半分の32bitで表現できます。

ロード時の再配置についてですが、前述したとおり、通常のポインタはロード時に再配置が発生するため、プログラムの起動までにオーバーヘッドが発生します。一方、二点間のアドレスの差の計算はリンク時に完了する再配置なので、ロード時のオーバーヘッドがありません。

またロード時の再配置を無くすことによって、メタデータはロード時のデータの書き換えを抑制し、位置独立なデータとして扱うことができます。つまり、実行プログラム本体と同じようにTEXTセグメントに配置して、ほかのプロセスとメモリ空間を共有できるようになります。

もちろんデリファレンス時にオフセットを足し合わせるオーバーヘッドが発生しますが、Swiftでは以上のメリットよりこのような手法を採用しています。

Indirect Pointer

ランタイムライブラリが扱うポインタにはRelative Pointerと組み合わせて、いくつかのテクニックが使われています。その1つの例がIndirect Pointerです。

Indirect Pointerはアライメントによるアドレスの使われないbitを利用してGOT経由のポインタと通常のポインタを同一の型で表現するテクニックです。

アライメントはプロセッサが効率的にデータを扱うために用いられるメモリ空間上のデータ配置規則です。例えば32bitの数値型はアドレスが4の倍数になるように配置されます。

つまり、Int32が配置されるアドレスの下位2bitは必ず0になるわけです。また、Relative Pointer自身も32bit符号付き整数なので、差の数値の下位2bitも0で固定されます。そのため、ポインタの型さえ分かっていればこの下位数ビットは無駄になってしまいます。

Swiftのランタイムライブラリはこの下位数ビットを使っていくつかの状態をポインタに保持しています。

Indirect Pointerは下位1bitをGOTを経由するか否かを表現するために使っています。

GOTを経由する場合はデリファレンスの際に2回デリファレンスします。

struct RelativeIndirectablePointer<Pointee> /*  where alignof(Pointee) => 2 */ {
    let offsetWithIndirectFlag: Int32

    mutating func pointee() -> Pointee {
        let offset: Int32
        if isIndirect {
            offset = offsetWithIndirectFlag & ~isIndirectMask
        } else {
            offset = offsetWithIndirectFlag
        }
        return withUnsafePointer(to: &self) { pointer -> UnsafePointer<Pointee> in
            let rawPointer = UnsafeRawPointer(pointer)
            let advanced = rawPointer.advanced(by: Int(offset))
            if isIndirect {
                let got = advanced.assuimgMemoryBound(to: UnsafePointer<Pointee>.self)
                return got.assumingMemoryBound(to: Pointee.self)
            } else {
                return advanced.assumingMemoryBound(to: Pointee.self)
            }
        }.pointee
    }

    var isIndirect: Bool {
        offsetWithIndirectFlag & isIndirectMask != 0
    }

    var isIndirectMask: Int32 { 0x01 }
}

Int Paired Pointer

また、使われない下位ビットをそのまま数値として取り出すポインタもあります。

struct RelativeDirectPointerIntPair<Pointee, IntTy: BinaryInteger>
    /*  where alignof(Pointee) => 2 */
{
    let offsetWithInt: Int32

    mutating func pointee() -> Pointee {
        let offset = offsetWithInt & ~intMask
        return withUnsafePointer(to: &self) { pointer -> UnsafePointer<Pointee> in
            let rawPointer = UnsafeRawPointer(pointer)
            let advanced = rawPointer.advanced(by: Int(offset))
            return advanced.assumingMemoryBound(to: Pointee.self)
        }.pointee
    }

    var value: IntTy {
        IntTy(offsetWithInt & intMask)
    }

    var intMask: Int32 {
        Int32(
            min(
                MemoryLayout<Pointee>.alignment,
                MemoryLayout<Int32>.alignment
            ) - 1
        )
    }
}

IndirectableとInt Pairedを組み合わせた型もあり、この場合、Indirectか否かを1bit、もう1bitを値として取り出せるようになっています。

Symbolic Reference

Symbolic Referenceはマングリングの種類の1つとして使われています。

通常、型名からメタデータを取得する場合、マングルされた文字列をデマングルしてメタデータを探索します。しかし、対象のオブジェクトがモジュール内に存在する場合、マングルされた名前を介することなく直接参照した方が効率的です。

通常のマングリングは対象のオブジェクトを一意に示す文字列表現ですが、Symbolic Referenceは文字列の一部に対象オブジェクトのアドレスを直接埋め込みます。

Symbolic Referenceは通常のマングルされた文字列と区別するために先頭が制御文字で始まっており、0x01~0x0CまではSymbolic Referenceのために確保されています。

制御文字のあとには4byteのRelative Pointerが埋め込まれており、ランタイムライブラリでデコードされます。

この仕組みにより、モジュール内に定義されたメタデータの参照はほぼコスト無しに行えます。

__swift_instantiateConcreteTypeFromMangledName

この関数は型名からメタデータを取得するために使われます。引数にはこの関数の結果をキャッシュするための { i32, i32 }の構造が渡ってきます。

擬似C++で表現するとこんな感じです。

union Input {
  struct {
    RelativePointer<CChar> typeName;               // 4byte
    int32_t                negativeTypeNameLength; // 4byte
  } nonCached;
  
  TypeMetadata *cached; // 8 byte
}

キャッシュオブジェクトの状態はキャッシュなしとキャッシュ済みの2つあり、それぞれ64bitの使い方が変わってきます。エンディアンによってレイアウトが変わるので、ひとまずリトルエンディアンを仮定して話を進めます。

キャッシュなしの状態では、最初の32bitには型名へのRelative Pointer、後ろの32bitには型名の長さが負号をつけて配置されます。型名はSymbolic Referenceになりうるため、アドレスの一部としてnull文字が入り込む場合があります。そのためnull文字をターミネータとして使えないため、型名の長さが必要になります。

キャッシュありの状態では、64bit全体をキャッシュしたメタデータへの絶対ポインタがそのまま入っています。

この2つの状態はキャッシュオブジェクトを符号付き64bit整数として見た時、負数であればキャッシュなし、正数であればキャッシュあり、として区別できます。これはキャッシュなしの状態で後方32bitに型名の長さを負号をつけて保存したために、64bit全体を符号付き整数として見ると必ず負数になることを利用しています。また、単純に後方32bitの符号を比較するより64bit全体を見るほうが効率的な命令数になります。

まとめ

この様にランタイムライブラリではメモリ空間を最大限に活用するためのテクニックが多く詰まっています。一方でRelative Pointerを前提に組まれた構造がほとんどなので、32bit以上のポインタサイズをサポートしようとすると、ランタイム構造体の調整が必要になってきます。この調整をする場合、コンパイラが出力するメタデータのレイアウトとランタイムライブラリの想定するレイアウトを同期する必要がありますが、現状の実装では特に同期のための仕組みがあるわけではなく、全てのレイアウトを手動で調整する必要があります。

これはSwiftの移植性に大きく関わってくるため、メタデータのレイアウトを一元的に管理する何かしらの仕組みが必要です。例えば、コンパイラが出力したLLVM IRからランタイムライブラリ用のCの構造体を生成するようなコードジェネレータがあれば解決できるかもしれません。(SwiftのメタデータはTrailingObjectsによって可変長なレイアウトを持つため難しいかもしれませんが。)

LLVMコンパイラ実装の基盤として大きな役割を果たしていますが、ランタイムライブラリとコンパイラの間のインターフェイス定義をサポートするようなライブラリはあまり見たことがありません。そのようなレイヤーの技術が充実してくればもっと多くの言語がABI安定を達成できるような世界になるかもしれませんね。

19になりました。

こんにちは。

例年のごとく今年もやってきた誕生日ですが、今年は気がつくと謎のアドベントカレンダーにjoinしてました。

誕生日を迎えました(9/16追記)|noppe*|note

誕生日アドベントカレンダー、次回は家庭の医学くんです。(ノンアポ)

ということで当日からしばらく経ってしまいましたが、欲しいものリストからいただいたものリストを公開していこうと思います。

リストには図々しくも、普通に読みたかった技術書たちをドサっと入れました。

@kishikawakatsumiさんから

詳解UNIXプログラミング 第3版

詳解UNIXプログラミング 第3版

どデカいダンボールで届いたので、むむ、これは本では無いな、とエスパーしたのですが、本という名の鈍器が入っていました。

Amazonの商品画像は正面の図しか教えてくれないので、まさかここまでとは思わず…

ドンッ

先人が築いてきた土台の厚みですね。

最近は興味の方向が段々と下に向いてきたので、楽しく読めそうです。

オーデュボンの祈り (新潮文庫)

オーデュボンの祈り (新潮文庫)

もう一つ、伊坂幸太郎の作品をいただきました。友人に勧められて以来この作家さんの作品をいくつか読んでいるのですが、珍しくハマった作家さんです。

ありがとうございます!

@bannzaiさんから

bannzaiさんのnは2つなんですね。

きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~

きつねさんでもわかるLLVM ~コンパイラを自作するためのガイドブック~

わいわいswifrcのイベントページに載っている、もはや検定教科書と言っても過言では無い本です。

じつはまだ読んだことなくて、雰囲気でLLVM触ってるのでありがたいです!

果たして僕はきつねさんになれるのでしょうか。

乞うご期待です。

‪@noppefoxwolf‬さんから

伊藤園 1日分の野菜 (紙パック) 200ml×24本

伊藤園 1日分の野菜 (紙パック) 200ml×24本

野菜ジュースをいただきました!

大学の食堂の混み具合が異常すぎて、入学後2週間で昼のメニューを野菜ジュースとラムネにシフトした僕にとっては非常にありがたいです。

メッセージに

異世界転生の仕方を教えてください

とあったのですが、たまたまそれを見た母親に怪訝な目をされました!今日も我が家は平和です!


9/17 追記

@kishikawakatsumiさんから(その2)

なんと第2弾があったようで、リンカの本をいただきました。

今年の夏の前半でLinkers & Loadersを読んで「リンカ完全に理解した」状態になったのですが、最近また「何も分からん」のフェーズに入ったので最高のタイミングでした!

パラパラめくってみたところ、Linkers & Loadersとはまた毛色が違うようなので、まだまだ学べることがありそうです。ありがとうございます!!

@d_dateさんから

データ構造とアルゴリズム

データ構造とアルゴリズム

大学の図書館で読めそうじゃんこれ というものにしました

というメッセージと共に、まさに図書館にありそうな本をいただきました!

絶対に図書館にあると思いますが、手元にある安心感は何にも代え難いです!

実はアルゴリズムの類の本は全く読んだことが無く、必要に応じてwikipediaで理解していただけなので、脳の引き出しを拡張するために勉強したいと思います。

ありがとうございます!

@takasekさんから

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

これからも無双してください

最近は暇さえあればマイクロサービスのアーキテクチャについて思いを馳せているので、これを読んで無双できるようなアーキテクチャへの道を見出したいと思います!

あと🐝が思ったよりリアルでした!!

ありがとうございます!

@giginetさんから

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

完全にたかりの図ですが、本当にありがたいことにDDD本を頂きました。

正しくドメインを切り出せるようになるのが直近の目標なので、無限に吸収できることが詰まっている予感です。

これでOSSを全部いい感じにしてくれ〜

という雑で壮大なタスクがメッセージとして降ってきたので、全部いい感じにしたいとおもいます。

僕とgiginetさんがメインでメンテナをしてるIBLinterをよろしくお願いします GitHub - IBDecodable/IBLinter: A linter tool for Interface Builder

@crcrpar

森永製菓 大粒ラムネ 41g×10袋

森永製菓 大粒ラムネ 41g×10袋

僕の生活を支える技術の一つである「大粒ラムネ」をデプロイしていただきました! 野菜ジュースとラムネの最強タッグでしばらくお昼ご飯難民にならなくて済みそうです!

ありがとうございます〜!


多分全部読むのに1年くらいかかりそうですが、無限の伸び代を感じることができて非常にワクワクしてます。

やるぞやるぞ〜

みなさんありがとうございました!

この恩はいろんな形で還元していきたいと思います。

これからも仲良くしてください〜〜

Inside SwiftUI (About @State)

SwiftUI was announced at this WWDC and we've tried it for a few days. This WWDC was the moment everything changed for me.

But SwiftUI is still a private source software as well as UIKit and we need to develop applications while speculating the behavior of it.

There is some features which I felt is a mystery in SwiftUI. I investigated them and internal implementation of SwiftUI.

(This post is just a my prediction)

@State

@State var value: Int

You may write this many time while doing tutorials of SwiftUI. Do you understand how State works?

This feature uses Property Delegates introduced from Swift5.1 and it allows to delegate the implementation of getter and setter to another instance (In this case "another instance" means State).

State is a struct defined in SwiftUI using @propertyDelegate. If State is changed, View will be re-rendered.

First, let's implement State by myself!

@propertyDelegate
struct State<Value> {
    var storage: Value
    var value: Value {
        get { storage }
        set {
            storage = newValue
            renderView()
        }
    }
    init(initialValue value: Value) {
        self.storage = value
    }
    
    func renderView() {
        // ???
    }
}

struct ContentView: View {
    @State var text: String
}

It's almost easy to implement but I couldn't figure out how re-render View. SwiftUI seems not re-render the all Views but some particular views whose state is modified. So I need to link ContentView and text: State<String> to update the view.

Internal structure of State

Let's dump the state instance to investigate internal structure of State by using dump.

struct ContentView: View {
    var text = State<String>(initialValue: "Hello")
    
    init() {
        print("Init:")
        dump(text)
    }
    
    var body: some View {
        print("Body:")
        dump(text)
        return Text("Hello, world")
    }
}

I dumped text in init and body but their outputs are different.

Init:
▿ SwiftUI.State<Swift.String>
  - _value: "Hello"
  - _location: nil

Body:
▿ SwiftUI.State<Swift.String>
  - _value: "Hello"
  ▿ _location: Optional(SwiftUI.StoredLocation<Swift.String>)
    ▿ some: SwiftUI.StoredLocation<Swift.String> #0
      - super: SwiftUI.AnyLocation<Swift.String>
        - super: SwiftUI.AnyLocationBase
      ▿ viewGraph: Optional(SwiftUI.ViewGraph)
        ...

This result shows ViewGraph appears after init but before body. It seems there is a way to inject ViewGraph into State outside of View.

Since ViewGraph is an internal type of SwiftUI, I can only speculate it but it seems ViewGraph manages tree structure of View. If this ViewGraph can render View selectively, it's easy to imitate the re-rendering system.

Then, let's fill the body of renderView.

 @propertyDelegate
 struct State<Value> {
     var storage: Value
+    var viewGraph: ViewGraph?
     var value: Value {
         get { storage }
         set {
             storage = newValue
             renderView()
         }
     }
     init(initialValue value: Value) {
         self.storage = value
     }
 
+    func setViewGraph(_ viewGraph: ViewGraph) {
+        self.viewGraph = viewGraph
+    }
 
     func renderView() {
+        viewGraph.render()
     }
 }

It became clear that how re-render view but it's still uncertain that how inject the ViewGraph into ContentView and make the relationship between State and View.

@State filed belongs to View but there is no way to access text: State<String> through View protocol because the field name is not bound by the protocol. For example, it can be easy to inject if State field is bound by the protocol as follows code. But actually, fields can be named freely.

protocol ViewGraphInjectable {
    var state: State<Value> { get }
}

struct ContentView: View, ViewGraphInjectable {
    @State var state: String
}

Reflection API

The only way to get fields without protocol I first thought is Mirror. It's very simple that getting fields of View using Mirror and if there is State, call setViewGraph.

But does SwiftUI really use Mirror? If Mirror is used, CustomReflectable.customMirror should be called and print "Mirror is used!".

struct ContentView: View, CustomReflectable {
    @State var text: String
    var customMirror: Mirror {
        print("Mirror is used!")
        return Mirror(reflecting: self)
    }
}

But customMirror is not called. For checking, I tried to set breakpoint at swift_reflectionMirror_count which is runtime function used by Mirror but it also doesn't break.

This result indicates that SwiftUI get fields without using Mirror.

Reflection without Mirror

There is still a way to get fields without using Mirror. It's using metadata.

If you don't know metadata, you can learn it by my try! Swift presentation.

Metadata has Field Descriptor which contains accessors for fields of the type. It's possible to get fields by using it.

My various experiments result AttributeGraph.framework uses metadata internally. AttributeGraph.framework is a private framework that SwiftUI use internally for constructing ViewGraph.

You can see it by the symbols of the framework.

$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph

There is AG::swift::metadata_visitor::visit_field in the list of symbols. i didn't analysis the whole of assembly code but the name implies that AttributeGraph use visitor pattern to parse metadata. If you set break point, it surely hit.

Why not use Mirror

So why is metadata used instead of Mirror? I think it's for performance.

Mirror.children is represented as [(label: String, value: Any)] to hold any type of value. But Any wraps the actual value and when you use it, Any is unwrapped every time. SwiftUI uses View many times and the overheads can be critical problem.

On the other hand, Using raw pointer through Field Descriptor doesn't affect performance seriously.

Flow to update View

  1. Find State of View using Field Descriptor
  2. Inject ViewGraph into State
  3. Render View.body
  4. State is changed
  5. State notify the ViewGraph to update view
  6. Re-render View.body

Only Apple knows the actual implementation. But it's certain that AttributeGraph.framework has its own reflection system.

I said "I'm looking forward to your great libraries using metadata" in try! Swift but I had never thought Apple do it, I think Apple started to use metadata because ABI stability was built since Swift5. In fact, ABI stability brings us great benefits!

I'll write an article about DynamicViewProperty by next week.

Inside SwiftUI @State編

WWDCでSwiftUIが発表されてから数日が経ちました。一気に世界が変わった気がしますね。 ただ、UIKitと同様にSwiftUIはオープンソースでは無いため、我々開発者は依然挙動をエスパーしながら開発する必要があります。

その中でも、SwiftUIのチュートリアルを試している中で不思議な仕組みがいくつかあったので、僕が調べたSwiftUIの内部構造について書き留めておきます。

(あくまで考察なので間違っていても悪しからず)

追記(2022-09-15)

未だにアクセスがあるので追記。

TokamakというSwiftWasmでSwiftUI互換なコードを書くためのUIフレームワークがあり、 ここで考察した内容とほぼ同じようなテクニックを使ってStateを実装しています。 具体的な実装が気になる方は読んでみてください。

github.com

目次

  • @State編(今ココ)
  • DynamicViewProperty編(来週くらいには書きます)
  • 差分更新編(調査中)

@State

@State var value: Int

チュートリアルをこなした方であれば何度も書いたことでしょう。

Swift5.1からのProperty Delegatesを使った記法で、プロパティの値のgetterとsetterを別の型(ここではState)に委譲できます。

StateというのはSwiftUIに定義された@propertyDelegateなstructです。変更されるとStateが定義されているViewが再レンダリングされる、という振る舞いをします。

ではこの振る舞いからStateの実装を予想してみます。

@propertyDelegate
struct State<Value> {
    var storage: Value
    var value: Value {
        get { storage }
        set {
            storage = newValue
            renderView()
        }
    }
    init(initialValue value: Value) {
        self.storage = value
    }
    
    func renderView() {
        // ???
    }
}

struct ContentView: View {
    @State var text: String
}

(本当はvalueのsetterがnonmutatingなのでbox化されてるはずですが説明のため省略してます。)

簡単に実装できましたがViewを再レンダリングする部分だけは想像できません。keyWindow配下のViewを全て再レンダリングしているのか?、と一瞬思いましたが、Stateが変更されてもルートのViewから全て再レンダリングされる訳では無さそうです。 では、どうやってtext: State<String>ContentViewを紐付けてContentViewだけ再レンダリングしているのでしょうか。

Stateの内部構造

とりあえずStateのpublicなフィールドにはそれっぽい物は無いので、内部フィールドをdumpで調べてみます。

struct ContentView: View {
    var text = State<String>(initialValue: "Hello")
    
    init() {
        print("Init:")
        dump(text)
    }
    
    var body: some View {
        print("Body:")
        dump(text)
        return Text("Hello, world")
    }
}

(StatedelegateValueを使っており$textStateを返さないのでProperty Delegateを使わない記法にあえてしています。)

initbodyでそれぞれtext: State<String>をdumpしましたが、実は違った結果が出力されます。

Init:
▿ SwiftUI.State<Swift.String>
  - _value: "Hello"
  - _location: nil

Body:
▿ SwiftUI.State<Swift.String>
  - _value: "Hello"
  ▿ _location: Optional(SwiftUI.StoredLocation<Swift.String>)
    ▿ some: SwiftUI.StoredLocation<Swift.String> #0
      - super: SwiftUI.AnyLocation<Swift.String>
        - super: SwiftUI.AnyLocationBase
      ▿ viewGraph: Optional(SwiftUI.ViewGraph)
        ...

ViewGraphという怪しいオブジェクトが見えるようになりました。initとbodyの間に何かしらの方法でStateにViewGraphが注入されています。

Internalな型なので名前から推測するしかありませんが、どうやらViewGraphはViewのツリー構造を管理するオブジェクトのようです。このViewGraphがContentViewのみを選択的にレンダリングできる、と仮定すると再レンダリングの仕組みは説明できそうです。 では、StateがViewGraphを持つことを考慮して最初のStateの実装の???を埋めてみます。

 @propertyDelegate
 struct State<Value> {
     var storage: Value
+    var viewGraph: ViewGraph?
     var value: Value {
         get { storage }
         set {
             storage = newValue
             renderView()
         }
     }
     init(initialValue value: Value) {
         self.storage = value
     }
 
+    func setViewGraph(_ viewGraph: ViewGraph) {
+        self.viewGraph = viewGraph
+    }
 
     func renderView() {
+        viewGraph.render()
     }
 }

なるほど、Viewを再レンダリングする方法は分かりました。しかし、未だどうやってContentViewStateを紐付けてContentViewViewGraphを注入するのか、は謎のままです。

先ほどの実験からinitbodyの間に呼ばれることは分かっていますが、どこから呼ばれるかは不明です。

また、@StateフィールドはViewに対して生えていますが、protocolでフィールド名が縛られている訳では無いのでtext: State<String>にアクセスする方法がありません。 例えば以下のようにprotocolで縛られていれば注入できると思いますが、実際フィールド名は自由に付けられるので他の方法で実現されているようです。

protocol ViewGraphInjectable {
    var state: State<Value> { get }
}

struct ContentView: View, ViewGraphInjectable {
    @State var state: String
}

Reflection API

protocolを使わずに任意の構造体から動的に値を取り出す、となると真っ先に思いつくのがMirrorです。ViewMirrorにかけて、childrenを取り出して、その中にState型があればsetViewGraphを呼ぶ、という比較的単純な方法です。

それでは本当にMirrorを使っているか実験してみましょう。CustomReflectableが実装されている場合、MirrorにかけるとcustomMirrorが呼ばれるはずなので、"Mirror is used!"が出力されるはずです。

struct ContentView: View, CustomReflectable {
    @State var text: String
    var customMirror: Mirror {
        print("Mirror is used!")
        return Mirror(reflecting: self)
    }
}

しかし、実際はcustomMirrorは呼ばれません。念のため、Mirrorが使っているランタイムAPIswift_reflectionMirror_countブレークポイントを貼ってみましたがヒットしませんでした。

つまり、SwiftUIは内部でMirrorを使わずにViewのフィールドを取得している訳です。

Mirrorを使わないReflection

Mirrorを使わずともフィールドを取得する方法はまだあります。そうですメタデータを利用する方法です。メタデータについては僕のtry! Swiftの発表を見てもらえると雰囲気が掴めると思います。

メタデータにはField Descriptorという、その型のフィールドのアクセサ群が格納されています。それを利用すればフィールドの一覧を取得できるのでMirrorの代わりに使っている可能性は十分にあります。

色々と試行錯誤した結果1、SwiftUIが内部でViewGraphを組み立てるのに使っているAttributeGraph.frameworkというプライベートフレームワークが内部でメタデータを使っている事が分かりました。

では早速AttributeGraph.frameworkのシンボルをnmコマンドで見てみましょう。

$ nm /System/Library/PrivateFrameworks/AttributeGraph.framework/AttributeGraph

AG::swift::metadata_visitor::visit_field というシンボルが見えると思います。アセンブリを全て読んだ訳ではありませんが、名前から察するにメタデータからフィールドをVisitorパターンで回していそうです。ブレークポイントを貼ってみると実際に動いている様子が確認できます。

なぜMirrorを使わないのか

ではなぜMirrorでもできることをメタデータで実装しているのでしょうか。完全に私の考察ですが、パフォーマンスが主な理由だと思っています。

Mirrorchildren[(label: String, value: Any)]で表現します。任意の型を表現するのにAnyを使っていますが、Anyは値をラップしているため、実際の値を使うには毎回取り出す必要があります。Viewが多くなればなるほどこのオーバーヘッドは無視できなくなってくるため、フィールドをポインタで直接操作できるField Descriptorを使っている、というのが予想です。

StateがViewに更新されるまで

ここまでの実験でStateが変更された際にどうやってViewが再レンダリングされるか予想できたのでまとめます。

  1. Viewに対してField DescriptorStateを探してViewGraphを注入する
  2. View.bodyを評価してレンダリングする。
  3. Stateが変更される
  4. StateViewGraphに変更を通知する
  5. View.bodyが再評価されViewが更新される。

実際の実装はAppleのみぞ知るところですが、これ以上のアイディアは思いつきませんでした。ただ、AttributeGraph.frameworkが独自にリフレクションの仕組みを使っているのは確実です。

try! Swiftの発表で「メタデータを使ったライブラリをみんな作ってみてね」と言いましたが、まさかAppleがABI安定化を期に率先して使ってくるとは思いませんでした。今年のWWDCは驚くことばかりですね。

次はDynamicViewPropertyについて書きます。


  1. これは先に調査していたDynamicViewPropertyの仕組みを考えた上でのエスパーです。