iOSDCで登壇したよ
今年はスピーカーとして参加しました。
発表内容
とりあえずスライドを貼ります。コンパイラのコード部分は口頭で喋った部分が多いので動画が公開されるのを待ってください。
スライドも貼ったのでこの記事の役割は終わりました。ここからは思ったことをつらつらと書いていきます。
スピーカーとして
登壇するのは今回が初めてだったのでスライドを準備しながら結構緊張してました。実際、テーマがニッチかつ聴衆のコンテキストがほぼ無いので、「うーむわからん」という感想になるのが1番怖かったです。
ただ、事前に練習会に参加してレビューしてもらったためか、実際の登壇時には話しながら自分で楽しくなるくらいの感覚で話せました。本当はExistentialとかLLVM IRレベルの挙動、ディスパッチ機構の設計についても話したかったですが、まあどう考えても30分に収まらないので泣く泣くスライドを埋葬しました。
個人的にはQAコーナーが1番楽しかったです。きちんと受け答えできたか今となっては忘却の彼方ですが、セッションの本編で時間の都合上話せなかったことを質問してくれたり、勝手にわいわいし始めたり、とても濃かったです。
面白かったトーク
オープンソースMDMのmicromdmを用いて、個人でデバイス管理をやってみる
iOSDC2018-MicroMDM.pdf - Speaker Deck
家で簡易MDMを運用しているので、無料で証明書を発行できるサービスは有益情報でした。落ち着いたらゴチャゴチャ触ってみます。
MicroViewControllerで無限にスケールするiOS開発
前から内容自体は聞いていましたが @tarunon のトークと相まって素晴らしい内容でした。ベストトークおめでとうございます :tada:
Swift Compilerの最適化入門 - AllocBoxToStack編
SIL Optimizations - AllocBoxToStack
まだ追えていない範囲の話だったのでなるほど~という感じでした。QAでわいわいしてた
Swiftの型システムに入門する - lib/Semaの歩き方
Swiftの型システムに入門する - iOSDC Japan 2018 - Speaker Deck
今回の登壇資料を作る時ブログにお世話になりました。Semaはデカイのでまだ手をつけてませんが、セッションを聴くと意外と読めそうな気がしてきました。
Swiftのジェネリクスはどうやって動いているのかコンパイラのソースから探る
Swiftのジェネリクスはどうやって動いているのかコンパイラのソースから探る - Speaker Deck
ギリギリついていけました。assoctypeのメタデータをVWT Protocol Witness table経由で取ってくるのは知らなかったので質問してよかったです。(引数に渡して取り回しているものだと思っていた)
@omochimetaruに1聞くと100返ってくるのでとても勉強になりました。
圏論とSwiftへの応用
圏論とSwiftへの応用 / iOSDC Japan 2018 - Speaker Deck
これは宿題になりました。高カインド多相の話はDiscordでもちょくちょく出ていて理解したいな〜と思っていたところだったので勉強します。
まとめ
登壇したことでいろいろな方とお話するきっかけになったり、コンパイラに興味を持ってもらったりと総じてとても良い体験になりました。やりきった感が若干あってマズイですが、よく考えるとやるべきことは山積みなので日々やっていきたいと思います。
iOSDC Japan 2018でコンパイラの話をします
お前は誰
- Twitter @kateinoigakukun
- GIthub @kateinoigakukun
- 今はStart Today TechnologiesでiOSエンジニアの学生バイト
話します
こんにちは。今週の8/31にiOSDC Japan 2018でコンパイラの話をします。
コンパイラから紐解くSwift method dispatch by 家庭の医学
Swiftには実行するメソッドを効率的に決定する機構があります。 しかし、静的型付け言語であるにも関わらず動的に決定せざるを得ないパターンや、finalやprivateなどの修飾子がパフォーマンスに影響するパターンなど、実際にはどのような動きをしているのでしょうか。 このトークではSwiftコンパイラの中からその疑問を紐解いていきます。
コンパイラに対する興味があったのでCfPを出してみたところ、ありがたいことに30分枠で採択されました。開発ですぐに使えるTipsなどは紹介できませんが、プログラミングオタクの皆さんには面白い話題が提供できると思います。
練習会
先日Timersさんのオフィスで登壇練習をしてきました。みなさんありがとうございます。いろいろとレビューを貰えてとても良い練習会でした。ここからブラッシュアップしていくぞ!
やるぞ
去年初めてiOSDCに参加したときは、来年はLT出来るくらいにはなりたいな〜程度に思っていたので採択メールが来たときはビビりました。 実はパブリックな場で登壇するのは初めてなのですが、頑張って30分で話したいと思います。
Swift LLVM IRを読む - classの初期化
swiftc -emit-ir foo.swift
で出力したLLVM IRを読んだ際のメモ
class A {} let a = A()
このコードからLLVM IRを出力してmain関数のみを取り出したのが以下である。
define i32 @main(i32, i8**) #0 { entry: %2 = bitcast i8** %1 to i8* %3 = call swiftcc %swift.metadata_response @"$S11class_alloc1ACMa"(i64 0) #3 %4 = extractvalue %swift.metadata_response %3, 0 %5 = call swiftcc %T11class_alloc1AC* @"$S11class_alloc1ACACycfC"(%swift.type* swiftself %4) store %T11class_alloc1AC* %5, %T11class_alloc1AC** @"$S11class_alloc1aAA1ACvp", align 8 ret i32 0 }
%2
はどこからも使われていないので一旦無視するとして、%3
から読み進めていこう。
まず、$S11class_alloc1ACMa
という関数を呼び出している。$S11class_alloc1ACMa
はdemangleすると type metadata accessor for class_alloc.A
となる。つまり、class Aのメタデータのaccessorだろう。
%3 = call swiftcc %swift.metadata_response @"$S11class_alloc1ACMa"(i64 0) #3
では、そのアクセサを見ていこう。
define hidden swiftcc %swift.metadata_response @"$S11class_alloc1ACMa"(i64) #1 { entry: %1 = load %swift.type*, %swift.type** @"$S11class_alloc1ACML", align 8 %2 = icmp eq %swift.type* %1, null br i1 %2, label %cacheIsNull, label %cont cacheIsNull: ; preds = %entry %3 = call %objc_class* @swift_getInitializedObjCClass( %objc_class* bitcast ( i64* getelementptr inbounds ( <{ void (%T11class_alloc1AC*)*, i8**, i64, %objc_class*, %swift.opaque*, %swift.opaque*, i64, i32, i32, i32, i16, i16, i32, i32, <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, %swift.method_descriptor }>*, i8*, %T11class_alloc1AC* (%T11class_alloc1AC*)* }>, <{ void (%T11class_alloc1AC*)*, i8**, i64, %objc_class*, %swift.opaque*, %swift.opaque*, i64, i32, i32, i32, i16, i16, i32, i32, <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, %swift.method_descriptor }>*, i8*, %T11class_alloc1AC* (%T11class_alloc1AC*)* }>* @"$S11class_alloc1ACMf", i32 0, i32 2 ) to %objc_class* ) ) %4 = bitcast %objc_class* %3 to %swift.type* store atomic %swift.type* %4, %swift.type** @"$S11class_alloc1ACML" release, align 8 br label %cont cont: ; preds = %cacheIsNull, %entry %5 = phi %swift.type* [ %1, %entry ], [ %4, %cacheIsNull ] %6 = insertvalue %swift.metadata_response undef, %swift.type* %5, 0 %7 = insertvalue %swift.metadata_response %6, i64 0, 1 ret %swift.metadata_response %7 }
$S11class_alloc1ACML
はlazy cache variable for type metadata for class_alloc.A
。文字通りmetadataのキャッシュだろう。初回のアクセスではキャッシュされていないので%1
にはnull
が入る。そのため、%2
がtrue
となりcacheIsNull
にジャンプする。
%1 = load %swift.type*, %swift.type** @"$S11class_alloc1ACML", align 8 %2 = icmp eq %swift.type* %1, null br i1 %2, label %cacheIsNull, label %cont
さて、cacheIsNull
を見ていくと、重複した長い型があるのでこれに一旦適当な名前を勝手に付けよう。
%ft_metadata = <{ void (%T11class_alloc1AC*)*, i8**, i64, %objc_class*, %swift.opaque*, %swift.opaque*, i64, i32, i32, i32, i16, i16, i32, i32, <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, %swift.method_descriptor }>*, i8*, %T11class_alloc1AC* (%T11class_alloc1AC*)* }>,
%3
は、
%ft_metadata
型のポインタから0番目の要素の2番目のフィールド(i64
型)を取得- それを
%objc_class
のポインタにキャスト %objc_class
のポインタを引数に取り、swift_getInitializedObjCClass
という関数を呼び出す%objc_class*
型が%3
に格納される
といった流れで詰められる。
では、%ft_metadata*
型の@S11class_alloc1ACMf
はどのような構造になっているだろうか。
%3 = call %objc_class* @swift_getInitializedObjCClass( %objc_class* bitcast ( i64* getelementptr inbounds ( %ft_metadata, %ft_metadata* @"$S11class_alloc1ACMf", i32 0, i32 2 ) to %objc_class* ) )
@S11class_alloc1ACMf
はfull type metadata for class_alloc.A
。
@S11class_alloc1ACMf
の定義を見ると、上で取り出したポインタの実体が@S11class_alloc1ACMm
であることが分かる。
@"$S11class_alloc1ACMf" = internal global <{ void (%T11class_alloc1AC*)*, i8**, i64, %objc_class*, %swift.opaque*, %swift.opaque*, i64, i32, i32, i32, i16, i16, i32, i32, <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, %swift.method_descriptor }>*, i8*, %T11class_alloc1AC* (%T11class_alloc1AC*)* }> <{ void (%T11class_alloc1AC*)* @"$S11class_alloc1ACfD", i8** @"$SBoWV", i64 ptrtoint (%objc_class* @"$S11class_alloc1ACMm" to i64), %objc_class* @"OBJC_CLASS_$_SwiftObject", %swift.opaque* @_objc_empty_cache, %swift.opaque* null, i64 add (i64 ptrtoint ({ i32, i32, i32, i32, i8*, i8*, i8*, i8*, i8*, i8*, i8* }* @_DATA__TtC11class_alloc1A to i64), i64 1), i32 3, i32 0, i32 16, i16 7, i16 0, i32 104, i32 16, <{ i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, %swift.method_descriptor }>* @"$S11class_alloc1ACMn", i8* null, %T11class_alloc1AC* (%T11class_alloc1AC*)* @"$S11class_alloc1ACACycfc" }>, align 8
@"$S11class_alloc1ACMm" = hidden global %objc_class { %objc_class* @"OBJC_METACLASS_$_SwiftObject", %objc_class* @"OBJC_METACLASS_$_SwiftObject", %swift.opaque* @_objc_empty_cache, %swift.opaque* null, i64 ptrtoint ({ i32, i32, i32, i32, i8*, i8*, i8*, i8*, i8*, i8*, i8* }* @_METACLASS_DATA__TtC11class_alloc1A to i64) }, align 8
cacheIsNull
の処理に戻ると、%objc_class*
を%swift.class*
にキャストして、@"S11class_alloc1ACML"
にキャッシュしている。
あとは%cont
で%metadata_response
を組み立てるだけ。
%objc_class*
を%swift.class*
にキャストできるのは、%swift.type = type { i64 }
なのでメモリレイアウトが被っているから。
%4 = bitcast %objc_class* %3 to %swift.type* store atomic %swift.type* %4, %swift.type** @"$S11class_alloc1ACML" release, align 8 br label %cont
無事に%metadata_response
を組み立てられたのでmain
に戻ろう。
得られた%metadata_response
から0番目のフィールドを取得する(%swift.type*
型、実体は%objc_class*
)
そのメタタイプを引数に@"S11class_alloc1ACACycfC"
を呼んでいる。
%4 = extractvalue %swift.metadata_response %3, 0 %5 = call swiftcc %T11class_alloc1AC* @"$S11class_alloc1ACACycfC"(%swift.type* swiftself %4) store %T11class_alloc1AC* %5, %T11class_alloc1AC** @"$S11class_alloc1aAA1ACvp", align 8
さて、@"S11class_alloc1ACACycfC"
を読んでいこう。
ここで再び@"$S11class_alloc1ACMa"
が呼ばれ、%metadata_response
から%swift.type
を取得している。@swift_allocObject
によってメモリ領域を確保し、%T11class_alloc1AC
型(A
型)にキャストする。こうしてA
型のインスタンスを作ることができる。
define hidden swiftcc %T11class_alloc1AC* @"$S11class_alloc1ACACycfC"(%swift.type* swiftself) #0 { entry: %1 = call swiftcc %swift.metadata_response @"$S11class_alloc1ACMa"(i64 0) #3 %2 = extractvalue %swift.metadata_response %1, 0 %3 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* %2, i64 16, i64 7) #2 %4 = bitcast %swift.refcounted* %3 to %T11class_alloc1AC* %5 = call swiftcc %T11class_alloc1AC* @"$S11class_alloc1ACACycfc"(%T11class_alloc1AC* swiftself %4) ret %T11class_alloc1AC* %5 } define hidden swiftcc %T11class_alloc1AC* @"$S11class_alloc1ACACycfc"(%T11class_alloc1AC* swiftself) #0 { entry: ret %T11class_alloc1AC* %0 }
おしまい。
追記 2018/08/04
メタデータのドキュメントを見つけた。どうやらobjcとの互換のためにkindフィールドが面倒くさくなってるっぽい。試しにstructでIRを吐いてみたところ、kindは1が直で書いてあった。
アクセサも一つの式で表現されてた。
define hidden swiftcc %swift.metadata_response @"$S12AnimalSimple3CatVMa"(i64) #2 { entry: ret %swift.metadata_response { %swift.type* bitcast (i64* getelementptr inbounds (<{ i8**, i64, <{ i32, i32, i32, i32, i32, i32 }>* }>, <{ i8**, i64, <{ i32, i32, i32, i32, i32, i32 }>* }>* @"$S12AnimalSimple3CatVMf", i32 0, i32 1) to %swift.type*), i64 0 } }
Enumの生成を制限する
Discordで話題に上がってたやつ。遊んでたら見つけた。
tarunon - 2018/04/26 午後4時2分
Enumを生成勝手に出来ないようにする方法って無いですよね structはinitをpublicにしなければ勝手に生成出来ないけど
protocol extensionで同名のstatic funcを生やすとambiguousでA.hoge
の呼び出しができなくなる。
enum A { case hoge(String) } protocol AP {} extension AP where Self == A { static var hoge: (String) -> Self { fatalError() } } extension A: AP {} A.hoge("aa") // error: ambiguous use of 'hoge'
├の読み方
「├ 読み方 数学」とかいうアホっぽいクエリを投げてしまった。
一番悲しくなったのは、この記号が出現した数行下にキチンと解説してあったこと。さすが型システム入門、素晴らしい入門書だ。
コンパイルが通ってほしいSwiftコード
見つけたら随時更新。バグレポ出してないやつもある
Protocol extension周り
protocol extensionの中でtypealiasを貼ると何かが壊れる
'T' does not have a member type named 'A'; did you mean 'A'?
func foo<T: P>(a: T) -> T.A { fatalError() } struct B {} protocol P {} extension P { typealias A = B }
訳が分からないのは
struct B {} protocol P {} extension P { typealias A = B } func foo<T: P>(a: T) -> T.A { fatalError() }
にすると通るようになる。P
のassoctypeにAを宣言しても通る。
assoctypeのデフォルト型
WIP
protocol P { associatedtype X = Int func f(_ x: X) } extension P where Self == S { func f(_ x: X) {} } struct S: P { func f(_ x: X) {} // error: reference to invalid associated type 'X' of type 'S' }
デフォルト引数
inner関数のデフォルト引数に外の関数の引数を詰めるとセグフォで落ちる。 SILを作る時にアサーションでコケてる。比較的コントリビュートしやすそうなので頑張りたい。
bool isGlobalLazilyInitialized(swift::VarDecl *): Assertion `!var->getDeclContext()->isLocalContext() && "not a global variable!"' failed.
func bar(foo: Int) { func inner(arg: Int = foo) {} print(inner()) } bar(foo: 3)
https://bugs.swift.org/browse/SR-7328
assoctypeのデフォルト型
2018/04/17追記
デフォルト型を持つassoctypeをシグネチャに持つ要素を2個実装するとコンパイルが通らなくなる。XをIntに展開して書くと通る。witness tableが壊れてそう。
ついでに、デフォルト型を持つassoctypeが補完に出ないので困ってる。
error: reference to invalid associated type 'X' of type 'S' func f2(_ x: X) {}
protocol P { associatedtype X = Int func f1(_ x: X) func f2(_ x: X) } struct S: P { func f1(_ x: X) {} func f2(_ x: X) {} }
https://bugs.swift.org/browse/SR-7467
where付きextensionでassoctypeを上書き
型を再帰的に決定させるためにassoctype
のデフォルト型で再帰の終了条件を表現できるなー、と思い書いてみたところクラッシュした。
protocol P { associatedtype Assoc = A } struct A {} struct B: P { typealias Assoc = Int } struct S<T>: P {} extension S where T: P { typealias Assoc = T.Assoc } print(type(of: S<B>.Assoc.self)) // expected 'Int'