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