kateinoigakukunのブログ

思考垂れ流しObserver

18になった

18歳になりました。1年間の振り返りと気持ちを書いておきます。

17歳

やったこととしてはこんな感じ。

  • iOSエンジニアのバイト
  • 勉強会参加
  • カンファレンス登壇
  • コンパイラいじり
  • 卒論
  • OSS開発
  • アニメ鑑賞

自分としては結構充実していたと思ってます。が、行き当たりばったりにやっていたので計画を立てていたらもう少し色々出来たかもなーという思いもあります。

バイト

スタートトゥデイテクノロジーズでのアルバイトはめちゃめちゃ楽しかったです。隣でコンパイラの穴を見つけてボコボコに壊してる人がいたり、AndroidエンジニアとRxの話ができる環境は楽しすぎました。あと、Type Orientedな設計のお陰でパチっと機能追加できた、みたいなことがたまにあって体験が良かったです。

元々複数人での開発を経験してみたいというモチベーションで始めたので、これは実績解除できました。

勉強会

主にわいわいswittcに参加してました。5月の#2くらいまでは興味はあるが話を聞いても「ふむふむ分からん」状態だったのでフワフワ聞いてました。7月に入ってからコードを読み進めながら勉強会のアーカイブ動画を見るというスタイルで勉強してみました。自分の勉強した範囲と発表のテーマの知識を補完し合いながら読んでいくのは効率が良かったのでオススメです。

カンファレンス登壇

これ。 iOSDCで登壇したよ - kateinoigakukunのブログ

登壇をきっかけに僕が一方的に認知していた人から認知してもらえたり、「あ、あの人か!」と言ってもらえることが増えました。あとフォロワーもチョット増えました。

卒論

iOSDCで話した内容で書いてます。

OSS開発

主にInterface Builder用のLinterのIBLinterを作ってました。 GitHub - IBDecodable/IBLinter: A linter tool for Interface Builder

ポコポコPRやissueが立ったりして嬉しいなーという感じです。オーガニゼーション作ろうぜ!って言ってくれる人とかオーガニゼーションのアイコンを作ってくれる人が出てきて、インターネットって凄い!

18歳

重いので反転。

17歳の反省ポイントは「自己肯定感の低さ」なので、これをどうにかしていきたいです。 自意識過剰かもしれないですが、最近自分の年齢と興味の範囲のニッチさによって、周囲から過剰に評価されている気がしています。 評価と現実が乖離していて辛い〜って感じです。 18歳はそこの乖離を正しく認めることから始めたいと思います。

今月の終わりからバイト先がメルカリになります。今最高に楽しいらしいので楽しみです。 やっていくぞ💪

アレ

http://amzn.asia/hs44Fgt

iOSDCで登壇したよ

今年はスピーカーとして参加しました。

発表内容

とりあえずスライドを貼ります。コンパイラのコード部分は口頭で喋った部分が多いので動画が公開されるのを待ってください。

スライドも貼ったのでこの記事の役割は終わりました。ここからは思ったことをつらつらと書いていきます。

スピーカーとして

登壇するのは今回が初めてだったのでスライドを準備しながら結構緊張してました。実際、テーマがニッチかつ聴衆のコンテキストがほぼ無いので、「うーむわからん」という感想になるのが1番怖かったです。

ただ、事前に練習会に参加してレビューしてもらったためか、実際の登壇時には話しながら自分で楽しくなるくらいの感覚で話せました。本当はExistentialとかLLVM IRレベルの挙動、ディスパッチ機構の設計についても話したかったですが、まあどう考えても30分に収まらないので泣く泣くスライドを埋葬しました。

個人的にはQAコーナーが1番楽しかったです。きちんと受け答えできたか今となっては忘却の彼方ですが、セッションの本編で時間の都合上話せなかったことを質問してくれたり、勝手にわいわいし始めたり、とても濃かったです。

面白かったトーク

オープンソースMDMのmicromdmを用いて、個人でデバイス管理をやってみる

iOSDC2018-MicroMDM.pdf - Speaker Deck

家で簡易MDMを運用しているので、無料で証明書を発行できるサービスは有益情報でした。落ち着いたらゴチャゴチャ触ってみます。

MicroViewControllerで無限にスケールするiOS開発

www.icloud.com

前から内容自体は聞いていましたが @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でコンパイラの話をします

お前は誰

話します

こんにちは。今週の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_alloc1ACMLlazy cache variable for type metadata for class_alloc.A。文字通りmetadataのキャッシュだろう。初回のアクセスではキャッシュされていないので%1にはnullが入る。そのため、%2trueとなり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は、

  1. %ft_metadata型のポインタから0番目の要素の2番目のフィールド(i64型)を取得
  2. それを%objc_classのポインタにキャスト
  3. %objc_classのポインタを引数に取り、swift_getInitializedObjCClassという関数を呼び出す
  4. %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_alloc1ACMffull 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が直で書いてあった。

github.com

アクセサも一つの式で表現されてた。

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を生やすとambiguousA.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'

├の読み方

「├ 読み方 数学」とかいうアホっぽいクエリを投げてしまった。

シークエント - Wikipedia

一番悲しくなったのは、この記号が出現した数行下にキチンと解説してあったこと。さすが型システム入門、素晴らしい入門書だ。

コンパイルが通ってほしい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

github.com

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'