From dca75c4e5c16a7159ac30571ca9a1e18f99aaa7a Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 26 Aug 2025 12:05:37 +0200 Subject: [PATCH 1/5] IRGen: revert hack to look for specialized deinits --- lib/IRGen/GenType.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/lib/IRGen/GenType.cpp b/lib/IRGen/GenType.cpp index 0d3b16a77111a..26dc3e4b5f277 100644 --- a/lib/IRGen/GenType.cpp +++ b/lib/IRGen/GenType.cpp @@ -26,7 +26,6 @@ #include "swift/Basic/Platform.h" #include "swift/Basic/SourceManager.h" #include "swift/IRGen/Linking.h" -#include "swift/SIL/GenericSpecializationMangler.h" #include "swift/SIL/SILModule.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/ADT/SmallString.h" @@ -2938,27 +2937,11 @@ static bool tryEmitDeinitCall(IRGenFunction &IGF, return true; } - auto deinitSILFn = deinitTable->getImplementation(); - - // Look for a specialization of deinit that we can call. - auto substitutions = ty->getContextSubstitutionMap(); - if (!substitutions.empty() && - !substitutions.getRecursiveProperties().hasArchetype()) { - Mangle::GenericSpecializationMangler mangler( - deinitSILFn->getASTContext(), deinitSILFn, - deinitSILFn->getSerializedKind()); - - auto specializedName = mangler.mangleReabstracted( - substitutions, /*needAlternativeMangling=*/false); - auto specializedFn = IGF.IGM.getSILModule().lookUpFunction(specializedName); - if (specializedFn) - deinitSILFn = specializedFn; - } - // The deinit should take a single value parameter of the nominal type, either // by @owned or indirect @in convention. - auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitSILFn, NotForDefinition); - auto deinitTy = deinitSILFn->getLoweredFunctionType(); + auto deinitFn = IGF.IGM.getAddrOfSILFunction(deinitTable->getImplementation(), + NotForDefinition); + auto deinitTy = deinitTable->getImplementation()->getLoweredFunctionType(); auto deinitFP = FunctionPointer::forDirect(IGF.IGM, deinitFn, nullptr, deinitTy); assert(deinitTy->getNumParameters() == 1 @@ -2966,6 +2949,8 @@ static bool tryEmitDeinitCall(IRGenFunction &IGF, && !deinitTy->hasError() && "deinit should have only one parameter"); + auto substitutions = ty->getContextSubstitutionMap(); + CalleeInfo info(deinitTy, deinitTy->substGenericArgs(IGF.getSILModule(), substitutions, From ab10dc23a572eed6dd581f373ada6dfd14431c2e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 26 Aug 2025 12:07:04 +0200 Subject: [PATCH 2/5] SIL: add `Context.getTupleType` --- SwiftCompilerSources/Sources/SIL/Context.swift | 7 +++++++ include/swift/SIL/SILBridging.h | 1 + include/swift/SIL/SILBridgingImpl.h | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/SwiftCompilerSources/Sources/SIL/Context.swift b/SwiftCompilerSources/Sources/SIL/Context.swift index 2c6fc029e40ff..1db07567efaeb 100644 --- a/SwiftCompilerSources/Sources/SIL/Context.swift +++ b/SwiftCompilerSources/Sources/SIL/Context.swift @@ -48,6 +48,13 @@ extension Context { public func getBuiltinIntegerType(bitWidth: Int) -> Type { _bridged.getBuiltinIntegerType(bitWidth).type } + public func getTupleType(elements: [Type]) -> AST.`Type` { + let bridgedElements = elements.map { $0.bridged } + return bridgedElements.withBridgedArrayRef { + AST.`Type`(bridged: _bridged.getTupleType($0)) + } + } + public var swiftArrayDecl: NominalTypeDecl { _bridged.getSwiftArrayDecl().getAs(NominalTypeDecl.self) } diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index bf38cec201f12..3079541cdae83 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -1425,6 +1425,7 @@ struct BridgedContext { SWIFT_IMPORT_UNSAFE OptionalBridgedFunction lookUpNominalDeinitFunction(BridgedDeclObj nominal) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedSubstitutionMap getContextSubstitutionMap(BridgedType type) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedType getBuiltinIntegerType(SwiftInt bitWidth) const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType getTupleType(BridgedArrayRef elementTypes) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj getSwiftArrayDecl() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj getSwiftMutableSpanDecl() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedValue getSILUndef(BridgedType type) const; diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index 0d2cbef48701e..c70c56d0c0f26 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -2847,6 +2847,14 @@ BridgedType BridgedContext::getBuiltinIntegerType(SwiftInt bitWidth) const { return swift::SILType::getBuiltinIntegerType(bitWidth, context->getModule()->getASTContext()); } +BridgedASTType BridgedContext::getTupleType(BridgedArrayRef elementTypes) const { + llvm::SmallVector elements; + for (auto bridgedElmtTy : elementTypes.unbridged()) { + elements.push_back(bridgedElmtTy.unbridged()); + } + return {swift::TupleType::get(elements, context->getModule()->getASTContext())}; +} + BridgedDeclObj BridgedContext::getSwiftArrayDecl() const { return {context->getModule()->getASTContext().getArrayDecl()}; } From 3da2c1476b11550464081e9c461c2cc0e74f10a1 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 26 Aug 2025 12:07:31 +0200 Subject: [PATCH 3/5] SIL: add `Builder.createCondBranch` --- SwiftCompilerSources/Sources/SIL/Builder.swift | 6 ++++++ include/swift/SIL/SILBridging.h | 3 +++ include/swift/SIL/SILBridgingImpl.h | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/SwiftCompilerSources/Sources/SIL/Builder.swift b/SwiftCompilerSources/Sources/SIL/Builder.swift index 59f4cd86263ea..55257f7711c05 100644 --- a/SwiftCompilerSources/Sources/SIL/Builder.swift +++ b/SwiftCompilerSources/Sources/SIL/Builder.swift @@ -567,6 +567,12 @@ public struct Builder { } } + @discardableResult + public func createCondBranch(condition: Value, trueBlock: BasicBlock, falseBlock: BasicBlock) -> CondBranchInst { + let condBr = bridged.createCondBranch(condition.bridged, trueBlock.bridged, falseBlock.bridged) + return notifyNew(condBr.getAs(CondBranchInst.self)) + } + @discardableResult public func createUnreachable() -> UnreachableInst { let ui = bridged.createUnreachable() diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index 3079541cdae83..bbbb87ee1f5d1 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -1282,6 +1282,9 @@ struct BridgedBuilder{ bool isOnStack = false) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createBranch(BridgedBasicBlock destBlock, BridgedValueArray arguments) const; + SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createCondBranch(BridgedValue condition, + BridgedBasicBlock trueBlock, + BridgedBasicBlock falseBlock) const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createUnreachable() const; SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createObject(BridgedType type, BridgedValueArray arguments, SwiftInt numBaseElements) const; diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index c70c56d0c0f26..f9e152428022b 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -2531,6 +2531,13 @@ BridgedInstruction BridgedBuilder::createBranch(BridgedBasicBlock destBlock, Bri arguments.getValues(argValues))}; } +BridgedInstruction BridgedBuilder::createCondBranch(BridgedValue condition, + BridgedBasicBlock trueBlock, + BridgedBasicBlock falseBlock) const { + return {unbridged().createCondBranch(regularLoc(), condition.getSILValue(), trueBlock.unbridged(), + falseBlock.unbridged())}; +} + BridgedInstruction BridgedBuilder::createUnreachable() const { return {unbridged().createUnreachable(loc.getLoc().getLocation())}; } From 49d3960c68eb292e303ffd082c9bfc9697ad3646 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 26 Aug 2025 12:09:36 +0200 Subject: [PATCH 4/5] MandatoryPerformanceOptimization: de-virtualize deinits of `builtin "destroyArray"` This is required for embedded swift. rdar://157131184 --- .../MandatoryPerformanceOptimizations.swift | 8 ++- .../Utilities/Devirtualization.swift | 62 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift index 6245f43c376b3..819e9657b0656 100644 --- a/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift +++ b/SwiftCompilerSources/Sources/Optimizer/ModulePasses/MandatoryPerformanceOptimizations.swift @@ -153,10 +153,14 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ modu worklist.addWitnessMethods(of: conformance, moduleContext) default: - break + if !devirtualizeDeinits(of: bi, simplifyCtxt) { + // If invoked from SourceKit avoid reporting false positives when WMO is turned off for indexing purposes. + if moduleContext.enableWMORequiredDiagnostics { + context.diagnosticEngine.diagnose(.deinit_not_visible, at: bi.location) + } + } } - // We need to de-virtualize deinits of non-copyable types to be able to specialize the deinitializers. case let destroyValue as DestroyValueInst: if !devirtualizeDeinits(of: destroyValue, simplifyCtxt) { diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/Devirtualization.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/Devirtualization.swift index 6d05399758d53..45fc75c3d8d64 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/Devirtualization.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/Devirtualization.swift @@ -28,6 +28,15 @@ func devirtualizeDeinits(of destroy: DestroyAddrInst, _ context: some MutatingCo return devirtualize(destroy: destroy, context) } +func devirtualizeDeinits(of builtin: BuiltinInst, _ context: some MutatingContext) -> Bool { + switch builtin.id { + case .DestroyArray: + return devirtualize(builtinDestroyArray: builtin, context) + default: + return true + } +} + private func devirtualize(destroy: some DevirtualizableDestroy, _ context: some MutatingContext) -> Bool { let type = destroy.type if !type.isMoveOnly { @@ -254,6 +263,59 @@ extension DestroyAddrInst : DevirtualizableDestroy { } } +private func devirtualize(builtinDestroyArray: BuiltinInst, _ context: some MutatingContext) -> Bool { + let function = builtinDestroyArray.parentFunction + let elementType = builtinDestroyArray.substitutionMap.replacementType.loweredType(in: function) + guard elementType.isMoveOnly, + // This avoids lowering the loop if the element is a non-copyable generic type. + (elementType.isStruct || elementType.isEnum) + else { + return true + } + + // Lower the `builtin "destroyArray" to a loop which destroys all elements + + let basePointer = builtinDestroyArray.arguments[1] + let arrayCount = builtinDestroyArray.arguments[2] + let indexType = arrayCount.type + let boolType = context.getBuiltinIntegerType(bitWidth: 1) + + let preheaderBlock = builtinDestroyArray.parentBlock + let exitBlock = context.splitBlock(after: builtinDestroyArray) + let headerBlock = context.createBlock(after: preheaderBlock) + let bodyBlock = context.createBlock(after: headerBlock) + + let preheaderBuilder = Builder(atEndOf: preheaderBlock, location: builtinDestroyArray.location, context) + let zero = preheaderBuilder.createIntegerLiteral(0, type: indexType) + let one = preheaderBuilder.createIntegerLiteral(1, type: indexType) + let falseValue = preheaderBuilder.createIntegerLiteral(0, type: boolType) + let baseAddress = preheaderBuilder.createPointerToAddress(pointer: basePointer, + addressType: elementType.addressType, + isStrict: true, isInvariant: false) + preheaderBuilder.createBranch(to: headerBlock, arguments: [zero]) + + let inductionVariable = headerBlock.addArgument(type: indexType, ownership: .none, context) + let headerBuilder = Builder(atEndOf: headerBlock, location: builtinDestroyArray.location, context) + let cmp = headerBuilder.createBuiltinBinaryFunction(name: "cmp_slt", operandType: indexType, resultType: boolType, + arguments: [inductionVariable, arrayCount]) + headerBuilder.createCondBranch(condition: cmp, trueBlock: bodyBlock, falseBlock: exitBlock) + + let bodyBuilder = Builder(atEndOf: bodyBlock, location: builtinDestroyArray.location, context) + let elementAddr = bodyBuilder.createIndexAddr(base: baseAddress, index: inductionVariable, + needStackProtection: false) + let destroy = bodyBuilder.createDestroyAddr(address: elementAddr) + let resultType = context.getTupleType(elements: [indexType, boolType]).loweredType(in: function) + let increment = bodyBuilder.createBuiltinBinaryFunction(name: "sadd_with_overflow", operandType: indexType, + resultType: resultType, + arguments: [inductionVariable, one, falseValue]) + let incrResult = bodyBuilder.createTupleExtract(tuple: increment, elementIndex: 0) + bodyBuilder.createBranch(to: headerBlock, arguments: [incrResult]) + + context.erase(instruction: builtinDestroyArray) + + return devirtualize(destroy: destroy, context) +} + private extension EnumCases { func allPayloadsAreTrivial(in function: Function) -> Bool { allSatisfy({ $0.payload?.isTrivial(in: function) ?? true }) From 8e3348747184117574e73ee9fa4efe0ef6a98f7a Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Tue, 26 Aug 2025 21:28:13 +0200 Subject: [PATCH 5/5] DeinitDevirtualizer: de-virtualize deinits of `builtin "destroyArray"` --- .../FunctionPasses/DeinitDevirtualizer.swift | 2 + test/SILOptimizer/devirt_deinits.sil | 28 ++++++++++++++ test/SILOptimizer/devirt_deinits.swift | 37 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeinitDevirtualizer.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeinitDevirtualizer.swift index aa8553adefbc7..3682d78de5025 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeinitDevirtualizer.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeinitDevirtualizer.swift @@ -27,6 +27,8 @@ let deinitDevirtualizer = FunctionPass(name: "deinit-devirtualizer") { _ = devirtualizeDeinits(of: destroyValue, context) case let destroyAddr as DestroyAddrInst: _ = devirtualizeDeinits(of: destroyAddr, context) + case let builtin as BuiltinInst: + _ = devirtualizeDeinits(of: builtin, context) default: break } diff --git a/test/SILOptimizer/devirt_deinits.sil b/test/SILOptimizer/devirt_deinits.sil index 4b47b1fca2e55..cdb9a8cbd124f 100644 --- a/test/SILOptimizer/devirt_deinits.sil +++ b/test/SILOptimizer/devirt_deinits.sil @@ -304,6 +304,34 @@ bb0(%0 : $*S1): return %r : $() } +// CHECK-LABEL: sil [ossa] @test_destroyArray : +// CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Word, 0 +// CHECK: [[ONE:%.*]] = integer_literal $Builtin.Word, 1 +// CHECK: [[FALSE:%.*]] = integer_literal $Builtin.Int1, 0 +// CHECK: [[PTA:%.*]] = pointer_to_address %0 : $Builtin.RawPointer to [strict] $*S1 +// CHECK: br bb1([[ZERO]] : $Builtin.Word) +// CHECK: bb1([[IV:%.*]] : $Builtin.Word): +// CHECK: [[CMP:%.*]] = builtin "cmp_slt_Word"([[IV]] : $Builtin.Word, %1 : $Builtin.Word) +// CHECK: cond_br [[CMP]], bb2, bb3 +// CHECK: bb2: +// CHECK: [[ADDR:%.*]] = index_addr [[PTA]] : $*S1, [[IV]] +// CHECK: [[DEINIT:%.*]] = function_ref @s1_deinit : +// CHECK: [[ELEMENT:%.*]] = load [take] [[ADDR]] +// CHECK: apply [[DEINIT]]([[ELEMENT]]) +// CHECK: [[ADD:%.*]] = builtin "sadd_with_overflow_Word"([[IV]] : $Builtin.Word, [[ONE]] : $Builtin.Word, [[FALSE]] : $Builtin.Int1) +// CHECK: [[INCR:%.*]] = tuple_extract [[ADD]] : $(Builtin.Word, Builtin.Int1), 0 +// CHECK: br bb1([[INCR]] : $Builtin.Word) +// CHECKL bb3: +// CHECK: } // end sil function 'test_destroyArray' +sil [ossa] @test_destroyArray : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> () { +bb0(%0 : $Builtin.RawPointer, %1 : $Builtin.Word): + %2 = metatype $@thin S1.Type // user: %10 + %3 = builtin "destroyArray"(%2, %0, %1) : $() + %r = tuple () + return %r +} + + sil @s1_deinit : $@convention(method) (@owned S1) -> () sil @s2_deinit : $@convention(method) (@owned S2) -> () sil @s3_deinit : $@convention(method) (@in S3) -> () diff --git a/test/SILOptimizer/devirt_deinits.swift b/test/SILOptimizer/devirt_deinits.swift index 30a972c45e7a7..6c0836a79aab7 100644 --- a/test/SILOptimizer/devirt_deinits.swift +++ b/test/SILOptimizer/devirt_deinits.swift @@ -51,6 +51,16 @@ struct S3: ~Copyable { } } +struct S4: ~Copyable { + var x: Int + + @inline(never) + deinit { + print(x) + } +} + + enum EnumWithDeinit: ~Copyable { case A(Int) case B @@ -136,6 +146,18 @@ func testNestedDeinit(_ s: consuming S3) { log("### testNestedDeinit") } +// CHECK-LABEL: sil hidden [noinline] @$s4test0A12DestroyArrayyySryAA2S4VGF : +// CHECK-NOT: "destroyArray" +// CHECK: [[D:%.*]] = function_ref @$s4test2S4VfD : +// CHECK: apply [[D]] +// CHECK-NOT: "destroyArray" +// CHECK: } // end sil function '$s4test0A12DestroyArrayyySryAA2S4VGF' +@inline(never) +func testDestroyArray(_ p: UnsafeMutableBufferPointer) { + p.deinitialize() +} + + @main struct Main { static func main() { @@ -182,6 +204,21 @@ struct Main { // CHECK-OUTPUT-NEXT: --- testNestedDeinit(S3()) log("---") + + let b = UnsafeMutableBufferPointer.allocate(capacity: 3) + for i in 0..<3 { + b.initializeElement(at: i, to: S4(x: i)) + } + + // CHECK-OUTPUT-LABEL: ### testDestroyArray + // CHECK-OUTPUT-NEXT: 0 + // CHECK-OUTPUT-NEXT: 1 + // CHECK-OUTPUT-NEXT: 2 + // CHECK-OUTPUT-NEXT: --- + log("### testDestroyArray") + testDestroyArray(b) + b.deallocate() + log("---") } }