diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift index f2e8ceb89af3c..3ba102e6ac78a 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceDiagnostics.swift @@ -9,6 +9,15 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +/// +/// Pass dependencies: +/// +/// - After MoveOnly checking fixes non-Copyable lifetimes. +/// +/// - Before MoveOnlyTypeEliminator removes ownership operations on trivial types, which loses variable information +/// required for diagnostics. +/// +//===----------------------------------------------------------------------===// import AST import SIL @@ -250,22 +259,6 @@ private struct DiagnoseDependence { } } -private extension Instruction { - func findVarDecl() -> VarDecl? { - if let varDeclInst = self as? VarDeclInstruction { - return varDeclInst.varDecl - } - for result in results { - for use in result.uses { - if let debugVal = use.instruction as? DebugValueInst { - return debugVal.varDecl - } - } - } - return nil - } -} - // Identify a best-effort variable declaration based on a defining SIL // value or any lifetime dependent use of that SIL value. private struct LifetimeVariable { @@ -327,7 +320,7 @@ private struct LifetimeVariable { self = Self(introducer: allocStack) case .global(let globalVar): self.varDecl = globalVar.varDecl - self.sourceLoc = nil + self.sourceLoc = varDecl?.nameLoc case .class(let refAddr): self.varDecl = refAddr.varDecl self.sourceLoc = refAddr.location.sourceLoc diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift index 8ec392a88d6bd..4a49ab4e1dda2 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift @@ -564,3 +564,22 @@ extension AddressOwnershipLiveRange { return .local(allocation, range) } } + +let addressOwnershipLiveRangeTest = FunctionTest("address_ownership_live_range") { + function, arguments, context in + let address = arguments.takeValue() + print("Address: \(address)") + print("Base: \(address.accessBase)") + let begin = address.definingInstructionOrTerminator ?? { + assert(address is FunctionArgument) + return function.instructions.first! + }() + let localReachabilityCache = LocalVariableReachabilityCache() + guard var ownershipRange = AddressOwnershipLiveRange.compute(for: address, at: begin, + localReachabilityCache, context) else { + print("Error: indeterminate live range") + return + } + defer { ownershipRange.deinitialize() } + print(ownershipRange) +} diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift index 85a6913565607..fd14c264f0104 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift @@ -52,6 +52,7 @@ // //===----------------------------------------------------------------------===// +import AST import SIL private let verbose = false @@ -593,17 +594,13 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker { } mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult { - switch value.definingInstruction { - case let moveInst as MoveValueInst: - if moveInst.isFromVarDecl { - return introducer(moveInst, owner) - } - case let borrow as BeginBorrowInst: - if borrow.isFromVarDecl { - return introducer(borrow, owner) - } - default: - break + if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil { + return introducer(value, owner) + } + // Finding a variable introducer requires following the mark_dependence forwarded value, not the base value like the + // default LifetimeDependenceUseDefWalker. + if value is MarkDependenceInst { + return walkUpDefault(forwarded: value, owner) } return walkUpDefault(dependent: value, owner: owner) } @@ -749,6 +746,11 @@ extension LifetimeDependenceUseDefWalker { return walkUp(newLifetime: store.source) case let srcDestInst as SourceDestAddrInstruction: return walkUp(address: srcDestInst.sourceOperand.value) + case let apply as FullApplySite: + if let f = apply.referencedFunction, + f.isConvertPointerToPointerArgument { + return walkUp(address: apply.parameterOperands[0].value) + } default: break } diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/Test.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/Test.swift index ededad1076663..4b0117080b1fe 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/Test.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/Test.swift @@ -154,6 +154,7 @@ public func registerOptimizerTests() { // Register each test. registerFunctionTests( getAccessBaseTest, + addressOwnershipLiveRangeTest, argumentConventionsTest, borrowIntroducersTest, enclosingValuesTest, diff --git a/SwiftCompilerSources/Sources/SIL/Function.swift b/SwiftCompilerSources/Sources/SIL/Function.swift index b446274b756af..f4d3a18c2cb08 100644 --- a/SwiftCompilerSources/Sources/SIL/Function.swift +++ b/SwiftCompilerSources/Sources/SIL/Function.swift @@ -40,6 +40,8 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash public var isAutodiffVJP: Bool { bridged.isAutodiffVJP() } + public var isConvertPointerToPointerArgument: Bool { bridged.isConvertPointerToPointerArgument() } + public var specializationLevel: Int { bridged.specializationLevel() } public var hasOwnership: Bool { bridged.hasOwnership() } diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 37ad1d5fcf12a..c4811fc51592b 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -401,6 +401,88 @@ public protocol VarDeclInstruction { var varDecl: VarDecl? { get } } +/// A scoped instruction whose single result introduces a variable scope. +/// +/// The scope-ending uses represent the end of the variable scope. This allows trivial 'let' variables to be treated +/// like a value with ownership. 'var' variables are primarily represented as addressable allocations via alloc_box or +/// alloc_stack, but may have redundant VariableScopeInstructions. +public enum VariableScopeInstruction { + case beginBorrow(BeginBorrowInst) + case moveValue(MoveValueInst) + + public init?(_ inst: Instruction?) { + switch inst { + case let bbi as BeginBorrowInst: + guard bbi.isFromVarDecl else { + return nil + } + self = .beginBorrow(bbi) + case let mvi as MoveValueInst: + guard mvi.isFromVarDecl else { + return nil + } + self = .moveValue(mvi) + default: + return nil + } + } + + public var instruction: Instruction { + switch self { + case let .beginBorrow(bbi): + return bbi + case let .moveValue(mvi): + return mvi + } + } + + public var scopeBegin: Value { + instruction as! SingleValueInstruction + } + + public var endOperands: LazyFilterSequence { + return scopeBegin.uses.endingLifetime + } + + // TODO: with SIL verification, we might be able to make varDecl non-Optional. + public var varDecl: VarDecl? { + if let debugVarDecl = instruction.debugVarDecl { + return debugVarDecl + } + // SILGen may produce double var_decl instructions for the same variable: + // %box = alloc_box [var_decl] "x" + // begin_borrow %box [var_decl] + // + // Assume that, if the begin_borrow or move_value does not have its own debug_value, then it must actually be + // associated with its operand's var_decl. + return instruction.operands[0].value.definingInstruction?.findVarDecl() + } +} + +extension Instruction { + /// Find a variable declaration assoicated with this instruction. + public func findVarDecl() -> VarDecl? { + if let varDeclInst = self as? VarDeclInstruction { + return varDeclInst.varDecl + } + if let varScopeInst = VariableScopeInstruction(self) { + return varScopeInst.varDecl + } + return debugVarDecl + } + + var debugVarDecl: VarDecl? { + for result in results { + for use in result.uses { + if let debugVal = use.instruction as? DebugValueInst { + return debugVal.varDecl + } + } + } + return nil + } +} + public protocol DebugVariableInstruction : VarDeclInstruction { typealias DebugVariable = OptionalBridgedSILDebugVariable diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index c7174b6609fc1..8a21cf8a37631 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -525,6 +525,7 @@ struct BridgedFunction { BRIDGED_INLINE void setLinkage(BridgedLinkage linkage) const; BRIDGED_INLINE void setIsSerialized(bool isSerialized) const; bool isTrapNoReturn() const; + bool isConvertPointerToPointerArgument() const; bool isAutodiffVJP() const; SwiftInt specializationLevel() const; SWIFT_IMPORT_UNSAFE BridgedSubstitutionMap getMethodSubstitutions(BridgedSubstitutionMap contextSubs) const; diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index cdd868299f32e..536c208c24729 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -17,6 +17,7 @@ #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/Module.h" #include "swift/AST/ParameterList.h" +#include "swift/AST/SourceFile.h" #include "swift/AST/Type.h" #include "swift/AST/TypeRepr.h" #include "swift/Basic/Assertions.h" @@ -538,10 +539,18 @@ LifetimeDependenceInfo::infer(AbstractFunctionDecl *afd) { : afd->getParameters()->size(); auto *cd = dyn_cast(afd); - if (cd && cd->isImplicit()) { - if (cd->getParameters()->size() == 0) { + if (cd && cd->getParameters()->size() == 0) { + if (cd->isImplicit()) { return std::nullopt; } + if (auto *sf = afd->getParentSourceFile()) { + // The AST printer makes implicit initializers explicit, but does not + // print the @lifetime annotations. Until that is fixed, avoid diagnosing + // this as an error. + if (sf->Kind == SourceFileKind::SIL) { + return std::nullopt; + } + } } if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)) { diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 046847f1779e3..5f7bf4f225d8b 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -5006,8 +5006,7 @@ ParserResult Parser::parseLifetimeEntry(SourceLoc loc) { auto lParenLoc = consumeAttributeLParen(); // consume the l_paren std::optional targetDescriptor; - if (!isInSILMode() && - Tok.isAny(tok::identifier, tok::integer_literal, tok::kw_self) && + if (Tok.isAny(tok::identifier, tok::integer_literal, tok::kw_self) && peekToken().is(tok::colon)) { targetDescriptor = parseLifetimeDescriptor(*this); if (!targetDescriptor) { diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index aff13a390692e..64e1e3d37ede5 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1608,6 +1608,15 @@ bool BridgedFunction::isTrapNoReturn() const { return swift::isTrapNoReturnFunction(getFunction()); } +bool BridgedFunction::isConvertPointerToPointerArgument() const { + if (auto declRef = getFunction()->getDeclRef()) { + auto *conversionDecl = + declRef.getASTContext().getConvertPointerToPointerArgument(); + return declRef.getFuncDecl() == conversionDecl; + } + return false; +} + bool BridgedFunction::isAutodiffVJP() const { return swift::isDifferentiableFuncComponent( getFunction(), swift::AutoDiffFunctionComponent::VJP); diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow_fail.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow_fail.swift index 918f7eeedb255..350b0d63691cc 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow_fail.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow_fail.swift @@ -77,10 +77,11 @@ func bv_incorrect_annotation2(_ w1: borrowing Wrapper, _ w2: borrowing Wrapper) } // expected-note @-1{{this use causes the lifetime-dependent value to escape}} let ptr = UnsafeRawPointer(bitPattern: 1)! -let nc = NC(ptr, 0) // expected-error {{lifetime-dependent variable 'nc' escapes its scope}} +let nc = NC(ptr, 0) func bv_global(dummy: BV) -> BV { - nc.getBV() + nc.getBV() // expected-error {{lifetime-dependent value escapes its scope}} + // expected-note @-4{{it depends on the lifetime of variable 'nc'}} } // expected-note {{this use causes the lifetime-dependent value to escape}} func testEmpty(nc: consuming NC) { diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_insertion.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_insertion.swift index 22c0a578f1d63..02398dd258327 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_insertion.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_insertion.swift @@ -48,3 +48,17 @@ func bv_borrow_var(p: UnsafeRawPointer, i: Int) { let bv = nc.getBV() use(bv) } + +// LifetimeDependence.Scope needs to see through typed-to-raw pointer conversion. +// +// CHECK-LABEL: sil hidden [ossa] @$s4test18bv_pointer_convert1pAA2BVVSPySiG_tF : $@convention(thin) (UnsafePointer) -> @lifetime(borrow 0) @owned BV { +// CHECK: bb0(%0 : $UnsafePointer): +// CHECK: apply %{{.*}}, UnsafeRawPointer>([[RAW:%.*]], %{{.*}}) : $@convention(thin) <τ_0_0, τ_0_1 where τ_0_0 : _Pointer, τ_0_1 : _Pointer> (@in_guaranteed τ_0_0) -> @out τ_0_1 +// CHECK: [[RAW:%.*]] = load [trivial] %6 : $*UnsafeRawPointer +// CHECK: [[BV:%.*]] = apply %13([[RAW]], {{.*}}) : $@convention(method) (UnsafeRawPointer, Int, @thin BV.Type) -> @lifetime(borrow 0) @owned BV +// CHECK: return [[BV]] : $BV +// CHECK-LABEL: } // end sil function '$s4test18bv_pointer_convert1pAA2BVVSPySiG_tF' +@lifetime(borrow p) +func bv_pointer_convert(p: UnsafePointer) -> BV { + BV(p, 0) +} diff --git a/test/SILOptimizer/ownership_utils/address_ownership_live_range.sil b/test/SILOptimizer/ownership_utils/address_ownership_live_range.sil new file mode 100644 index 0000000000000..78b56ec3e6f9c --- /dev/null +++ b/test/SILOptimizer/ownership_utils/address_ownership_live_range.sil @@ -0,0 +1,35 @@ +// RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s + +sil_stage canonical + +import Builtin + +class C {} +class D { + var object: C +} + +// An address live range can be extended by a dependent value. +// +// CHECK-LABEL: testMarkDepAddressBase: address_ownership_live_range with: %f0 +// CHECK-NEXT: Address: [[F0:%.*]] = ref_element_addr %0 : $D, #D.object +// CHECK-NEXT: Base: class - [[F0]] = ref_element_addr %0 : $D, #D.object +// CHECK-NEXT: borrow: functionArgument(%0 = argument of bb0 : $D +// CHECK-NEXT: begin: [[F0]] = ref_element_addr %0 : $D, #D.object +// CHECK-NEXT: ends: end_borrow %{{.*}} : $C +// CHECK-NEXT: exits: +// CHECK-NEXT: interiors: %{{.*}} = load_borrow %{{.*}} : $*C +// CHECK-NEXT: %{{.*}} = mark_dependence %{{.*}} : $*C on [[F0]] : $*C +// CHECK-NEXT: [[F0]] = ref_element_addr %0 : $D, #D.object +// CHECK-LABEL: end running test 1 of 1 on testMarkDepAddressBase: address_ownership_live_range with: %f0 +sil [ossa] @testMarkDepAddressBase : $@convention(thin) (@guaranteed D, @guaranteed D) -> () { +bb0(%0 : @guaranteed $D, %1 : @guaranteed $D): + %f0 = ref_element_addr %0 : $D, #D.object + %f1 = ref_element_addr %1 : $D, #D.object + specify_test "address_ownership_live_range %f0" + %dependence = mark_dependence %f1 on %f0 + %load = load_borrow %dependence + end_borrow %load + %99 = tuple() + return %99 : $() +}