-
Notifications
You must be signed in to change notification settings - Fork 10.5k
libswift: implement ReleaseDevirtualizer
in Swift
#40800
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
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
0b34baa
libswift: bridge `RCIdentityAnalysis` code
MaxDesiatov a0a2d88
libswift: clean up `Instruction.swift` formatting
MaxDesiatov 196140a
libswift: clean up `SIL/Utils.swift` formatting
MaxDesiatov c780969
libswift: clean up `SIL/Type.swift` formatting
MaxDesiatov f2aefad
libswift: clean up `SIL/Function.swift` formatting
MaxDesiatov 7eb3bdd
libswift: add `SubstitutionMap` to Swift code
MaxDesiatov 7d96100
libswift: bridge more functions from `SILBuilder`
MaxDesiatov b964dba
libswift: implement `ReleaseDevirtualizer` in Swift
MaxDesiatov cec4b82
swift SIL: add some SIL type related bridging
eeckstein 848fa70
libswift: reimplement `Instruction` helpers in Swift
MaxDesiatov d7144c0
libswift: implement `isFieldOnlyNonTrivialField`
MaxDesiatov 40e805d
libswift: simplify `ReleaseDevirtualizer.swift`
MaxDesiatov 1f53563
libswift: remove `Instruction.mayReadRefCount`
MaxDesiatov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
219 changes: 219 additions & 0 deletions
219
SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ReleaseDevirtualizer.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
//===--- ReleaseDevirtualizer.swift - Devirtualizes release-instructions --===// | ||
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2022 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 release instructions which are known to destruct the object. | ||
/// | ||
/// This means, it replaces a sequence of | ||
/// %x = alloc_ref [stack] $X | ||
/// ... | ||
/// strong_release %x | ||
/// dealloc_stack_ref %x | ||
/// with | ||
/// %x = alloc_ref [stack] $X | ||
/// ... | ||
/// set_deallocating %x | ||
/// %d = function_ref @dealloc_of_X | ||
/// %a = apply %d(%x) | ||
/// dealloc_stack_ref %x | ||
/// | ||
/// The optimization is only done for stack promoted objects because they are | ||
/// known to have no associated objects (which are not explicitly released | ||
/// in the deinit method). | ||
let releaseDevirtualizerPass = FunctionPass( | ||
name: "release-devirtualizer", { function, context in | ||
for block in function.blocks { | ||
// The last `release_value`` or `strong_release`` instruction before the | ||
// deallocation. | ||
var lastRelease: RefCountingInst? | ||
|
||
for instruction in block.instructions { | ||
if let release = lastRelease { | ||
// We only do the optimization for stack promoted object, because for | ||
// these we know that they don't have associated objects, which are | ||
// _not_ released by the deinit method. | ||
if let deallocStackRef = instruction as? DeallocStackRefInst { | ||
tryDevirtualizeReleaseOfObject(context, release, deallocStackRef) | ||
lastRelease = nil | ||
continue | ||
} | ||
} | ||
|
||
if instruction is ReleaseValueInst || instruction is StrongReleaseInst { | ||
lastRelease = instruction as? RefCountingInst | ||
} else if instruction.mayRelease { | ||
lastRelease = nil | ||
} | ||
} | ||
} | ||
} | ||
) | ||
|
||
/// Tries to de-virtualize the final release of a stack-promoted object. | ||
private func tryDevirtualizeReleaseOfObject( | ||
_ context: PassContext, | ||
_ release: RefCountingInst, | ||
_ deallocStackRef: DeallocStackRefInst | ||
) { | ||
let allocRefInstruction = deallocStackRef.allocRef | ||
var root = release.operands[0].value | ||
while let newRoot = stripRCIdentityPreservingInsts(root) { | ||
root = newRoot | ||
} | ||
|
||
if root != allocRefInstruction { | ||
return | ||
} | ||
|
||
let type = allocRefInstruction.type | ||
|
||
guard let dealloc = context.getDestructor(ofClass: type) else { | ||
return | ||
} | ||
|
||
let builder = Builder(at: release, location: release.location, context) | ||
|
||
var object: Value = allocRefInstruction | ||
if object.type != type { | ||
object = builder.createUncheckedRefCast(object: object, type: type) | ||
} | ||
|
||
// Do what a release would do before calling the deallocator: set the object | ||
// in deallocating state, which means set the RC_DEALLOCATING_FLAG flag. | ||
builder.createSetDeallocating(operand: object, isAtomic: release.isAtomic) | ||
|
||
// Create the call to the destructor with the allocated object as self | ||
// argument. | ||
let functionRef = builder.createFunctionRef(dealloc) | ||
|
||
let substitutionMap = context.getContextSubstitutionMap(for: type) | ||
builder.createApply(function: functionRef, substitutionMap, arguments: [object]) | ||
context.erase(instruction: release) | ||
} | ||
|
||
private func stripRCIdentityPreservingInsts(_ value: Value) -> Value? { | ||
guard let inst = value as? Instruction else { return nil } | ||
|
||
switch inst { | ||
// First strip off RC identity preserving casts. | ||
case is UpcastInst, | ||
is UncheckedRefCastInst, | ||
is InitExistentialRefInst, | ||
is OpenExistentialRefInst, | ||
is RefToBridgeObjectInst, | ||
is BridgeObjectToRefInst, | ||
is ConvertFunctionInst, | ||
is UncheckedEnumDataInst: | ||
return inst.operands[0].value | ||
|
||
// Then if we have a struct_extract that is extracting a non-trivial member | ||
// from a struct with no other non-trivial members, a ref count operation on | ||
// the struct is equivalent to a ref count operation on the extracted | ||
// member. Strip off the extract. | ||
case let sei as StructExtractInst where sei.isFieldOnlyNonTrivialField: | ||
return sei.operand | ||
|
||
// If we have a struct or tuple instruction with only one non-trivial operand, the | ||
// only reference count that can be modified is the non-trivial operand. Return | ||
// the non-trivial operand. | ||
case is StructInst, is TupleInst: | ||
return inst.uniqueNonTrivialOperand | ||
|
||
// If we have an enum instruction with a payload, strip off the enum to | ||
// expose the enum's payload. | ||
case let ei as EnumInst where !ei.operands.isEmpty: | ||
return ei.operand | ||
|
||
// If we have a tuple_extract that is extracting the only non trivial member | ||
// of a tuple, a retain_value on the tuple is equivalent to a retain_value on | ||
// the extracted value. | ||
case let tei as TupleExtractInst where tei.isEltOnlyNonTrivialElt: | ||
return tei.operand | ||
|
||
default: | ||
return nil | ||
} | ||
} | ||
|
||
private extension Instruction { | ||
/// Search the operands of this tuple for a unique non-trivial elt. If we find | ||
/// it, return it. Otherwise return `nil`. | ||
var uniqueNonTrivialOperand: Value? { | ||
var candidateElt: Value? | ||
let function = self.function | ||
|
||
for op in operands { | ||
if !op.value.type.isTrivial(in: function) { | ||
if candidateElt == nil { | ||
candidateElt = op.value | ||
continue | ||
} | ||
|
||
// Otherwise, we have two values that are non-trivial. Bail. | ||
return nil | ||
} | ||
} | ||
|
||
return candidateElt | ||
} | ||
} | ||
|
||
private extension TupleExtractInst { | ||
var isEltOnlyNonTrivialElt: Bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the use in the release-devirtualizer it would be simpler to count the number of non-trivial elements and bail if it's not 1. |
||
let function = self.function | ||
|
||
if type.isTrivial(in: function) { | ||
return false | ||
} | ||
|
||
let opType = operand.type | ||
|
||
var nonTrivialEltsCount = 0 | ||
for elt in opType.tupleElements { | ||
if elt.isTrivial(in: function) { | ||
nonTrivialEltsCount += 1 | ||
} | ||
|
||
if nonTrivialEltsCount > 1 { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
} | ||
|
||
private extension StructExtractInst { | ||
var isFieldOnlyNonTrivialField: Bool { | ||
let function = self.function | ||
|
||
if type.isTrivial(in: function) { | ||
return false | ||
} | ||
|
||
let structType = operand.type | ||
|
||
var nonTrivialFieldsCount = 0 | ||
for field in structType.getStructFields(in: function) { | ||
if field.isTrivial(in: function) { | ||
nonTrivialFieldsCount += 1 | ||
} | ||
|
||
if nonTrivialFieldsCount > 1 { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do a
guard let inst = value as? Instruction else { return }
before the switch, you can switch overinst
which simplifies the code below: