Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions Sources/Echo/ContextDescriptor/ContextDescriptorValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ extension GenericMetadataPattern {

/// A discriminator to determine what type of parameter a generic parameter is.
public enum GenericParameterKind: UInt8 {
/// An ordinary type parameter, e.g. `T`.
case type = 0x0

/// A type parameter pack, e.g. `each T` (variadic generics).
case typePack = 0x1

/// A value type parameter, e.g. `let N: Int` (integer generic parameters).
case value = 0x2
}

/// The flags that describe a generic parameter.
Expand Down Expand Up @@ -157,6 +164,15 @@ public enum GenericRequirementKind: UInt8 {
case sameType = 0x1
case baseClass = 0x2
case sameConformance = 0x3

/// A same-shape requirement between two generic parameter packs (variadic
/// generics).
case sameShape = 0x4

/// A requirement listing the invertible-protocol checks (e.g. `Copyable`,
/// `Escapable`) that are *inverted* for a parameter — an "anti-requirement".
case invertedProtocols = 0x5

case layout = 0x1F
}

Expand Down
114 changes: 111 additions & 3 deletions Sources/Echo/ContextDescriptor/GenericContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,112 @@ public struct GenericContext: LayoutWrapper {
let base = MemoryLayout<_GenericContextDescriptorHeader>.size
return base + parameterSize + requirementSize
}

/// Flags describing what trailing records this generic context carries (type
/// packs, conditional inverted protocols, value parameters).
public var descriptorFlags: GenericContextDescriptorFlags {
// The header's 4th word was originally "NumExtraArguments" (always 0 in
// pre-5.8 runtimes) and is now repurposed as the flags word.
GenericContextDescriptorFlags(bits: layout._numExtraArguments)
}

/// The pack-shape header, present when this context has type-pack parameters
/// (variadic generics). Describes how many packs and same-shape equivalence
/// classes the context has.
public var packShapeHeader: GenericPackShapeHeader? {
guard descriptorFlags.hasTypePacks else {
return nil
}

// Trails the parameter and requirement arrays.
let offset = parameterSize + requirementSize
return (trailing + offset).load(as: GenericPackShapeHeader.self)
}

/// The pack-shape descriptors, one per pack metadata/witness-table argument,
/// each tagged with its same-shape equivalence class. Empty for contexts
/// without type packs.
public var packShapeDescriptors: [GenericPackShapeDescriptor] {
guard let header = packShapeHeader else {
return []
}

let base = trailing + parameterSize + requirementSize
+ MemoryLayout<GenericPackShapeHeader>.size

return Array(unsafeUninitializedCapacity: Int(header.numShapeClasses)) {
for i in 0 ..< Int(header.numShapeClasses) {
$0[i] = base.load(
fromByteOffset: i * MemoryLayout<GenericPackShapeDescriptor>.stride,
as: GenericPackShapeDescriptor.self
)
}

$1 = Int(header.numShapeClasses)
}
}
}

/// Flags describing the trailing records a generic context carries.
public struct GenericContextDescriptorFlags {
/// Flags as represented in bits.
public let bits: UInt16

/// Whether at least one generic parameter is a type pack (`each T`), in which
/// case the context carries a trailing `GenericPackShapeHeader`.
public var hasTypePacks: Bool {
bits & 0x1 != 0
}

/// Whether the context has conditional conformances to inverted protocols
/// (e.g. conditional `~Copyable`), with trailing inverted-protocol records.
public var hasConditionalInvertedProtocols: Bool {
bits & 0x2 != 0
}

/// Whether the context has at least one value generic parameter
/// (`let N: Int`), with a trailing value header.
public var hasValues: Bool {
bits & 0x4 != 0
}
}

/// The header preceding a generic context's pack-shape descriptors.
public struct GenericPackShapeHeader {
/// The number of generic parameters and conformance requirements that are
/// packs.
public let numPacks: UInt16

/// The number of equivalence classes in the same-shape relation.
public let numShapeClasses: UInt16
}

/// What a pack-shape descriptor describes — a metadata pack or a witness-table
/// pack.
public enum GenericPackKind: UInt16 {
case metadata = 0
case witnessTable = 1
}

/// Describes a single generic parameter/requirement pack and its same-shape
/// equivalence class.
public struct GenericPackShapeDescriptor {
let _kind: UInt16

/// The index of this metadata pack or witness-table pack in the generic
/// arguments array.
public let index: UInt16

/// The equivalence class of this pack under the same-shape relation
/// (`< GenericPackShapeHeader.numShapeClasses`).
public let shapeClass: UInt16

let _unused: UInt16

/// Whether this describes a metadata pack or a witness-table pack.
public var kind: GenericPackKind {
GenericPackKind(rawValue: _kind) ?? .metadata
}
}

/// This descriptor describes any generic requirement in either a generic
Expand Down Expand Up @@ -116,10 +222,12 @@ public struct GenericRequirementDescriptor: LayoutWrapper {
address(for: \._param)
}

/// If this requirement is a sameType or baseClass, this is the mangled name
/// for the type that's being constrained.
/// If this requirement is a `sameType`, `baseClass`, or `sameShape`, this is
/// the mangled name for the type (or, for `sameShape`, the parameter pack)
/// that's being constrained against.
public var mangledTypeName: UnsafeRawPointer {
assert(flags.kind == .sameType || flags.kind == .baseClass)
assert(flags.kind == .sameType || flags.kind == .baseClass
|| flags.kind == .sameShape)
let addr = address(for: \._requirement)
return addr.relativeDirectAddress(as: CChar.self)
}
Expand Down
33 changes: 33 additions & 0 deletions Sources/Echo/ContextDescriptor/OpaqueDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,39 @@ public struct OpaqueDescriptor: ContextDescriptor, LayoutWrapper {
$1 = numUnderlyingTypes
}
}

/// Realizes the concrete underlying type for the underlying-type entry at
/// `index`, resolving its mangled name in this opaque descriptor's context.
///
/// An opaque type (`some P`) stores its underlying type only as a mangled
/// name; this turns it into a live metatype. `genericArguments` supplies the
/// type arguments when the opaque type is nested in a generic context — pass
/// `nil` (the default) for a self-contained underlying type, e.g. a
/// non-generic `func f() -> some P` returning a concrete type.
/// - Parameters:
/// - index: Which underlying type (`0 ..< numUnderlyingTypes`).
/// - genericArguments: Pointer to the generic arguments, if any.
/// - Returns: The underlying type's metatype, or `nil` if out of range or
/// unresolvable.
public func underlyingType(
at index: Int,
genericArguments: UnsafeRawPointer? = nil
) -> Any.Type? {
let names = underlyingTypeMangledNames
guard names.indices.contains(index) else {
return nil
}

let mangledName = names[index]
let length = getSymbolicMangledNameLength(mangledName)
let name = mangledName.assumingMemoryBound(to: UInt8.self)
return _getTypeByMangledNameInContext(
name,
UInt(length),
genericContext: flags.isGeneric ? genericContext!.ptr : nil,
genericArguments: genericArguments
)
}
}

extension OpaqueDescriptor: Equatable {}
Expand Down
104 changes: 104 additions & 0 deletions Sources/Echo/Metadata/ExtendedExistentialMetadata.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//
// ExtendedExistentialMetadata.swift
// Echo
//
// Metadata for a generalized / constrained existential — e.g. a
// parameterized-protocol existential like `any Collection<Int>`. These carry
// a *shape* describing the existential's requirement and generalization
// signatures, distinct from the classic protocol-composition layout modeled by
// `ExistentialMetadata`. Reflecting one previously crashed Echo with an
// "unknown kind 775".
//

/// The metadata structure that represents a generalized existential type, such
/// as `any Collection<Int>` or other constrained / parameterized-protocol
/// existentials.
///
/// ABI Stability: Unstable across all platforms
public struct ExtendedExistentialMetadata: Metadata, LayoutWrapper {
typealias Layout = _ExtendedExistentialMetadata

/// Backing extended existential metadata pointer.
public let ptr: UnsafeRawPointer

/// The existential shape describing this existential's constraints and
/// generalization signature.
public var shape: ExtendedExistentialTypeShape {
ExtendedExistentialTypeShape(ptr: layout._shape.signed)
}
}

extension ExtendedExistentialMetadata: Equatable {}

struct _ExtendedExistentialMetadata {
let _kind: Int
let _shape: SignedPointer<_ExtendedExistentialTypeShape>
}

/// Describes the shape of a generalized existential: how its value is
/// represented and what optional records (generalization signature, type
/// expression, suggested value witnesses) it carries.
///
/// ABI Stability: Unstable across all platforms
public struct ExtendedExistentialTypeShape: LayoutWrapper {
typealias Layout = _ExtendedExistentialTypeShape

/// Backing shape pointer.
let ptr: UnsafeRawPointer

/// Flags describing this existential shape.
public var flags: Flags {
layout._flags
}
}

extension ExtendedExistentialTypeShape {
/// Flags for an extended existential type shape.
public struct Flags {
/// Flags as represented in bits.
public let bits: UInt32

/// How the existential value is represented.
public var specialKind: SpecialKind {
SpecialKind(rawValue: UInt8(bits & 0xFF)) ?? .none
}

/// Whether the shape has a generalization signature — the substituted
/// generic arguments of the existential (e.g. the `Int` in
/// `any Collection<Int>`).
public var hasGeneralizationSignature: Bool {
bits & 0x100 != 0
}

/// Whether the shape carries a mangled type expression.
public var hasTypeExpression: Bool {
bits & 0x200 != 0
}

/// Whether the shape carries suggested value witnesses.
public var hasSuggestedValueWitnesses: Bool {
bits & 0x400 != 0
}
}

/// How a generalized existential's value is represented.
public enum SpecialKind: UInt8 {
/// An opaque value existential (the general case).
case none = 0

/// A class existential — a single retainable pointer.
case `class` = 1

/// A metatype existential.
case metatype = 2

/// An existential with an explicitly-described layout.
case explicitLayout = 3
}
}

extension ExtendedExistentialTypeShape: Equatable {}

struct _ExtendedExistentialTypeShape {
let _flags: ExtendedExistentialTypeShape.Flags
}
2 changes: 2 additions & 0 deletions Sources/Echo/Metadata/Metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func getMetadata(at ptr: UnsafeRawPointer) -> Metadata {
return ObjCClassWrapperMetadata(ptr: ptr)
case .existentialMetatype:
return ExistentialMetatypeMetadata(ptr: ptr)
case .extendedExistential:
return ExtendedExistentialMetadata(ptr: ptr)
case .heapLocalVariable:
return HeapLocalVariableMetadata(ptr: ptr)
case .heapGenericLocalVariable:
Expand Down
6 changes: 5 additions & 1 deletion Sources/Echo/Metadata/MetadataValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public enum MetadataKind: Int {

// (6 | Flags.isRuntimePrivate | Flags.isNonHeap)
case existentialMetatype = 774


// (7 | Flags.isRuntimePrivate | Flags.isNonHeap)
// A generalized/constrained existential, e.g. `any Collection<Int>`.
case extendedExistential = 775

// (0 | Flags.isNonType)
case heapLocalVariable = 1024

Expand Down
60 changes: 60 additions & 0 deletions Tests/EchoTests/Context Descriptor/GenericKinds.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import XCTest
import Echo

struct PlainGeneric<T, U> {
var t: T
var u: U
}

// Variadic generics (Swift 5.9+): a type parameter pack.
struct PackGeneric<each Element> {}

enum GenericKindsTests {
static func testOrdinaryParameterKinds() throws {
let metadata = reflectStruct(PlainGeneric<Int, String>.self)!
let context = try XCTUnwrap(metadata.descriptor.genericContext)
XCTAssertEqual(context.parameters.count, 2)
for parameter in context.parameters {
XCTAssertEqual(parameter.kind, .type)
}
}

static func testParameterPackKind() throws {
// Reflecting a variadic-generic type must not crash decoding the pack
// parameter's kind (previously force-unwrapped against a .type-only enum).
let metadata = reflectStruct(PackGeneric<Int, String, Bool>.self)!
let context = try XCTUnwrap(metadata.descriptor.genericContext)
let kinds = context.parameters.map(\.kind)
XCTAssertTrue(kinds.contains(.typePack))
}

static func testPackShapeDescriptors() throws {
let metadata = reflectStruct(PackGeneric<Int, String, Bool>.self)!
let context = try XCTUnwrap(metadata.descriptor.genericContext)

XCTAssertTrue(context.descriptorFlags.hasTypePacks)
let header = try XCTUnwrap(context.packShapeHeader)
XCTAssertGreaterThanOrEqual(Int(header.numShapeClasses), 1)
XCTAssertEqual(context.packShapeDescriptors.count, Int(header.numShapeClasses))
// The single `each Element` parameter is a metadata pack.
XCTAssertTrue(context.packShapeDescriptors.contains { $0.kind == .metadata })
}

static func testNonPackHasNoShapes() throws {
let metadata = reflectStruct(PlainGeneric<Int, String>.self)!
let context = try XCTUnwrap(metadata.descriptor.genericContext)

XCTAssertFalse(context.descriptorFlags.hasTypePacks)
XCTAssertNil(context.packShapeHeader)
XCTAssertTrue(context.packShapeDescriptors.isEmpty)
}
}

extension EchoTests {
func testGenericKinds() throws {
try GenericKindsTests.testOrdinaryParameterKinds()
try GenericKindsTests.testParameterPackKind()
try GenericKindsTests.testPackShapeDescriptors()
try GenericKindsTests.testNonPackHasNoShapes()
}
}
Loading