mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #85533 from eeckstein/fix-access-simplification
SILOptimizer: don't remove empty conflicting access scopes
This commit is contained in:
@@ -17,6 +17,7 @@ swift_compiler_sources(Optimizer
|
||||
ComputeEscapeEffects.swift
|
||||
ComputeSideEffects.swift
|
||||
CopyToBorrowOptimization.swift
|
||||
DeadAccessScopeElimination.swift
|
||||
DeadStoreElimination.swift
|
||||
DeinitDevirtualizer.swift
|
||||
DestroyHoisting.swift
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
//===--- DeadAccessScopeElimination.swift ----------------------------------==//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2025 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
|
||||
|
||||
/// Eliminates dead access scopes if they are not conflicting with other scopes.
|
||||
///
|
||||
/// Removes:
|
||||
/// ```
|
||||
/// %2 = begin_access [modify] [dynamic] %1
|
||||
/// ... // no uses of %2
|
||||
/// end_access %2
|
||||
/// ```
|
||||
///
|
||||
/// However, dead _conflicting_ access scopes are not removed.
|
||||
/// If a conflicting scope becomes dead because an optimization e.g. removed a load, it is still
|
||||
/// important to get an access violation at runtime.
|
||||
/// Even a propagated value of a redundant load from a conflicting scope is undefined.
|
||||
///
|
||||
/// ```
|
||||
/// %2 = begin_access [modify] [dynamic] %1
|
||||
/// store %x to %2
|
||||
/// %3 = begin_access [read] [dynamic] %1 // conflicting with %2!
|
||||
/// %y = load %3
|
||||
/// end_access %3
|
||||
/// end_access %2
|
||||
/// use(%y)
|
||||
/// ```
|
||||
/// After redundant-load-elimination:
|
||||
/// ```
|
||||
/// %2 = begin_access [modify] [dynamic] %1
|
||||
/// store %x to %2
|
||||
/// %3 = begin_access [read] [dynamic] %1 // now dead, but still conflicting with %2
|
||||
/// end_access %3
|
||||
/// end_access %2
|
||||
/// use(%x) // propagated from the store, but undefined here!
|
||||
/// ```
|
||||
/// In this case the scope `%3` is not removed because it's important to get an access violation
|
||||
/// error at runtime before the undefined value `%x` is used.
|
||||
///
|
||||
/// This pass considers potential conflicting access scopes in called functions.
|
||||
/// But it does not consider potential conflicting access in callers (because it can't!).
|
||||
/// However, optimizations, like redundant-load-elimination, can only do such transformations if
|
||||
/// the outer access scope is within the function, e.g.
|
||||
///
|
||||
/// ```
|
||||
/// bb0(%0 : $*T): // an inout from a conflicting scope in the caller
|
||||
/// store %x to %0
|
||||
/// %3 = begin_access [read] [dynamic] %1
|
||||
/// %y = load %3 // cannot be propagated because it cannot be proved that %1 is the same address as %0
|
||||
/// end_access %3
|
||||
/// ```
|
||||
///
|
||||
/// All those checks are only done for dynamic access scopes, because they matter for runtime
|
||||
/// exclusivity checking. Dead static scopes are removed unconditionally.
|
||||
///
|
||||
let deadAccessScopeElimination = FunctionPass(name: "dead-access-scope-elimination") {
|
||||
(function: Function, context: FunctionPassContext) in
|
||||
|
||||
// Add all dead scopes here and then remove the ones which turn out to be conflicting.
|
||||
var removableScopes = SpecificIterableInstructionSet<BeginAccessInst>(context)
|
||||
defer { removableScopes.deinitialize() }
|
||||
|
||||
var scopeTree = ScopeTree(context)
|
||||
|
||||
// The payload is the recent access instruction at the block begin, e.g.
|
||||
// ```
|
||||
// %1 = begin_acces %0
|
||||
// br bb2
|
||||
// bb2: // recent access instruction at begin of bb2 is: `%1 = begin_acces %0`
|
||||
// ```
|
||||
// It's nil if the block is not within an access scope.
|
||||
//
|
||||
var blockWorklist = BasicBlockWorklistWithPayload<Instruction?>(context)
|
||||
defer { blockWorklist.deinitialize() }
|
||||
|
||||
blockWorklist.pushIfNotVisited(function.entryBlock, with: nil)
|
||||
|
||||
// Walk through the control flow in depth-first order. Note that we don't need to do any kind
|
||||
// of state merging at merge-points, because access scopes must be consistent on all paths.
|
||||
while let (block, recentAccessInstAtBlockBegin) = blockWorklist.pop() {
|
||||
|
||||
// The last seen `begin_access` (or `end_access` in case of not perfectly nested scopes; see ScopeTree.backlinks)
|
||||
var recentAccessInst = recentAccessInstAtBlockBegin
|
||||
|
||||
for inst in block.instructions {
|
||||
process(instruction: inst, updating: &recentAccessInst, &scopeTree, &removableScopes)
|
||||
}
|
||||
|
||||
blockWorklist.pushIfNotVisited(contentsOf: block.successors, with: recentAccessInst)
|
||||
}
|
||||
|
||||
for deadBeginAccess in removableScopes {
|
||||
context.erase(instructionIncludingAllUsers: deadBeginAccess)
|
||||
}
|
||||
}
|
||||
|
||||
private func process(instruction: Instruction,
|
||||
updating recentAccessInst: inout Instruction?,
|
||||
_ scopeTree: inout ScopeTree,
|
||||
_ removableScopes: inout SpecificIterableInstructionSet<BeginAccessInst>)
|
||||
{
|
||||
switch instruction {
|
||||
|
||||
case let beginAccess as BeginAccessInst:
|
||||
if beginAccess.isDead {
|
||||
// Might be removed again later if it turns out to be in a conflicting scope.
|
||||
removableScopes.insert(beginAccess)
|
||||
}
|
||||
if beginAccess.enforcement != .dynamic {
|
||||
// We unconditionally remove dead _static_ scopes, because they don't have any impact at runtime.
|
||||
// Usually static scopes are already removed in the optimization pipeline. However optimizations
|
||||
// might turn dynamic into static scopes. So let's handle them.
|
||||
break
|
||||
}
|
||||
scopeTree.visitEnclosingScopes(of: recentAccessInst) { enclosingBeginAccess in
|
||||
if beginAccess.accessKind.conflicts(with: enclosingBeginAccess.accessKind),
|
||||
// Avoid computing alias info if both scopes are not removable anyway.
|
||||
removableScopes.contains(beginAccess) || removableScopes.contains(enclosingBeginAccess),
|
||||
scopeTree.context.aliasAnalysis.mayAlias(beginAccess.address, enclosingBeginAccess.address)
|
||||
{
|
||||
// Conflicting enclosing scopes are not removable.
|
||||
removableScopes.erase(enclosingBeginAccess)
|
||||
// ... as well as the inner scope (which conflicts with the enclosing scope).
|
||||
removableScopes.erase(beginAccess)
|
||||
}
|
||||
}
|
||||
scopeTree.update(recent: &recentAccessInst, with: beginAccess)
|
||||
|
||||
case let endAccess as EndAccessInst where endAccess.beginAccess.enforcement == .dynamic:
|
||||
scopeTree.update(recent: &recentAccessInst, with: endAccess)
|
||||
|
||||
default:
|
||||
if instruction.mayCallFunction {
|
||||
// Check for potential conflicting scopes in called functions.
|
||||
scopeTree.visitEnclosingScopes(of: recentAccessInst) { enclosingBeginAccess in
|
||||
if removableScopes.contains(enclosingBeginAccess),
|
||||
instruction.mayHaveAccessScopeWhichConflicts(with: enclosingBeginAccess, scopeTree.context)
|
||||
{
|
||||
removableScopes.erase(enclosingBeginAccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the tree of access scopes in a function.
|
||||
/// Note that if the scopes are not nested perfectly, it's strictly speaking not a tree.
|
||||
private struct ScopeTree {
|
||||
|
||||
// Links `begin_access` and `end_access` instructions in backward control flow direction.
|
||||
// This is used to visit all enclosing scopes of a `begin_access`.
|
||||
// As an optimization, `end_access`es are ignored for scopes which are perfectly nested - which is
|
||||
// by far the most common case. In this case the backlinks simply are the parent links in the scope tree.
|
||||
//
|
||||
// Example of not perfectly nested scopes:
|
||||
// ```
|
||||
// %1 = begin_access <------------------+
|
||||
// ... |
|
||||
// %2 = begin_access <--------------+ -+
|
||||
// ... |
|
||||
// end_access %1 <---------+ -+
|
||||
// ... |
|
||||
// %3 = begin_access <-----+ -+
|
||||
// ... |
|
||||
// end_access %2 <-+ -+
|
||||
// ... |
|
||||
// end_access %3 -+
|
||||
// ```
|
||||
//
|
||||
// Perfectly nested scopes:
|
||||
// ```
|
||||
// %1 = begin_access <-+ <-+
|
||||
// ... | |
|
||||
// %2 = begin_access -+ |
|
||||
// end_access %2 | <- ignored
|
||||
// ... |
|
||||
// %3 = begin_access -------+
|
||||
// end_access %3 <- ignored
|
||||
// ...
|
||||
// end_access %1 <- ignored
|
||||
// ```
|
||||
private var backlinks = Dictionary<Instruction, Instruction>()
|
||||
|
||||
let context: FunctionPassContext
|
||||
|
||||
init(_ context: FunctionPassContext) { self.context = context }
|
||||
|
||||
mutating func update(recent: inout Instruction?, with beginAccess: BeginAccessInst) {
|
||||
backlinks[beginAccess] = recent
|
||||
recent = beginAccess
|
||||
}
|
||||
|
||||
mutating func update(recent: inout Instruction?, with endAccess: EndAccessInst) {
|
||||
if endAccess.beginAccess == recent {
|
||||
// The scope is perfectly nested. Ignore it and directly backlink to the parent of the `begin_access`
|
||||
recent = backlinks[endAccess.beginAccess]
|
||||
} else {
|
||||
backlinks[endAccess] = recent
|
||||
recent = endAccess
|
||||
}
|
||||
}
|
||||
|
||||
func visitEnclosingScopes(of accessInstruction: Instruction?, closure: (BeginAccessInst) -> ()) {
|
||||
// Ignore scopes which are already closed
|
||||
var ignore = SpecificInstructionSet<BeginAccessInst>(context)
|
||||
defer { ignore.deinitialize() }
|
||||
|
||||
var enclosingScope = accessInstruction
|
||||
|
||||
while let parent = enclosingScope {
|
||||
switch parent {
|
||||
case let parentBeginAccess as BeginAccessInst where !ignore.contains(parentBeginAccess):
|
||||
closure(parentBeginAccess)
|
||||
case let parentEndAccess as EndAccessInst:
|
||||
// Seeing an `end_access` in the backlink chain means that this scope is already closed.
|
||||
ignore.insert(parentEndAccess.beginAccess)
|
||||
default:
|
||||
break
|
||||
}
|
||||
enclosingScope = backlinks[parent]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension Instruction {
|
||||
func mayHaveAccessScopeWhichConflicts(with beginAccess: BeginAccessInst, _ context: FunctionPassContext) -> Bool {
|
||||
if beginAccess.accessKind == .read {
|
||||
return mayWrite(toAddress: beginAccess.address, context.aliasAnalysis)
|
||||
} else {
|
||||
return mayReadOrWrite(address: beginAccess.address, context.aliasAnalysis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension BeginAccessInst {
|
||||
var isDead: Bool { users.allSatisfy({ $0.isIncidentalUse }) }
|
||||
}
|
||||
@@ -94,6 +94,7 @@ private func registerSwiftPasses() {
|
||||
registerPass(cleanupDebugStepsPass, { cleanupDebugStepsPass.run($0) })
|
||||
registerPass(namedReturnValueOptimization, { namedReturnValueOptimization.run($0) })
|
||||
registerPass(stripObjectHeadersPass, { stripObjectHeadersPass.run($0) })
|
||||
registerPass(deadAccessScopeElimination, { deadAccessScopeElimination.run($0) })
|
||||
registerPass(deadStoreElimination, { deadStoreElimination.run($0) })
|
||||
registerPass(redundantLoadElimination, { redundantLoadElimination.run($0) })
|
||||
registerPass(mandatoryRedundantLoadElimination, { mandatoryRedundantLoadElimination.run($0) })
|
||||
|
||||
@@ -287,3 +287,66 @@ extension IntrusiveSet {
|
||||
insert(contentsOf: source)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set which supports iterating over its elements.
|
||||
/// The iteration order is deterministic. All set operations are O(1).
|
||||
public struct IterableSet<Set: IntrusiveSet> : CollectionLikeSequence {
|
||||
public typealias Element = Set.Element
|
||||
|
||||
private var set: Set
|
||||
|
||||
// Erased elements do not get removed from `list`, because this would be O(n).
|
||||
private var list: Stack<Element>
|
||||
|
||||
// Elements which are in `list. This is different from `set` if elements get erased.
|
||||
private var inList: Set
|
||||
|
||||
public init(_ context: some Context) {
|
||||
self.set = Set(context)
|
||||
self.list = Stack(context)
|
||||
self.inList = Set(context)
|
||||
}
|
||||
|
||||
public mutating func deinitialize() {
|
||||
inList.deinitialize()
|
||||
list.deinitialize()
|
||||
set.deinitialize()
|
||||
}
|
||||
|
||||
public func contains(_ element: Element) -> Bool {
|
||||
set.contains(element)
|
||||
}
|
||||
|
||||
/// Returns true if `element` was not contained in the set before inserting.
|
||||
@discardableResult
|
||||
public mutating func insert(_ element: Element) -> Bool {
|
||||
if inList.insert(element) {
|
||||
list.append(element)
|
||||
}
|
||||
return set.insert(element)
|
||||
}
|
||||
|
||||
public mutating func erase(_ element: Element) {
|
||||
set.erase(element)
|
||||
}
|
||||
|
||||
public func makeIterator() -> Iterator { Iterator(base: list.makeIterator(), set: set) }
|
||||
|
||||
public struct Iterator: IteratorProtocol {
|
||||
var base: Stack<Element>.Iterator
|
||||
let set: Set
|
||||
|
||||
public mutating func next() -> Element? {
|
||||
while let n = base.next() {
|
||||
if set.contains(n) {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias IterableInstructionSet = IterableSet<InstructionSet>
|
||||
public typealias SpecificIterableInstructionSet<InstType: Instruction> = IterableSet<SpecificInstructionSet<InstType>>
|
||||
public typealias IterableBasicBlockSet = IterableSet<BasicBlockSet>
|
||||
|
||||
@@ -70,7 +70,54 @@ public struct Worklist<Set: IntrusiveSet> : CustomStringConvertible, NoReflectio
|
||||
}
|
||||
}
|
||||
|
||||
/// Like `Worklist` but can store an additional arbitrary payload per element.
|
||||
public struct WorklistWithPayload<Set: IntrusiveSet, Payload> : CustomStringConvertible, NoReflectionChildren {
|
||||
public typealias Element = Set.Element
|
||||
private var worklist: Stack<(Element, Payload)>
|
||||
private var pushedElements: Set
|
||||
|
||||
public init(_ context: some Context) {
|
||||
self.worklist = Stack(context)
|
||||
self.pushedElements = Set(context)
|
||||
}
|
||||
|
||||
public mutating func pop() -> (Element, Payload)? { return worklist.pop() }
|
||||
|
||||
public mutating func pushIfNotVisited(_ element: Element, with payload: Payload) {
|
||||
if pushedElements.insert(element) {
|
||||
worklist.append((element, payload))
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func pushIfNotVisited<S: Sequence>(contentsOf other: S, with payload: Payload)
|
||||
where S.Element == Element
|
||||
{
|
||||
for element in other {
|
||||
pushIfNotVisited(element, with: payload)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if \p element was pushed to the worklist, regardless if it's already popped or not.
|
||||
public func hasBeenPushed(_ element: Element) -> Bool { pushedElements.contains(element) }
|
||||
|
||||
public var isEmpty: Bool { worklist.isEmpty }
|
||||
|
||||
public var description: String {
|
||||
"""
|
||||
worklist: \(worklist)
|
||||
pushed: \(pushedElements)
|
||||
"""
|
||||
}
|
||||
|
||||
/// TODO: once we have move-only types, make this a real deinit.
|
||||
public mutating func deinitialize() {
|
||||
pushedElements.deinitialize()
|
||||
worklist.deinitialize()
|
||||
}
|
||||
}
|
||||
|
||||
public typealias BasicBlockWorklist = Worklist<BasicBlockSet>
|
||||
public typealias BasicBlockWorklistWithPayload<Payload> = WorklistWithPayload<BasicBlockSet, Payload>
|
||||
public typealias InstructionWorklist = Worklist<InstructionSet>
|
||||
public typealias SpecificInstructionWorklist<InstType: Instruction> = Worklist<SpecificInstructionSet<InstType>>
|
||||
public typealias ValueWorklist = Worklist<ValueSet>
|
||||
|
||||
@@ -143,6 +143,11 @@ public class Instruction : CustomStringConvertible, Hashable {
|
||||
return bridged.mayAccessPointer()
|
||||
}
|
||||
|
||||
/// True if arbitrary functions may be called by this instruction.
|
||||
/// This can be either directly, e.g. by an `apply` instruction, or indirectly by destroying a value which
|
||||
/// might have a deinitializer which can call functions.
|
||||
public var mayCallFunction: Bool { false }
|
||||
|
||||
public final var mayLoadWeakOrUnowned: Bool {
|
||||
return bridged.mayLoadWeakOrUnowned()
|
||||
}
|
||||
@@ -331,6 +336,8 @@ final public class StoreInst : Instruction, StoringInstruction {
|
||||
public var storeOwnership: StoreOwnership {
|
||||
StoreOwnership(rawValue: bridged.StoreInst_getStoreOwnership())!
|
||||
}
|
||||
|
||||
public override var mayCallFunction: Bool { storeOwnership == .assign }
|
||||
}
|
||||
|
||||
final public class StoreWeakInst : Instruction, StoringInstruction { }
|
||||
@@ -345,6 +352,15 @@ final public class AssignInst : Instruction, StoringInstruction {
|
||||
public var assignOwnership: AssignOwnership {
|
||||
AssignOwnership(rawValue: bridged.AssignInst_getAssignOwnership())!
|
||||
}
|
||||
|
||||
public override var mayCallFunction: Bool {
|
||||
switch assignOwnership {
|
||||
case .unknown, .reassign, .reinitialize:
|
||||
true
|
||||
case .initialize:
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final public class AssignOrInitInst : Instruction, StoringInstruction {}
|
||||
@@ -382,6 +398,7 @@ final public class CopyAddrInst : Instruction, SourceDestAddrInstruction {
|
||||
bridged.CopyAddrInst_setIsInitializationOfDest(isInitializationOfDestination)
|
||||
context.notifyInstructionChanged(self)
|
||||
}
|
||||
public override var mayCallFunction: Bool { !isInitializationOfDestination }
|
||||
}
|
||||
|
||||
final public class ExplicitCopyAddrInst : Instruction, SourceDestAddrInstruction {
|
||||
@@ -394,6 +411,7 @@ final public class ExplicitCopyAddrInst : Instruction, SourceDestAddrInstruction
|
||||
public var isInitializationOfDestination: Bool {
|
||||
bridged.ExplicitCopyAddrInst_isInitializationOfDest()
|
||||
}
|
||||
public override var mayCallFunction: Bool { !isInitializationOfDestination }
|
||||
}
|
||||
|
||||
final public class MarkUninitializedInst : SingleValueInstruction, UnaryInstruction {
|
||||
@@ -639,10 +657,12 @@ final public class RetainValueAddrInst : RefCountingInst {
|
||||
}
|
||||
|
||||
final public class ReleaseValueAddrInst : RefCountingInst {
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class StrongReleaseInst : RefCountingInst {
|
||||
public var instance: Value { operand.value }
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class UnownedReleaseInst : RefCountingInst {
|
||||
@@ -651,10 +671,12 @@ final public class UnownedReleaseInst : RefCountingInst {
|
||||
|
||||
final public class ReleaseValueInst : RefCountingInst {
|
||||
public var value: Value { return operand.value }
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class UnmanagedReleaseValueInst : RefCountingInst {
|
||||
public var value: Value { return operand.value }
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class AutoreleaseValueInst : RefCountingInst {}
|
||||
@@ -667,10 +689,14 @@ final public class DestroyValueInst : Instruction, UnaryInstruction {
|
||||
/// end the lifetime of its operand.
|
||||
/// Such `destroy_value` instructions are lowered to no-ops.
|
||||
public var isDeadEnd: Bool { bridged.DestroyValueInst_isDeadEnd() }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class DestroyAddrInst : Instruction, UnaryInstruction {
|
||||
public var destroyedAddress: Value { operand.value }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class EndLifetimeInst : Instruction, UnaryInstruction {}
|
||||
@@ -762,6 +788,15 @@ final public class BuiltinInst : SingleValueInstruction {
|
||||
public var arguments: LazyMapSequence<OperandArray, Value> {
|
||||
operands.values
|
||||
}
|
||||
|
||||
public override var mayCallFunction: Bool {
|
||||
switch id {
|
||||
case .Once, .OnFastPath, .Destroy, .DestroyArray, .DestroyTaskGroup, .DestroyDefaultActor, .Release:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final public class UpcastInst : SingleValueInstruction, UnaryInstruction {
|
||||
@@ -1371,6 +1406,8 @@ final public class ApplyInst : SingleValueInstruction, FullApplySite {
|
||||
public typealias SpecializationInfo = BridgedGenericSpecializationInformation
|
||||
|
||||
public var specializationInfo: SpecializationInfo { bridged.ApplyInst_getSpecializationInfo() }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class FunctionExtractIsolationInst : SingleValueInstruction {}
|
||||
@@ -1648,7 +1685,12 @@ final public class BeginAccessInst : SingleValueInstruction, UnaryInstruction {
|
||||
case read = 1
|
||||
case modify = 2
|
||||
case `deinit` = 3
|
||||
|
||||
public func conflicts(with other: AccessKind) -> Bool {
|
||||
return self != .read || other != .read
|
||||
}
|
||||
}
|
||||
|
||||
public var accessKind: AccessKind {
|
||||
AccessKind(rawValue: bridged.BeginAccessInst_getAccessKind())!
|
||||
}
|
||||
@@ -1730,11 +1772,15 @@ final public class BeginApplyInst : MultipleValueInstruction, FullApplySite {
|
||||
|
||||
public var isNonThrowing: Bool { bridged.BeginApplyInst_getNonThrowing() }
|
||||
public var isNonAsync: Bool { bridged.BeginApplyInst_getNonAsync() }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class EndApplyInst : SingleValueInstruction, UnaryInstruction {
|
||||
public var token: MultipleValueInstructionResult { operand.value as! MultipleValueInstructionResult }
|
||||
public var beginApply: BeginApplyInst { token.parentInstruction as! BeginApplyInst }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class AbortApplyInst : Instruction, UnaryInstruction {
|
||||
@@ -1931,6 +1977,8 @@ final public class TryApplyInst : TermInst, FullApplySite {
|
||||
|
||||
public var isNonAsync: Bool { bridged.TryApplyInst_getNonAsync() }
|
||||
public var specializationInfo: ApplyInst.SpecializationInfo { bridged.TryApplyInst_getSpecializationInfo() }
|
||||
|
||||
public override var mayCallFunction: Bool { true }
|
||||
}
|
||||
|
||||
final public class BranchInst : TermInst {
|
||||
|
||||
@@ -101,6 +101,8 @@ PASS(EarlyRedundantLoadElimination, "early-redundant-load-elimination",
|
||||
"Early Redundant Load Elimination")
|
||||
PASS(RedundantLoadElimination, "redundant-load-elimination",
|
||||
"Redundant Load Elimination")
|
||||
PASS(DeadAccessScopeElimination, "dead-access-scope-elimination",
|
||||
"Removes dead, non-conflicting access scopes")
|
||||
PASS(DeadStoreElimination, "dead-store-elimination",
|
||||
"Dead Store Elimination")
|
||||
PASS(LifetimeDependenceDiagnostics,
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace {
|
||||
SILValue
|
||||
visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *UTBCI);
|
||||
SILValue visitEndCOWMutationInst(EndCOWMutationInst *ECM);
|
||||
SILValue visitBeginAccessInst(BeginAccessInst *BAI);
|
||||
SILValue visitMetatypeInst(MetatypeInst *MTI);
|
||||
SILValue visitConvertFunctionInst(ConvertFunctionInst *cfi);
|
||||
|
||||
@@ -419,16 +418,6 @@ visitUncheckedBitwiseCastInst(UncheckedBitwiseCastInst *UBCI) {
|
||||
return SILValue();
|
||||
}
|
||||
|
||||
SILValue InstSimplifier::visitBeginAccessInst(BeginAccessInst *BAI) {
|
||||
// Remove "dead" begin_access.
|
||||
if (llvm::all_of(BAI->getUses(), [](Operand *operand) -> bool {
|
||||
return isIncidentalUse(operand->getUser());
|
||||
})) {
|
||||
return BAI->getOperand();
|
||||
}
|
||||
return SILValue();
|
||||
}
|
||||
|
||||
SILValue InstSimplifier::visitConvertFunctionInst(ConvertFunctionInst *cfi) {
|
||||
// Eliminate round trip convert_function. Non round-trip is performed in
|
||||
// SILCombine.
|
||||
|
||||
@@ -599,6 +599,7 @@ void addFunctionPasses(SILPassPipelinePlan &P,
|
||||
P.addRedundantPhiElimination();
|
||||
P.addCSE();
|
||||
P.addDCE();
|
||||
P.addDeadAccessScopeElimination();
|
||||
|
||||
// Perform retain/release code motion and run the first ARC optimizer.
|
||||
P.addEarlyCodeMotion();
|
||||
|
||||
@@ -54,7 +54,7 @@ static bool seemsUseful(SILInstruction *I) {
|
||||
// Even though begin_access/destroy_value/copy_value/end_lifetime have
|
||||
// side-effects, they can be DCE'ed if they do not have useful
|
||||
// dependencies/reverse dependencies
|
||||
if (isa<BeginAccessInst>(I) || isa<CopyValueInst>(I) ||
|
||||
if (isa<CopyValueInst>(I) ||
|
||||
isa<DestroyValueInst>(I) || isa<EndLifetimeInst>(I) ||
|
||||
isa<EndBorrowInst>(I))
|
||||
return false;
|
||||
@@ -361,12 +361,6 @@ void DCE::markLive() {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SILInstructionKind::EndAccessInst: {
|
||||
// An end_access is live only if it's begin_access is also live.
|
||||
auto *beginAccess = cast<EndAccessInst>(&I)->getBeginAccess();
|
||||
addReverseDependency(beginAccess, &I);
|
||||
break;
|
||||
}
|
||||
case SILInstructionKind::DestroyValueInst: {
|
||||
auto phi = PhiValue(I.getOperand(0));
|
||||
// Disable DCE of phis which are lexical or may have a pointer escape.
|
||||
|
||||
309
test/SILOptimizer/dead-access-scope-elimination.sil
Normal file
309
test/SILOptimizer/dead-access-scope-elimination.sil
Normal file
@@ -0,0 +1,309 @@
|
||||
// RUN: %target-sil-opt %s -dead-access-scope-elimination | %FileCheck %s
|
||||
|
||||
sil_stage canonical
|
||||
|
||||
import Swift
|
||||
import Builtin
|
||||
|
||||
sil_global @g1: $Int
|
||||
sil_global @g2: $Int
|
||||
|
||||
sil @unknown : $@convention(thin) () -> ()
|
||||
sil @readonly : $@convention(thin) () -> () {
|
||||
[global: read]
|
||||
}
|
||||
sil @pure : $@convention(thin) () -> () {
|
||||
[global: ]
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @simple_dead :
|
||||
// CHECK-NOT: begin_access
|
||||
// CHECK-NOT: end_access
|
||||
// CHECK: } // end sil function 'simple_dead'
|
||||
sil [ossa] @simple_dead : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%2 = begin_access [modify] [dynamic] %0
|
||||
fix_lifetime %2
|
||||
debug_value %2 : $*Int, name "g1"
|
||||
end_access %2
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @simple_alive :
|
||||
// CHECK: [[A:%.*]] = begin_access
|
||||
// CHECK: [[L:%.*]] = load [trivial] [[A]]
|
||||
// CHECK: return [[L]]
|
||||
// CHECK: } // end sil function 'simple_alive'
|
||||
sil [ossa] @simple_alive : $@convention(thin) () -> Int {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%2 = begin_access [modify] [dynamic] %0
|
||||
%3 = load [trivial] %2
|
||||
end_access %2
|
||||
return %3
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @conflicting :
|
||||
// CHECK: %1 = begin_access [modify]
|
||||
// CHECK: %2 = begin_access [read]
|
||||
// CHECK: end_access %2
|
||||
// CHECK: end_access %1
|
||||
// CHECK: } // end sil function 'conflicting'
|
||||
sil [ossa] @conflicting : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [modify] [dynamic] %0
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
end_access %2
|
||||
end_access %1
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @not_conflicting :
|
||||
// CHECK-NOT: begin_access
|
||||
// CHECK-NOT: end_access
|
||||
// CHECK: } // end sil function 'not_conflicting'
|
||||
sil [ossa] @not_conflicting : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [read] [dynamic] %0
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
end_access %2
|
||||
end_access %1
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @not_aliasing :
|
||||
// CHECK-NOT: begin_access
|
||||
// CHECK-NOT: end_access
|
||||
// CHECK: } // end sil function 'not_aliasing'
|
||||
sil [ossa] @not_aliasing : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = global_addr @g2 : $*Int
|
||||
%2 = begin_access [modify] [dynamic] %0
|
||||
%3 = begin_access [read] [dynamic] %1
|
||||
end_access %3
|
||||
end_access %2
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @call_with_potential_access :
|
||||
// CHECK: %2 = begin_access [read]
|
||||
// CHECK: %3 = begin_access [read]
|
||||
// CHECK: end_access %3
|
||||
// CHECK: end_access %2
|
||||
// CHECK: } // end sil function 'call_with_potential_access'
|
||||
sil [ossa] @call_with_potential_access : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = global_addr @g2 : $*Int
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
%3 = begin_access [read] [dynamic] %1
|
||||
%4 = function_ref @unknown : $@convention(thin) () -> ()
|
||||
%5 = apply %4() : $@convention(thin) () -> ()
|
||||
end_access %3
|
||||
end_access %2
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @call_pure :
|
||||
// CHECK-NOT: begin_access
|
||||
// CHECK-NOT: end_access
|
||||
// CHECK: } // end sil function 'call_pure'
|
||||
sil [ossa] @call_pure : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = global_addr @g2 : $*Int
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
%3 = begin_access [read] [dynamic] %1
|
||||
%4 = function_ref @pure : $@convention(thin) () -> ()
|
||||
%5 = apply %4() : $@convention(thin) () -> ()
|
||||
end_access %3
|
||||
end_access %2
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @call_readonly :
|
||||
// CHECK: %3 = begin_access
|
||||
// CHECK-NEXT: apply
|
||||
// CHECK-NEXT: end_access %3
|
||||
// CHECK-NEXT: apply
|
||||
// CHECK-NEXT: tuple
|
||||
// CHECK: } // end sil function 'call_readonly'
|
||||
sil [ossa] @call_readonly : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = global_addr @g2 : $*Int
|
||||
%2 = function_ref @readonly : $@convention(thin) () -> ()
|
||||
%3 = begin_access [modify] [dynamic] %0
|
||||
%4 = apply %2() : $@convention(thin) () -> ()
|
||||
end_access %3
|
||||
%6 = begin_access [read] [dynamic] %0
|
||||
%7 = apply %2() : $@convention(thin) () -> ()
|
||||
end_access %6
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @unknown_call_via_destructor :
|
||||
// CHECK: %2 = begin_access [read]
|
||||
// CHECK: end_access %2
|
||||
// CHECK: } // end sil function 'unknown_call_via_destructor'
|
||||
sil [ossa] @unknown_call_via_destructor : $@convention(thin) (@owned AnyObject) -> () {
|
||||
bb0(%0: @owned $AnyObject):
|
||||
%1 = global_addr @g1 : $*Int
|
||||
%2 = begin_access [read] [dynamic] %1
|
||||
destroy_value %0
|
||||
end_access %2
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @two_inner_accesses :
|
||||
// CHECK: %1 = begin_access [read]
|
||||
// CHECK-NEXT: %2 = begin_access [modify]
|
||||
// CHECK-NEXT: end_access %2
|
||||
// CHECK-NEXT: end_access %1
|
||||
// CHECK: } // end sil function 'two_inner_accesses'
|
||||
sil [ossa] @two_inner_accesses : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [read] [dynamic] %0
|
||||
%2 = begin_access [modify] [dynamic] %0
|
||||
end_access %2
|
||||
%4 = begin_access [read] [dynamic] %0
|
||||
end_access %4
|
||||
end_access %1
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @partially_overlapping_accesses :
|
||||
// CHECK: %2 = begin_access [read] [dynamic] %1
|
||||
// CHECK: apply
|
||||
// CHECK-NEXT: end_access %2
|
||||
// CHECK-NEXT: tuple
|
||||
// CHECK: } // end sil function 'partially_overlapping_accesses'
|
||||
sil [ossa] @partially_overlapping_accesses : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = global_addr @g2 : $*Int
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
%3 = begin_access [read] [dynamic] %1
|
||||
end_access %2
|
||||
%4 = function_ref @unknown : $@convention(thin) () -> ()
|
||||
%5 = apply %4() : $@convention(thin) () -> ()
|
||||
%6 = begin_access [read] [dynamic] %0
|
||||
end_access %3
|
||||
end_access %6
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @multi_block1 :
|
||||
// CHECK: bb0:
|
||||
// CHECK: %1 = begin_access
|
||||
// CHECK: bb1:
|
||||
// CHECK: %3 = begin_access
|
||||
// CHECK: bb2:
|
||||
// CHECK: %6 = begin_access
|
||||
// CHECK: bb3:
|
||||
// CHECK: %9 = begin_access
|
||||
// CHECK: } // end sil function 'multi_block1'
|
||||
sil [ossa] @multi_block1 : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [modify] [dynamic] %0
|
||||
cond_br undef, bb1, bb2
|
||||
|
||||
bb1:
|
||||
%3 = begin_access [read] [dynamic] %0
|
||||
end_access %3
|
||||
br bb3
|
||||
|
||||
bb2:
|
||||
%6 = begin_access [read] [dynamic] %0
|
||||
end_access %6
|
||||
br bb3
|
||||
|
||||
bb3:
|
||||
%9 = begin_access [read] [dynamic] %0
|
||||
end_access %9
|
||||
end_access %1
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @multi_block2 :
|
||||
// CHECK: bb0:
|
||||
// CHECK: %1 = begin_access
|
||||
// CHECK: bb1:
|
||||
// CHECK: %3 = begin_access
|
||||
// CHECK: bb2:
|
||||
// CHECK: end_access %1
|
||||
// CHECK-NEXT: br bb3
|
||||
// CHECK: bb3:
|
||||
// CHECK-NEXT: tuple
|
||||
// CHECK: } // end sil function 'multi_block2'
|
||||
sil [ossa] @multi_block2 : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [modify] [dynamic] %0
|
||||
cond_br undef, bb1, bb2
|
||||
|
||||
bb1:
|
||||
%3 = begin_access [read] [dynamic] %0
|
||||
end_access %3
|
||||
end_access %1
|
||||
br bb3
|
||||
|
||||
bb2:
|
||||
end_access %1
|
||||
%8 = begin_access [read] [dynamic] %0
|
||||
end_access %8
|
||||
br bb3
|
||||
|
||||
bb3:
|
||||
%11 = begin_access [read] [dynamic] %0
|
||||
end_access %11
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @nested_static :
|
||||
// CHECK-NOT: begin_access [modify] [static]
|
||||
// CHECK: } // end sil function 'nested_static'
|
||||
sil [ossa] @nested_static : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [modify] [dynamic] %0
|
||||
%2 = begin_access [modify] [static] %1
|
||||
end_access %2
|
||||
end_access %1
|
||||
%r = tuple ()
|
||||
return %r
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @non_dead_inside_dead :
|
||||
// CHECK: begin_access [modify]
|
||||
// CHECK: begin_access [read]
|
||||
// CHECK: } // end sil function 'non_dead_inside_dead'
|
||||
sil [ossa] @non_dead_inside_dead : $@convention(thin) () -> Int {
|
||||
bb0:
|
||||
%0 = global_addr @g1 : $*Int
|
||||
%1 = begin_access [modify] [dynamic] %0
|
||||
%2 = begin_access [read] [dynamic] %0
|
||||
%3 = load [trivial] %2
|
||||
end_access %2
|
||||
end_access %1
|
||||
return %3
|
||||
}
|
||||
|
||||
@@ -253,11 +253,10 @@ bb3:
|
||||
br bb1
|
||||
}
|
||||
|
||||
// Check that DCE eliminates dead access instructions.
|
||||
// Dead accesses must not be removed
|
||||
// CHECK-LABEL: sil @dead_access
|
||||
// CHECK: bb0
|
||||
// CHECK-NEXT: tuple
|
||||
// CHECK-NEXT: return
|
||||
// CHECK: begin_access
|
||||
// CHECK: begin_access
|
||||
// CHECK-LABEL: end sil function 'dead_access'
|
||||
sil @dead_access : $@convention(thin) (@in Container) -> () {
|
||||
bb0(%0 : $*Container):
|
||||
|
||||
@@ -290,11 +290,10 @@ bb3:
|
||||
br bb1
|
||||
}
|
||||
|
||||
// Check that DCE eliminates dead access instructions.
|
||||
// Dead accesses must not be removed
|
||||
// CHECK-LABEL: sil [ossa] @dead_access :
|
||||
// CHECK: bb0
|
||||
// CHECK-NEXT: tuple
|
||||
// CHECK-NEXT: return
|
||||
// CHECK: begin_access
|
||||
// CHECK: begin_access
|
||||
// CHECK-LABEL: } // end sil function 'dead_access'
|
||||
sil [ossa] @dead_access : $@convention(thin) (@in Container) -> () {
|
||||
bb0(%0 : $*Container):
|
||||
|
||||
67
test/SILOptimizer/exclusivity_violation.swift
Normal file
67
test/SILOptimizer/exclusivity_violation.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
// RUN: %target-run-simple-swift(-Onone)
|
||||
// RUN: %target-run-simple-swift(-O)
|
||||
|
||||
// REQUIRES: executable_test
|
||||
|
||||
import StdlibUnittest
|
||||
|
||||
|
||||
var tests = TestSuite("exclusivity checking")
|
||||
|
||||
struct NC: ~Copyable {
|
||||
var i: Int = 1
|
||||
|
||||
mutating func add(_ other: borrowing Self) {
|
||||
i += other.i
|
||||
i += other.i
|
||||
print(self.i, other.i)
|
||||
}
|
||||
}
|
||||
|
||||
class C1 {
|
||||
var nc = NC()
|
||||
|
||||
func foo() {
|
||||
nc.add(nc)
|
||||
}
|
||||
}
|
||||
|
||||
struct S {
|
||||
var i: Int = 1
|
||||
|
||||
mutating func add(_ c: C2) {
|
||||
let other = c.getS()
|
||||
i += other.i
|
||||
i += other.i
|
||||
print(self.i, other.i)
|
||||
}
|
||||
}
|
||||
|
||||
final class C2 {
|
||||
var s = S()
|
||||
|
||||
@inline(never)
|
||||
func getS() -> S { s }
|
||||
|
||||
func foo() {
|
||||
s.add(self)
|
||||
}
|
||||
}
|
||||
|
||||
tests.test("non-copyable type")
|
||||
.crashOutputMatches("Simultaneous accesses")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
|
||||
C1().foo()
|
||||
}
|
||||
|
||||
tests.test("copyable type")
|
||||
.crashOutputMatches("Simultaneous accesses")
|
||||
.code {
|
||||
expectCrashLater()
|
||||
|
||||
C2().foo()
|
||||
}
|
||||
|
||||
runAllTests()
|
||||
@@ -325,34 +325,18 @@ public class B {
|
||||
deinit
|
||||
}
|
||||
|
||||
// simplify access markers
|
||||
sil @simplify_accesssil : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK-LABEL: sil @dont_remove_empty_access_scopes : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK: begin_access
|
||||
// CHECK-LABEL: } // end sil function 'dont_remove_empty_access_scopes'
|
||||
sil @dont_remove_empty_access_scopes : $@convention(method) (@guaranteed A) -> () {
|
||||
bb0(%0 : $A):
|
||||
%badr = ref_element_addr %0 : $A, #A.b
|
||||
%ba1 = begin_access [read] [dynamic] %badr : $*B
|
||||
end_access %ba1 : $*B
|
||||
%ba2 = begin_access [read] [dynamic] %badr : $*B
|
||||
fix_lifetime %ba2 : $*B
|
||||
end_access %ba2 : $*B
|
||||
%ba3 = begin_access [read] [dynamic] %badr : $*B
|
||||
%b = load %ba3 : $*B
|
||||
end_access %ba3 : $*B
|
||||
%xadr = ref_element_addr %b : $B, #B.x
|
||||
%xaccess = begin_access [read] [dynamic] %xadr : $*Int
|
||||
end_access %xaccess : $*Int
|
||||
|
||||
%r = tuple ()
|
||||
return %r : $()
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil @simplify_accesssil : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK: bb0(%0 : $A):
|
||||
// CHECK-NEXT: [[ADR:%.*]] = ref_element_addr %0 : $A, #A.b
|
||||
// CHECK-NEXT: fix_lifetime [[ADR]] : $*B
|
||||
// CHECK-NEXT: %{{.*}} = tuple ()
|
||||
// CHECK-NEXT: return %{{.*}} : $()
|
||||
// CHECK-LABEL: } // end sil function 'simplify_accesssil'
|
||||
|
||||
// Test replacement of metatype instructions with metatype arguments.
|
||||
protocol SomeP {}
|
||||
|
||||
|
||||
@@ -370,31 +370,14 @@ public class B {
|
||||
|
||||
// simplify access markers
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @simplify_accesssil : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK: bb0(%0 : @guaranteed $A):
|
||||
// CHECK-NEXT: [[ADR:%.*]] = ref_element_addr %0 : $A, #A.b
|
||||
// CHECK-NEXT: fix_lifetime [[ADR]] : $*B
|
||||
// CHECK-NEXT: %{{.*}} = tuple ()
|
||||
// CHECK-NEXT: return %{{.*}} : $()
|
||||
// CHECK: } // end sil function 'simplify_accesssil'
|
||||
sil [ossa] @simplify_accesssil : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK-LABEL: sil [ossa] @dont_remove_empty_access_scopes : $@convention(method) (@guaranteed A) -> () {
|
||||
// CHECK: begin_access
|
||||
// CHECK: } // end sil function 'dont_remove_empty_access_scopes'
|
||||
sil [ossa] @dont_remove_empty_access_scopes : $@convention(method) (@guaranteed A) -> () {
|
||||
bb0(%0 : @guaranteed $A):
|
||||
%badr = ref_element_addr %0 : $A, #A.b
|
||||
%ba1 = begin_access [read] [dynamic] %badr : $*B
|
||||
end_access %ba1 : $*B
|
||||
%ba2 = begin_access [read] [dynamic] %badr : $*B
|
||||
fix_lifetime %ba2 : $*B
|
||||
end_access %ba2 : $*B
|
||||
%ba3 = begin_access [read] [dynamic] %badr : $*B
|
||||
%b = load [copy] %ba3 : $*B
|
||||
end_access %ba3 : $*B
|
||||
%bb = begin_borrow %b : $B
|
||||
%xadr = ref_element_addr %bb : $B, #B.x
|
||||
%xaccess = begin_access [read] [dynamic] %xadr : $*Int
|
||||
end_access %xaccess : $*Int
|
||||
end_borrow %bb : $B
|
||||
destroy_value %b : $B
|
||||
|
||||
%r = tuple ()
|
||||
return %r : $()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user