diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift index 0b2082a6ba7eb..13db38c7f8088 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceInsertion.swift @@ -130,15 +130,16 @@ extension LifetimeDependentApply { var info = LifetimeSourceInfo() let hasScopedYield = applySite.parameterOperands.contains { if let dep = applySite.resultDependence(on: $0) { - return dep == .scope + return dep.isScoped } return false } if hasScopedYield { - // for consistency, we you yieldAddress if any yielded value is an address. + // for consistency, we use yieldAddress if any yielded value is an address. let targetKind = beginApply.yieldedValues.contains(where: { $0.type.isAddress }) ? TargetKind.yieldAddress : TargetKind.yield - info.sources.push(LifetimeSource(targetKind: targetKind, convention: .scope, value: beginApply.token)) + info.sources.push(LifetimeSource(targetKind: targetKind, convention: .scope(addressable: false), + value: beginApply.token)) } for operand in applySite.parameterOperands { guard let dep = applySite.resultDependence(on: operand) else { @@ -216,6 +217,11 @@ private extension LifetimeDependentApply.LifetimeSourceInfo { // A coroutine creates its own borrow scope, nested within its borrowed operand. bases.append(source.value) case .result, .inParameter, .inoutParameter: + // addressable dependencies directly depend on the incoming address. + if context.options.enableAddressDependencies() && source.convention.isAddressable { + bases.append(source.value) + return + } // Create a new dependence on the apply's access to the argument. for varIntoducer in gatherVariableIntroducers(for: source.value, context) { let scope = LifetimeDependence.Scope(base: varIntoducer, context) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift index 4f69ff7f5f0f3..915e11e6ae8df 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LifetimeDependenceScopeFixup.swift @@ -249,7 +249,7 @@ private struct ScopeExtension { private extension LifetimeDependence.Scope { /// The instruction that introduces an extendable scope. This returns a non-nil scope introducer for - /// Extendable.nestedScopes. + /// ScopeExtension.nestedScopes. var extendableBegin: Instruction? { switch self { case let .access(beginAccess): @@ -258,6 +258,17 @@ private extension LifetimeDependence.Scope { return beginBorrow.value.definingInstruction! case let .yield(yieldedValue): return yieldedValue.definingInstruction! + case let .initialized(initializer): + switch initializer { + case let .store(initializingStore: store, initialAddress: _): + if let sb = store as? StoreBorrowInst { + return sb + } + return nil + case .argument, .yield: + // TODO: extend indirectly yielded scopes. + return nil + } default: return nil } @@ -277,29 +288,31 @@ private extension LifetimeDependence.Scope { let accessExtension = gatherAccessExtension(beginAccess: beginAccess, innerScopes: &innerScopes) return SingleInlineArray(element: accessExtension) case let .borrowed(beginBorrow): - let borrowedValue = beginBorrow.baseOperand!.value - let enclosingScope = LifetimeDependence.Scope(base: borrowedValue, context) - innerScopes.push(self) - var innerBorrowScopes = innerScopes - innerBorrowScopes.push(enclosingScope) - if let extensions = enclosingScope.gatherExtensions(innerScopes: innerBorrowScopes, context) { - return extensions - } - // This is the outermost scope to be extended because gatherExtensions did not find an enclosing scope. - return SingleInlineArray(element: getOuterExtension(owner: enclosingScope.parentValue, nestedScopes: innerScopes, - context)) + return gatherBorrowExtension(borrowedValue: beginBorrow.baseOperand!.value, innerScopes: &innerScopes, context) + case let .yield(yieldedValue): innerScopes.push(self) var extensions = SingleInlineArray() let applySite = yieldedValue.definingInstruction as! BeginApplyInst for operand in applySite.parameterOperands { - guard let dep = applySite.resultDependence(on: operand), dep == .scope else { + guard let dep = applySite.resultDependence(on: operand), dep.isScoped else { continue } // Pass a copy of innerScopes without modifying this one. extensions.append(contentsOf: gatherOperandExtension(on: operand, innerScopes: innerScopes, context)) } return extensions + case let .initialized(initializer): + switch initializer { + case let .store(initializingStore: store, initialAddress: _): + if let sb = store as? StoreBorrowInst { + return gatherBorrowExtension(borrowedValue: sb.source, innerScopes: &innerScopes, context) + } + return nil + case .argument, .yield: + // TODO: extend indirectly yielded scopes. + return nil + } default: return nil } @@ -360,6 +373,23 @@ private extension LifetimeDependence.Scope { } return ScopeExtension(owner: outerBeginAccess, nestedScopes: innerScopes, dependsOnArg: nil) } + + func gatherBorrowExtension(borrowedValue: Value, + innerScopes: inout SingleInlineArray, + _ context: FunctionPassContext) + -> SingleInlineArray { + + let enclosingScope = LifetimeDependence.Scope(base: borrowedValue, context) + innerScopes.push(self) + var innerBorrowScopes = innerScopes + innerBorrowScopes.push(enclosingScope) + if let extensions = enclosingScope.gatherExtensions(innerScopes: innerBorrowScopes, context) { + return extensions + } + // This is the outermost scope to be extended because gatherExtensions did not find an enclosing scope. + return SingleInlineArray(element: getOuterExtension(owner: enclosingScope.parentValue, nestedScopes: innerScopes, + context)) + } } /// Compute the range of the a scope owner. Nested scopes must stay within this range. @@ -584,6 +614,17 @@ private extension LifetimeDependence.Scope { case let .yield(yieldedValue): let beginApply = yieldedValue.definingInstruction as! BeginApplyInst return beginApply.createEnd(builder, context) + case let .initialized(initializer): + switch initializer { + case let .store(initializingStore: store, initialAddress: _): + if let sb = store as? StoreBorrowInst { + return builder.createEndBorrow(of: sb) + } + return nil + case .argument, .yield: + // TODO: extend indirectly yielded scopes. + return nil + } default: return nil } diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Options.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Options.swift index a58b49dabda74..5536e67817200 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Options.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Options.swift @@ -32,6 +32,10 @@ struct Options { _bridged.enableSimplificationFor(inst.bridged) } + func enableAddressDependencies() -> Bool { + _bridged.enableAddressDependencies() + } + var enableEmbeddedSwift: Bool { _bridged.hasFeature(.Embedded) } diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift index f62122682a2b9..1e77cd531e54b 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/LifetimeDependenceUtils.swift @@ -105,10 +105,12 @@ struct LifetimeDependence : CustomStringConvertible { case let .initialized(initializer): let initialAddress = initializer.initialAddress precondition(initialAddress.type.isAddress, "expected an address") - precondition(initialAddress is AllocStackInst || initialAddress is FunctionArgument, + precondition(initialAddress is AllocStackInst || initialAddress is FunctionArgument + || initialAddress is StoreBorrowInst, "expected storage for a a local 'let'") if case let .store(store, _) = initializer { - precondition(store is StoringInstruction || store is SourceDestAddrInstruction || store is FullApplySite, + precondition(store is StoringInstruction || store is SourceDestAddrInstruction || store is FullApplySite + || store is StoreBorrowInst, "expected a store") } } @@ -217,8 +219,8 @@ extension LifetimeDependence.Scope { /// Construct a lifetime dependence scope from the base value that other values depend on. This derives the kind of /// dependence scope and its parentValue from `base`. /// - /// The returned Scope must be the only scope for the given 'base' value. This is generally non-recursive, except - /// that finds the single borrow introducer. Use-def walking is handled by a utility such as + /// The returned Scope must be the only scope for the given 'base' value. This is generally non-recursive, except + /// that it tries to find the single borrow introducer. General use-def walking is handled by a utility such as /// VariableIntroducerUseDefWalker, which can handle multiple introducers. /// /// `base` represents the OSSA lifetime that the dependent value must be used within. If `base` is owned, then it @@ -291,7 +293,8 @@ extension LifetimeDependence.Scope { case let .yield(result): self.init(yield: result) case .storeBorrow(let sb): - self = Self(base: sb.source, context) + // Don't follow the stored value in case the dependence requires addressability. + self = .initialized(.store(initializingStore: sb, initialAddress: sb)) } } @@ -905,7 +908,7 @@ extension LifetimeDependenceDefUseWalker { return leafUse(of: operand) } if let dep = apply.resultDependence(on: operand), - dep == .inherit { + !dep.isScoped { // Operand is nonescapable and passed as a call argument. If the // result inherits its lifetime, then consider any nonescapable // result value to be a dependent use. diff --git a/SwiftCompilerSources/Sources/SIL/FunctionConvention.swift b/SwiftCompilerSources/Sources/SIL/FunctionConvention.swift index 818a4f6b6c05c..59b45cf8f34fd 100644 --- a/SwiftCompilerSources/Sources/SIL/FunctionConvention.swift +++ b/SwiftCompilerSources/Sources/SIL/FunctionConvention.swift @@ -237,7 +237,25 @@ extension FunctionConvention { public enum LifetimeDependenceConvention : CustomStringConvertible { case inherit - case scope + case scope(addressable: Bool) + + public var isScoped: Bool { + switch self { + case .inherit: + return false + case .scope: + return true + } + } + + public var isAddressable: Bool { + switch self { + case .inherit: + return false + case let .scope(addressable): + return addressable + } + } public var description: String { switch self { @@ -294,7 +312,8 @@ extension FunctionConvention { return .inherit } if scope { - return .scope + let addressable = bridged.checkAddressable(bridgedIndex(parameterIndex: index)) + return .scope(addressable: addressable) } return nil } diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index 43e33e2c1428d..263885ba3cb6d 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -300,6 +300,11 @@ class LifetimeDependenceInfo { && scopeLifetimeParamIndices->contains(index); } + bool checkAddressable(int index) const { + return hasAddressableParamIndices() + && getAddressableIndices()->contains(index); + } + std::string getString() const; void Profile(llvm::FoldingSetNodeID &ID) const; void getConcatenatedData(SmallVectorImpl &concatenatedData) const; diff --git a/include/swift/AST/SILOptions.h b/include/swift/AST/SILOptions.h index 1323b8a30cbdc..3d53a891e861b 100644 --- a/include/swift/AST/SILOptions.h +++ b/include/swift/AST/SILOptions.h @@ -331,6 +331,10 @@ class SILOptions { /// optimizer. bool UseAggressiveReg2MemForCodeSize = true; + /// Enable enforcement of lifetime dependencies on addressable arguments. + /// Temporarily used to bootstrap the AddressableParameters feature. + bool EnableAddressDependencies = false; + SILOptions() {} /// Return a hash code of any components from these options that should diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index 68b2ff4eef8a4..f2e82bd503c94 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -1489,6 +1489,9 @@ def platform_availability_inheritance_map_path : Separate<["-"], "platform-availability-inheritance-map-path">, MetaVarName<"">, HelpText<"Path of the platform inheritance platform map">; +def enable_address_dependencies : Flag<["-"], "enable-address-dependencies">, + HelpText<"Enable enforcement of lifetime dependencies on addressable values.">; + } // end let Flags = [FrontendOption, NoDriverOption, HelpHidden] def disable_experimental_parser_round_trip : Flag<["-"], diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index 1cb872829d915..410209dabae05 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -156,6 +156,7 @@ struct BridgedYieldInfoArray { struct BridgedLifetimeDependenceInfo { swift::IndexSubset *_Nullable inheritLifetimeParamIndices; swift::IndexSubset *_Nullable scopeLifetimeParamIndices; + swift::IndexSubset *_Nullable addressableParamIndices; SwiftUInt targetIndex; bool immortal; @@ -164,6 +165,7 @@ struct BridgedLifetimeDependenceInfo { BRIDGED_INLINE bool empty() const; BRIDGED_INLINE bool checkInherit(SwiftInt index) const; BRIDGED_INLINE bool checkScope(SwiftInt index) const; + BRIDGED_INLINE bool checkAddressable(SwiftInt index) const; BRIDGED_INLINE SwiftInt getTargetIndex() const; BRIDGED_INLINE BridgedOwnedString getDebugDescription() const; diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index 8a653621e96d4..af9cd10fdfaa6 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -146,6 +146,7 @@ BridgedParameterInfo BridgedParameterInfoArray::at(SwiftInt parameterIndex) cons BridgedLifetimeDependenceInfo::BridgedLifetimeDependenceInfo(swift::LifetimeDependenceInfo info) : inheritLifetimeParamIndices(info.getInheritIndices()), scopeLifetimeParamIndices(info.getScopeIndices()), + addressableParamIndices(info.getAddressableIndices()), targetIndex(info.getTargetIndex()), immortal(info.isImmortal()) {} SwiftInt BridgedLifetimeDependenceInfoArray::count() const { @@ -172,6 +173,10 @@ bool BridgedLifetimeDependenceInfo::checkScope(SwiftInt index) const { scopeLifetimeParamIndices->contains(index); } +bool BridgedLifetimeDependenceInfo::checkAddressable(SwiftInt index) const { + return addressableParamIndices && addressableParamIndices->contains(index); +} + SwiftInt BridgedLifetimeDependenceInfo::getTargetIndex() const { return targetIndex; } diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 82ef4f3403c68..6a3955e2546bb 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -382,6 +382,9 @@ struct BridgedPassContext { bool enableSimplificationFor(BridgedInstruction inst) const; BRIDGED_INLINE bool enableWMORequiredDiagnostics() const; + // Temporary for AddressableParameters Bootstrapping. + BRIDGED_INLINE bool enableAddressDependencies() const; + // Closure specializer SWIFT_IMPORT_UNSAFE BridgedFunction ClosureSpecializer_createEmptyFunctionWithSpecializedSignature(BridgedStringRef specializedName, const BridgedParameterInfo * _Nullable specializedBridgedParams, diff --git a/include/swift/SILOptimizer/OptimizerBridgingImpl.h b/include/swift/SILOptimizer/OptimizerBridgingImpl.h index 63cf5eb82ffac..ecba04a07ed6f 100644 --- a/include/swift/SILOptimizer/OptimizerBridgingImpl.h +++ b/include/swift/SILOptimizer/OptimizerBridgingImpl.h @@ -573,6 +573,11 @@ bool BridgedPassContext::enableWMORequiredDiagnostics() const { return mod->getOptions().EnableWMORequiredDiagnostics; } +bool BridgedPassContext::enableAddressDependencies() const { + swift::SILModule *mod = invocation->getPassManager()->getModule(); + return mod->getOptions().EnableAddressDependencies; +} + static_assert((int)BridgedPassContext::SILStage::Raw == (int)swift::SILStage::Raw); static_assert((int)BridgedPassContext::SILStage::Canonical == (int)swift::SILStage::Canonical); static_assert((int)BridgedPassContext::SILStage::Lowered == (int)swift::SILStage::Lowered); diff --git a/lib/DriverTool/sil_opt_main.cpp b/lib/DriverTool/sil_opt_main.cpp index ff7d8ced2db3b..277a0a868885b 100644 --- a/lib/DriverTool/sil_opt_main.cpp +++ b/lib/DriverTool/sil_opt_main.cpp @@ -592,6 +592,10 @@ struct SILOptOptions { "swift-version", llvm::cl::desc( "The swift version to assume AST declarations correspond to")); + + llvm::cl::opt EnableAddressDependencies = llvm::cl::opt( + "enable-address-dependencies", + llvm::cl::desc("Enable enforcement of lifetime dependencies on addressable values.")); }; /// Regular expression corresponding to the value given in one of the @@ -909,6 +913,8 @@ int sil_opt_main(ArrayRef argv, void *MainAddr) { SILOpts.EnablePackMetadataStackPromotion = options.EnablePackMetadataStackPromotion; + SILOpts.EnableAddressDependencies = options.EnableAddressDependencies; + if (options.OptModeFlag == OptimizationMode::NotSet) { if (options.OptimizationGroup == OptGroup::Diagnostics) SILOpts.OptMode = OptimizationMode::NoOptimization; diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 622a70ce891ee..90057f7a30bc2 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -3013,6 +3013,9 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args, Opts.ShouldFunctionsBePreservedToDebugger &= LTOKind.value() == IRGenLLVMLTOKind::None; + + Opts.EnableAddressDependencies = Args.hasArg(OPT_enable_address_dependencies); + return false; } diff --git a/test/SILOptimizer/lifetime_dependence/dependence_insertion.sil b/test/SILOptimizer/lifetime_dependence/dependence_insertion.sil index 809924ad5bdad..60e8c8082eca5 100644 --- a/test/SILOptimizer/lifetime_dependence/dependence_insertion.sil +++ b/test/SILOptimizer/lifetime_dependence/dependence_insertion.sil @@ -1,11 +1,16 @@ -// RUN: %target-sil-opt %s \ -// RUN: --lifetime-dependence-insertion \ +// RUN: %target-sil-opt \ +// RUN: -lifetime-dependence-insertion \ +// RUN: -enable-address-dependencies \ // RUN: -sil-verify-all \ // RUN: -enable-experimental-feature LifetimeDependence \ -// RUN: 2>&1 | %FileCheck %s +// RUN: -enable-experimental-feature AddressableParameters \ +// RUN: -enable-experimental-feature AddressableTypes \ +// RUN: %s | %FileCheck %s // REQUIRES: swift_in_compiler // REQUIRES: swift_feature_LifetimeDependence +// REQUIRES: swift_feature_AddressableParameters +// REQUIRES: swift_feature_AddressableTypes sil_stage raw @@ -25,10 +30,23 @@ struct NCE: ~Escapable, ~Copyable { init() } +struct TrivialHolder { + var pointer: UnsafeRawPointer +} + +struct Holder { + var object: AnyObject +} + +@_addressableForDependencies +struct AddressableForDeps {} + sil @getPtr : $@convention(thin) () -> @out UnsafeRawPointer sil @getSpan : $@convention(thin) (@in_guaranteed AnyObject) -> @lifetime(borrow 0) @out NE sil @getInoutSpan : $@convention(thin) (@inout AnyObject) -> @lifetime(borrow 0) @out NCE +sil @useNE : $@convention(thin) (@guaranteed NE) -> () + // Check that the inserted dependence is on the 'self' argument, not the temporary borrow. // // CHECK-LABEL: sil [available 9999] [ossa] @testSpanProp : $@convention(method) (@guaranteed AnyObject) -> @lifetime(borrow 0) @owned NE { @@ -92,3 +110,65 @@ bb0(%0 : $*AnyObject): dealloc_stack %1 return %7 } + +// ============================================================================= +// @_addressable +// ============================================================================= + +sil [ossa] @loadFromPointer : $@convention(thin) <τ_0_0> (UnsafeRawPointer) -> @out τ_0_0 + +sil [ossa] @addressableTrivialArg : $@convention(thin) (@in_guaranteed TrivialHolder) -> @lifetime(borrow address 0) @owned NE + +// CHECK-LABEL: sil hidden [ossa] @testAddressableTrivialArg : $@convention(thin) (TrivialHolder) -> Int { +// CHECK: [[ALLOC:%.*]] = alloc_stack $TrivialHolder +// CHECK: [[APPLY:%.*]] = apply %{{.*}}([[ALLOC]]) : $@convention(thin) (@in_guaranteed TrivialHolder) -> @lifetime(borrow address 0) @owned NE +// CHECK: mark_dependence [unresolved] [[APPLY]] on [[ALLOC]] +// CHECK-LABEL: } // end sil function 'testAddressableTrivialArg' +sil hidden [ossa] @testAddressableTrivialArg : $@convention(thin) (TrivialHolder) -> Int { +bb0(%0 : $TrivialHolder): + debug_value %0, let, name "arg", argno 1 + %2 = alloc_stack $TrivialHolder + store %0 to [trivial] %2 + %4 = function_ref @addressableTrivialArg : $@convention(thin) (@in_guaranteed TrivialHolder) -> @lifetime(borrow address 0) @owned NE + %5 = apply %4(%2) : $@convention(thin) (@in_guaranteed TrivialHolder) -> @lifetime(borrow address 0) @owned NE + %7 = move_value [var_decl] %5 + debug_value %7, let, name "ne" + %9 = alloc_stack $Int + %10 = begin_borrow %7 + %11 = struct_extract %10, #NE.p + %16 = function_ref @loadFromPointer : $@convention(thin) <τ_0_0> (UnsafeRawPointer) -> @out τ_0_0 + %17 = apply %16(%9, %11) : $@convention(thin) <τ_0_0> (UnsafeRawPointer) -> @out τ_0_0 + end_borrow %10 + %19 = load [trivial] %9 + dealloc_stack %9 + destroy_value %7 + dealloc_stack %2 + return %19 +} + +sil [ossa] @addressableArg : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + +// CHECK-LABEL: sil hidden [ossa] @testAddressableArg : $@convention(thin) (@guaranteed Holder) -> () { +// CHECK: bb0(%0 : @guaranteed $Holder): +// CHECK: [[ALLOC:%.*]] = alloc_stack $Holder +// CHECK: [[SB:%.*]] = store_borrow %0 to [[ALLOC]] +// CHECK: [[APPLY:%.*]] = apply %{{.*}}([[SB]]) : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE +// CHECK: mark_dependence [unresolved] [[APPLY]] on [[SB]] +// CHECK-LABEL: } // end sil function 'testAddressableArg' +sil hidden [ossa] @testAddressableArg : $@convention(thin) (@guaranteed Holder) -> () { +bb0(%0 : @guaranteed $Holder): + debug_value %0, let, name "arg", argno 1 + %2 = alloc_stack $Holder + %3 = store_borrow %0 to %2 + %4 = function_ref @addressableArg : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + %5 = apply %4(%3) : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + end_borrow %3 + %8 = move_value [var_decl] %5 + debug_value %8, let, name "ne" + %useNE = function_ref @useNE : $@convention(thin) (@guaranteed NE) -> () + %18 = apply %useNE(%8) : $@convention(thin) (@guaranteed NE) -> () + destroy_value %8 + dealloc_stack %2 + %99 = tuple () + return %99 : $() +} diff --git a/test/SILOptimizer/lifetime_dependence/scopefixup.sil b/test/SILOptimizer/lifetime_dependence/scope_fixup.sil similarity index 65% rename from test/SILOptimizer/lifetime_dependence/scopefixup.sil rename to test/SILOptimizer/lifetime_dependence/scope_fixup.sil index e1f453c738864..1c8a33460c5a4 100644 --- a/test/SILOptimizer/lifetime_dependence/scopefixup.sil +++ b/test/SILOptimizer/lifetime_dependence/scope_fixup.sil @@ -1,11 +1,15 @@ -// RUN: %target-sil-opt %s \ -// RUN: --lifetime-dependence-scope-fixup \ +// RUN: %target-sil-opt \ +// RUN: -lifetime-dependence-scope-fixup \ // RUN: -sil-verify-all \ // RUN: -enable-experimental-feature LifetimeDependence \ -// RUN: 2>&1 | %FileCheck %s +// RUN: -enable-experimental-feature AddressableParameters \ +// RUN: -enable-experimental-feature AddressableTypes \ +// RUN: %s | %FileCheck %s // REQUIRES: swift_in_compiler // REQUIRES: swift_feature_LifetimeDependence +// REQUIRES: swift_feature_AddressableParameters +// REQUIRES: swift_feature_AddressableTypes // Test the SIL representation for lifetime dependence scope fixup. @@ -15,6 +19,10 @@ import Builtin import Swift struct NE : ~Escapable { + var p: UnsafeRawPointer + + @lifetime(immortal) + init() } struct Wrapper : ~Escapable { @@ -31,6 +39,22 @@ struct NCContainer : ~Copyable { var wrapper: Wrapper { get } // _read } +struct TrivialHolder { + var pointer: UnsafeRawPointer +} + +struct Holder { + var object: AnyObject +} + +@_addressableForDependencies +struct AddressableForDeps {} + +sil @getPtr : $@convention(thin) () -> @out UnsafeRawPointer +sil @getSpan : $@convention(thin) (@in_guaranteed AnyObject) -> @lifetime(borrow 0) @out NE + +sil @useNE : $@convention(thin) (@guaranteed NE) -> () + sil [ossa] @Wrapper_init : $@convention(method) (@owned NE, @thin Wrapper.Type) -> @lifetime(copy 0) @owned Wrapper sil [ossa] @NCContainer_ne_read : $@yield_once @convention(method) (@guaranteed NCContainer) -> @lifetime(borrow 0) @yields @guaranteed NE @@ -120,3 +144,40 @@ bb0(%0 : $UnsafePointer): %19 = tuple () return %19 } + +// ============================================================================= +// @_addressable +// ============================================================================= + +sil [ossa] @addressableArg : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + +// CHECK-LABEL: sil hidden [ossa] @testAddressableArg : $@convention(thin) (@guaranteed Holder) -> () { +// CHECK: bb0(%0 : @guaranteed $Holder): +// CHECK: [[ALLOC:%.*]] = alloc_stack $Holder +// CHECK: [[SB:%.*]] = store_borrow %0 to [[ALLOC]] +// CHECK: [[APPLY:%.*]] = apply %{{.*}}([[SB]]) : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE +// CHECK: [[MD:%.*]] = mark_dependence [unresolved] [[APPLY]] on [[SB]] +// CHECK: [[MV:%.*]] = move_value [var_decl] [[MD]] +// CHECK: apply %{{.*}}([[MV]]) : $@convention(thin) (@guaranteed NE) -> () +// CHECK: destroy_value [[MV]] +// CHECK: end_borrow [[SB]] +// CHECK: dealloc_stack [[ALLOC]] +// CHECK-LABEL: } // end sil function 'testAddressableArg' +sil hidden [ossa] @testAddressableArg : $@convention(thin) (@guaranteed Holder) -> () { +bb0(%0 : @guaranteed $Holder): + debug_value %0, let, name "arg", argno 1 + %2 = alloc_stack $Holder + %3 = store_borrow %0 to %2 + %4 = function_ref @addressableArg : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + %5 = apply %4(%3) : $@convention(thin) (@in_guaranteed Holder) -> @lifetime(borrow address 0) @owned NE + %6 = mark_dependence [unresolved] %5 on %3 + end_borrow %3 + %8 = move_value [var_decl] %6 + debug_value %8, let, name "ne" + %useNE = function_ref @useNE : $@convention(thin) (@guaranteed NE) -> () + %18 = apply %useNE(%8) : $@convention(thin) (@guaranteed NE) -> () + destroy_value %8 + dealloc_stack %2 + %99 = tuple () + return %99 : $() +} diff --git a/test/SILOptimizer/lifetime_dependence/semantics.swift b/test/SILOptimizer/lifetime_dependence/semantics.swift index 2712b9fb11070..28e25744614f7 100644 --- a/test/SILOptimizer/lifetime_dependence/semantics.swift +++ b/test/SILOptimizer/lifetime_dependence/semantics.swift @@ -3,10 +3,17 @@ // RUN: -verify \ // RUN: -sil-verify-all \ // RUN: -module-name test \ -// RUN: -enable-experimental-feature LifetimeDependence +// RUN: -enable-builtin-module \ +// RUN: -enable-experimental-feature LifetimeDependence \ +// RUN: -enable-experimental-feature AddressableParameters \ +// RUN: -enable-experimental-feature AddressableTypes // REQUIRES: swift_in_compiler // REQUIRES: swift_feature_LifetimeDependence +// REQUIRES: swift_feature_AddressableParameters +// REQUIRES: swift_feature_AddressableTypes + +import Builtin @_unsafeNonescapableResult @_transparent @@ -67,6 +74,12 @@ public struct Span: ~Escapable { self.base = base self.count = count } + + public subscript(_ position: Int) -> T { + unsafeAddress { + return base!.advanced(by: position) + } + } } extension Span { @@ -129,9 +142,24 @@ struct InnerTrivial { } } -struct InnerObject { +struct TrivialHolder { + var p: UnsafePointer + var pa: UnsafePointer + + var addressableInt: AddressableInt { unsafeAddress { pa } } + + @lifetime(borrow self) + borrowing func span() -> Span { + Span(base: p, count: 1) + } +} + +struct Holder { let object: AnyObject var p: UnsafePointer + var pa: UnsafePointer + + var addressableInt: AddressableInt { unsafeAddress { pa } } @lifetime(borrow self) borrowing func span() -> Span { @@ -139,11 +167,37 @@ struct InnerObject { } } +@_addressableForDependencies +struct AddressableInt { + let value: Int + + @lifetime(borrow self) + borrowing func span() -> Span { + // TODO: we actually want the address of self.value + let p = UnsafePointer(Builtin.unprotectedAddressOfBorrow(self)) + let span = Span(base: p, count: 1) + return _overrideLifetime(span, borrowing: self) + } +} + +@_addressableForDependencies +struct AddressableObject { + let object: AnyObject + + @lifetime(borrow self) + borrowing func span() -> Span { + // TODO: we actually want the address of self.object + let p = UnsafePointer(Builtin.unprotectedAddressOfBorrow(self)) + let span = Span(base: p, count: 1) + return _overrideLifetime(span, borrowing: self) + } +} + struct Outer { var _innerTrivial: InnerTrivial - var _innerObject: InnerObject + var _innerObject: Holder let trivialPointer: UnsafePointer - let objectPointer: UnsafePointer + let objectPointer: UnsafePointer var innerTrivialAddress: InnerTrivial { unsafeAddress { @@ -151,7 +205,7 @@ struct Outer { } } - var innerObjectAddress: InnerObject { + var innerObjectAddress: Holder { unsafeAddress { objectPointer } @@ -161,7 +215,7 @@ struct Outer { get { _innerTrivial } } - var innerObjectTemp: InnerObject { + var innerObjectTemp: Holder { get { _innerObject } } @@ -475,3 +529,74 @@ func testReturnObjectTemp(outer: Outer) -> Span { // expected-error @-1{{lifetime-dependent value escapes its scope}} // expected-note @-2{{it depends on the lifetime of this parent value}} } // expected-note {{this use causes the lifetime-dependent value to escape}} + +// ============================================================================= +// Scoped dependence on addressable parameters +// ============================================================================= + +// @_addressableForDependencies supports returning a Span. +@lifetime(borrow arg) +func testAddressableInt(arg: AddressableInt) -> Span { + arg.span() +} + +// @_addressableForDependencies supports returning a Span. +@lifetime(borrow arg) +func testAddressableObject(arg: AddressableObject) -> Span { + arg.span() +} + +// Helper: create a dependence on the argument's address. +@lifetime(borrow arg) +func dependsOnTrivialAddressHelper(arg: @_addressable TrivialHolder) -> Span { + arg.span() +} + +// Helper: create a dependence on the argument's address. +@lifetime(borrow arg) +func dependsOnAddressHelper(arg: @_addressable Holder) -> Span { + arg.span() +} + +/* TODO: requires -enable-address-dependencies + +// Non-addressable error returning a Span. +@lifetime(borrow arg) +func testTrivialNonAddressable(arg: TrivialHolder) -> Span { + dependsOnTrivialAddressHelper(arg: arg) + // todo-error @-1{{lifetime-dependent value escapes its scope} + // todo-note @-3{{it depends on the lifetime of variable 'arg'}} +} // todo-note {{this use causes the lifetime-dependent value to escape}} + +// Non-addressable error returning a Span. +@lifetime(borrow arg) +func testNonAddressable(arg: Holder) -> Span { + dependsOnAddressHelper(arg: arg) + // todo-error @-1{{lifetime-dependent value escapes its scope} + // todo-note @-3{{it depends on the lifetime of variable 'arg'}} +} // todo-note {{this use causes the lifetime-dependent value to escape}} +*/ + +/* TODO: rdar://145872854 (SILGen: @addressable inout arguments are copied) +@lifetime(borrow arg) +func test(arg: inout AddressableInt) -> Span { + arg.span() +} + +// unsafeAddress generates an addressable value with a local scope. +@lifetime(borrow arg) +func testBorrowedAddressableInt(arg: Holder) -> Int { + let span = arg.addressableInt.span() + return span[0] +} + +// unsafeAddress generates an addressable value. +// Error returning a dependence on its local scope. +@lifetime(borrow arg) +func testBorrowedAddressableIntReturn(arg: Holder) -> Span { + arg.addressableInt.span() + // todo-error @-1{{lifetime-dependent value escapes its scope} + // todo-note @-2{{it depends on the lifetime of this parent value}} +} // todo-note {{this use causes the lifetime-dependent value to escape}} + +*/