Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 })
Expand Down
6 changes: 6 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 7 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 4 additions & 0 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1425,6 +1428,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;
Expand Down
15 changes: 15 additions & 0 deletions include/swift/SIL/SILBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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())};
}
Expand Down Expand Up @@ -2847,6 +2854,14 @@ BridgedType BridgedContext::getBuiltinIntegerType(SwiftInt bitWidth) const {
return swift::SILType::getBuiltinIntegerType(bitWidth, context->getModule()->getASTContext());
}

BridgedASTType BridgedContext::getTupleType(BridgedArrayRef elementTypes) const {
llvm::SmallVector<swift::TupleTypeElt, 8> elements;
for (auto bridgedElmtTy : elementTypes.unbridged<BridgedASTType>()) {
elements.push_back(bridgedElmtTy.unbridged());
}
return {swift::TupleType::get(elements, context->getModule()->getASTContext())};
}

BridgedDeclObj BridgedContext::getSwiftArrayDecl() const {
return {context->getModule()->getASTContext().getArrayDecl()};
}
Expand Down
25 changes: 5 additions & 20 deletions lib/IRGen/GenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2938,34 +2937,20 @@ 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
&& deinitTy->getNumResults() == 0
&& !deinitTy->hasError()
&& "deinit should have only one parameter");

auto substitutions = ty->getContextSubstitutionMap();

CalleeInfo info(deinitTy,
deinitTy->substGenericArgs(IGF.getSILModule(),
substitutions,
Expand Down
28 changes: 28 additions & 0 deletions test/SILOptimizer/devirt_deinits.sil
Original file line number Diff line number Diff line change
Expand Up @@ -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"<S1>(%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) <T> (@in S3<T>) -> ()
Expand Down
37 changes: 37 additions & 0 deletions test/SILOptimizer/devirt_deinits.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<S4>) {
p.deinitialize()
}


@main
struct Main {
static func main() {
Expand Down Expand Up @@ -182,6 +204,21 @@ struct Main {
// CHECK-OUTPUT-NEXT: ---
testNestedDeinit(S3())
log("---")

let b = UnsafeMutableBufferPointer<S4>.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("---")
}
}