mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
338 lines
11 KiB
Swift
338 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2026 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import SwiftShims
|
|
|
|
struct DynamicCastFlags {
|
|
static let Default = UInt(bitPattern: 0x0)
|
|
static let Unconditional = UInt(bitPattern: 0x1)
|
|
static let TakeOnSuccess = UInt(bitPattern: 0x2)
|
|
static let DestroyOnFailure = UInt(bitPattern: 0x4)
|
|
}
|
|
|
|
struct ValueWitnessFlags {
|
|
static let IsNonInline = UInt32(0x00020000)
|
|
}
|
|
|
|
/// The representation of an existential type, which describes its layout it
|
|
/// memory.
|
|
enum ExistentialTypeRepresentation: UInt8 {
|
|
/// An opaque existential, which has a 3-word buffer followed by the type
|
|
/// metadata.
|
|
case opaque = 0
|
|
|
|
/// A class-bound existential, which stores the object.
|
|
case `class` = 1
|
|
|
|
/// An error existential, i.e., `any Error`, which stores a boxed error.
|
|
case error = 2
|
|
|
|
/// Given an address of an existential value that has the representation
|
|
/// described by `self`, project to the stored metadata and value.
|
|
func project(
|
|
_ existentialRaw: UnsafeRawPointer
|
|
) -> (metadata: UnsafeRawPointer, value: UnsafeRawPointer) {
|
|
// Extract the metadata pointer.
|
|
let existential = unsafe existentialRaw.assumingMemoryBound(to: UnsafeRawPointer.self)
|
|
switch self {
|
|
case .opaque:
|
|
// For an opaque existential, there is a 3-word buffer followed by the
|
|
// metadata. If the value is stored out-of-line, word 0 holds a pointer to
|
|
// a heap box instead.
|
|
let storedMetadata = unsafe existential[3]
|
|
return unsafe (
|
|
metadata: storedMetadata,
|
|
value: projectOpaqueValue(existentialRaw, storedMetadata: storedMetadata)
|
|
)
|
|
|
|
case .class:
|
|
/// For a class-bound existential, the payload is the object itself and
|
|
/// the metadata is available within the object.
|
|
let object = unsafe existential[0]
|
|
return unsafe (
|
|
metadata: unsafe object.assumingMemoryBound(to: UnsafeRawPointer.self)
|
|
.pointee,
|
|
value: object
|
|
)
|
|
|
|
case .error:
|
|
/// For an 'any Error' existential, we have an error box containing the
|
|
/// metadata and the value.
|
|
let (type, _, value) = unsafe _errorBoxContents(UnsafeRawPointer(existential[0]))
|
|
return unsafe (metadata: type, value: value)
|
|
}
|
|
}
|
|
|
|
/// Project the value address from an opaque existential, handling both
|
|
/// inline storage and non-inline (heap-boxed) storage.
|
|
private func projectOpaqueValue(
|
|
_ existentialRaw: UnsafeRawPointer,
|
|
storedMetadata: UnsafeRawPointer
|
|
) -> UnsafeRawPointer {
|
|
let flags = unsafe UInt32(_swift_embedded_metadata_get_vwt_flags(
|
|
UnsafeMutableRawPointer(mutating: storedMetadata)))
|
|
if flags & ValueWitnessFlags.IsNonInline != 0 {
|
|
// Non-inline: word 0 is a box pointer; project through to the value.
|
|
let box = unsafe existentialRaw.assumingMemoryBound(to: UnsafeRawPointer.self)[0]
|
|
let alignMask = Int(unsafe _swift_embedded_metadata_get_align_mask(
|
|
UnsafeMutableRawPointer(mutating: storedMetadata)))
|
|
let startOfValue = unsafe (MemoryLayout<HeapObject>.size + alignMask) & ~alignMask
|
|
return unsafe box.advanced(by: startOfValue)
|
|
}
|
|
// Inline: value starts at word 0 of the existential buffer.
|
|
return unsafe existentialRaw
|
|
}
|
|
}
|
|
|
|
/// Describes the different kinds of metadata.
|
|
enum MetadataKind: Equatable {
|
|
/// The type is a class.
|
|
case `class`
|
|
|
|
/// The type is a struct.
|
|
case `struct`
|
|
|
|
/// The type is an enum.
|
|
case `enum`
|
|
|
|
/// The type is an existential with the given representation.
|
|
case existential(ExistentialTypeRepresentation)
|
|
|
|
/// Given a pointer to metadata, figure out its kind.
|
|
init?(metadata: UnsafeRawPointer) {
|
|
// The kinds here match those in swift/ABI/MetadataKind.def.
|
|
let addrOfMetadataKind = unsafe metadata.assumingMemoryBound(to: UInt.self)
|
|
let kind = unsafe addrOfMetadataKind.pointee
|
|
switch kind {
|
|
case 0: self = .class
|
|
case 0 | Self.metadataKindIsNonHeap: self = .struct
|
|
case 1 | Self.metadataKindIsNonHeap: self = .enum
|
|
|
|
case 3 | Self.metadataKindIsNonHeap | Self.metadataKindIsRuntimePrivate:
|
|
let rawRepr = unsafe (addrOfMetadataKind + 1).pointee
|
|
if let rawRepr8 = UInt8(exactly: rawRepr),
|
|
let repr = ExistentialTypeRepresentation(rawValue: rawRepr8) {
|
|
self = .existential(repr)
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
default:
|
|
if kind > Self.lastEnumerated {
|
|
self = .class
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Metadata that represents something that isn't a type.
|
|
static let metadataKindIsNonType: UInt = 0x400
|
|
|
|
/// Metadata that represents something that isn't always stored on the heap.
|
|
static let metadataKindIsNonHeap: UInt = 0x200
|
|
|
|
/// Metadata that is private to the ABI-stable runtime.
|
|
static let metadataKindIsRuntimePrivate: UInt = 0x100
|
|
|
|
/// The last enumerated value for the metadata. Any value greater than this is
|
|
/// considered a class.
|
|
static let lastEnumerated = 0x7FF
|
|
}
|
|
|
|
func projectHeapObject(_ exist: UnsafeRawPointer) -> UnsafeRawPointer{
|
|
return unsafe exist.assumingMemoryBound(to: UnsafeRawPointer.self).pointee
|
|
}
|
|
|
|
/// The result of performing a dynamic cast.
|
|
private enum DynamicCastResult {
|
|
/// The cast failed.
|
|
case failure
|
|
|
|
/// The cast succeeded by copying the source. The source is still valid.
|
|
case successViaCopy
|
|
|
|
/// The cast succeeded by taking from the source. The source is now
|
|
/// uninitialized.
|
|
case successViaTake
|
|
}
|
|
|
|
/// Attempt to perform a dynamic cast from src to dst, where we have information
|
|
/// about the metadata for each.
|
|
private func tryCast(
|
|
dst: UnsafeMutableRawPointer,
|
|
dstMetadata: UnsafeRawPointer,
|
|
src: UnsafeRawPointer,
|
|
srcMetadata: UnsafeRawPointer,
|
|
takeOnSuccess: Bool,
|
|
) -> DynamicCastResult {
|
|
// If types match exactly, the cast succeeds. We just move/copy the data.
|
|
if unsafe srcMetadata == dstMetadata {
|
|
switch takeOnSuccess {
|
|
case true:
|
|
unsafe _swift_embedded_metadata_initialize_with_take(
|
|
srcMetadata,
|
|
dst,
|
|
UnsafeMutableRawPointer(mutating: src)
|
|
)
|
|
return .successViaTake
|
|
|
|
case false:
|
|
unsafe _swift_embedded_metadata_initialize_with_copy(
|
|
srcMetadata,
|
|
dst,
|
|
src
|
|
)
|
|
return .successViaCopy
|
|
}
|
|
}
|
|
|
|
// If we can't figure out the source metadata kind, only the exact match
|
|
// above can result in a successful cast, so fail quickly.
|
|
guard let srcMetadataKind = unsafe MetadataKind(metadata: srcMetadata) else {
|
|
return .failure
|
|
}
|
|
|
|
switch srcMetadataKind {
|
|
case .existential(let repr):
|
|
// Project the existential, then try to cast it.
|
|
// Note that we can only "take" on success if the projection didn't change
|
|
// the address. Otherwise, we would be destroying an inner object.
|
|
let (srcInnerMetadata, srcInner) = unsafe repr.project(src)
|
|
return unsafe tryCast(
|
|
dst: dst,
|
|
dstMetadata: dstMetadata,
|
|
src: srcInner,
|
|
srcMetadata: srcInnerMetadata,
|
|
takeOnSuccess: takeOnSuccess && src == srcInner
|
|
)
|
|
|
|
case .class:
|
|
// If the destination is not a class, this cannot succeed.
|
|
guard unsafe MetadataKind(metadata: dstMetadata) == .class else {
|
|
return .failure
|
|
}
|
|
|
|
// Attempt to perform the class downcast.
|
|
let srcObject = unsafe projectHeapObject(src)
|
|
guard let resultObject = unsafe swift_dynamicCastClass(
|
|
object: srcObject,
|
|
targetMetadata: dstMetadata
|
|
) else {
|
|
return .failure
|
|
}
|
|
|
|
// Success! Write the resulting object into the destination.
|
|
unsafe UnsafeMutableRawPointer(mutating: dst)
|
|
.assumingMemoryBound(to: UnsafeRawPointer.self).pointee = resultObject
|
|
|
|
// Adjust reference counting if needed.
|
|
switch takeOnSuccess {
|
|
case true:
|
|
return .successViaTake
|
|
|
|
case false:
|
|
// The dynamic cast operation is +0, so perform an extra retain on the
|
|
// result object when we were asked to copy.
|
|
swift_retain(object: resultObject._rawValue)
|
|
return .successViaCopy
|
|
}
|
|
|
|
default:
|
|
// We handled equivalence above. There are no other subtyping relations
|
|
// permitted in Embedded Swift.
|
|
return .failure
|
|
}
|
|
}
|
|
|
|
@c
|
|
public func swift_dynamicCast(
|
|
_ dest: Builtin.RawPointer, /* points to a concrete type */
|
|
_ src: Builtin.RawPointer, /* points to an existential or class */
|
|
_ srcMetadata: Builtin.RawPointer,
|
|
_ dstMetadata: Builtin.RawPointer,
|
|
_ flags: UInt
|
|
) -> Bool {
|
|
|
|
let isUnconditionalCast : Bool = (flags & DynamicCastFlags.Unconditional) != 0
|
|
let isTakeOnSuccess : Bool = (flags & DynamicCastFlags.TakeOnSuccess) != 0
|
|
let isDestroyOnFailure : Bool =
|
|
(flags & DynamicCastFlags.DestroyOnFailure) != 0
|
|
|
|
let castResult = unsafe tryCast(
|
|
dst: UnsafeMutableRawPointer(dest),
|
|
dstMetadata: UnsafeRawPointer(dstMetadata),
|
|
src: UnsafeRawPointer(src),
|
|
srcMetadata: UnsafeRawPointer(srcMetadata),
|
|
takeOnSuccess: isTakeOnSuccess
|
|
)
|
|
|
|
switch castResult {
|
|
case .failure:
|
|
if isUnconditionalCast {
|
|
fatalError("failed cast")
|
|
}
|
|
|
|
if isDestroyOnFailure {
|
|
unsafe _swift_embedded_metadata_destroy(
|
|
UnsafeRawPointer(srcMetadata),
|
|
UnsafeMutableRawPointer(src)
|
|
)
|
|
}
|
|
|
|
return false
|
|
|
|
case .successViaCopy:
|
|
// If we were asked to do a take, but ended up having to copy, destroy the
|
|
// original source.
|
|
if isTakeOnSuccess {
|
|
unsafe _swift_embedded_metadata_destroy(
|
|
UnsafeRawPointer(srcMetadata),
|
|
UnsafeMutableRawPointer(src)
|
|
)
|
|
}
|
|
|
|
return true
|
|
|
|
case .successViaTake:
|
|
return true
|
|
}
|
|
}
|
|
|
|
@c
|
|
public func swift_dynamicCastClass(
|
|
object: UnsafeRawPointer,
|
|
targetMetadata: UnsafeRawPointer
|
|
) -> UnsafeRawPointer? {
|
|
let sourceObj = unsafe object.assumingMemoryBound(to: HeapObject.self)
|
|
var type = unsafe _swift_embedded_get_heap_object_metadata_pointer(
|
|
UnsafeMutableRawPointer(mutating: sourceObj)
|
|
).assumingMemoryBound(to: ClassMetadata.self)
|
|
let targetType = unsafe targetMetadata.assumingMemoryBound(to: ClassMetadata.self)
|
|
while unsafe type != targetType {
|
|
guard let superType = unsafe type.pointee.superclassMetadata else {
|
|
return nil
|
|
}
|
|
unsafe type = UnsafePointer(superType)
|
|
}
|
|
return unsafe object
|
|
}
|
|
|
|
@c
|
|
public func swift_dynamicCastClassUnconditional(object: UnsafeMutableRawPointer, targetMetadata: UnsafeRawPointer,
|
|
file: UnsafePointer<CChar>, line: CUnsignedInt, column: CUnsignedInt) -> UnsafeRawPointer {
|
|
guard let result = unsafe swift_dynamicCastClass(object: object, targetMetadata: targetMetadata) else {
|
|
fatalError("failed cast")
|
|
}
|
|
return unsafe result
|
|
}
|