Merge pull request #85602 from aschwaighofer/wip_embedded_exit_cast

[embedded] Implement swift_dynamicCast suport for casts from existential to concrete type
This commit is contained in:
Arnold Schwaighofer
2025-11-20 21:01:41 -05:00
committed by GitHub
9 changed files with 318 additions and 15 deletions

View File

@@ -90,6 +90,10 @@ private struct FunctionChecker {
is ExistentialMetatypeInst:
if !context.options.enableEmbeddedSwiftExistentials {
throw Diagnostic(.embedded_swift_existential_type, instruction.operands[0].value.type, at: instruction.location)
} else if let ie = instruction as? InitExistentialAddrInst {
for conf in ie.conformances {
try checkConformance(conf, location: ie.location)
}
}
case let aeb as AllocExistentialBoxInst:
@@ -116,7 +120,20 @@ private struct FunctionChecker {
case is CheckedCastAddrBranchInst,
is UnconditionalCheckedCastAddrInst:
throw Diagnostic(.embedded_swift_dynamic_cast, at: instruction.location)
if !context.options.enableEmbeddedSwiftExistentials {
throw Diagnostic(.embedded_swift_dynamic_cast, at: instruction.location)
} else {
if let checkedCast = instruction as? CheckedCastAddrBranchInst {
if !checkedCast.supportedInEmbeddedSwift {
throw Diagnostic(.embedded_swift_dynamic_cast, at: instruction.location)
}
} else {
let checkedCast = instruction as! UnconditionalCheckedCastAddrInst
if !checkedCast.supportedInEmbeddedSwift {
throw Diagnostic(.embedded_swift_dynamic_cast, at: instruction.location)
}
}
}
case let abi as AllocBoxInst:
// It needs a bit of work to support alloc_box of generic non-copyable structs/enums with deinit,
@@ -221,7 +238,8 @@ private struct FunctionChecker {
else {
return
}
if !conformance.protocol.requiresClass {
if !context.options.enableEmbeddedSwiftExistentials &&
!conformance.protocol.requiresClass {
throw Diagnostic(.embedded_swift_existential_protocol, conformance.protocol.name, at: location)
}

View File

@@ -1001,6 +1001,23 @@ func canDynamicallyCast(from sourceType: CanonicalType, to destType: CanonicalTy
}
}
func isCastSupportedInEmbeddedSwift(from sourceType: Type,
to destType: Type ) -> Bool {
if !sourceType.isExistential {
return false
}
if destType.hasArchetype {
return false
}
// Tuple?
if !destType.isStruct && !destType.isClass && !destType.isEnum {
return false
}
return true
}
extension CheckedCastAddrBranchInst {
var dynamicCastResult: Bool? {
switch classifyDynamicCastBridged(bridged) {
@@ -1010,6 +1027,18 @@ extension CheckedCastAddrBranchInst {
default: fatalError("unknown result from classifyDynamicCastBridged")
}
}
var supportedInEmbeddedSwift: Bool {
return isCastSupportedInEmbeddedSwift(from: source.type,
to: destination.type)
}
}
extension UnconditionalCheckedCastAddrInst {
var supportedInEmbeddedSwift: Bool {
return isCastSupportedInEmbeddedSwift(from: source.type,
to: destination.type)
}
}
extension CopyAddrInst {

View File

@@ -879,7 +879,11 @@ final public
class OpenExistentialValueInst : SingleValueInstruction, UnaryInstruction {}
final public
class InitExistentialAddrInst : SingleValueInstruction, UnaryInstruction {}
class InitExistentialAddrInst : SingleValueInstruction, UnaryInstruction {
public var conformances: ConformanceArray {
ConformanceArray(bridged: bridged.InitExistentialAddrInst_getConformances())
}
}
final public
class DeinitExistentialAddrInst : Instruction, UnaryInstruction {}

View File

@@ -808,6 +808,7 @@ struct BridgedInstruction {
BRIDGED_INLINE bool IndexAddrInst_needsStackProtection() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformanceArray InitExistentialRefInst_getConformances() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedCanType InitExistentialRefInst_getFormalConcreteType() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedConformanceArray InitExistentialAddrInst_getConformances() const;
BRIDGED_INLINE bool OpenExistentialAddr_isImmutable() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedGlobalVar GlobalAccessInst_getGlobal() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedGlobalVar AllocGlobalInst_getGlobal() const;

View File

@@ -1288,6 +1288,9 @@ BridgedCanType BridgedInstruction::InitExistentialRefInst_getFormalConcreteType(
return getAs<swift::InitExistentialRefInst>()->getFormalConcreteType();
}
BridgedConformanceArray BridgedInstruction::InitExistentialAddrInst_getConformances() const {
return {getAs<swift::InitExistentialAddrInst>()->getConformances()};
}
bool BridgedInstruction::OpenExistentialAddr_isImmutable() const {
switch (getAs<swift::OpenExistentialAddrInst>()->getAccessKind()) {
case swift::OpenedExistentialAccess::Immutable: return true;

View File

@@ -85,7 +85,14 @@ llvm::Value *irgen::emitCheckedCast(IRGenFunction &IGF,
src = IGF.Builder.CreateElementBitCast(src, IGF.IGM.OpaqueTy);
// Load type metadata for the source's static type and the target type.
llvm::Value *srcMetadata = IGF.emitTypeMetadataRef(srcType);
llvm::Value *srcMetadata = nullptr;
// Embedded swift currently only supports existential -> concrete type casts.
if (IGF.IGM.Context.LangOpts.hasFeature(Feature::EmbeddedExistentials)) {
srcMetadata = llvm::ConstantPointerNull::get(IGF.IGM.TypeMetadataPtrTy);
} else {
srcMetadata = IGF.emitTypeMetadataRef(srcType);
}
llvm::Value *targetMetadata = IGF.emitTypeMetadataRef(targetType);
llvm::Value *args[] = {

View File

@@ -96,7 +96,11 @@ typedef struct {
void* (*initializeWithCopyFn)(void*, void*, void*);
#endif
void *assignWithCopyFn;
void *initializeWithTakeFn;
#if __has_feature(ptrauth_calls)
void* (* __ptrauth(0, 1, 0x48d8) initializeWithTakeFn)(void*, void*, void*);
#else
void* (*initializeWithTakeFn)(void *, void*, void*);
#endif
void *assignWithTakeFn;
void *getEnumTagSinglePayloadFn;
void *storeEnumTagSinglePayload;
@@ -113,43 +117,105 @@ typedef struct {
#endif
} EmbeddedMetaDataPrefix;
typedef enum {
AlignmentMask = 0x000000FF,
IsNonInline = 0x00020000,
} ValueWitnessTableFlags;
static inline
EmbeddedMetaDataPrefix *_swift_embedded_get_full_metadata(void *metadata) {
EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1];
return fullmeta;
}
static inline __swift_size_t
_swift_embedded_metadata_get_size(void *metadata) {
EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1];
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
return fullmeta->vwt->size;
}
static inline __swift_size_t
_swift_embedded_metadata_get_align_mask_impl(EmbeddedMetaDataPrefix *fullMetadata) {
unsigned flags = fullMetadata->vwt->flags;
unsigned embeddedValueWitnessTableFlagsMask = 0xFF;
return flags & embeddedValueWitnessTableFlagsMask;
ValueWitnessTableFlags alignMask = AlignmentMask;
return flags & alignMask;
}
static inline __swift_size_t
_swift_embedded_metadata_get_align_mask(void *metadata) {
EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1];
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
return _swift_embedded_metadata_get_align_mask_impl(fullmeta);
}
static inline void
_swift_embedded_invoke_box_destroy(void *object) {
void *metadata = ((EmbeddedHeapObject *)object)->metadata;
EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1];
static inline void *
_swift_embedded_box_project(void *object, EmbeddedMetaDataPrefix *fullmeta) {
__swift_size_t alignMask = _swift_embedded_metadata_get_align_mask_impl(fullmeta);
__swift_size_t headerSize = sizeof(void*) + sizeof(__swift_size_t);
__swift_size_t startOfBoxedValue = (headerSize + alignMask) & ~alignMask;
void *addrInBox = (void *)(((unsigned char *)object) + startOfBoxedValue);
return addrInBox;
}
static inline void
_swift_embedded_invoke_box_destroy(void *object) {
void *metadata = ((EmbeddedHeapObject *)object)->metadata;
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
void *addrInBox = _swift_embedded_box_project(object, fullmeta);
fullmeta->vwt->destroyFn(addrInBox, metadata);
}
static inline void
_swift_embedded_initialize_box(void *metadata, void *newObjectAddr, void *oldObjectAddr) {
EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1];
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
fullmeta->vwt->initializeWithCopyFn(newObjectAddr, oldObjectAddr, metadata);
}
typedef struct {
void *inlineBuffer[3];
void *metadata;
} ExistentialValue;
static inline void
_swift_embedded_existential_destroy(void *exist) {
ExistentialValue* existVal = (ExistentialValue*)exist;
void *metadata = existVal->metadata;
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
ValueWitnessTableFlags isNonInlineMask = IsNonInline;
if (fullmeta->vwt->flags & IsNonInline) {
void *addrInBox = _swift_embedded_box_project(existVal->inlineBuffer[0], fullmeta);
fullmeta->vwt->destroyFn(addrInBox, metadata);
} else {
fullmeta->vwt->destroyFn(&(existVal->inlineBuffer[0]), metadata);
}
}
static inline void
_swift_embedded_existential_init_with_take(void *dst, void *srcExist) {
ExistentialValue* existVal = (ExistentialValue*)srcExist;
void *metadata = existVal->metadata;
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
ValueWitnessTableFlags isNonInlineMask = IsNonInline;
if (fullmeta->vwt->flags & IsNonInline) {
void *addrInBox = _swift_embedded_box_project(existVal->inlineBuffer[0], fullmeta);
fullmeta->vwt->initializeWithTakeFn(dst, addrInBox, metadata);
} else {
fullmeta->vwt->initializeWithTakeFn(dst, &(existVal->inlineBuffer[0]), metadata);
}
}
static inline void
_swift_embedded_existential_init_with_copy(void *dst, void *srcExist) {
ExistentialValue* existVal = (ExistentialValue*)srcExist;
void *metadata = existVal->metadata;
EmbeddedMetaDataPrefix *fullmeta = _swift_embedded_get_full_metadata(metadata);
ValueWitnessTableFlags isNonInlineMask = IsNonInline;
if (fullmeta->vwt->flags & IsNonInline) {
void *addrInBox = _swift_embedded_box_project(existVal->inlineBuffer[0], fullmeta);
fullmeta->vwt->initializeWithCopyFn(dst, addrInBox, metadata);
} else {
fullmeta->vwt->initializeWithCopyFn(dst, &(existVal->inlineBuffer[0]), metadata);
}
}
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -315,6 +315,113 @@ public func swifft_makeBoxUnique(buffer: Builtin.RawPointer, metadata: Builtin.R
return (box, oldObjectAddr._rawValue)
}
}
/// Dynamic cast support
/// Only supports existential container to concrete casts.
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 MetadataKind {
static let LastEnumerated = UInt(bitPattern: 0x7FF)
}
func projectExistentialMetadata(
_ exist: Builtin.RawPointer
) -> Builtin.RawPointer {
let offset = (3 * MemoryLayout<Builtin.RawPointer>.size)
let addrOfMetadata = unsafe UnsafeMutableRawPointer(exist) + offset
let metadataPtrAddr = unsafe addrOfMetadata.bindMemory(to: Builtin.RawPointer.self, capacity: 1)
return unsafe metadataPtrAddr.pointee
}
func isClassMetadata(_ metadata: Builtin.RawPointer) -> Bool {
let addrOfMetadataKind = unsafe UnsafePointer<UInt>(metadata)
let kind = unsafe addrOfMetadataKind.pointee
return kind == 0 || kind > MetadataKind.LastEnumerated
}
func projectHeapObject(_ exist: Builtin.RawPointer) -> UnsafeMutableRawPointer{
let addrOfHeapObject = unsafe UnsafePointer<UnsafeMutableRawPointer>(exist)
return unsafe addrOfHeapObject.pointee
}
@c
public func swift_dynamicCast(
_ dest: Builtin.RawPointer, /* points to a concrete type */
_ src: Builtin.RawPointer, /* points to an existential */
_ srcMetadata: Builtin.RawPointer, /* always nullptr */
_ 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 srcMetadata = projectExistentialMetadata(src)
let isClass = isClassMetadata(dstMetadata)
if isClass {
var success = false
let obj = unsafe projectHeapObject(src)
if isClassMetadata(srcMetadata) {
if srcMetadata != dstMetadata {
// check parent chain
success = unsafe swift_dynamicCastClass(object: obj, targetMetadata: UnsafeMutableRawPointer(dstMetadata)) != nil
} else {
success = true
}
}
if !success {
if isDestroyOnFailure {
unsafe _swift_embedded_existential_destroy(UnsafeMutableRawPointer(src))
}
if isUnconditionalCast {
fatalError("failed cast")
}
return false
}
if isTakeOnSuccess {
let dst = unsafe UnsafeMutablePointer<UnsafeMutableRawPointer>(dest)
unsafe dst.pointee = obj
} else {
let dst = unsafe UnsafeMutablePointer<UnsafeMutableRawPointer>(dest)
unsafe dst.pointee = obj
swift_retain(object: obj._rawValue)
}
return true;
}
// destintation type is not a class. Test exact match.
let success = srcMetadata == dstMetadata
if !success {
if isDestroyOnFailure {
unsafe _swift_embedded_existential_destroy(UnsafeMutableRawPointer(src))
}
if isUnconditionalCast {
fatalError("failed cast")
}
return false
}
if isTakeOnSuccess {
// take from an existential to a concrete type
unsafe _swift_embedded_existential_init_with_take(
UnsafeMutableRawPointer(dest), UnsafeMutableRawPointer(src))
} else {
// copy from an existential to a concrete type
unsafe _swift_embedded_existential_init_with_copy(
UnsafeMutableRawPointer(dest), UnsafeMutableRawPointer(src))
}
return true
}
/// Refcounting

View File

@@ -190,6 +190,32 @@ func test4(_ p: any WithAssoc) {
c.printValue()
}
func test5(_ p: any Any) {
print("test any as? MyStruct")
if let c = p as? MyStruct {
print("success")
c.a()
} else {
print("cast failed")
}
}
func test6(_ p: any Any) {
print("test any as? LargeMyStruct")
if let c = p as? LargeMyStruct {
print("success")
c.a()
} else {
print("cast failed")
}
}
func test7(_ p: any Any) {
print("test any as! LargeMyStruct")
let c = p as! LargeMyStruct
c.a()
}
@main
struct Main {
static func main() {
@@ -220,6 +246,48 @@ struct Main {
// OUTPUT: my value of LargeMyStruct (mutating expect 10): 10
// OUTPUT: my value of LargeMyStruct: 5
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test5(MyStruct())
// OUTPUT: test any as? MyStruct
// OUTPUT: success
// OUTPUT: a MyStruct 5
test5(GC<Int>())
// OUTPUT: test any as? MyStruct
// OUTPUT: cast failed
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test5(LargeMyStruct())
// OUTPUT: test any as? MyStruct
// OUTPUT: deinit called
// OUTPUT: cast failed
// OUTPUT-NOT: deinit called
test5(GenericStructWithClass<Int>())
// OUTPUT: test any as? MyStruct
// OUTPUT: cast failed
// OUTPUT: deinit called
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test6(MyStruct())
// OUTPUT: test any as? LargeMyStruct
// OUTPUT: cast failed
// OUTPUT-NOT: deinit called
test6(LargeMyStruct())
// OUTPUT: test any as? LargeMyStruct
// OUTPUT: success
// OUTPUT: a LargeMyStruct 5
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test6(GenericStructWithClass<Int>())
// OUTPUT: test any as? LargeMyStruct
// OUTPUT: cast failed
// OUTPUT: deinit called
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
test7(LargeMyStruct())
// OUTPUT: test any as! LargeMyStruct
// OUTPUT: a LargeMyStruct 5
// OUTPUT: deinit called
// OUTPUT-NOT: deinit called
}
}