diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index 56681d4fc08..ba86fb4c734 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -289,11 +289,6 @@ public: && 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/Types.h b/include/swift/AST/Types.h index b5a94988ced..7dc36c98bcb 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -5649,6 +5649,13 @@ public: return getLifetimeDependenceFor(getNumParameters()); } + /// Return true of the specified parameter is addressable based on its type + /// lowering in 'caller's context. This includes @_addressableForDependencies + /// parameter types. + /// + /// Defined in SILType.cpp. + bool isAddressable(unsigned paramIdx, SILFunction *caller); + /// Returns true if the function type stores a Clang type that cannot /// be derived from its Swift type. Returns false otherwise, including if /// the function type is not @convention(c) or @convention(block). diff --git a/include/swift/SIL/ApplySite.h b/include/swift/SIL/ApplySite.h index 5fcf3ffa0be..0dbd1c7f2e8 100644 --- a/include/swift/SIL/ApplySite.h +++ b/include/swift/SIL/ApplySite.h @@ -641,6 +641,10 @@ public: return getArgumentParameterInfo(oper).hasOption(SILParameterInfo::Sending); } + /// Return true if 'operand' is addressable after type substitution in the + /// caller's context. + bool isAddressable(const Operand &operand) const; + static ApplySite getFromOpaqueValue(void *p) { return ApplySite(p); } friend bool operator==(ApplySite lhs, ApplySite rhs) { diff --git a/include/swift/SILOptimizer/Utils/Generics.h b/include/swift/SILOptimizer/Utils/Generics.h index 493dbcd1c0b..0dc4743ef38 100644 --- a/include/swift/SILOptimizer/Utils/Generics.h +++ b/include/swift/SILOptimizer/Utils/Generics.h @@ -84,7 +84,8 @@ class ReabstractionInfo { /// specializer. bool ConvertIndirectToDirect = true; - /// If true, drop unused arguments. + /// If true, drop unused arguments. Dropping unused arguments is a + /// prerequisite before promoting an indirect argument to a direct argument. /// See `droppedArguments`. bool dropUnusedArguments = false; @@ -204,7 +205,7 @@ public: ReabstractionInfo(ModuleDecl *targetModule, bool isModuleWholeModule, ApplySite Apply, SILFunction *Callee, SubstitutionMap ParamSubs, SerializedKind_t Serialized, - bool ConvertIndirectToDirect, bool dropMetatypeArgs, + bool ConvertIndirectToDirect, bool dropUnusedArguments, OptRemark::Emitter *ORE = nullptr); /// Constructs the ReabstractionInfo for generic function \p Callee with diff --git a/lib/SIL/IR/ApplySite.cpp b/lib/SIL/IR/ApplySite.cpp index 264ed5dd66d..dfdbde25998 100644 --- a/lib/SIL/IR/ApplySite.cpp +++ b/lib/SIL/IR/ApplySite.cpp @@ -45,3 +45,12 @@ void ApplySite::insertAfterApplication( llvm_unreachable("covered switch isn't covered"); } +bool ApplySite::isAddressable(const Operand &operand) const { + unsigned calleeArgIndex = getCalleeArgIndex(operand); + assert(calleeArgIndex >= getSubstCalleeConv().getSILArgIndexOfFirstParam()); + unsigned paramIdx = + calleeArgIndex - getSubstCalleeConv().getSILArgIndexOfFirstParam(); + + CanSILFunctionType calleeType = getSubstCalleeType(); + return calleeType->isAddressable(paramIdx, getFunction()); +} diff --git a/lib/SIL/IR/SILType.cpp b/lib/SIL/IR/SILType.cpp index f12c27d4742..52c21c45797 100644 --- a/lib/SIL/IR/SILType.cpp +++ b/lib/SIL/IR/SILType.cpp @@ -706,6 +706,29 @@ bool SILFunctionType::isNoReturnFunction(SILModule &M, return false; } +bool SILFunctionType::isAddressable(unsigned paramIdx, SILFunction *caller) { + SILParameterInfo paramInfo = getParameters()[paramIdx]; + for (auto &depInfo : getLifetimeDependencies()) { + auto *addressableIndices = depInfo.getAddressableIndices(); + if (addressableIndices && addressableIndices->contains(paramIdx)) { + return true; + } + auto *condAddressableIndices = depInfo.getConditionallyAddressableIndices(); + if (condAddressableIndices && condAddressableIndices->contains(paramIdx)) { + CanType argType = paramInfo.getArgumentType( + caller->getModule(), this, caller->getTypeExpansionContext()); + CanType contextType = + argType->hasTypeParameter() + ? caller->mapTypeIntoContext(argType)->getCanonicalType() + : argType; + auto &tl = caller->getTypeLowering(contextType); + if (tl.getRecursiveProperties().isAddressableForDependencies()) + return true; + } + } + return false; +} + #ifndef NDEBUG static bool areOnlyAbstractionDifferent(CanType type1, CanType type2) { assert(type1->isLegalSILType()); diff --git a/lib/SILOptimizer/IPO/CapturePropagation.cpp b/lib/SILOptimizer/IPO/CapturePropagation.cpp index 6227e4a84fe..57fe19e6cda 100644 --- a/lib/SILOptimizer/IPO/CapturePropagation.cpp +++ b/lib/SILOptimizer/IPO/CapturePropagation.cpp @@ -496,7 +496,7 @@ static SILFunction *getSpecializedWithDeadParams( FuncBuilder.getModule().getSwiftModule(), FuncBuilder.getModule().isWholeModule(), ApplySite(), Specialized, PAI->getSubstitutionMap(), Specialized->getSerializedKind(), - /* ConvertIndirectToDirect */ false, /*dropMetatypeArgs=*/false); + /* ConvertIndirectToDirect */ false, /*dropUnusedArguments=*/false); GenericFuncSpecializer FuncSpecializer(FuncBuilder, Specialized, ReInfo.getClonerParamSubstitutionMap(), diff --git a/lib/SILOptimizer/IPO/UsePrespecialized.cpp b/lib/SILOptimizer/IPO/UsePrespecialized.cpp index 83178343118..0ee9236aa36 100644 --- a/lib/SILOptimizer/IPO/UsePrespecialized.cpp +++ b/lib/SILOptimizer/IPO/UsePrespecialized.cpp @@ -96,7 +96,7 @@ bool UsePrespecialized::replaceByPrespecialized(SILFunction &F) { ReabstractionInfo ReInfo(M.getSwiftModule(), M.isWholeModule(), AI, ReferencedF, Subs, IsNotSerialized, /*ConvertIndirectToDirect=*/ true, - /*dropMetatypeArgs=*/ false); + /*dropUnusedArguments=*/ false); if (!ReInfo.canBeSpecialized()) continue; diff --git a/lib/SILOptimizer/Utils/Generics.cpp b/lib/SILOptimizer/Utils/Generics.cpp index 2e6c0512a7b..656132f5a3b 100644 --- a/lib/SILOptimizer/Utils/Generics.cpp +++ b/lib/SILOptimizer/Utils/Generics.cpp @@ -455,6 +455,82 @@ static bool shouldNotSpecialize(SILFunction *Callee, SILFunction *Caller, return false; } +// Addressable parameters cannot be dropped because the address may +// escape. They also can't be promoted to direct convention, so there +// is no danger in preserving them. +static bool canConvertArg(CanSILFunctionType substType, unsigned paramIdx, + SILFunction *caller) { + return !substType->isAddressable(paramIdx, caller); +} + +// If there is no read from an indirect argument, this argument has to be +// dropped. At the call site the store to the argument's memory location could +// have been removed (based on the callee's memory effects). Therefore, +// converting such an unused indirect argument to a direct argument, would load +// an uninitialized value at the call site. This would lead to verifier errors +// and in worst case to a miscompile because IRGen can implicitly use dead +// arguments, e.g. for getting the type of a class reference. +static bool canDropUnusedArg(ApplySite apply, SILFunction *callee, + CanSILFunctionType substType, + unsigned paramIdx) { + FullApplySite fas = apply.asFullApplySite(); + if (!fas) { + return false; + } + Operand &op = fas.getOperandsWithoutIndirectResults()[paramIdx]; + return !callee->argumentMayRead(&op, op.get()); +} + +static bool isUsedAsDynamicSelf(SILArgument *arg) { + for (Operand *use : arg->getUses()) { + if (use->isTypeDependent()) + return true; + } + return false; +} + +static bool canDropMetatypeArg(ApplySite apply, SILFunction *callee, + unsigned paramIdx) { + if (!callee->isDefinition()) + return false; + + unsigned calleeArgIdx = + apply.getSubstCalleeConv().getSILArgIndexOfFirstParam() + paramIdx; + SILArgument *calleeArg = callee->getArguments()[calleeArgIdx]; + + if (isUsedAsDynamicSelf(calleeArg)) + return false; + + if (calleeArg->getType().getASTType()->hasDynamicSelfType()) + return false; + + // We don't drop metatype arguments of not applied arguments (in case of + // `partial_apply`). + unsigned firstAppliedArgIdx = apply.getCalleeArgIndexOfFirstAppliedArg(); + if (firstAppliedArgIdx > calleeArgIdx) + return false; + + auto mt = calleeArg->getType().castTo(); + if (mt->hasRepresentation() + && mt->getRepresentation() == MetatypeRepresentation::Thin) { + return true; + } + // If the passed thick metatype value is not a `metatype` instruction + // we don't know the real metatype at runtime. It's not necessarily the + // same as the declared metatype. It could e.g. be an upcast of a class + // metatype. + SILValue callerArg = apply.getArguments()[calleeArgIdx - firstAppliedArgIdx]; + if (isa(callerArg)) + return true; + + // But: if the metatype is not used in the callee we don't have to care + // what metatype value is passed. We can just remove it. + if (onlyHaveDebugUses(calleeArg)) + return true; + + return false; +} + /// Prepares the ReabstractionInfo object for further processing and checks /// if the current function can be specialized at all. /// Returns false, if the current function cannot be specialized. @@ -771,7 +847,7 @@ void ReabstractionInfo::createSubstitutedAndSpecializedTypes() { for (SILParameterInfo PI : SubstitutedType->getParameters()) { auto IdxToInsert = IdxForParam; ++IdxForParam; - unsigned argIdx = i++; + unsigned paramIdx = i++; SILFunctionConventions substConv(SubstitutedType, getModule()); TypeCategory tc = getParamTypeCategory(PI, substConv, getResilienceExpansion()); @@ -782,22 +858,14 @@ void ReabstractionInfo::createSubstitutedAndSpecializedTypes() { case ParameterConvention::Indirect_In_CXX: case ParameterConvention::Indirect_In: case ParameterConvention::Indirect_In_Guaranteed: { - if (Callee && Apply && dropUnusedArguments) { - // If there is no read from an indirect argument, this argument has to - // be dropped. At the call site the store to the argument's memory location - // could have been removed (based on the callee's memory effects). - // Therefore, converting such an unused indirect argument to a direct - // argument, would load an uninitialized value at the call site. - // This would lead to verifier errors and in worst case to a miscompile - // because IRGen can implicitly use dead arguments, e.g. for getting the - // type of a class reference. - if (FullApplySite fas = Apply.asFullApplySite()) { - Operand &op = fas.getOperandsWithoutIndirectResults()[argIdx]; - if (!Callee->argumentMayRead(&op, op.get())) { - droppedArguments.set(IdxToInsert); - break; - } - } + if (Apply && !canConvertArg(SubstitutedType, paramIdx, + Apply.getFunction())) { + continue; + } + if (Callee && Apply && dropUnusedArguments + && canDropUnusedArg(Apply, Callee, SubstitutedType, paramIdx)) { + droppedArguments.set(IdxToInsert); + break; } Conversions.set(IdxToInsert); if (tc == LoadableAndTrivial) @@ -822,8 +890,10 @@ void ReabstractionInfo::createSubstitutedAndSpecializedTypes() { case ParameterConvention::Direct_Unowned: case ParameterConvention::Direct_Guaranteed: { CanType ty = PI.getInterfaceType(); - if (dropUnusedArguments && isa(ty) && !ty->hasArchetype()) + if (dropUnusedArguments && isa(ty) && !ty->hasArchetype() + && Apply && Callee && canDropMetatypeArg(Apply, Callee, paramIdx)) { droppedArguments.set(IdxToInsert); + } break; } } @@ -2916,7 +2986,8 @@ static bool createPrespecialized(StringRef UnspecializedName, ReabstractionInfo ReInfo(M.getSwiftModule(), M.isWholeModule(), ApplySite(), UnspecFunc, Apply.getSubstitutionMap(), IsNotSerialized, - /*ConvertIndirectToDirect= */true, /*dropMetatypeArgs=*/ false); + /*ConvertIndirectToDirect= */true, + /*dropUnusedArguments=*/ false); if (!ReInfo.canBeSpecialized()) return false; @@ -3005,7 +3076,7 @@ static bool usePrespecialized( funcBuilder.getModule().isWholeModule(), apply, refF, apply.getSubstitutionMap(), IsNotSerialized, /*ConvertIndirectToDirect=*/ true, - /*dropMetatypeArgs=*/ false); + /*dropUnusedArguments=*/ false); for (auto *SA : refF->getSpecializeAttrs()) { if (!SA->isExported()) @@ -3149,7 +3220,8 @@ static bool usePrespecialized( funcBuilder.getModule().getSwiftModule(), funcBuilder.getModule().isWholeModule(), apply, refF, newSubstMap, apply.getFunction()->getSerializedKind(), - /*ConvertIndirectToDirect=*/ true, /*dropMetatypeArgs=*/ false, nullptr); + /*ConvertIndirectToDirect=*/ true, + /*dropUnusedArguments=*/ false, nullptr); if (layoutReInfo.getSpecializedType() == reInfo.getSpecializedType()) { layoutMatches.push_back( @@ -3209,57 +3281,6 @@ static bool usePrespecialized( return false; } -static bool isUsedAsDynamicSelf(SILArgument *arg) { - for (Operand *use : arg->getUses()) { - if (use->isTypeDependent()) - return true; - } - return false; -} - -static bool canDropMetatypeArgs(ApplySite apply, SILFunction *callee) { - if (!callee->isDefinition()) - return false; - - auto calleeArgs = callee->getArguments(); - unsigned firstAppliedArgIdx = apply.getCalleeArgIndexOfFirstAppliedArg(); - for (unsigned calleeArgIdx = 0; calleeArgIdx < calleeArgs.size(); ++calleeArgIdx) { - SILArgument *calleeArg = calleeArgs[calleeArgIdx]; - auto mt = calleeArg->getType().getAs(); - if (!mt) - continue; - - if (isUsedAsDynamicSelf(calleeArg)) - return false; - - if (calleeArg->getType().getASTType()->hasDynamicSelfType()) - return false; - - // We don't drop metatype arguments of not applied arguments (in case of `partial_apply`). - if (firstAppliedArgIdx > calleeArgIdx) - return false; - - if (mt->hasRepresentation() && mt->getRepresentation() == MetatypeRepresentation::Thin) - continue; - - // If the passed thick metatype value is not a `metatype` instruction - // we don't know the real metatype at runtime. It's not necessarily the - // same as the declared metatype. It could e.g. be an upcast of a class - // metatype. - SILValue callerArg = apply.getArguments()[calleeArgIdx - firstAppliedArgIdx]; - if (isa(callerArg)) - continue; - - // But: if the metatype is not used in the callee we don't have to care - // what metatype value is passed. We can just remove it. - if (callee->isDefinition() && onlyHaveDebugUses(calleeArg)) - continue; - - return false; - } - return true; -} - void swift::trySpecializeApplyOfGeneric( SILOptFunctionBuilder &FuncBuilder, ApplySite Apply, DeadInstructionSet &DeadApplies, @@ -3312,7 +3333,7 @@ void swift::trySpecializeApplyOfGeneric( FuncBuilder.getModule().isWholeModule(), Apply, RefF, Apply.getSubstitutionMap(), serializedKind, /*ConvertIndirectToDirect=*/ true, - /*dropMetatypeArgs=*/ canDropMetatypeArgs(Apply, RefF), + /*dropUnusedArguments=*/ true, &ORE); if (!ReInfo.canBeSpecialized()) return; diff --git a/lib/SILOptimizer/Utils/OptimizerBridging.cpp b/lib/SILOptimizer/Utils/OptimizerBridging.cpp index e52352ced1c..02390165da1 100644 --- a/lib/SILOptimizer/Utils/OptimizerBridging.cpp +++ b/lib/SILOptimizer/Utils/OptimizerBridging.cpp @@ -206,7 +206,7 @@ OptionalBridgedFunction BridgedPassContext::specializeFunction(BridgedFunction f ReabstractionInfo ReInfo(mod->getSwiftModule(), mod->isWholeModule(), ApplySite(), origFunc, subs, IsNotSerialized, /*ConvertIndirectToDirect=*/true, - /*dropMetatypeArgs=*/false); + /*dropUnusedArguments=*/false); if (!ReInfo.canBeSpecialized()) { return {nullptr}; diff --git a/test/SILOptimizer/addressable_dependency_optimization.swift b/test/SILOptimizer/addressable_dependency_optimization.swift new file mode 100644 index 00000000000..3d5cb4cccfd --- /dev/null +++ b/test/SILOptimizer/addressable_dependency_optimization.swift @@ -0,0 +1,24 @@ +// RUN: %target-swift-frontend -emit-sil -parse-as-library -O -module-name=test \ +// RUN: -enable-experimental-feature LifetimeDependence \ +// RUN: -enable-experimental-feature AddressableTypes \ +// RUN: %s | %FileCheck %s + +// REQUIRES: swift_feature_AddressableTypes +// REQUIRES: swift_feature_LifetimeDependence + +// Enable this test as soon as CollectionOfOne is marked @_addressableForDependencies. +// REQUIRES: rdar145687827 + +// CHECK-LABEL: sil {{.*}}@$s4test0A10OneIntSpan1cs0D0VySiGs012CollectionOfB0VySiG_tF : $@convention(thin) (@in_guaranteed CollectionOfOne) -> @lifetime(borrow address_for_deps 0) @owned Span { +// CHECK: bb0(%0 : $*CollectionOfOne): +// CHECK: [[RP:%.*]] = address_to_pointer {{.*}}%0 to $Builtin.RawPointer +// CHECK: [[UP:%.*]] = struct $UnsafeRawPointer ([[RP]]) +// CHECK: [[OP:%.*]] = enum $Optional, #Optional.some!enumelt, [[UP]] +// CHECK: [[SPAN:%.*]] = struct $Span ([[OP]] +// CHECK: return [[SPAN]] +// CHECK-LABEL: } // end sil function '$s4test0A10OneIntSpan1cs0D0VySiGs012CollectionOfB0VySiG_tF' +@available(SwiftStdlib 6.2, *) +@lifetime(borrow c) +public func testOneIntSpan(c: CollectionOfOne) -> Span { + c.span +} diff --git a/test/SILOptimizer/lifetime_dependence/specialize.sil b/test/SILOptimizer/lifetime_dependence/specialize.sil new file mode 100644 index 00000000000..8574cfed4b7 --- /dev/null +++ b/test/SILOptimizer/lifetime_dependence/specialize.sil @@ -0,0 +1,54 @@ +// RUN: %target-sil-opt \ +// RUN: -generic-specializer \ +// RUN: -sil-verify-all \ +// RUN: -enable-experimental-feature LifetimeDependence \ +// 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. + +sil_stage raw + +import Builtin +import Swift + +sil @makeSpan : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafePointer<τ_0_0>, Int, @thin Span<τ_0_0>.Type) -> @lifetime(borrow 0) @owned Span<τ_0_0> + +// Ensure that the addressable argument is not deleted. +// +// CHECK-LABEL: sil shared [ossa] @$s12getSpanOfOneSi_Tg5 : $@convention(thin) (@in_guaranteed CollectionOfOne) -> @lifetime(borrow address_for_deps 0) @owned Span { +sil shared [ossa] @getSpanOfOne : $@convention(thin) (@in_guaranteed CollectionOfOne) -> @lifetime(borrow address_for_deps 0) @owned Span { +[global: ] +bb0(%0 : $*CollectionOfOne): + %1 = address_to_pointer [stack_protection] %0 : $*CollectionOfOne to $Builtin.RawPointer + %2 = struct $UnsafePointer (%1 : $Builtin.RawPointer) + %3 = metatype $@thin Span.Type + %4 = integer_literal $Builtin.Int64, 1 + %5 = struct $Int (%4 : $Builtin.Int64) + %6 = function_ref @makeSpan : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafePointer<τ_0_0>, Int, @thin Span<τ_0_0>.Type) -> @lifetime(borrow 0) @owned Span<τ_0_0> + %7 = apply %6(%2, %5, %3) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafePointer<τ_0_0>, Int, @thin Span<τ_0_0>.Type) -> @lifetime(borrow 0) @owned Span<τ_0_0> + return %7 : $Span +} + +sil hidden [ossa] @testGetSpanOfOne : $@convention(thin) (@in_guaranteed CollectionOfOne) -> () { +bb0(%0 : $*CollectionOfOne): + %temp = alloc_stack $CollectionOfOne + %load = load [trivial] %0 : $*CollectionOfOne + %borrow = store_borrow %load to %temp : $*CollectionOfOne + + %fget = function_ref @getSpanOfOne : $@convention(thin) (@in_guaranteed CollectionOfOne) -> @lifetime(borrow address_for_deps 0) @owned Span + %span = apply %fget(%borrow) : $@convention(thin) <τ_0_0> (@in_guaranteed CollectionOfOne<τ_0_0>) -> @lifetime(borrow address_for_deps 0) @owned Span<τ_0_0> + + destroy_value %span : $Span + end_borrow %borrow : $*CollectionOfOne + dealloc_stack %temp : $*CollectionOfOne + + %99 = tuple () + return %99 : $() +}