Skip to content

Optimizer: de-virtualize deinits of non-copyable types #69955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 27, 2023
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 @@ -13,6 +13,7 @@ swift_compiler_sources(Optimizer
ComputeEscapeEffects.swift
ComputeSideEffects.swift
DeadStoreElimination.swift
DeinitDevirtualizer.swift
InitializeStaticGlobals.swift
LetPropertyLowering.swift
ObjectOutliner.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//===--- DeinitDevirtualizer.swift ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 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 SIL

/// Devirtualizes destroys of non-copyable values.
///
let deinitDevirtualizer = FunctionPass(name: "deinit-devirtualizer") {
(function: Function, context: FunctionPassContext) in

for inst in function.instructions {
switch inst {
case let destroyValue as DestroyValueInst:
_ = devirtualizeDeinits(of: destroyValue, context)
case let destroyAddr as DestroyAddrInst:
_ = devirtualizeDeinits(of: destroyAddr, context)
default:
break
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,13 @@ private func getSequenceOfElementStores(firstStore: StoreInst) -> ([StoreInst],
return nil
}
let structAddr = elementAddr.struct
let numElements = structAddr.type.getNominalFields(in: firstStore.parentFunction).count
if structAddr.type.isMoveOnly {
return nil
}
guard let fields = structAddr.type.getNominalFields(in: firstStore.parentFunction) else {
return nil
}
let numElements = fields.count
var elementStores = Array<StoreInst?>(repeating: nil, count: numElements)
var numStoresFound = 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ private func optimizeNonOptionalBridging(_ apply: ApplyInst,
// empty value.
// Create the needed blocks of the `switch_enum` CFG diamond.
let origBlock = bridgeToSwiftCall.parentBlock
let someBlock = context.splitBlock(at: bridgeToSwiftCall)
let noneBlock = context.splitBlock(at: bridgeToSwiftCall)
let continueBlock = context.splitBlock(at: bridgeToSwiftCall)
let someBlock = context.splitBlock(before: bridgeToSwiftCall)
let noneBlock = context.splitBlock(before: bridgeToSwiftCall)
let continueBlock = context.splitBlock(before: bridgeToSwiftCall)


let builder = Builder(atEndOf: origBlock, location: bridgeToSwiftCall.location, context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ private extension AllocRefInstBase {

var numClassFields: Int {
assert(type.isClass)
return type.getNominalFields(in: parentFunction).count
return type.getNominalFields(in: parentFunction)!.count
}

var numStoresPerTailElement: Int {
Expand Down Expand Up @@ -475,7 +475,7 @@ private func replace(findStringCall: ApplyInst,
with cachedFindStringFunc: Function,
_ context: FunctionPassContext) {
let cacheType = cachedFindStringFunc.argumentTypes[2].objectType
let wordTy = cacheType.getNominalFields(in: findStringCall.parentFunction)[0]
let wordTy = cacheType.getNominalFields(in: findStringCall.parentFunction)![0]

let name = context.mangleOutlinedVariable(from: findStringCall.parentFunction)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private extension Type {
return true
}
if isStruct {
return getNominalFields(in: function).containsSingleReference(in: function)
return getNominalFields(in: function)?.containsSingleReference(in: function) ?? false
} else if isTuple {
return tupleElements.containsSingleReference(in: function)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private extension ForwardingInstruction {
return false
}
let structFields = si.struct.type.getNominalFields(in: parentFunction)
return structFields.hasSingleNonTrivialElement(at: si.fieldIndex, in: parentFunction)
return structFields?.hasSingleNonTrivialElement(at: si.fieldIndex, in: parentFunction) ?? false
case let ti as TupleExtractInst:
return ti.tuple.type.tupleElements.hasSingleNonTrivialElement(at: ti.fieldIndex, in: parentFunction)
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable {
case let sea as StructElementAddrInst:
let structType = sea.struct.type
if structType.nominal.name == "_SwiftArrayBodyStorage" {
switch structType.getNominalFields(in: parentFunction).getNameOfField(withIndex: sea.fieldIndex) {
guard let fields = structType.getNominalFields(in: parentFunction) else {
return false
}
switch fields.getNameOfField(withIndex: sea.fieldIndex) {
case "count":
break
case "_capacityAndFlags":
Expand All @@ -153,7 +156,10 @@ extension LoadInst : OnoneSimplifyable, SILCombineSimplifyable {
case "__RawDictionaryStorage",
"__RawSetStorage":
// For Dictionary and Set we support "count" and "capacity".
switch classType.getNominalFields(in: parentFunction).getNameOfField(withIndex: rea.fieldIndex) {
guard let fields = classType.getNominalFields(in: parentFunction) else {
return false
}
switch fields.getNameOfField(withIndex: rea.fieldIndex) {
case "_count", "_capacity":
break
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ private func optimize(function: Function, _ context: FunctionPassContext, _ work
_ = context.specializeClassMethodInst(classMethod)
}

// 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) {
context.diagnosticEngine.diagnose(destroyValue.location.sourceLoc, .deinit_not_visible)
}
case let destroyAddr as DestroyAddrInst:
if !devirtualizeDeinits(of: destroyAddr, simplifyCtxt) {
context.diagnosticEngine.diagnose(destroyAddr.location.sourceLoc, .deinit_not_visible)
}

default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ protocol Context {
extension Context {
var options: Options { Options(_bridged: _bridged) }

var diagnosticEngine: DiagnosticEngine {
return DiagnosticEngine(bridged: _bridged.getDiagnosticEngine())
}

// The calleeAnalysis is not specific to a function and therefore can be provided in
// all contexts.
var calleeAnalysis: CalleeAnalysis {
Expand All @@ -38,6 +42,10 @@ extension Context {
default: fatalError("unhandled SILStage case")
}
}

func lookupDeinit(ofNominal: NominalTypeDecl) -> Function? {
_bridged.lookUpNominalDeinitFunction(ofNominal.bridged).function
}
}

/// A context which allows mutation of a function's SIL.
Expand All @@ -55,9 +63,24 @@ extension MutatingContext {
///
/// `inst` and all subsequent instructions are moved to the new block, while all
/// instructions _before_ `inst` remain in the original block.
func splitBlock(at inst: Instruction) -> BasicBlock {
func splitBlock(before inst: Instruction) -> BasicBlock {
notifyBranchesChanged()
return _bridged.splitBlockBefore(inst.bridged).block
}

/// Splits the basic block, which contains `inst`, after `inst` and returns the
/// new block.
///
/// All subsequent instructions after `inst` are moved to the new block, while `inst` and all
/// instructions _before_ `inst` remain in the original block.
func splitBlock(after inst: Instruction) -> BasicBlock {
notifyBranchesChanged()
return _bridged.splitBlockAfter(inst.bridged).block
}

func createBlock(after block: BasicBlock) -> BasicBlock {
notifyBranchesChanged()
return _bridged.splitBlock(inst.bridged).block
return _bridged.createBlockAfter(block.bridged).block
}

func erase(instruction: Instruction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ private func registerSwiftPasses() {
registerPass(deadStoreElimination, { deadStoreElimination.run($0) })
registerPass(redundantLoadElimination, { redundantLoadElimination.run($0) })
registerPass(earlyRedundantLoadElimination, { earlyRedundantLoadElimination.run($0) })
registerPass(deinitDevirtualizer, { deinitDevirtualizer.run($0) })

// Instruction passes
registerForSILCombine(BeginCOWMutationInst.self, { run(BeginCOWMutationInst.self, $0) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private func handle(instruction: Instruction, _ context: FunctionPassContext) {
case "delete_branches":
deleteAllInstructions(ofType: BranchInst.self, in: instruction.parentBlock, context)
case "split_block":
_ = context.splitBlock(at: instruction)
_ = context.splitBlock(before: instruction)
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

swift_compiler_sources(Optimizer
DiagnosticEngine.swift
Devirtualization.swift
EscapeUtils.swift
OptUtils.swift
ForwardingUtils.swift
Expand Down
Loading