Swift Type metadata

Swiftには実行時に型情報を保持するための`Type metadata`という仕組みがあります。我々が頻繁に使うことはありませんが、Swiftのランタイムの動作を理解するための重要な要素です。

この記事では `Type metadata` についてコンパイラのコードとドキュメントから調べたことを簡単に解説します。

## `Type metadata` とは

Swiftが実行時に保持している型情報です。ジェネリックでない`class`、`struct`、`enum`はコンパイル時に静的に作られますが、ジェネリックな型と関数型、タプルなどのnon-nominalな型については実行時に動的に作られます。
この`Type metadata` には

- その型の種類（`enum`なのか`struct`なのか、それとも`class`なのか）
- `ValueWitnessTable`（型を操作するための関数群）
- 型パラメータ
- タプルのラベル

などの情報が含まれています。型の種類によってレイアウトが大幅に違うので逐一[ドキュメント](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/docs/ABI/TypeMetadata.rst)を読んでいくのがオススメです。

## `Type metadata` をSwiftから扱う

では、`Type metadata` を使ってSwiftからクラスのインスタンスサイズを取得してみましょう。

`Type metadata`から目的の情報を取得するには、その情報がメタデータのメモリレイアウト上のどの位置に存在するかを知る必要があります。[swift/TypeMetadata.rst](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/docs/ABI/TypeMetadata.rst#class-metadata) を参考にメモリレイアウトを再現していくと以下のようになります。

```swift
struct ClassMetadata {
    let isaPointer: Int
    let superClass: Any.Type
    let objcRuntimeReserved1: Int
    let objcRuntimeReserved2: Int
    let rodataPointer: Int
    let classFlags: Int32
    let instanceAddressPoint: Int32
    let instanceSize: Int32
}
```

さて、メモリ上の表現が分かったので実際に`Type metadata`を取得してみましょう。
Swiftにおける`Metatype`はその型のインスタンスの`Type metadata`へのポインタになっています。とりあえず`unsafeBitCast`でポインタ型にキャストして試してみます。

```swift
class Cat {}
let catType: Cat.Type = Cat.self
MemoryLayout.size(ofValue: Cat.self) // 8
let metadataPointer = unsafeBitCast(catType, to: UnsafePointer<ClassMetadata>.self)
let metadata = metadataPointer.pointee
print(metadata.instanceSize) // 16
```

目的のインスタンスサイズが取得できました :tada: 

しかし、この方法で`struct`のメタデータを取得しようとしてもうまくいきません。

```swift
struct Stone {}
let stoneType: Stone.Type = Stone.self
MemoryLayout.size(ofValue: Stone.self) // 0
let metadataPointer = unsafeBitCast(stoneType, to: UnsafePointer<StructMetadata>.self)
let metadata = metadataPointer.pointee // Crash! :bomb: 
```

どうやら`Type metadata`を正しく使うにはもう少し知るべきことがあるようです。

## Thin Metatype

`struct`のメタタイプのサイズが0になってしまう原因を探るために、メタタイプを生成しているコンパイラのコードを読んでみましょう。

[swift/MetadataRequest.cpp](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/lib/IRGen/MetadataRequest.cpp#L2412-L2429)

```cpp
/// Emit a metatype value for a known type.
void irgen::emitMetatypeRef(IRGenFunction &IGF, CanMetatypeType type,
                            Explosion &explosion) {
  switch (type->getRepresentation()) {
  case MetatypeRepresentation::Thin:
    // Thin types have a trivial representation.
    break;

  case MetatypeRepresentation::Thick:
    explosion.add(IGF.emitTypeMetadataRef(type.getInstanceType()));
    break;

  case MetatypeRepresentation::ObjC:
    explosion.add(emitClassHeapMetadataRef(IGF, type.getInstanceType(),
                                           MetadataValueType::ObjCClass,
                                           MetadataState::Complete));
    break;
  }
}
```

型の表現方法が`MetatypeRepresentation::Thin`になっているとランタイム情報が出力されていません。では、`MetatypeRepresentation`とは何でしょう。

[swift/Types.h](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/include/swift/AST/Types.h#L2253-L2268)

```cpp
enum class MetatypeRepresentation : char {
  /// A thin metatype requires no runtime information, because the
  /// type itself provides no dynamic behavior.
  ///
  /// Struct and enum metatypes are thin, because dispatch to static
  /// struct and enum members is completely static.
  Thin,
  /// A thick metatype refers to a complete metatype representation
  /// that allows introspection and dynamic dispatch. 
  ///
  /// Thick metatypes are used for class and existential metatypes,
  /// which permit dynamic behavior.
  Thick,
  /// An Objective-C metatype refers to an Objective-C class object.
  ObjC
};
```
SwiftのメタタイプはThick、Thin、ObjCの3種類に分類されています。

- Thin
    - `struct`や`enum`などの静的に挙動が決まる型
- Thick
    - `class`などの動的な挙動をする型
- ObjC
    - Objective-C由来の型

Thinな型は静的に振る舞いが決まるためランタイム情報が必要にならず、メタタイプのサイズが0になります。これが`struct`のメタタイプの取得が失敗した理由です。

```swift
protocol Animal {
    static func kind() -> String
}
struct Cat {
    static func kind() -> String {
        return "猫"
    }
}

let catType: Cat.Type = Cat.self // Thin
let animalType: Animal.Type = Cat.self // Thick
animalType.kind() // 猫
```

一方で上記の例のようにメタタイプのサブタイピングによってThin型をThick型に代入することができます。
また`animalType.kind()`が正しく動作することから、Existentialのメタタイプには実際のメタタイプが保持されていることも分かります。つまり、Existential Metatypeを経由すればThin型の`Type metadata`も取得できそうです。

## Existential Metatype container

Existential Metatype containerはExistential Metatypeの実行時表現で、Existential containerのメタタイプ版のようなものです。内部に実際の型のインスタンスの`Type metadata`とwitness tableへのポインタ保持しています。
[swift/GenExistential.cpp](https://github.com/apple/swift/blob/b701dc4f10c95ad7308a346a64d02ea892b459d4/lib/IRGen/GenExistential.cpp#L1742-L1765)

```swift
struct ExistentialMetatypeContainer<Metadata> {
    let metadata: UnsafePointer<Metadata>
    let witnessTable: UnsafePointer<WitnessTable>
}
```

これを使って先程失敗した`struct`のメタデータを取得してみましょう。

```swift
struct StructMetadata {
    let kind: Int
}
protocol Animal {}
struct Cat: Animal {}

let animalType: Animal.Type = Cat.self
let metatypeContainer = unsafeBitCast(animalType, to: ExistentialMetatypeContainer<StructMetadata>.self)
metatypeContainer.metadataPointer.pointee.kind // 1
```

[struct metadataのkindは1であるため](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/docs/ABI/TypeMetadata.rst#common-metadata-layout)、無事目的の`Type metadata`が取得できたことが分かります。

しかし、この方法には一つ問題点があります。メタデータを取得したいThin型を何かしらのプロトコルに準拠させなければExistential Metatype containerに詰めることができないのです。 :scream: 

## Any.Type

しかし、我々は全ての型のsupertypeとして振る舞う`Any`を持っています。加えてAnyはnon-nominalであるためThickな型として振る舞い、ランタイム情報を保持しています。

```swift
struct Stone {}
let stoneType: Any.Type = Stone.self
let metadataPointer = unsafeBitCast(stoneType, to: UnsafePointer<StructMetadata>.self)
let metadata = metadataPointer.pointee
metadata.kind // 1
```

こうして安全（？）な`Type metadata`の取得方法が確立しました :tada: 

## まとめ

`Type metadata`は実際に[Server Side SwiftのフレームワークZewoなどで使われており](https://github.com/Zewo/Reflection)、Swiftの言語機能を拡張できる夢の情報源です。一方で、ドキュメント化されていない部分が多く、変更が加えられる可能性もあります。使う際は入念にテストを書くなどの対策が必要です。

楽しく適切に`Type metadata`と戯れましょう。


## 参考

- [swift/TypeMetadata.rst · apple/swift](https://github.com/apple/swift/blob/5a969418030f008c2276e9e4d482928d2b1b50e4/docs/ABI/TypeMetadata.rst)