HeapObject - ShenYj/ShenYj.github.io GitHub Wiki
HeapObject
源码学习
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata), refCounts(InlineRefCounts::Initialized)
{ }
// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata, InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata), refCounts(InlineRefCounts::Immortal)
{ }
};
...
最核心的成员变量 metadata
, 实际类型为 TargetHeapMetadata
通常我们使用
typedef
来起别名,这里是C++
的另一种别名的语法, 利用using
using
在c++
中有三个作用
- 引入命名空间
- 指定别名
- 在子类中引用基类的成员
using HeapMetadata = TargetHeapMetadata<InProcess>;
/// The common structure of all metadata for heap-allocated types. A
/// pointer to one of these can be retrieved by loading the 'isa'
/// field of any heap object, whether it was managed by Swift or by
/// Objective-C. However, when loading from an Objective-C object,
/// this metadata may not have the heap-metadata header, and it may
/// not be the Swift type metadata for the object's dynamic type.
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
TargetHeapMetadata
是一个模板类型TargetHeapMetadata<InProcess>
,依赖于传入的参数InProcess
constexpr
是一个c++
关键字,有点类似于const
但又不同,可以在编译期间告诉编译器对此处进行优化
结构体的定义中代码不多,关键点是构造函数, 可获得如下信息
- 构造函数中会传递一个参数
MetadataKind kind
,而这个参数正是模板中传入的参数InProcess
- 继承自
TargetMetadata
-
InProcess
同样是一个结构体类型InProcess 代码
/// In-process native runtime target. /// /// For interactions in the runtime, this should be the equivalent of working /// with a plain old pointer type. struct InProcess { static constexpr size_t PointerSize = sizeof(uintptr_t); using StoredPointer = uintptr_t; using StoredSignedPointer = uintptr_t; using StoredSize = size_t; using StoredPointerDifference = ptrdiff_t; static_assert(sizeof(StoredSize) == sizeof(StoredPointerDifference), "target uses differently-sized size_t and ptrdiff_t"); template <typename T> using Pointer = T*; template <typename T> using SignedPointer = T; template <typename T, bool Nullable = false> using FarRelativeDirectPointer = FarRelativeDirectPointer<T, Nullable>; template <typename T, bool Nullable = false> using RelativeIndirectablePointer = RelativeIndirectablePointer<T, Nullable>; template <typename T, bool Nullable = true> using RelativeDirectPointer = RelativeDirectPointer<T, Nullable>; };
/// The common structure of all type metadata.
template <typename Runtime>
struct TargetMetadata {
using StoredPointer = typename Runtime::StoredPointer;
/// The basic header type.
typedef TargetTypeMetadataHeader<Runtime> HeaderType;
constexpr TargetMetadata()
: Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
constexpr TargetMetadata(MetadataKind Kind)
: Kind(static_cast<StoredPointer>(Kind)) {}
private:
/// The kind. Only valid for non-class metadata; getKind() must be used to get
/// the kind value.
StoredPointer Kind;
public:
/// Get the metadata kind.
MetadataKind getKind() const {
return getEnumeratedMetadataKind(Kind);
}
/// Set the metadata kind.
void setKind(MetadataKind kind) {
Kind = static_cast<StoredPointer>(kind);
}
...
/// Get the class object for this type if it has one, or return null if the
/// type is not a class (or not a class with a class object).
const TargetClassMetadata<Runtime> *getClassObject() const;
...
成员变量 StoredPointer Kind;
-
StoredPointer
的真实类型对应在InProcess
中using StoredPointer = uintptr_t;
, ->uintptr_t
类型 ->unsigned long
类型 -
Kind
用于区分元数据的类型, 通过注释中提到的getKind()
以及返回值类型MetadataKind
如果继承自
NSObject
, 那么Kind 相当于 isa指针
在
MetadataKind.def
文件中, 记录了所有元数据的类型,比如class、struct、enum、tuple等等。
除了Kind
外,getClassObject()
方法是用来获取当前的class object的元数据
getClassObject()
实现(swift/stdib/public/runtime/Private.h
)
template<> inline const ClassMetadata *
Metadata::getClassObject() const {
switch (getKind()) {
case MetadataKind::Class: {
// Native Swift class metadata is also the class object.
return static_cast<const ClassMetadata *>(this);
}
case MetadataKind::ObjCClassWrapper: {
// Objective-C class objects are referenced by their Swift metadata wrapper.
auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
return wrapper->Class;
}
// Other kinds of types don't have class objects.
default:
return nullptr;
}
实现中也有使用到getKind()
-
Class
-> Swift 对象/// A class type. NOMINALTYPEMETADATAKIND(Class, 0)
using ClassMetadata = TargetClassMetadata<InProcess>;
- 将当前
TargetMetadata
类型强转为TargetClassMetadata
指针类型返回, 相当于 swift 中 对象的实际类型为TargetClassMetadata
-
ObjCClassWrapper
-> Object-C 对象/// An ObjC class wrapper. METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
-
default
-> 非 Class 对象
由此得到 swift 的元数据类型 TargetClassMetadata
-
摘自源码中的一些描述信息
TypeMetadata.rst
:orphan: .. _ABI: .. highlight:: none Type Metadata ------------- The Swift runtime keeps a **metadata record** for every type used in a program, including every instantiation of generic types. These metadata records can be used by (TODO: reflection and) debugger tools to discover information about types. For non-generic nominal types, these metadata records are generated statically by the compiler. For instances of generic types, and for intrinsic types such as tuples, functions, protocol compositions, etc., metadata records are lazily created by the runtime as required. Every type has a unique metadata record; two **metadata pointer** values are equal iff the types are equivalent. In the layout descriptions below, offsets are given relative to the metadata pointer as an index into an array of pointers. On a 32-bit platform, **offset 1** means an offset of 4 bytes, and on 64-bit platforms, it means an offset of 8 bytes. Common Metadata Layout ~~~~~~~~~~~~~~~~~~~~~~ All metadata records share a common header, with the following fields: - The **value witness table** pointer references a vtable of functions that implement the value semantics of the type, providing fundamental operations such as allocating, copying, and destroying values of the type. The value witness table also records the size, alignment, stride, and other fundamental properties of the type. The value witness table pointer is at **offset -1** from the metadata pointer, that is, the pointer-sized word **immediately before** the pointer's referenced address. - The **kind** field is a pointer-sized integer that describes the kind of type the metadata describes. This field is at **offset 0** from the metadata pointer. The current kind values are as follows: * `Class metadata`_ has of kind of **0** unless the class is required to interoperate with Objective-C. If the class is required to interoperate with Objective-C, the kind field is instead an *isa pointer* to an Objective-C metaclass. Such a pointer can be distinguished from an enumerated metadata kind because it is guaranteed to have a value larger than **2047**. Note that this is a more basic sense of interoperation than is meant by the ``@objc`` attribute: it is what is required to support Objective-C message sends and retain/release. All classes are required to interoperate with Objective-C on this level when building for an Apple platform. * `Struct metadata`_ has a kind of **1**. * `Enum metadata`_ has a kind of **2**. * `Optional metadata`_ has a kind of **3**. * **Opaque metadata** has a kind of **8**. This is used for compiler ``Builtin`` primitives that have no additional runtime information. * `Tuple metadata`_ has a kind of **9**. * `Function metadata`_ has a kind of **10**. * `Protocol metadata`_ has a kind of **12**. This is used for protocol types, for protocol compositions, and for the ``Any`` type. * `Metatype metadata`_ has a kind of **13**. * `Objective C class wrapper metadata`_ has a kind of **14**. * `Existential metatype metadata`_ has a kind of **15**. Struct Metadata ~~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, struct metadata records contain the following fields: - The `nominal type descriptor`_ is referenced at **offset 1**. - If the struct is generic, then the `generic argument vector`_ begins at **offset 2**. - A vector of **field offsets** begins immediately after the generic argument vector. For each field of the struct, in ``var`` declaration order, the field's offset in bytes from the beginning of the struct is stored as a pointer-sized integer. Enum Metadata ~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, enum metadata records contain the following fields: - The `nominal type descriptor`_ is referenced at **offset 1**. - If the enum is generic, then the `generic argument vector`_ begins at **offset 2**. Optional Metadata ~~~~~~~~~~~~~~~~~ Optional metadata share the same basic layout as enum metadata. They are distinguished from enum metadata because of the importance of the ``Optional`` type for various reflection and dynamic-casting purposes. Tuple Metadata ~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, tuple metadata records contain the following fields: - The **number of elements** in the tuple is a pointer-sized integer at **offset 1**. - The **labels string** is a pointer to the labels for the tuple elements at **offset 2**. Labels are encoded in UTF-8 and separated by spaces, and the entire string is terminated with a null character. For example, the labels in the tuple type ``(x: Int, Int, z: Int)`` would be encoded as the character array ``"x z \0"``. A label (possibly zero-length) is provided for each element of the tuple, meaning that the label string for a tuple of **n** elements always contains exactly **n** spaces. If the tuple has no labels at all, the label string is a null pointer. - The **element vector** begins at **offset 3** and consists of an array of type-offset pairs. The metadata for the *n*\ th element's type is a pointer at **offset 3+2*n**. The offset in bytes from the beginning of the tuple to the beginning of the *n*\ th element is at **offset 3+2*n+1**. Function Metadata ~~~~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, function metadata records contain the following fields: - The function flags are stored at **offset 1**. This includes information such as the semantic convention of the function, whether the function throws, and how many parameters the function has. - A reference to the **result type** metadata record is stored at *offset 2**. If the function has multiple returns, this references a `tuple metadata`_ record. - The **parameter type vector** follows the result type and consists of **NumParameters** type metadata pointers corresponding to the types of the parameters. - The optional **parameter flags vector** begins after the end of **parameter type vector** and consists of **NumParameters** 32-bit flags. This includes information such as whether the parameter is ``inout`` or whether it is variadic. The presence of this vector is signalled by a flag in the function flags; if no parameter has any non-default flags, the flag is not set. Currently we have specialized ABI endpoints to retrieve metadata for functions with 0/1/2/3 parameters - `swift_getFunctionTypeMetadata{0|1|2|3}` and the general one `swift_getFunctionTypeMetadata` which handles all other function types and functions with parameter flags e.g. `(inout Int) -> Void`. Based on the usage information collected from Swift Standard Library and Overlays as well as Source Compatibility Suite it was decided not to have specialized ABI endpoints for functions with parameter flags due their minimal use. Protocol Metadata ~~~~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, protocol metadata records contain the following fields: - A **layout flags** word is stored at **offset 1**. The bits of this word describe the existential container layout used to represent values of the type. The word is laid out as follows: * The **number of witness tables** is stored in the least significant 24 bits. Values of the protocol type contain this number of witness table pointers in their layout. * The **special protocol kind** is stored in 6 bits starting at bit 24. Only one special protocol kind is defined: the `Error` protocol has value 1. * The **superclass constraint indicator** is stored at bit 30. When set, the protocol type includes a superclass constraint (described below). * The **class constraint** is stored at bit 31. This bit is set if the type is **not** class-constrained, meaning that struct, enum, or class values can be stored in the type. If not set, then only class values can be stored in the type, and the type uses a more efficient layout. - The **number of protocols** that make up the protocol composition is stored at **offset 2**. For the "any" types ``Any`` or ``AnyObject``, this is zero. For a single-protocol type ``P``, this is one. For a protocol composition type ``P & Q & ...``, this is the number of protocols. - If the **superclass constraint indicator** is set, type metadata for the superclass follows at the next offset. - The **protocol vector** follows. This is an inline array of pointers to descriptions of each protocol in the composition. Each pointer references either a Swift `protocol descriptor`_ or an Objective-C `Protocol`; the low bit will be set to indicate when it references an Objective-C protocol. For an "any" or "AnyObject" type, there is no protocol descriptor vector. Metatype Metadata ~~~~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, metatype metadata records contain the following fields: - A reference to the metadata record for the **instance type** that the metatype represents is stored at **offset 1**. Existential Metatype Metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to the `common metadata layout`_ fields, existential metatype metadata records contain the following fields: - A reference to the metadata record for the **instance type** of the metatype is stored at **offset 1**. This is always either an existential type metadata or another existential metatype. - A word of flags summarizing the existential type are stored at **offset 2**. Class Metadata ~~~~~~~~~~~~~~ Class metadata is designed to interoperate with Objective-C; all class metadata records are also valid Objective-C ``Class`` objects. Class metadata pointers are used as the values of class metatypes, so a derived class's metadata record also serves as a valid class metatype value for all of its ancestor classes. - The **destructor pointer** is stored at **offset -2** from the metadata pointer, behind the value witness table. This function is invoked by Swift's deallocator when the class instance is destroyed. - The **isa pointer** pointing to the class's Objective-C-compatible metaclass record is stored at **offset 0**, in place of an integer kind discriminator. - The **super pointer** pointing to the metadata record for the superclass is stored at **offset 1**. If the class is a root class, it is null. - On platforms which support Objective-C interoperability, two words are reserved for use by the Objective-C runtime at **offset 2** and **offset 3**; on other platforms, nothing is reserved. - On platforms which support Objective-C interoperability, the **rodata pointer** is stored at **offset 4**; on other platforms, it is not present. The rodata pointer points to an Objective-C compatible rodata record for the class. This pointer value includes a tag. The **low bit is always set to 1** for Swift classes and always set to 0 for Objective-C classes. - The **class flags** are a 32-bit field at **offset 5** on platforms which support Objective-C interoperability; on other platforms, the field is at **offset 2**. - The **instance address point** is a 32-bit field following the class flags. A pointer to an instance of this class points this number of bytes after the beginning of the instance. - The **instance size** is a 32-bit field following the instance address point. This is the number of bytes of storage present in every object of this type. - The **instance alignment mask** is a 16-bit field following the instance size. This is a set of low bits which must not be set in a pointer to an instance of this class. - The **runtime-reserved field** is a 16-bit field following the instance alignment mask. The compiler initializes this to zero. - The **class object size** is a 32-bit field following the runtime-reserved field. This is the total number of bytes of storage in the class metadata object. - The **class object address point** is a 32-bit field following the class object size. This is the number of bytes of storage in the class metadata object. - The `nominal type descriptor`_ for the most-derived class type is referenced at an offset immediately following the class object address point. On 64-bit and 32-bit platforms which support Objective-C interoperability, this is, respectively, at **offset 8** and at **offset 11**; in platforms that do not support Objective-C interoperability, this is, respectively, at **offset 5** and at **offset 8**. - For each Swift class in the class's inheritance hierarchy, in order starting from the root class and working down to the most derived class, the following fields are present: * First, a reference to the **parent** metadata record is stored. For classes that are members of an enclosing nominal type, this is a reference to the enclosing type's metadata. For top-level classes, this is null. TODO: The parent pointer is currently always null. * If the class is generic, its `generic argument vector`_ is stored inline. * The **vtable** is stored inline and contains a function pointer to the implementation of every method of the class in declaration order. * If the layout of a class instance is dependent on its generic parameters, then a **field offset vector** is stored inline, containing offsets in bytes from an instance pointer to each field of the class in declaration order. (For classes with fixed layout, the field offsets are accessible statically from global variables, similar to Objective-C ivar offsets.) Note that none of these fields are present for Objective-C base classes in the inheritance hierarchy. Objective C class wrapper metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Objective-C class wrapper metadata are used when an Objective-C ``Class`` object is not a valid Swift type metadata. In addition to the `common metadata layout`_ fields, Objective-C class wrapper metadata records have the following fields: - A ``Class`` value at **offset 1** which is known to not be a Swift type metadata. Generic Argument Vector ~~~~~~~~~~~~~~~~~~~~~~~ Metadata records for instances of generic types contain information about their generic arguments. For each parameter of the type, a reference to the metadata record for the type argument is stored. After all of the type argument metadata references, for each type parameter, if there are protocol requirements on that type parameter, a reference to the witness table for each protocol it is required to conform to is stored in declaration order. For example, given a generic type with the parameters ``<T, U, V>``, its generic parameter record will consist of references to the metadata records for ``T``, ``U``, and ``V`` in succession, as if laid out in a C struct:: struct GenericParameterVector { TypeMetadata *T, *U, *V; }; If we add protocol requirements to the parameters, for example, ``<T: Runcible, U: Fungible & Ansible, V>``, then the type's generic parameter vector contains witness tables for those protocols, as if laid out:: struct GenericParameterVector { TypeMetadata *T, *U, *V; RuncibleWitnessTable *T_Runcible; FungibleWitnessTable *U_Fungible; AnsibleWitnessTable *U_Ansible; }; Foreign Class Metadata ~~~~~~~~~~~~~~~~~~~~~~ Foreign class metadata describes "foreign" class types, which support Swift reference counting but are otherwise opaque to the Swift runtime. - The `nominal type descriptor`_ for the most-derived class type is stored at **offset 0**. - The **super pointer** pointing to the metadata record for the superclass is stored at **offset 1**. If the class is a root class, it is null. - Three **pointer-sized fields**, starting at **offset 2**, are reserved for future use. Nominal Type Descriptor ~~~~~~~~~~~~~~~~~~~~~~~ **Warning: this is all out of date!** The metadata records for class, struct, and enum types contain a pointer to a **nominal type descriptor**, which contains basic information about the nominal type such as its name, members, and metadata layout. For a generic type, one nominal type descriptor is shared for all instantiations of the type. The layout is as follows: - The **kind** of type is stored at **offset 0**, which is as follows: * **0** for a class, * **1** for a struct, or * **2** for an enum. - The mangled **name** is referenced as a null-terminated C string at **offset 1**. This name includes no bound generic parameters. - The following four fields depend on the kind of nominal type. * For a struct or class: + The **number of fields** is stored at **offset 2**. This is the length of the field offset vector in the metadata record, if any. + The **offset to the field offset vector** is stored at **offset 3**. This is the offset in pointer-sized words of the field offset vector for the type in the metadata record. If no field offset vector is stored in the metadata record, this is zero. + The **field names** are referenced as a doubly-null-terminated list of C strings at **offset 4**. The order of names corresponds to the order of fields in the field offset vector. + The **field type accessor** is a function pointer at **offset 5**. If non-null, the function takes a pointer to an instance of type metadata for the nominal type, and returns a pointer to an array of type metadata references for the types of the fields of that instance. The order matches that of the field offset vector and field name list. * For an enum: + The **number of payload cases** and **payload size offset** are stored at **offset 2**. The least significant 24 bits are the number of payload cases, and the most significant 8 bits are the offset of the payload size in the type metadata, if present. + The **number of no-payload cases** is stored at **offset 3**. + The **case names** are referenced as a doubly-null-terminated list of C strings at **offset 4**. The names are ordered such that payload cases come first, followed by no-payload cases. Within each half of the list, the order of names corresponds to the order of cases in the enum declaration. + The **case type accessor** is a function pointer at **offset 5**. If non-null, the function takes a pointer to an instance of type metadata for the enum, and returns a pointer to an array of type metadata references for the types of the cases of that instance. The order matches that of the case name list. This function is similar to the field type accessor for a struct, except also the least significant bit of each element in the result is set if the enum case is an **indirect case**. - If the nominal type is generic, a pointer to the **metadata pattern** that is used to form instances of the type is stored at **offset 6**. The pointer is null if the type is not generic. - The **generic parameter descriptor** begins at **offset 7**. This describes the layout of the generic parameter vector in the metadata record: * The **offset of the generic parameter vector** is stored at **offset 7**. This is the offset in pointer-sized words of the generic parameter vector inside the metadata record. If the type is not generic, this is zero. * The **number of type parameters** is stored at **offset 8**. This count includes associated types of type parameters with protocol constraints. * The **number of type parameters** is stored at **offset 9**. This count includes only the primary formal type parameters. * For each type parameter **n**, the following fields are stored: + The **number of witnesses** for the type parameter is stored at **offset 10+n**. This is the number of witness table pointers that are stored for the type parameter in the generic parameter vector. Note that there is no nominal type descriptor for protocols or protocol types. See the `protocol descriptor`_ description below. Protocol Descriptor ~~~~~~~~~~~~~~~~~~~ Protocol descriptors describe the requirements of a protocol, and act as a handle for the protocol itself. They are referenced by `Protocol metadata`_, as well as `Protocol Conformance Records`_ and generic requirements. Protocol descriptors are only created for non-`@objc` Swift protocols: `@objc` protocols are emitted as Objective-C metadata. The layout of Swift protocol descriptors is as follows: - Protocol descriptors are context descriptors, so they are prefixed by context descriptor metadata. (FIXME: these are not yet documented) - The 16-bit kind-specific flags of a protocol are defined as follows: * **Bit 0** is the **class constraint bit**. It is set if the protocol is **not** class-constrained, meaning that any struct, enum, or class type may conform to the protocol. It is unset if only classes can conform to the protocol. * **Bit 1** indicates that the protocol is **resilient**. * **Bits 2-7** indicate specify the **special protocol kind**. Only one special protocol kind is defined: the `Error` protocol has value 1. - A pointer to the **name** of the protocol. - The number of generic requirements within the **requirement signature** of the protocol. The generic requirements themselves follow the fixed part of the protocol descriptor. - The number of **protocol requirements** in the protocol. The protocol requirements follow the generic requirements that form the **requirement signature**. - A string containing the **associated type names**, a C string comprising the names of all of the associated types in this protocol, separated by spaces, and in the same order as they appear in the protocol requirements. - The **generic requirements** that form the **requirement signature**. - The **protocol requirements** of the protocol. Protocol Conformance Records ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A *protocol conformance record* states that a given type conforms to a particular protocol. Protocol conformance records are emitted into their own section, which is scanned by the Swift runtime when needed (e.g., in response to a `swift_conformsToProtocol()` query). Each protocol conformance record contains: - The `protocol descriptor`_ describing the protocol of the conformance, represented as an (possibly indirect) 32-bit offset relative to the field. The low bit indicates whether it is an indirect offset; the second lowest bit is reserved for future use. - A reference to the **conforming type**, represented as a 32-bit offset relative to the field. The lower two bits indicate how the conforming type is represented: 0. A direct reference to a nominal type descriptor. 1. An indirect reference to a nominal type descriptor. 2. Reserved for future use. 3. A reference to a pointer to an Objective-C class object. - The **witness table field** that provides access to the witness table describing the conformance itself, represented as a direct 32-bit relative offset. The lower two bits indicate how the witness table is represented: 0. The **witness table field** is a reference to a witness table. 1. The **witness table field** is a reference to a **witness table accessor** function for an unconditional conformance. 2. The **witness table field** is a reference to a **witness table accessor** function for a conditional conformance. 3. Reserved for future use. - A 32-bit value reserved for future use. Recursive Type Metadata Dependencies ------------------------------------ The Swift type system is built up inductively by the application of higher-kinded type constructors (such as "tuple" or "function", as well as user-defined generic types) to other, existing types. Crucially, it is the "least fixed point" of that inductive system, meaning that it does not include **infinite types** (µ-types) whose basic identity can only be defined in terms of themselves. That is, it is possible to write the type:: typealias IntDict = Dictionary<String, Int> but it is not possible to directly express the type:: typealias RecursiveDict = Dictionary<String, RecursiveDict> However, Swift does permit the expression of types that have recursive dependencies upon themselves in ways other than their basic identity. For example, class ``A`` may inherit from a superclass ``Base<A>``, or it may contain a field of type ``(A, A)``. In order to support the dynamic reification of such types into type metadata, as well as to support the dynamic layout of such types, Swift's metadata runtime supports a system of metadata dependency and iterative initialization. Metadata States ~~~~~~~~~~~~~~~ A type metadata may be in one of several different dynamic states: - An **abstract** metadata stores just enough information to allow the identity of the type to be recovered: namely, the metadata's kind (e.g. **struct**) and any kind-specific identity information it entails (e.g. the `nominal type descriptor`_ and any generic arguments). - A **layout-complete** metadata additionally stores the components of the type's "external layout", necessary to compute the layout of any type that directly stores a value of the type. In particular, a metadata in this state has a meaningful value witness table. - A **non-transitively complete** metadata has undergone any additional initialization that is required in order to support basic operations on the type. For example, a metadata in this state will have undergone any necessary "internal layout" that might be required in order to create values of the type but not to allocate storage to hold them. For example, a class metadata will have an instance layout, which is not required in order to compute the external layout, but is required in order to allocate instances or create a subclass. - A **complete** metadata additionally makes certain guarantees of transitive completeness of the metadata referenced from the metadata. For example, a complete metadata for ``Array<T>`` guarantees that the metadata for ``T`` stored in the generic arguments vector is also complete. Metadata never backtrack in their state. In particular, once metadata is complete, it remains complete forever. Transitive Completeness Guarantees ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A complete class metadata makes the following guarantees: - Its superclass metadata (if it has a superclass) is complete. - Its generic arguments (if it has any) are complete. - By implication, the generic arguments of its superclasses are complete. A complete struct, enum, or optional metadata makes the following guarantees: - Its generic arguments (if it has any) are complete. A complete tuple metadata makes the following guarantees: - Its element types are complete. Other kinds of type metadata do not make any completeness guarantees. The metadata kinds with transitive guarantees are the metadata kinds that potentially require two-phase initialization anyway. Other kinds of metadata could otherwise declare themselves complete immediately on allocation, so the transitive completeness guarantee would add significant complexity to both the runtime interface and its implementation, as well as adding probably-unrecoverable memory overhead to the allocation process. It is also true that it is far more important to be able to efficiently recover complete metadata from the stored arguments of a generic type than it is to be able to recover such metadata from a function metadata. Completeness Requirements ~~~~~~~~~~~~~~~~~~~~~~~~~ Type metadata are required to be transitively complete when they are presented to most code. This allows that code to work with the metadata without explicitly checking for its completeness. Metadata in the other states are typically encountered only when initializing or building up metadata. Specifically, a type metadata record is required to be complete when: - It is passed as a generic argument to a function (other than a metadata access function, witness table access function, or metadata initialization function). - It is used as a metatype value, including as the ``Self`` argument to a ``static`` or ``class`` method, including initializers. - It is used to build an opaque existential value. Metadata Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When calling a metadata access function, code must provide the following information: - the required state of the metadata, and - whether the callee should block until the metadata is available in that state. The access function will then return: - the metadata and - the current dynamic state of the metadata. Access functions will always return the correct metadata record; they will never return a null pointer. If the metadata has not been allocated at the time of the request, it will at least be allocated before the access function returns. The runtime will block the current thread until the allocation completes if necessary, and there is currently no way to avoid this. Since access functions always return metadata that is at least in the abstract state, it is not meaningful to make a non-blocking request for abstract metadata. The returned dynamic state of the metadata may be less than the requested state if the request was non-blocking. It is not otherwise affected by the request; it is the known dynamic state of the metadata at the time of the call. Note that of course this dynamic state is just a lower bound on the actual dynamic state of the metadata, since the actual dynamic state may be getting concurrently advanced by another thread. In general, most code should request metadata in the **complete** state (as discussed above) and should block until the metadata is available in that state. However: - When requesting metadata solely to serve as a generic argument of another metadata, code should request **abstract** metadata. This can potentially unblock cycles involving the two metadata. - Metadata initialization code should generally make non-blocking requests; see the next section. Metadata access functions that cache their results should only cache if the dynamic state is complete; this substantially simplifies the caching logic, and in practice most metadata will be dynamically complete. Note that this rule can be applied without considering the request. Code outside of the runtime should never attempt to ascertain a metadata's current state by inspecting it, e.g. to see if it has a value witness table. Metadata initialization is not required to use synchronization when initializing the metadata record; the necessary synchronization is done at a higher level in the structures which record the metadata's dynamic state. Because of this, code inspecting aspects of the metadata that have not been guaranteed by the returned dynamic state may observe partially-initialized state, such as a value witness table with a meaningless size value. Instead, that code should call the ``swift_checkMetadataState`` function. Metadata Allocation and Initialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order to support recursive dependencies between type metadata, the creation of type metadata is divided into two phases: - allocation, which creates an abstract metadata, and - initialization, which advances the metadata through the progression of states. Allocation cannot fail. It should return relatively quickly and should not make any metadata requests. The initialization phase will be repeatedly executed until it reaches completion. It is only executed by one thread at a time. Compiler-emitted initialization functions are given a certain amount of scratch space that is passed to all executions; this can be used to skip expensive or unrepeatable steps in later re-executions. Any particular execution of the initialization phase can fail due to an unsatisfied dependency. It does so by returning a **metadata dependency**, which is a pair of a metadata and a required state for that metadata. The initialization phase is expected to make only non-blocking requests for metadata. If a response does not satisfy the requirement, the returned metadata and the requirement should be presented to the caller as a dependency. The runtime does two things with this dependency: - It attempts to add the initialization to the **completion queue** of the dependent metadata. If this succeeds, the initialization is considered blocked; it will be unblocked as soon as the dependent metadata reaches the required state. But it can also fail if the dependency is already resolved due to concurrent initialization; if so, the initialization is immediately resumed. - If it succeeds in blocking the initialization on the dependency, it will check for an unresolvable dependency cycle. If a cycle exists, it will be reported on stderr and the runtime will abort the process. This depends on the proper use of non-blocking requests; the runtime does not make any effort to detect deadlock due to cycles of blocking requests. Initialization must not repeatedly report failure based on stale information about the dynamic state of a metadata. (For example, it must not cache metadata states from previous executions in the initialization scratch space.) If this happens, the runtime may spin, repeatedly executing the initialization phase only to have it fail in the same place due to the same stale dependency. Compiler-emitted initialization functions are only responsible for ensuring that the metadata is **non-transitively complete**. They signal this by returning a null dependency to the runtime. The runtime will then ensure transitive completion. The initialization function should not try to "help out" by requesting complete metadata instead of non-transitively-complete metadata; it is impossible to resolve certain recursive transitive-closure problems without the more holistic information available to the runtime. In general, if an initialization function seems to require transitively-complete metadata for something, try to make it not. If a compiler-emitted initialization function returns a dependency, the current state of the metadata (**abstract** vs. **layout-complete**) will be determined by inspecting the **incomplete** bit in the flags of the value witness table. Compiler-emitted initialization functions are therefore responsible for ensuring that this bit is set correctly.
TargetClassMetadata 代码
/// The structure of all class metadata. This structure is embedded
/// directly within the class's heap metadata structure and therefore
/// cannot be extended without an ABI break.
///
/// Note that the layout of this type is compatible with the layout of
/// an Objective-C class.
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
using StoredPointer = typename Runtime::StoredPointer;
using StoredSize = typename Runtime::StoredSize;
TargetClassMetadata() = default;
constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
ClassFlags flags,
ClassIVarDestroyer *ivarDestroyer,
StoredPointer size, StoredPointer addressPoint,
StoredPointer alignMask,
StoredPointer classSize, StoredPointer classAddressPoint)
: TargetAnyClassMetadata<Runtime>(base),
Flags(flags), InstanceAddressPoint(addressPoint),
InstanceSize(size), InstanceAlignMask(alignMask),
Reserved(0), ClassSize(classSize), ClassAddressPoint(classAddressPoint),
Description(nullptr), IVarDestroyer(ivarDestroyer) {}
// The remaining fields are valid only when isTypeMetadata().
// The Objective-C runtime knows the offsets to some of these fields.
// Be careful when accessing them.
/// Swift-specific class flags.
ClassFlags Flags;
/// The address point of instances of this type.
uint32_t InstanceAddressPoint;
/// The required size of instances of this type.
/// 'InstanceAddressPoint' bytes go before the address point;
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
uint32_t InstanceSize;
/// The alignment mask of the address point of instances of this type.
uint16_t InstanceAlignMask;
/// Reserved for runtime use.
uint16_t Reserved;
/// The total size of the class object, including prefix and suffix
/// extents.
uint32_t ClassSize;
/// The offset of the address point within the class object.
uint32_t ClassAddressPoint;
// Description is by far the most likely field for a client to try
// to access directly, so we force access to go through accessors.
private:
/// An out-of-line Swift-specific description of the type, or null
/// if this is an artificial subclass. We currently provide no
/// supported mechanism for making a non-artificial subclass
/// dynamically.
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
public:
/// A function for destroying instance variables, used to clean up after an
/// early return from a constructor. If null, no clean up will be performed
/// and all ivars must be trivial.
TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
// After this come the class members, laid out as follows:
// - class members for the superclass (recursively)
// - metadata reference for the parent, if applicable
// - generic parameters for this class
// - class variables (if we choose to support these)
// - "tabulated" virtual methods
using TargetAnyClassMetadata<Runtime>::isTypeMetadata;
ConstTargetMetadataPointer<Runtime, TargetClassDescriptor>
getDescription() const {
assert(isTypeMetadata());
return Description;
}
typename Runtime::StoredSignedPointer
getDescriptionAsSignedPointer() const {
assert(isTypeMetadata());
return Description;
}
void setDescription(const TargetClassDescriptor<Runtime> *description) {
Description = description;
}
/// Is this class an artificial subclass, such as one dynamically
/// created for various dynamic purposes like KVO?
bool isArtificialSubclass() const {
assert(isTypeMetadata());
return Description == nullptr;
}
void setArtificialSubclass() {
assert(isTypeMetadata());
Description = nullptr;
}
ClassFlags getFlags() const {
assert(isTypeMetadata());
return Flags;
}
void setFlags(ClassFlags flags) {
assert(isTypeMetadata());
Flags = flags;
}
StoredSize getInstanceSize() const {
assert(isTypeMetadata());
return InstanceSize;
}
void setInstanceSize(StoredSize size) {
assert(isTypeMetadata());
InstanceSize = size;
}
StoredPointer getInstanceAddressPoint() const {
assert(isTypeMetadata());
return InstanceAddressPoint;
}
void setInstanceAddressPoint(StoredSize size) {
assert(isTypeMetadata());
InstanceAddressPoint = size;
}
StoredPointer getInstanceAlignMask() const {
assert(isTypeMetadata());
return InstanceAlignMask;
}
void setInstanceAlignMask(StoredSize mask) {
assert(isTypeMetadata());
InstanceAlignMask = mask;
}
StoredPointer getClassSize() const {
assert(isTypeMetadata());
return ClassSize;
}
void setClassSize(StoredSize size) {
assert(isTypeMetadata());
ClassSize = size;
}
StoredPointer getClassAddressPoint() const {
assert(isTypeMetadata());
return ClassAddressPoint;
}
void setClassAddressPoint(StoredSize offset) {
assert(isTypeMetadata());
ClassAddressPoint = offset;
}
uint16_t getRuntimeReservedData() const {
assert(isTypeMetadata());
return Reserved;
}
void setRuntimeReservedData(uint16_t data) {
assert(isTypeMetadata());
Reserved = data;
}
/// Get a pointer to the field offset vector, if present, or null.
const StoredPointer *getFieldOffsets() const {
assert(isTypeMetadata());
auto offset = getDescription()->getFieldOffsetVectorOffset();
if (offset == 0)
return nullptr;
auto asWords = reinterpret_cast<const void * const*>(this);
return reinterpret_cast<const StoredPointer *>(asWords + offset);
}
uint32_t getSizeInWords() const {
assert(isTypeMetadata());
uint32_t size = getClassSize() - getClassAddressPoint();
assert(size % sizeof(StoredPointer) == 0);
return size / sizeof(StoredPointer);
}
/// Given that this class is serving as the superclass of a Swift class,
/// return its bounds as metadata.
///
/// Note that the ImmediateMembersOffset member will not be meaningful.
TargetClassMetadataBounds<Runtime>
getClassBoundsAsSwiftSuperclass() const {
using Bounds = TargetClassMetadataBounds<Runtime>;
auto rootBounds = Bounds::forSwiftRootClass();
// If the class is not type metadata, just use the root-class bounds.
if (!isTypeMetadata())
return rootBounds;
// Otherwise, pull out the bounds from the metadata.
auto bounds = Bounds::forAddressPointAndSize(getClassAddressPoint(),
getClassSize());
// Round the bounds up to the required dimensions.
if (bounds.NegativeSizeInWords < rootBounds.NegativeSizeInWords)
bounds.NegativeSizeInWords = rootBounds.NegativeSizeInWords;
if (bounds.PositiveSizeInWords < rootBounds.PositiveSizeInWords)
bounds.PositiveSizeInWords = rootBounds.PositiveSizeInWords;
return bounds;
}
/// Given a statically-emitted metadata template, this sets the correct
/// "is Swift" bit for the current runtime. Depending on the deployment
/// target a binary was compiled for, statically emitted metadata templates
/// may have a different bit set from the one that this runtime canonically
/// considers the "is Swift" bit.
void setAsTypeMetadata() {
// If the wrong "is Swift" bit is set, set the correct one.
//
// Note that the only time we should see the "new" bit set while
// expecting the "old" one is when running a binary built for a
// new OS on an old OS, which is not supported, however we do
// have tests that exercise this scenario.
auto otherSwiftBit = (3ULL - SWIFT_CLASS_IS_SWIFT_MASK);
assert(otherSwiftBit == 1ULL || otherSwiftBit == 2ULL);
if ((this->Data & 3) == otherSwiftBit) {
this->Data ^= 3;
}
// Otherwise there should be nothing to do, since only the old "is
// Swift" bit is used for backward-deployed runtimes.
assert(isTypeMetadata());
}
bool isCanonicalStaticallySpecializedGenericMetadata() const {
auto *description = getDescription();
if (!description->isGeneric())
return false;
return this->Flags & ClassFlags::IsCanonicalStaticSpecialization;
}
static bool classof(const TargetMetadata<Runtime> *metadata) {
return metadata->getKind() == MetadataKind::Class;
}
};
- 通过源码可见
TargetClassMetadata
继承自TargetAnyClassMetadata
-
TargetClassMetadata
源码中,可以看到包含了很多类似于OC底层结构的信息ClassFlags Flags
uint32_t InstanceAddressPoint
uint32_t InstanceSize
uint16_t InstanceAlignMask
uint16_t Reserved
uint32_t ClassSize
uint32_t ClassAddressPoint
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
TargetAnyClassMetadata 源码
/// The portion of a class metadata object that is compatible with
/// all classes, even non-Swift ones.
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
using StoredPointer = typename Runtime::StoredPointer;
using StoredSize = typename Runtime::StoredSize;
#if SWIFT_OBJC_INTEROP
constexpr TargetAnyClassMetadata(TargetAnyClassMetadata<Runtime> *isa,
TargetClassMetadata<Runtime> *superclass)
: TargetHeapMetadata<Runtime>(isa),
Superclass(superclass),
CacheData{nullptr, nullptr},
Data(SWIFT_CLASS_IS_SWIFT_MASK) {}
#endif
constexpr TargetAnyClassMetadata(TargetClassMetadata<Runtime> *superclass)
: TargetHeapMetadata<Runtime>(MetadataKind::Class),
Superclass(superclass),
CacheData{nullptr, nullptr},
Data(SWIFT_CLASS_IS_SWIFT_MASK) {}
#if SWIFT_OBJC_INTEROP
// Allow setting the metadata kind to a class ISA on class metadata.
using TargetMetadata<Runtime>::getClassISA;
using TargetMetadata<Runtime>::setClassISA;
#endif
// Note that ObjC classes does not have a metadata header.
/// The metadata for the superclass. This is null for the root class.
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
// TODO: remove the CacheData and Data fields in non-ObjC-interop builds.
/// The cache data is used for certain dynamic lookups; it is owned
/// by the runtime and generally needs to interoperate with
/// Objective-C's use.
TargetPointer<Runtime, void> CacheData[2];
/// The data pointer is used for out-of-line metadata and is
/// generally opaque, except that the compiler sets the low bit in
/// order to indicate that this is a Swift metatype and therefore
/// that the type metadata header is present.
StoredSize Data;
static constexpr StoredPointer offsetToData() {
return offsetof(TargetAnyClassMetadata, Data);
}
/// Is this object a valid swift type metadata? That is, can it be
/// safely downcast to ClassMetadata?
bool isTypeMetadata() const {
return (Data & SWIFT_CLASS_IS_SWIFT_MASK);
}
/// A different perspective on the same bit
bool isPureObjC() const {
return !isTypeMetadata();
}
};
-
TargetAnyClassMetadata
又继承自TargetHeapMetadata
- 在这里定义了
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass
TargetPointer<Runtime, void> CacheData[2]
StoredSize Data
对比 Object-C的内存结构:
Swift | Object-C |
---|---|
Kind | isa |
Superclass | superclass |
CacheData | cache |
data | bits |
Swift 类的内存结构:
TargetHeapMetadata
-> TargetMetadata ≈ TargetClassMetadata -> TargetAnyClassMetadata -> TargetHeapMetadata
- 如果当前的对象的
metedata
Kind
为Class
类型,则返回的实际类型为TargetClassMetadata
- 继承关系:
TargetClassMetadata
->TargetAnyClassMetadata
->TargetHeapMetadata
->TargetMetadata (基类)
因此我们可以模拟出一个 Swift 的 class 内存结构至少为:
struct swift_class_t {
void *kind;
void *superClass;
void *cacheData
void *data
uint32_t flags; //4
uint32_t instanceAddressOffset; //4
uint32_t instanceSize;//4
uint16_t instanceAlignMask; //2
uint16_t reserved; //2
uint32_t classSize; //4
uint32_t classAddressOffset; //4
void *description;
...
}