Created
March 12, 2021 15:49
-
-
Save pofat/d3c77ca88c5b2a3019febcb073c3d879 to your computer and use it in GitHub Desktop.
Swift Struct Metadata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Kind of type | |
public enum Kind { | |
case `struct` | |
case `enum` | |
case optional | |
case opaque | |
case tuple | |
case function | |
case existential | |
case metatype | |
case objCClassWrapper | |
case existentialMetatype | |
case foreignClass | |
case heapLocalVariable | |
case heapGenericLocalVariable | |
case errorObject | |
case `class` | |
init(flag: Int) { | |
switch flag { | |
case 1: self = .struct | |
case (0 | Flags.kindIsNonHeap): self = .struct | |
case 2: self = .enum | |
case (1 | Flags.kindIsNonHeap): self = .enum | |
case 3: self = .optional | |
case (2 | Flags.kindIsNonHeap): self = .optional | |
case 8: self = .opaque | |
case (3 | Flags.kindIsNonHeap): self = .foreignClass | |
case 9: self = .tuple | |
case (0 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .opaque | |
case 10: self = .function | |
case (1 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .tuple | |
case 12: self = .existential | |
case (2 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .function | |
case 13: self = .metatype | |
case (3 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .existential | |
case 14: self = .objCClassWrapper | |
case (4 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .metatype | |
case 15: self = .existentialMetatype | |
case (5 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .objCClassWrapper | |
case 16: self = .foreignClass | |
case (6 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .existentialMetatype | |
case 64: self = .heapLocalVariable | |
case (0 | Flags.kindIsNonType): self = .heapLocalVariable | |
case 65: self = .heapGenericLocalVariable | |
case (0 | Flags.kindIsNonType | Flags.kindIsRuntimePrivate): self = .heapGenericLocalVariable | |
case 128: self = .errorObject | |
case (1 | Flags.kindIsNonType | Flags.kindIsRuntimePrivate): self = .errorObject | |
default: self = .class | |
} | |
} | |
enum Flags { | |
static let kindIsNonHeap = 0x200 | |
static let kindIsRuntimePrivate = 0x100 | |
static let kindIsNonType = 0x400 | |
} | |
init(type: Any.Type) { | |
let rawValue = getKindRawValue(of: type) | |
self.init(flag: rawValue) | |
} | |
} | |
private func getKindRawValue(of type: Any.Type) -> Int { | |
let pointer = unsafeBitCast(type, to: UnsafePointer<Int>.self) | |
return pointer.pointee | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct User { | |
var name = "Tom" | |
let age = 32 | |
var birthday = Date() | |
} | |
// Get metadata of struct | |
let ptr = unsafeBitCast(User.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self) | |
print("0x\(String(ptr.pointee.kind, radix: 16))") // 0x200 <--- struct | |
let descriptorPointer = ptr.pointee.descriptor | |
let name = descriptorPointer.pointee.name.advanced() | |
print("Type name : \(String(cString: name))") | |
print("parent: \(descriptorPointer.pointee.parent.advanced().pointee)") | |
let propertyCount = Int(descriptorPointer.pointee.numFields) | |
print("property count: \(propertyCount)") | |
print("properties:") | |
let offset = ptr.pointee.getFieldOffsets() | |
let genericVector = ptr.pointee.genericArgumentVector() | |
for index in 0..<propertyCount { | |
let propertyPointer = descriptorPointer.pointee.fields.advanced().pointee.getField(atIndex: Int32(index)) | |
let type = propertyPointer.pointee.getType(genericContext: descriptorPointer, genericArguments: genericVector) | |
print(""" | |
name: \(propertyPointer.pointee.getFieldName()) | |
type: \(type)) | |
is var: \(propertyPointer.pointee.flags.isVar) | |
""") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: Metadata of Struct | |
enum ContextDescriptorKind: UInt8 { | |
case module = 0 | |
case `extension` | |
case anonymous // such as closure | |
case `protocol` // A protocol | |
case opaque // opaque alias | |
case `class` = 16 | |
case `struct` | |
case `enum` | |
init?(flags: ContextDescriptorFlags) { | |
if let kind = ContextDescriptorKind(rawValue: numericCast(flags & 0x1F)) { | |
self = kind | |
} else { | |
return nil | |
} | |
} | |
} | |
// MARK: Struct | |
struct StructMetadata { | |
var kind: Int | |
var descriptor: UnsafeMutablePointer<StructTypeDescriptor> | |
var genericArguemntOffset: Int { | |
// It's at offset 2; 0 is kind, 1 is descriptor | |
return 2 | |
} | |
// Number of properties | |
func getNumberOfFields() -> Int { | |
return Int(descriptor.pointee.numFields) | |
} | |
// Get offset of each field (property) | |
mutating func getFieldOffsets() -> [Int] { | |
let offset = descriptor.pointee.fieldOffsetVectorOffset | |
let count = getNumberOfFields() | |
return withUnsafePointer(to: &self) { p in | |
let begin = UnsafeRawPointer(p).assumingMemoryBound(to: Int.self) | |
return begin.advanced(by: numericCast(offset)) | |
.raw.assumingMemoryBound(to: UInt32.self) // fieldOffsetVectorOffset is UInt32 | |
.makeBuffer(ofLength: count).map(numericCast) | |
} | |
} | |
mutating func genericArgumentVector() -> UnsafeMutablePointer<Any.Type> { | |
let genericAgrOffset = self.genericArguemntOffset | |
return withUnsafeMutablePointer(to: &self) { p in | |
return p.raw.advanced(by: genericAgrOffset * MemoryLayout<UnsafeRawPointer>.size) | |
.assumingMemoryBound(to: Any.Type.self) | |
} | |
} | |
} | |
// struct nominal type descriptor | |
typealias ContextDescriptorFlags = UInt32 | |
/// [Swift Source Code] Metadata.h -> class TargetTypeContextDescriptor | |
struct StructTypeDescriptor { | |
var flags: ContextDescriptorFlags | |
var parent: RelativePointer<Int32, Kind> // should always be 0, which means null | |
var name: RelativePointer<Int32, CChar> | |
/// A pointer to the metadata access function for this type. | |
/// | |
/// The function type here is a stand-in. You should use getAccessFunction() | |
/// to wrap the function pointer in an accessor that uses the proper calling | |
/// convention for a given number of arguments | |
var accessFunctionPtr: RelativePointer<Int32, UnsafeRawPointer> | |
var fields: RelativePointer<Int32, FieldDescriptor> | |
var numFields: UInt32 | |
/// The offset of the field offset vector for this struct's stored | |
/// properties in its metadata, if any. 0 means there is no field offset | |
/// vector. | |
var fieldOffsetVectorOffset: UInt32 | |
var hasStoredProperties: Bool { | |
return fieldOffsetVectorOffset != 0 | |
} | |
} | |
// [Swift Source Code] Records.h | |
struct FieldDescriptor { | |
// enum class FieldDescriptorKind : uint16_t | |
enum Kind: UInt16 { | |
case `struct`, `class`, `enum` | |
// Fixed-size multi-payload enums have a special descriptor format that | |
// encodes spare bits. | |
case multipPayloadEnum | |
// A Swift opaque protocol. There are no fields, just a record for the | |
// type itself. | |
case `protocol` | |
// A Swift class-bound protocol. | |
case classProtocol | |
// An Objective-C protocol, which may be imported or defined in Swift. | |
case objCProtocol | |
// An Objective-C class, which may be imported or defined in Swift. | |
// In the former case, field type metadata is not emitted, and | |
// must be obtained from the Objective-C runtime. | |
case objCClass | |
} | |
var mangeldTypeName: RelativePointer<Int32, CChar> | |
var superClass: RelativePointer<Int32, CChar> | |
var fieldDescriptorKind: Kind | |
var fieldRecordSize: UInt16 | |
var numFields: UInt32 | |
// Get record of each field | |
mutating func getField(atIndex index: Int32) -> UnsafeMutablePointer<FieldRecord> { | |
return withUnsafeMutablePointer(to: &self) { p in | |
return p.advanced(by: 1).raw | |
.assumingMemoryBound(to: FieldRecord.self) | |
.advanced(by: numericCast(index)) | |
} | |
} | |
} | |
struct FieldRecord { | |
// FieldRecordFlags | |
struct Flags { | |
private enum Constants { | |
// Is this an indirect enum case? | |
static let isIndirectCase: UInt32 = 0x1 | |
// Is this a mutable var property | |
static let isVar: UInt32 = 0x2 | |
// Is this an artificial field | |
static let isArtificial: UInt32 = 0x4 | |
} | |
private var data: UInt32 | |
public var isIndirectCase: Bool { | |
return (data & Constants.isIndirectCase) == Constants.isIndirectCase | |
} | |
public var isVar: Bool { | |
return (data & Constants.isVar) == Constants.isVar | |
} | |
public var isArtificial: Bool { | |
return (data & Constants.isArtificial) == Constants.isArtificial | |
} | |
} | |
var flags: Flags | |
var mangledtypeName: RelativePointer<Int32, UInt8> // The `Int8` here is actually CChar | |
var fieldName: RelativePointer<Int32, CChar> | |
// let or var | |
var isVar: Bool { | |
return flags.isVar | |
} | |
// Get name of this property | |
mutating func getFieldName() -> String { | |
return String(cString: fieldName.advanced()) | |
} | |
// Get type of this property | |
mutating func getType(genericContext: UnsafeRawPointer?, | |
genericArguments: UnsafeRawPointer?) -> Any.Type { | |
let typeName = mangledtypeName.advanced() | |
return _getTypeByMangledNameInContext(typeName, | |
UInt(getSymbolicMangledNameLength(typeName)), | |
genericContext: genericContext, | |
genericArguments: genericArguments) | |
} | |
// From `KeyPath.swift` | |
private func getSymbolicMangledNameLength(_ base: UnsafeRawPointer) -> Int { | |
var end = base | |
while let current = Optional(end.load(as: UInt8.self)), current != 0 { | |
// Skip the current character | |
end += 1 | |
// Skip over a symbolic reference | |
if current >= 0x1 && current <= 0x17 { | |
end += 4 | |
} else if current >= 0x18 && current <= 0x1F { | |
end += MemoryLayout<Int>.size | |
} | |
} | |
return end - base | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: Pointer | |
struct RelativePointer<Offset: FixedWidthInteger, Pointee> { | |
var offset: Offset | |
mutating func advanced() -> UnsafeMutablePointer<Pointee> { | |
let offset = self.offset | |
return withUnsafeMutablePointer(to: &self) { p in | |
return p.raw | |
.advanced(by: numericCast(offset)) | |
.assumingMemoryBound(to: Pointee.self) | |
} | |
} | |
} | |
extension UnsafeMutablePointer { | |
var raw: UnsafeMutableRawPointer { | |
return UnsafeMutableRawPointer(self) | |
} | |
} | |
extension UnsafePointer { | |
var raw: UnsafeRawPointer { | |
return UnsafeRawPointer(self) | |
} | |
func makeBuffer(ofLength length: Int) -> UnsafeBufferPointer<Pointee> { | |
return UnsafeBufferPointer(start: self, count: length) | |
} | |
var mutable: UnsafeMutablePointer<Pointee> { | |
return UnsafeMutablePointer<Pointee>(mutating: self) | |
} | |
} | |
// MARK: Demangle | |
@_silgen_name("swift_getTypeByMangledNameInContext") | |
func _getTypeByMangledNameInContext( | |
_ name: UnsafePointer<UInt8>, | |
_ nameLength: UInt, | |
genericContext: UnsafeRawPointer?, | |
genericArguments: UnsafeRawPointer?) | |
-> Any.Type |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment