kateinoigakukunのブログ

思考垂れ流しObserver

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 }
}