From 62ac48a17e0b2d27ce13b4782a343a470964bdbd Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Wed, 12 Nov 2025 13:51:41 -0800 Subject: [PATCH] Complete support for outline existential storage ... or so I believe --- include/swift/Runtime/RuntimeFunctions.def | 8 +++ lib/IRGen/GenExistential.cpp | 20 +++++- lib/IRGen/GenHeap.cpp | 6 ++ lib/IRGen/IRGenFunction.h | 3 + .../SwiftShims/swift/shims/EmbeddedShims.h | 32 +++++++-- stdlib/public/core/EmbeddedRuntime.swift | 40 +++++++++-- test/embedded/existential.swift | 66 +++++++++++++++++-- 7 files changed, 159 insertions(+), 16 deletions(-) diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index e82edee616f..d52331e3d5d 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -236,6 +236,14 @@ FUNCTION(NativeStrongReleaseDirect, Swift, swift_releaseDirect, SwiftDirectRR_CC EFFECT(RuntimeEffect::RefCounting, RuntimeEffect::Deallocating), UNKNOWN_MEMEFFECTS) +// void swift_releaseBox(void *ptr); +FUNCTION(ReleaseBox, Swift, swift_releaseBox, C_CC, AlwaysAvailable, + RETURNS(VoidTy), + ARGS(RefCountedPtrTy), + ATTRS(NoUnwind), + EFFECT(RuntimeEffect::RefCounting, RuntimeEffect::Deallocating), + UNKNOWN_MEMEFFECTS) + // void *swift_retain_n(void *ptr, int32_t n); FUNCTION(NativeStrongRetainN, Swift, swift_retain_n, C_CC, AlwaysAvailable, RETURNS(RefCountedPtrTy), diff --git a/lib/IRGen/GenExistential.cpp b/lib/IRGen/GenExistential.cpp index 1c39124ce0e..89e017d922d 100644 --- a/lib/IRGen/GenExistential.cpp +++ b/lib/IRGen/GenExistential.cpp @@ -2314,7 +2314,21 @@ Address irgen::emitAllocateBoxedOpaqueExistentialBuffer( if (fixedTI->getFixedPacking(IGF.IGM) == FixedPacking::OffsetZero) { return valueTI.getAddressForPointer(IGF.Builder.CreateBitCast( existentialBuffer.getAddress(), IGF.IGM.PtrTy)); + } else if (IGF.IGM.Context.LangOpts.hasFeature(Feature::EmbeddedExistentials)) { + llvm::Value *box, *address; + auto *metadata = existLayout.loadMetadataRef(IGF, existentialContainer); + IGF.emitAllocBoxCall(metadata, box, address); + llvm::Value *addressInBox = + IGF.Builder.CreateBitCast(address, IGF.IGM.OpaquePtrTy); + IGF.Builder.CreateStore( + box, Address(IGF.Builder.CreateBitCast( + existentialBuffer.getAddress(), IGF.IGM.PtrTy), + IGF.IGM.RefCountedPtrTy, + existLayout.getAlignment(IGF.IGM))); + + return valueTI.getAddressForPointer(addressInBox); } + // Otherwise, allocate a box with enough storage. Address addr = emitAllocateExistentialBoxInBuffer( IGF, valueType, existentialBuffer, genericEnv, "exist.box.addr", @@ -2883,7 +2897,11 @@ static llvm::Function *getDestroyBoxedOpaqueExistentialBufferFunction( Builder.CreateBitCast(buffer.getAddress(), IGM.PtrTy); auto *reference = Builder.CreateLoad(Address( referenceAddr, IGM.RefCountedPtrTy, buffer.getAlignment())); - IGF.emitNativeStrongRelease(reference, IGF.getDefaultAtomicity()); + if (IGF.IGM.Context.LangOpts + .hasFeature(Feature::EmbeddedExistentials)) { + IGF.emitReleaseBox(reference); + } else + IGF.emitNativeStrongRelease(reference, IGF.getDefaultAtomicity()); Builder.CreateRetVoid(); } diff --git a/lib/IRGen/GenHeap.cpp b/lib/IRGen/GenHeap.cpp index 6b41ddebf87..111077347f1 100644 --- a/lib/IRGen/GenHeap.cpp +++ b/lib/IRGen/GenHeap.cpp @@ -1274,6 +1274,12 @@ void IRGenFunction::emitNativeStrongRelease(llvm::Value *value, emitUnaryRefCountCall(*this, function, value); } +void IRGenFunction::emitReleaseBox(llvm::Value *value) { + if (doesNotRequireRefCounting(value)) + return; + emitUnaryRefCountCall(*this, IGM.getReleaseBoxFn(), value); +} + void IRGenFunction::emitNativeSetDeallocating(llvm::Value *value) { if (doesNotRequireRefCounting(value)) return; emitUnaryRefCountCall(*this, IGM.getNativeSetDeallocatingFn(), value); diff --git a/lib/IRGen/IRGenFunction.h b/lib/IRGen/IRGenFunction.h index e96f4fae589..c25fb901e8b 100644 --- a/lib/IRGen/IRGenFunction.h +++ b/lib/IRGen/IRGenFunction.h @@ -626,6 +626,9 @@ public: void emitNativeStrongRelease(llvm::Value *value, Atomicity atomicity); void emitNativeSetDeallocating(llvm::Value *value); + // Routines to deal with box (embedded) runtime calls. + void emitReleaseBox(llvm::Value *value); + // Routines for the ObjC reference-counting style. void emitObjCStrongRetain(llvm::Value *value); llvm::Value *emitObjCRetainCall(llvm::Value *value); diff --git a/stdlib/public/SwiftShims/swift/shims/EmbeddedShims.h b/stdlib/public/SwiftShims/swift/shims/EmbeddedShims.h index 08a998af11b..f73a40e63ae 100644 --- a/stdlib/public/SwiftShims/swift/shims/EmbeddedShims.h +++ b/stdlib/public/SwiftShims/swift/shims/EmbeddedShims.h @@ -113,20 +113,42 @@ typedef struct { #endif } EmbeddedMetaDataPrefix; -static inline __swift_size_t _swift_embedded_metadata_get_size(void *metadata) { +static inline __swift_size_t +_swift_embedded_metadata_get_size(void *metadata) { EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1]; return fullmeta->vwt->size; } -static inline __swift_size_t _swift_embedded_metadata_get_align_mask(void *metadata) { - EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1]; - - unsigned flags = fullmeta->vwt->flags; +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; } +static inline __swift_size_t +_swift_embedded_metadata_get_align_mask(void *metadata) { + EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1]; + 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]; + __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); + fullmeta->vwt->destroyFn(addrInBox, metadata); +} + +static inline void +_swift_embedded_initialize_box(void *metadata, void *newObjectAddr, void *oldObjectAddr) { + EmbeddedMetaDataPrefix *fullmeta = (EmbeddedMetaDataPrefix*)&((void **)metadata)[-1]; + fullmeta->vwt->initializeWithCopyFn(newObjectAddr, oldObjectAddr, metadata); +} #ifdef __cplusplus } // extern "C" diff --git a/stdlib/public/core/EmbeddedRuntime.swift b/stdlib/public/core/EmbeddedRuntime.swift index b2d64b2232b..e455926e5db 100644 --- a/stdlib/public/core/EmbeddedRuntime.swift +++ b/stdlib/public/core/EmbeddedRuntime.swift @@ -263,7 +263,7 @@ public func swift_allocEmptyBox() -> Builtin.RawPointer { @_silgen_name("swift_allocBox") -public func swift_allocBox(metadata: Builtin.RawPointer) -> (Builtin.RawPointer, Builtin.RawPointer) { +public func swift_allocBox(_ metadata: Builtin.RawPointer) -> (Builtin.RawPointer, Builtin.RawPointer) { let alignMask = Int(unsafe _swift_embedded_metadata_get_align_mask(UnsafeMutableRawPointer(metadata))) let size = Int(unsafe _swift_embedded_metadata_get_size(UnsafeMutableRawPointer(metadata))) let headerSize = unsafe MemoryLayout.size + MemoryLayout.size @@ -283,12 +283,29 @@ public func swift_allocBox(metadata: Builtin.RawPointer) -> (Builtin.RawPointer, return (object._rawValue, boxedValueAddr._rawValue) } -@_cdecl("swift_deallocBox") -public func swift_deallocBox(object: Builtin.RawPointer) { +@c +public func swift_deallocBox(_ object: Builtin.RawPointer) { unsafe free(UnsafeMutableRawPointer(object)) } +@_silgen_name("swift_makeBoxUnique") +public func swifft_makeBoxUnique(buffer: Builtin.RawPointer, metadata: Builtin.RawPointer, alignMask: Int) -> (Builtin.RawPointer, Builtin.RawPointer){ + let addrOfHeapObjectPtr = unsafe UnsafeMutablePointer(buffer) + let box = unsafe addrOfHeapObjectPtr.pointee + let headerSize = unsafe MemoryLayout.size + MemoryLayout.size + let startOfBoxedValue = ((headerSize + alignMask) & ~alignMask) + let oldObjectAddr = unsafe UnsafeMutableRawPointer(box) + startOfBoxedValue + if !swift_isUniquelyReferenced_native(object: box) { + let refAndObjectAddr = swift_allocBox(metadata) + unsafe _swift_embedded_initialize_box(UnsafeMutableRawPointer(metadata), UnsafeMutableRawPointer(refAndObjectAddr.1), oldObjectAddr) + swift_releaseBox(box) + unsafe addrOfHeapObjectPtr.pointee = refAndObjectAddr.0 + return refAndObjectAddr + } else { + return (box, oldObjectAddr._rawValue) + } +} /// Refcounting @@ -416,7 +433,7 @@ public func swift_release_n(object: Builtin.RawPointer, n: UInt32) { unsafe swift_release_n_(object: o, n: n) } -func swift_release_n_(object: UnsafeMutablePointer?, n: UInt32) { +func swift_release_n_(object: UnsafeMutablePointer?, n: UInt32, isBoxRelease: Bool = false) { guard let object = unsafe object else { return } @@ -441,12 +458,25 @@ func swift_release_n_(object: UnsafeMutablePointer?, n: UInt32) { let doNotFree = unsafe (loadedRefcount & HeapObject.doNotFreeBit) != 0 unsafe storeRelaxed(refcount, newValue: HeapObject.immortalRefCount | (doNotFree ? HeapObject.doNotFreeBit : 0)) - unsafe _swift_embedded_invoke_heap_object_destroy(object) + if isBoxRelease { + unsafe _swift_embedded_invoke_box_destroy(object) + } else { + unsafe _swift_embedded_invoke_heap_object_destroy(object) + } } else if resultingRefcount < 0 { fatalError("negative refcount") } } +@c +public func swift_releaseBox(_ object: Builtin.RawPointer) { + if !isValidPointerForNativeRetain(object: object) { + fatalError("not a valid pointer for releaseBox") + } + let o = unsafe UnsafeMutablePointer(object) + unsafe swift_release_n_(object: o, n: 1, isBoxRelease: true) +} + @c public func swift_bridgeObjectRelease(object: Builtin.RawPointer) { swift_bridgeObjectRelease_n(object: object, n: 1) diff --git a/test/embedded/existential.swift b/test/embedded/existential.swift index 031c4b909d4..5e2f787f428 100644 --- a/test/embedded/existential.swift +++ b/test/embedded/existential.swift @@ -57,6 +57,11 @@ enum GenericEnumWithClass { // OUTPUT: deinit called // OUTPUT: deinit called +// OUTPUT: deinit called +// OUTPUT: deinit called +// OUTPUT: deinit called +// OUTPUT: deinit called + // OUTPUT-NOT: deinit called func test() { @@ -68,6 +73,8 @@ func test() { let _: any Any = GenericEnumWithClass.c(GC()) let _: any Any = (3, 4) let _: any Any = (StructWithClass(), StructWithClass()) + // outline storage case + let _: any Any = (StructWithClass(), StructWithClass(), StructWithClass(), StructWithClass()) } protocol Basic { @@ -94,6 +101,14 @@ struct MyStruct : Derived { func b() { print("b MyStruct") } } +struct LargeMyStruct : Derived { + var x = (1, 2, 3, 4, 5) + var refCounted = StructWithClass() + + func a() { print("a LargeMyStruct \(self.x.4)") } + func b() { print("b LargeMyStruct") } +} + enum MyEnum : Derived { case a case b(Int) @@ -120,6 +135,7 @@ func test2(_ p: any Derived) { protocol ValuePrinter { func printValue() + mutating func mutate() } protocol WithAssoc { associatedtype Assoc : ValuePrinter @@ -130,6 +146,20 @@ extension Int : ValuePrinter { func printValue() { print("my value: \(self)") } + mutating func mutate() { + self = 8 + print("my value (mutating expect 8): \(self)") + } +} + +extension LargeMyStruct : ValuePrinter { + func printValue() { + print("my value of LargeMyStruct: \(self.x.4)") + } + mutating func mutate() { + self.x = (6, 7, 8, 9, 10) + print("my value of LargeMyStruct (mutating expect 10): \(self.x.4)") + } } struct ConformWithAssoc : WithAssoc { @@ -139,30 +169,56 @@ struct ConformWithAssoc : WithAssoc { } } +struct ConformWithLargeAssoc : WithAssoc { + var x = LargeMyStruct() + + func a() -> LargeMyStruct { + return x + } +} + func test3(_ p: any WithAssoc) { let x = p.a() x.printValue() } +func test4(_ p: any WithAssoc) { + var x = p.a() + let c = x + x.mutate() + c.printValue() +} + @main struct Main { static func main() { test() + test2(Implementor()) // OUTPUT: a // OUTPUT: b + test2(5) // OUTPUT: a Int 5 // OUTPUT: b Int 5 + test2(MyStruct()) // OUTPUT: a MyStruct 5 // OUTPUT: b MyStruct + test2(MyEnum.b(5)) // OUTPUT: a MyEnum // OUTPUT: 5 // OUTPUT: b MyEnum -// OUTPUT: my value: 1 - test2(Implementor()) - test2(5) - test2(MyStruct()) - test2(MyEnum.b(5)) test3(ConformWithAssoc()) +// OUTPUT: my value: 1 + test3(ConformWithLargeAssoc()) +// OUTPUT: my value of LargeMyStruct: 5 +// OUTPUT: deinit called + test4(ConformWithAssoc()) +// OUTPUT: my value (mutating expect 8): 8 +// OUTPUT: my value: 1 + test4(ConformWithLargeAssoc()) +// OUTPUT: my value of LargeMyStruct (mutating expect 10): 10 +// OUTPUT: my value of LargeMyStruct: 5 +// OUTPUT: deinit called +// OUTPUT-NOT: deinit called } }