Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
13c06ca
Remove a fatalError in ConformanceDescriptor
NSExceptional May 1, 2022
a74a394
Fix release build
ole May 23, 2022
680ab47
Make SignedPointer implicitly-unwrapped optional
NSExceptional Jul 3, 2022
b204531
Class descriptor is optional; safely unwrap it
NSExceptional Jul 3, 2022
f3f815d
Unwrap ClassMetadata.descriptor
NSExceptional Jul 3, 2022
b0155b5
Don't forcibly unwrap ClassMetadata.descriptor
NSExceptional Jul 3, 2022
c181c26
Update swift-atomics
NSExceptional Dec 22, 2024
502d3a4
Ignore Tuist files
NSExceptional Jan 5, 2025
443997a
Safely handle null indirect type descriptor in ConformanceDescriptor
NSExceptional Jun 5, 2026
3d1a884
Decode TypeReferenceKind in __swift5_types records
NSExceptional Jun 5, 2026
36228fa
Update ClassMetadata test expectations for current Swift ABI
NSExceptional Jun 5, 2026
eee06b8
Fix crash decoding resilient-superclass reference kind
NSExceptional Jun 6, 2026
09de17d
Fix createMetadataAccessBuffer storing the first generic arg N times
NSExceptional Jun 6, 2026
bcd4a65
Gaps #6/#7/#8: complete the metadata flag structs
NSExceptional Jun 6, 2026
4890007
Gap #1: general demangler + context-free type(named:)
NSExceptional Jun 6, 2026
016b73f
Gap #4: ReflectionMirror child enumeration (children(of:))
NSExceptional Jun 6, 2026
c325bb9
Gap #3: associated-type witness resolution
NSExceptional Jun 6, 2026
84dae8d
#3: associated conformance witness resolution
NSExceptional Jun 6, 2026
3b08081
#7: decode function metadata trailing types
NSExceptional Jun 6, 2026
fc3d46b
#8: decode invertible-protocol set in extended function flags
NSExceptional Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
.build/
.swiftpm/
Package.resolved
/Echo.xcodeproj
/Project.swift
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-atomics.git", from: "0.0.1")
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.0")
],
targets: [
.target(
Expand Down
31 changes: 29 additions & 2 deletions Sources/Echo/ContextDescriptor/ContextDescriptorValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,37 @@ public struct TypeContextDescriptorFlags {
public var hasImportInfo: Bool {
bits & 0x4 != 0
}


/// Whether this type's metadata carries a layout string (compact byte-coded
/// layout description used by the runtime for value operations).
public var hasLayoutString: Bool {
bits & 0x10 != 0
}

/// Whether this class has a default override table (a class-only flag).
public var classHasDefaultOverrideTable: Bool {
bits & 0x40 != 0
}

/// Whether this class is an `actor` (a class-only flag).
public var classIsActor: Bool {
bits & 0x80 != 0
}

/// Whether this class is a default actor — an actor with the default,
/// runtime-provided executor rather than a custom one (a class-only flag).
public var classIsDefaultActor: Bool {
bits & 0x100 != 0
}

/// The resilient superclass type reference kind.
public var resilientSuperclassRefKind: TypeReferenceKind {
TypeReferenceKind(rawValue: UInt16(bits) & 0xE00)!
// The reference kind occupies a 3-bit field starting at bit 9, so it must
// be shifted down after masking — without the shift, any non-direct kind
// (e.g. the indirect reference used for a cross-module resilient
// superclass) produces a value like 0x200 that is not a valid
// TypeReferenceKind raw value and traps the force-unwrap.
TypeReferenceKind(rawValue: UInt16(bits & (0x7 << 9)) >> 9)!
}

/// Whether or not this class has any immediate members negative.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Echo/ContextDescriptor/GenericContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public struct GenericRequirementDescriptor: LayoutWrapper {
and: UInt8.self
)
).signed
return ProtocolDescriptor(ptr: ptr)
return ProtocolDescriptor(ptr: ptr!)
}

/// If this requirement is some layout (currently can only be a class),
Expand Down
30 changes: 26 additions & 4 deletions Sources/Echo/Metadata/ClassMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,29 @@ public struct ClassMetadata: TypeMetadata, LayoutWrapper {
public let ptr: UnsafeRawPointer

/// The class context descriptor that describes this class.
public var descriptor: ClassDescriptor {
public var descriptor: ClassDescriptor? {
precondition(isSwiftClass)
return ClassDescriptor(ptr: layout._descriptor.signed)

if let descriptorPtr = layout._descriptor.signed {
return ClassDescriptor(ptr: descriptorPtr)
}

return nil
}


/// Whether this class is an `actor`. Always `false` for non-Swift (e.g.
/// Objective-C) classes.
public var isActor: Bool {
isSwiftClass && descriptor?.typeFlags.classIsActor == true
}

/// Whether this class is a *default* actor — an actor using the default,
/// runtime-provided executor rather than a custom one. Always `false` for
/// non-Swift classes.
public var isDefaultActor: Bool {
isSwiftClass && descriptor?.typeFlags.classIsDefaultActor == true
}

/// The Objective-C ISA pointer, if it has one.
public var isaPointer: UnsafeRawPointer? {
let int = ptr.load(as: Int.self)
Expand Down Expand Up @@ -101,7 +119,11 @@ public struct ClassMetadata: TypeMetadata, LayoutWrapper {

/// An array of field offsets for this class's stored representation.
public var fieldOffsets: [Int] {
Array(unsafeUninitializedCapacity: descriptor.numFields) {
guard let descriptor = descriptor else {
return []
}

return Array(unsafeUninitializedCapacity: descriptor.numFields) {
let start = ptr.offset(of: descriptor.fieldOffsetVectorOffset)

for i in 0 ..< descriptor.numFields {
Expand Down
142 changes: 142 additions & 0 deletions Sources/Echo/Metadata/FunctionMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,148 @@ public struct FunctionMetadata: Metadata, LayoutWrapper {
}
}

extension FunctionMetadata {
// The conditional trailing fields after the parameter array, in ABI order:
// parameters, parameter flags, differentiability, global actor, extended
// flags, thrown error. Each is laid out at its natural alignment.
private func trailingFieldOffsets() -> (
globalActor: Int?, extendedFlags: Int?, thrownError: Int?
) {
let pointerSize = MemoryLayout<UnsafeRawPointer>.size
let uint32Size = MemoryLayout<UInt32>.size

func aligned(_ offset: Int, to alignment: Int) -> Int {
(offset + alignment - 1) & ~(alignment - 1)
}

// After the parameter type array.
var offset = flags.numParams * pointerSize

if flags.hasParamFlags {
offset += flags.numParams * uint32Size
}

if flags.isDifferentiable {
offset = aligned(offset, to: pointerSize)
offset += pointerSize
}

var globalActor: Int?
if flags.hasGlobalActor {
offset = aligned(offset, to: pointerSize)
globalActor = offset
offset += pointerSize
}

var extendedFlags: Int?
if flags.hasExtendedFlags {
offset = aligned(offset, to: uint32Size)
extendedFlags = offset
offset += uint32Size
}

var thrownError: Int?
if let extendedFlags,
trailing.load(fromByteOffset: extendedFlags, as: ExtendedFunctionTypeFlags.self)
.isTypedThrows {
offset = aligned(offset, to: pointerSize)
thrownError = offset
offset += pointerSize
}

return (globalActor, extendedFlags, thrownError)
}

/// The global actor this function type is isolated to (e.g. `MainActor`), if
/// it has one (`flags.hasGlobalActor`).
public var globalActorType: Any.Type? {
guard let offset = trailingFieldOffsets().globalActor else {
return nil
}

return trailing.load(fromByteOffset: offset, as: Any.Type.self)
}

/// The extended function-type flags (typed throws, isolation kind, sending
/// result), if present (`flags.hasExtendedFlags`).
public var extendedFlags: ExtendedFunctionTypeFlags? {
guard let offset = trailingFieldOffsets().extendedFlags else {
return nil
}

return trailing.load(fromByteOffset: offset, as: ExtendedFunctionTypeFlags.self)
}

/// The error type of a typed-throws function (e.g. `MyError` for
/// `throws(MyError)`), if it has one.
public var thrownErrorType: Any.Type? {
guard let offset = trailingFieldOffsets().thrownError else {
return nil
}

return trailing.load(fromByteOffset: offset, as: Any.Type.self)
}
}

/// The extended flags carried by some function types, beyond the 32-bit
/// `FunctionMetadata.Flags`. Present only when `flags.hasExtendedFlags`.
public struct ExtendedFunctionTypeFlags {
/// Flags as represented in bits.
public let bits: UInt32

/// Whether this function uses typed throws (`throws(E)`); if so, the thrown
/// error type is available via `FunctionMetadata.thrownErrorType`.
public var isTypedThrows: Bool {
bits & 0x1 != 0
}

/// Whether this function has `@isolated(any)` isolation.
public var isIsolatedAny: Bool {
bits & 0xE == 0x2
}

/// Whether this function is `nonisolated(nonsending)`.
public var isNonIsolatedNonsending: Bool {
bits & 0xE == 0x4
}

/// Whether this function has a `sending` result.
public var hasSendingResult: Bool {
bits & 0x10 != 0
}

/// The set of invertible protocols (`Copyable`, `Escapable`) whose
/// requirement is *inverted* for this function type — i.e. that the function
/// does not require of its noncopyable/nonescapable inputs. Stored in the
/// high 16 bits.
public var invertedProtocols: InvertibleProtocolSet {
InvertibleProtocolSet(bits: UInt16(truncatingIfNeeded: bits >> 16))
}
}

/// A set of invertible protocols (`Copyable`, `Escapable`). When a protocol is
/// present in the set, its requirement is *inverted* — i.e. the corresponding
/// `~Copyable`/`~Escapable` capability is permitted rather than required.
public struct InvertibleProtocolSet {
/// Flags as represented in bits.
public let bits: UInt16

/// Whether `Copyable` is inverted (i.e. `~Copyable` is permitted).
public var invertsCopyable: Bool {
bits & (1 << 0) != 0
}

/// Whether `Escapable` is inverted (i.e. `~Escapable` is permitted).
public var invertsEscapable: Bool {
bits & (1 << 1) != 0
}

/// Whether the set is empty — no invertible-protocol requirement is inverted.
public var isEmpty: Bool {
bits == 0
}
}

extension FunctionMetadata: Equatable {}

struct _FunctionMetadata {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Echo/Metadata/MetadataAccessFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func createMetadataAccessBuffer(
// First loop is inserting the key arguments at the front of the buffer.
for i in 0 ..< args.count {
buffer.storeBytes(
of: args[0].0,
of: args[i].0,
toByteOffset: ptrSize * i,
as: Any.Type.self
)
Expand Down
43 changes: 43 additions & 0 deletions Sources/Echo/Metadata/MetadataValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,33 @@ extension FunctionMetadata {
public var isEscaping: Bool {
bits & 0x4000000 != 0
}

/// Whether or not this function is `@differentiable`.
public var isDifferentiable: Bool {
bits & 0x8000000 != 0
}

/// Whether or not this function is isolated to a global actor. When set, the
/// global actor type is stored in the function metadata's trailing objects.
public var hasGlobalActor: Bool {
bits & 0x10000000 != 0
}

/// Whether or not this is an `async` function.
public var isAsync: Bool {
bits & 0x20000000 != 0
}

/// Whether or not this function is `@Sendable`.
public var isSendable: Bool {
bits & 0x40000000 != 0
}

/// Whether or not this function carries extended flags (e.g. typed throws,
/// isolation, a sending result), stored in the trailing objects.
public var hasExtendedFlags: Bool {
bits & 0x80000000 != 0
}
}
}

Expand Down Expand Up @@ -237,6 +264,8 @@ extension ValueWitnessTable {
case isNonBitwiseTakable = 0x100000
case hasEnumWitnesses = 0x200000
case incomplete = 0x400000
case isNonCopyable = 0x800000
case isNonBitwiseBorrowable = 0x1000000
}

/// Flags as represented in bits.
Expand Down Expand Up @@ -277,5 +306,19 @@ extension ValueWitnessTable {
public var isIncomplete: Bool {
bits & Flags.incomplete.rawValue != 0
}

/// Whether or not this type is copyable. Non-copyable types (`~Copyable`)
/// must not be copied — only moved/consumed — so reflection code that copies
/// values (e.g. via `initializeWithCopy`) must check this first.
public var isCopyable: Bool {
bits & Flags.isNonCopyable.rawValue == 0
}

/// Whether or not this type is bitwise borrowable. A type is bitwise
/// borrowable when it is bitwise takable and the runtime has not marked it
/// non-bitwise-borrowable.
public var isBitwiseBorrowable: Bool {
isBitwiseTakable && bits & Flags.isNonBitwiseBorrowable.rawValue == 0
}
}
}
Loading