From 8618a39ac565cff5af90550cfe08cf3d5fd4172f Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 16 Oct 2025 12:35:09 +0200 Subject: [PATCH 1/5] TempRValueElimination: support consuming indirect apply operands --- .../TempRValueElimination.swift | 19 ++++++++++- .../Cxx/reference/rvalue-reference.swift | 2 +- test/SILOptimizer/temp_rvalue_opt_ossa.sil | 32 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift index 491d048ba972d..9c6ba906c7e77 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift @@ -177,6 +177,10 @@ private func hoistDestroy(of allocStack: AllocStackInst, after lastUse: Instruct case let li as LoadInst where li.loadOwnership == .take: assert(li.address == allocStack, "load must be not take a projected address") return + case let apply as ApplyInst where apply.consumes(address: allocStack): + if allocStack.uses.contains(where: { $0.instruction == apply && apply.convention(of: $0)!.isGuaranteed }) { + return + } default: break } @@ -266,6 +270,14 @@ private extension AllocStackInst { } } +private extension ApplySite { + func consumes(address: Value) -> Bool { + argumentOperands.contains { argOp in + argOp.value == address && convention(of: argOp)!.isConsumed + } + } +} + /// Tries to move an `end_access` down to extend the access scope over all uses of the `alloc_stack`. /// For example: /// ``` @@ -537,7 +549,12 @@ private struct UseCollector : AddressDefUseWalker { } private mutating func visitApply(address: Operand, apply: ApplySite) -> WalkResult { - if !apply.convention(of: address)!.isGuaranteed { + let argConvention = apply.convention(of: address)! + guard argConvention.isGuaranteed || + // Only accept consuming-in arguments if it consumes the whole `alloc_stack`. A consume from + // a projection would destroy only a part of the `alloc_stack` and we don't handle this. + (argConvention == .indirectIn && (copy.isTakeOfSource && address.value == copy.destinationAddress)) + else { return .abortWalk } uses.insert(apply) diff --git a/test/Interop/Cxx/reference/rvalue-reference.swift b/test/Interop/Cxx/reference/rvalue-reference.swift index e62c320fc8b28..b10c70e3be724 100644 --- a/test/Interop/Cxx/reference/rvalue-reference.swift +++ b/test/Interop/Cxx/reference/rvalue-reference.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -enable-experimental-cxx-interop -Onone) | %FileCheck -check-prefix=CHECK-DASH-ONONE %s +// RUN: %target-run-simple-swift(-I %S/Inputs/ -Xllvm -sil-disable-pass=mandatory-temp-rvalue-elimination -Xfrontend -enable-experimental-cxx-interop -Onone) | %FileCheck -check-prefix=CHECK-DASH-ONONE %s // RUN: %target-run-simple-swift(-I %S/Inputs/ -Xfrontend -enable-experimental-cxx-interop -O) | %FileCheck -check-prefix=CHECK-DASH-O %s // // REQUIRES: executable_test diff --git a/test/SILOptimizer/temp_rvalue_opt_ossa.sil b/test/SILOptimizer/temp_rvalue_opt_ossa.sil index a6a42fc2903ff..6dec1b1deb34b 100644 --- a/test/SILOptimizer/temp_rvalue_opt_ossa.sil +++ b/test/SILOptimizer/temp_rvalue_opt_ossa.sil @@ -65,6 +65,8 @@ sil [ossa] @guaranteed_user_with_result : $@convention(thin) (@guaranteed Klass) sil [ossa] @inguaranteed_user_without_result_NTS : $@convention(thin) (@in_guaranteed NonTrivialStruct) -> () sil [ossa] @inguaranteed_user_without_result_MOS : $@convention(thin) (@in_guaranteed MOS) -> () +sil [ossa] @consuming_indirect_arg : $@convention(thin) (@in Klass) -> () + sil [ossa] @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> () { bb0(%0 : $*Klass): %9999 = tuple() @@ -1643,3 +1645,33 @@ bb0(%0 : @owned $Klass): return %4 } +// CHECK-LABEL: sil [ossa] @take_and_consuming_arg : +// CHECK-NOT: alloc_stack +// CHECK: apply {{%[0-9]+}}(%0) +// CHECK-LABEL: } // end sil function 'take_and_consuming_arg' +sil [ossa] @take_and_consuming_arg : $@convention(thin) (@in Klass) -> () { +bb0(%0 : $*Klass): + %1 = alloc_stack $Klass + copy_addr [take] %0 to [init] %1 + %3 = function_ref @consuming_indirect_arg : $@convention(thin) (@in Klass) -> () + %4 = apply %3(%1) : $@convention(thin) (@in Klass) -> () + dealloc_stack %1 : $*Klass + %9 = tuple () + return %9 : $() +} + +// CHECK-LABEL: sil [ossa] @copy_and_consuming_arg : +// CHECK: alloc_stack +// CHECK-NEXT: copy_addr +// CHECK-LABEL: } // end sil function 'copy_and_consuming_arg' +sil [ossa] @copy_and_consuming_arg : $@convention(thin) (@inout Klass) -> () { +bb0(%0 : $*Klass): + %1 = alloc_stack $Klass + copy_addr %0 to [init] %1 + %3 = function_ref @consuming_indirect_arg : $@convention(thin) (@in Klass) -> () + %4 = apply %3(%1) : $@convention(thin) (@in Klass) -> () + dealloc_stack %1 : $*Klass + %9 = tuple () + return %9 : $() +} + From 62c3850d5596f5da01a73d19b23dc549cc128003 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 20 Oct 2025 12:17:12 +0200 Subject: [PATCH 2/5] TempRValueElimination: allow elimination of dead alloc_stacks in non-OSSA --- .../FunctionPasses/TempRValueElimination.swift | 6 +++++- test/IRGen/existentials_opaque_boxed.sil | 2 +- test/IRGen/outlined_copy_addr.sil | 4 ++-- test/SILOptimizer/temp_rvalue_opt.sil | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift index 9c6ba906c7e77..b9a8c94cc8be6 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift @@ -160,7 +160,11 @@ private func getRemovableAllocStackDestination( // %x = load %allocStack // looks like a load, but is a `load [take]` // strong_release %x // ``` - guard copy.parentFunction.hasOwnership || allocStack.isDestroyedOnAllPaths(context) else { + guard copy.parentFunction.hasOwnership || + allocStack.isDestroyedOnAllPaths(context) || + // We can easily remove a dead alloc_stack + allocStack.uses.ignore(user: copy).ignore(usersOfType: DeallocStackInst.self).isEmpty + else { return nil } diff --git a/test/IRGen/existentials_opaque_boxed.sil b/test/IRGen/existentials_opaque_boxed.sil index 044b906b96f0b..e8fd57c0f5c99 100644 --- a/test/IRGen/existentials_opaque_boxed.sil +++ b/test/IRGen/existentials_opaque_boxed.sil @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -disable-type-layout %s -emit-ir | %FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-%target-ptrsize -DINT=i%target-ptrsize --check-prefix=CHECK-%target-cpu +// RUN: %target-swift-frontend -Xllvm -sil-disable-pass=mandatory-temp-rvalue-elimination -disable-type-layout %s -emit-ir | %FileCheck %s -check-prefix=CHECK -check-prefix=CHECK-%target-ptrsize -DINT=i%target-ptrsize --check-prefix=CHECK-%target-cpu sil_stage canonical diff --git a/test/IRGen/outlined_copy_addr.sil b/test/IRGen/outlined_copy_addr.sil index d8e96acbd9920..989e6cde35329 100644 --- a/test/IRGen/outlined_copy_addr.sil +++ b/test/IRGen/outlined_copy_addr.sil @@ -1,6 +1,6 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend -emit-module -enable-library-evolution -emit-module-path=%t/resilient_struct.swiftmodule -module-name=resilient_struct %S/../Inputs/resilient_struct.swift -// RUN: %target-swift-frontend -emit-ir %s -I %t | %FileCheck %s +// RUN: %target-swift-frontend -emit-module -Xllvm -sil-disable-pass=mandatory-temp-rvalue-elimination -enable-library-evolution -emit-module-path=%t/resilient_struct.swiftmodule -module-name=resilient_struct %S/../Inputs/resilient_struct.swift +// RUN: %target-swift-frontend -Xllvm -sil-disable-pass=mandatory-temp-rvalue-elimination -emit-ir %s -I %t | %FileCheck %s import Swift import resilient_struct diff --git a/test/SILOptimizer/temp_rvalue_opt.sil b/test/SILOptimizer/temp_rvalue_opt.sil index c7924fa07b6b1..962d6c4790a36 100644 --- a/test/SILOptimizer/temp_rvalue_opt.sil +++ b/test/SILOptimizer/temp_rvalue_opt.sil @@ -29,6 +29,9 @@ class C { @_hasStorage let x: String } +struct NC: ~Copyable { + var i: Int +} sil @unknown : $@convention(thin) () -> () sil @load_string : $@convention(thin) (@in_guaranteed String) -> String @@ -842,3 +845,16 @@ bb0(%0 : $*Klass): return %4 } +// CHECK-LABEL: sil @dead_alloc_stack : +// CHECK: bb0(%0 : $*NC): +// CHECK-NEXT: tuple +// CHECK-LABEL: } // end sil function 'dead_alloc_stack' +sil @dead_alloc_stack : $@convention(thin) (@in NC) -> () { +bb0(%0 : $*NC): + %1 = alloc_stack $NC + copy_addr [take] %0 to [init] %1 + dealloc_stack %1 + %r = tuple () + return %r +} + From dbeda9612c771936fc507166cecc2bed87592c8e Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 20 Oct 2025 12:26:13 +0200 Subject: [PATCH 3/5] TempRValueElimination: handle `debug_value` instructions --- .../Optimizer/FunctionPasses/TempRValueElimination.swift | 3 +++ test/SILOptimizer/temp_rvalue_opt_ossa.sil | 2 ++ 2 files changed, 5 insertions(+) diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift index b9a8c94cc8be6..c776f82036b1b 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/TempRValueElimination.swift @@ -547,6 +547,9 @@ private struct UseCollector : AddressDefUseWalker { uses.insert(copyFromStack) return .continueWalk + case is DebugValueInst: + return .continueWalk + default: return .abortWalk } diff --git a/test/SILOptimizer/temp_rvalue_opt_ossa.sil b/test/SILOptimizer/temp_rvalue_opt_ossa.sil index 6dec1b1deb34b..ee2b71e3a08b2 100644 --- a/test/SILOptimizer/temp_rvalue_opt_ossa.sil +++ b/test/SILOptimizer/temp_rvalue_opt_ossa.sil @@ -96,6 +96,7 @@ sil_global @globalString : $String // CHECK: [[V1:%.*]] = load [trivial] [[A1]] : $*Builtin.Int64 // CHECK-NOT: alloc_stack // CHECK-NOT: copy_addr +// CHECK: debug_value %1 // CHECK: [[A2:%.*]] = struct_element_addr %1 : $*GS, #GS._value // CHECK: [[V2:%.*]] = load [trivial] [[A2]] : $*Builtin.Int64 // CHECK: %{{.*}} = builtin "cmp_slt_Int64"([[V1]] : $Builtin.Int64, [[V2]] : $Builtin.Int64) : $Builtin.Int1 @@ -109,6 +110,7 @@ bb0(%0 : $*GS, %1 : $*GS): %3 = load [trivial] %2 : $*Builtin.Int64 %4 = alloc_stack $GS copy_addr %1 to [init] %4 : $*GS + debug_value %4, let, name "x" %6 = struct_element_addr %4 : $*GS, #GS._value %7 = load [trivial] %6 : $*Builtin.Int64 %8 = builtin "cmp_slt_Int64"(%3 : $Builtin.Int64, %7 : $Builtin.Int64) : $Builtin.Int1 From 4035c18470ff72a9d93c0ed4b4f41c4aa79bb729 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 20 Oct 2025 11:50:35 +0200 Subject: [PATCH 4/5] SIL: relax the runtime effect of `copy_addr` It only needs metadata if the copied type has an archetype --- lib/SIL/Utils/InstructionUtils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 813e7c91bddaa..9aea7ad4d2d32 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -841,7 +841,9 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) return RuntimeEffect::MetaData | RuntimeEffect::Releasing; if (!ca->isTakeOfSrc()) return RuntimeEffect::MetaData | RuntimeEffect::RefCounting; - return RuntimeEffect::MetaData; + if (ca->getSrc()->getType().hasArchetype()) + return RuntimeEffect::MetaData; + return RuntimeEffect::NoEffect; } case SILInstructionKind::TupleAddrConstructorInst: { auto *ca = cast(inst); From 7a7004927cae9d7e80e3b14bd9b143c05cf7e927 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Thu, 9 Oct 2025 06:50:44 +0200 Subject: [PATCH 5/5] Optimizer: remove the CopyForwarding pass This pass removes `copy_addr` instructions. However, it has some problems which causes compiler crashes. It's not worth fixing these bugs because 1. Most copy_addrs can be eliminated by TempRValueElimination and TempLValueElimination. 2. Once we have opaque value we don't need copy_addr elimination, anyway. rdar://162212460 --- .../swift/SILOptimizer/PassManager/Passes.def | 2 - lib/SILOptimizer/PassManager/PassPipeline.cpp | 6 - lib/SILOptimizer/Transforms/CMakeLists.txt | 1 - .../Transforms/CopyForwarding.cpp | 1318 ----------------- test/DebugInfo/copyforward.sil | 84 -- .../assemblyvision_remark/cast_remarks.swift | 8 - .../cast_remarks_objc.swift | 4 - test/SILOptimizer/copyforward.sil | 57 - test/SILOptimizer/copyforward_ossa.sil | 516 ------- .../sil_combiner_concrete_prop_all_args.sil | 2 +- 10 files changed, 1 insertion(+), 1997 deletions(-) delete mode 100644 lib/SILOptimizer/Transforms/CopyForwarding.cpp delete mode 100644 test/DebugInfo/copyforward.sil delete mode 100644 test/SILOptimizer/copyforward.sil delete mode 100644 test/SILOptimizer/copyforward_ossa.sil diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index d9e6a2678c118..a92e0ac08775e 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -254,8 +254,6 @@ LEGACY_PASS(ConstantEvaluatorTester, "test-constant-evaluator", "Test constant evaluator") LEGACY_PASS(ConstantEvaluableSubsetChecker, "test-constant-evaluable-subset", "Test Swift code snippets expected to be constant evaluable") -LEGACY_PASS(CopyForwarding, "copy-forwarding", - "Copy Forwarding to Remove Redundant Copies") LEGACY_PASS(CopyPropagation, "copy-propagation", "Copy propagation to Remove Redundant SSA Copies, pruning debug info") LEGACY_PASS(MandatoryCopyPropagation, "mandatory-copy-propagation", diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index d599808fb5e4f..b4667d5b3776a 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -459,12 +459,6 @@ void addFunctionPasses(SILPassPipelinePlan &P, P.addDestroyAddrHoisting(); } - // Propagate copies through stack locations. Should run after - // box-to-stack promotion since it is limited to propagating through - // stack locations. Should run before aggregate lowering since that - // splits up copy_addr. - P.addCopyForwarding(); - // This DCE pass is the only DCE on ownership SIL. It can cleanup OSSA related // dead code, e.g. left behind by the ObjCBridgingOptimization. P.addDCE(); diff --git a/lib/SILOptimizer/Transforms/CMakeLists.txt b/lib/SILOptimizer/Transforms/CMakeLists.txt index 01755e1eab34d..3fce18655c887 100644 --- a/lib/SILOptimizer/Transforms/CMakeLists.txt +++ b/lib/SILOptimizer/Transforms/CMakeLists.txt @@ -9,7 +9,6 @@ target_sources(swiftSILOptimizer PRIVATE COWOpts.cpp CSE.cpp ConditionForwarding.cpp - CopyForwarding.cpp CopyPropagation.cpp DeadCodeElimination.cpp DeadObjectElimination.cpp diff --git a/lib/SILOptimizer/Transforms/CopyForwarding.cpp b/lib/SILOptimizer/Transforms/CopyForwarding.cpp deleted file mode 100644 index 9cf693a5b7913..0000000000000 --- a/lib/SILOptimizer/Transforms/CopyForwarding.cpp +++ /dev/null @@ -1,1318 +0,0 @@ -//===--- CopyForwarding.cpp - Forward local copies from caller to callee --===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 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 -// -//===----------------------------------------------------------------------===// -// -// Eliminate local copies of either address-only or reference types. -// -// This opportunity frequently results from a calling convention that transfers -// object ownership from caller to callee. In this convention, the caller -// creates a local copy before passing it to the callee. If the original object -// is immediately destroyed after passing off the copy, then the copy was -// unnecessary. Removing the useless copy can be thought of as forwarding the -// original object directly to the call argument in place of the copy. Hence -// "copy forwarding". -// -// There are two classifications of types that copy forwarding applies to: -// address-only types and references. -// -// Useless copies of address-only types look like this: -// -// %copy = alloc_stack $T -// copy_addr %arg to [init] %copy : $*T -// %ret = apply %callee(%copy) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () -// dealloc_stack %copy : $*T -// destroy_addr %arg : $*T -// -// Eliminating the address-only copies eliminates a very expensive call to -// getGenericMetadata. -// -// Useless copies of references look like this: -// -// strong_retain %arg : $A -// %ret = apply %callee(%arg) : $@convention(thin) (@owned A) -> () -// strong_release %arg : $A -// -// Eliminating the reference copies, avoids artificially bumping the refcount -// which could save a copy of all elements in a COW container. -// -// The actual analysis and optimization do not depend on the copy being linked -// to call arguments. Any obviously useless copy will be eliminated. -// -// TODO: Currently we only handle the address-only case, not the retain/release -// case. -// -// TODO: We should run this at -Onone even though it's not diagnostic. -// -// TODO: Currently we only handle cases in which one side of the copy is block -// local. Either: -// (1) Forward propagate: copy src -> dest; deinit(dest) -// (2) Backward propagate: init(src); copy src -> dest -//===----------------------------------------------------------------------===// - -#define DEBUG_TYPE "copy-forwarding" -#include "swift/Basic/Assertions.h" -#include "swift/SIL/DebugUtils.h" -#include "swift/SIL/SILArgument.h" -#include "swift/SIL/SILBuilder.h" -#include "swift/SIL/SILVisitor.h" -#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" -#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" -#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" -#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h" -#include "swift/SILOptimizer/PassManager/Passes.h" -#include "swift/SILOptimizer/PassManager/Transforms.h" -#include "swift/SILOptimizer/Utils/CFGOptUtils.h" -#include "swift/SILOptimizer/Utils/DebugOptUtils.h" -#include "swift/SILOptimizer/Utils/ValueLifetime.h" -#include "llvm/ADT/SetVector.h" -#include "llvm/ADT/Statistic.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Debug.h" - -STATISTIC(NumCopyForward, "Number of copies removed via forward propagation"); -STATISTIC(NumCopyBackward, - "Number of copies removed via backward propagation"); -STATISTIC(NumDeadTemp, "Number of copies removed from unused temporaries"); - -using namespace swift; - -// Temporary debugging flag until this pass is better tested. -static llvm::cl::opt EnableCopyForwarding("enable-copyforwarding", - llvm::cl::init(true)); - -/// \return true if the given copy source value can only be accessed via the -/// given def (this def uniquely identifies the object). -/// -/// (1) An "in" argument. -/// (inouts are also nonaliased, but won't be destroyed in scope) -/// -/// (2) A local alloc_stack variable. -static bool isIdentifiedSourceValue(SILValue Def) { - if (auto *Arg = dyn_cast(Def)) { - // Check that the argument is passed as an in type. This means there are - // no aliases accessible within this function scope. - SILArgumentConvention Conv = Arg->getArgumentConvention(); - switch (Conv) { - case SILArgumentConvention::Indirect_In: - case SILArgumentConvention::Indirect_In_Guaranteed: - return true; - default: - LLVM_DEBUG(llvm::dbgs() << " Skipping Def: Not an @in argument!\n"); - return false; - } - } - - if (isa(Def)) - return true; - - return false; -} - -/// \return true if the given copy dest value can only be accessed via the given -/// def (this def uniquely identifies the object). -/// -/// (1) An "out" or inout argument. -/// -/// (2) A local alloc_stack variable. -static bool isIdentifiedDestValue(SILValue Def) { - if (auto *Arg = dyn_cast(Def)) { - // Check that the argument is passed as an out type. This means there are - // no aliases accessible within this function scope. - SILArgumentConvention Conv = Arg->getArgumentConvention(); - switch (Conv) { - case SILArgumentConvention::Indirect_Inout: - case SILArgumentConvention::Indirect_Out: - return true; - default: - LLVM_DEBUG(llvm::dbgs() << " Skipping Def: Not an @in argument!\n"); - return false; - } - } - - if (isa(Def)) - return true; - - return false; -} - -/// Return the parameter convention used by Apply to pass an argument -/// indirectly via Address. -/// -/// Set Oper to the Apply operand that passes Address. -static SILArgumentConvention getAddressArgConvention(ApplyInst *Apply, - SILValue Address, - Operand *&Oper) { - Oper = nullptr; - auto Args = Apply->getArgumentOperands(); - for (auto ArgIdx : indices(Args)) { - if (Args[ArgIdx].get() != Address) - continue; - - assert(!Oper && "Address can only be passed once as an indirection."); - Oper = &Args[ArgIdx]; -#ifdef NDEBUG - break; -#endif - } - assert(Oper && "Address value not passed as an argument to this call."); - return ApplySite(Apply).getArgumentConvention(*Oper); -} - -/// If the given instruction is a store, return the stored value. -static SILValue getStoredValue(SILInstruction *I) { - switch (I->getKind()) { -#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ - case SILInstructionKind::Store##Name##Inst: -#include "swift/AST/ReferenceStorage.def" - case SILInstructionKind::StoreInst: - case SILInstructionKind::StoreBorrowInst: - return I->getOperand(0); - default: - return SILValue(); - } -} - -//===----------------------------------------------------------------------===// -// Forward and backward copy propagation -//===----------------------------------------------------------------------===// - -// Visitor for visitAddressUsers. -namespace { -class AddressUserVisitor { -public: - virtual ~AddressUserVisitor() {} - - virtual bool visitNormalUse(SILInstruction *user) = 0; - virtual bool visitTake(CopyAddrInst *copy) = 0; - virtual bool visitDestroy(DestroyAddrInst *destroy) = 0; - virtual bool visitDebugValue(DebugValueInst *debugValue) = 0; -}; -} // namespace - -/// Gather all instructions that use the given `address` -/// -/// "Normal" uses are a allowlisted set of uses that guarantees the address is -/// only used as if it refers to a single value and all uses are accounted for -/// (no address projections). -/// -/// Takes are "copy_addr [take]" -/// -/// Destroys are "destroy_addr" -/// - -/// -/// If we are unable to find all uses, for example, because we don't look -/// through struct_element_addr, then return false. -/// -/// The collected use points will be consulted during forward and backward -/// copy propagation. -/// -/// \param ignoredUser will be ignored if it is is non-null. -static bool visitAddressUsers(SILValue address, SILInstruction *ignoredUser, - AddressUserVisitor &visitor) { - for (Operand *use : address->getUses()) { - SILInstruction *UserInst = use->getUser(); - if (UserInst == ignoredUser) - continue; - - if (auto *Apply = dyn_cast(UserInst)) { - /// A call to materializeForSet exposes an address within the parent - /// object. However, we can rely on a subsequent mark_dependent - /// instruction to take that object as an operand, causing it to escape - /// for the purpose of this analysis. - assert(Apply->getSubstCalleeConv() - .getSILArgumentConvention(use->getOperandNumber() - - Apply->getArgumentOperandNumber()) - .isIndirectConvention() - && "copy_addr location should be passed indirect"); - if (!visitor.visitNormalUse(UserInst)) - return false; - - continue; - } - if (auto *CopyInst = dyn_cast(UserInst)) { - if (CopyInst->getSrc() == use->get() && CopyInst->isTakeOfSrc()) { - if (!visitor.visitTake(CopyInst)) - return false; - } else { - if (!visitor.visitNormalUse(CopyInst)) - return false; - } - continue; - } - if (auto *Destroy = dyn_cast(UserInst)) { - if (!visitor.visitDestroy(Destroy)) - return false; - - continue; - } - switch (UserInst->getKind()) { - case SILInstructionKind::LoadInst: - if (!visitor.visitNormalUse(UserInst)) - return false; - - break; - case SILInstructionKind::ExistentialMetatypeInst: - case SILInstructionKind::InjectEnumAddrInst: - case SILInstructionKind::StoreInst: - if (!visitor.visitNormalUse(UserInst)) - return false; - break; - case SILInstructionKind::DebugValueInst: - if (auto *DV = DebugValueInst::hasAddrVal(UserInst)) { - if (!visitor.visitDebugValue(DV)) - return false; - } else { - LLVM_DEBUG(llvm::dbgs() << " Skipping copy: use exposes def" - << *UserInst); - return false; - } - break; - case SILInstructionKind::DeallocStackInst: - break; - default: - // Most likely one of: - // init_enum_data_addr - // open_existential_addr - // partial_apply - // struct_element_addr - // unchecked_take_enum_data_addr - // - // TODO: Peek through struct element users like COWArrayOpts. - // - // TODO: Attempt to analyze partial applies or run closure propagation - // first. - // - // TODO: assert that this list is consistent with - // isTransitiveEscapeInst(). - LLVM_DEBUG(llvm::dbgs() << " Skipping copy: use exposes def" - << *UserInst); - return false; - } - } - return true; -} - -namespace { -/// Analyze an instruction that operates on the Address of a forward propagated -/// value. -/// -/// Set Oper to the operand that may be safely replaced by an address -/// pointing to an equivalent value. If UserInst cannot be analyzed, Oper is set -/// to nullptr. -/// -/// Return true if the instruction destroys the value at Address. -/// -/// This checks for the following cases of deinit: -/// - 'in' argument -/// - copy_addr [take] src -/// - copy_addr [!init] dest -/// - destroy_addr -/// - unchecked_take_enum_data_addr -/// -/// The copy_addr [!init] case is special because the operand cannot simply be -/// replaced with a new address without causing that location to be -/// reinitialized (after being deinitialized). The caller must check for and -/// handle this case. -/// -/// This returns false and sets Oper to a valid operand if the instruction is a -/// projection of the value at the given address. The assumption is that we -/// cannot deinitialize memory via projections. -/// -/// This returns true with Oper == nullptr for trivial stores (without a proper -/// deinit). -class AnalyzeForwardUse - : public SILInstructionVisitor { -public: - SILValue Address; - Operand *Oper; - - AnalyzeForwardUse(SILValue Address): Address(Address), Oper(nullptr) {} - - bool visitApplyInst(ApplyInst *Apply) { - switch (getAddressArgConvention(Apply, Address, Oper)) { - case SILArgumentConvention::Indirect_In: - return true; - case SILArgumentConvention::Indirect_In_Guaranteed: - case SILArgumentConvention::Indirect_In_CXX: - case SILArgumentConvention::Indirect_Inout: - case SILArgumentConvention::Indirect_InoutAliasable: - return false; - default: - llvm_unreachable("unexpected calling convention for copy_addr user"); - } - } - bool visitCopyAddrInst(CopyAddrInst *CopyInst) { - if (CopyInst->getSrc() == Address) { - Oper = &CopyInst->getAllOperands()[CopyAddrInst::Src]; - return CopyInst->isTakeOfSrc(); - } - assert(!CopyInst->isInitializationOfDest() && "illegal reinitialization"); - Oper = &CopyInst->getAllOperands()[CopyAddrInst::Dest]; - return true; - } - bool visitStoreInst(StoreInst *Store) { - // Trivial values may be stored prior to the next deinit. A store is an - // implicit "deinit" with no operand to replace. - assert(Store->getOperand(0)->getType().isTrivial(*Store->getFunction())); - return true; - } - bool visitDestroyAddrInst(DestroyAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return true; - } - bool visitUncheckedTakeEnumDataAddrInst( - UncheckedTakeEnumDataAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return true; - } - bool visitExistentialMetatypeInst(ExistentialMetatypeInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitLoadInst(LoadInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitOpenExistentialAddrInst(OpenExistentialAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitStructElementAddrInst(StructElementAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitDebugValueInst(DebugValueInst *UserInst) { - if (UserInst->hasAddrVal()) { - Oper = &UserInst->getOperandRef(); - return false; - } - return true; - } - bool visitInitEnumDataAddrInst(InitEnumDataAddrInst *UserInst) { - llvm_unreachable("illegal reinitialization"); - } - bool visitInjectEnumAddrInst(InjectEnumAddrInst *UserInst) { - llvm_unreachable("illegal reinitialization"); - } - bool visitSILInstruction(SILInstruction *UserInst) { - return false; - } -}; - -/// Analyze an instruction that operates on the Address of a backward propagated -/// value. -/// -/// Set Oper to the operand that my be safely replaced by an address -/// pointing to an equivalent value. If UserInst cannot be analyzed, Oper is set -/// to nullptr. -/// -/// Return true if the instruction initializes the value at Address. -/// -/// We currently check for the following cases of init: -/// - 'out' argument -/// - copy_addr [init] dest -/// - copy_addr [!init] dest -/// - store -/// -/// The copy_addr [!init] case is special because the operand cannot simply be -/// replaced with a new address without causing that location to be -/// deinitialized (before being initialized). The caller must check for and -/// handle this case. -/// -/// This returns false and sets Oper to nullptr for projections of the value at -/// the given address. For example, init_enum_data_addr and struct_element_addr -/// may be part of a decoupled initialization sequence. -class AnalyzeBackwardUse - : public SILInstructionVisitor { -public: - SILValue Address; - Operand *Oper; - - AnalyzeBackwardUse(SILValue Address): Address(Address), Oper(nullptr) {} - - bool visitApplyInst(ApplyInst *Apply) { - switch (getAddressArgConvention(Apply, Address, Oper)) { - case SILArgumentConvention::Indirect_Out: - return true; - case SILArgumentConvention::Indirect_Inout: - case SILArgumentConvention::Indirect_InoutAliasable: - case SILArgumentConvention::Indirect_In_Guaranteed: - return false; - case SILArgumentConvention::Indirect_In_CXX: - case SILArgumentConvention::Indirect_In: - llvm_unreachable("copy_addr src destroyed without reinitialization"); - default: - llvm_unreachable("unexpected calling convention for copy_addr user"); - } - } - bool visitCopyAddrInst(CopyAddrInst *CopyInst) { - if (CopyInst->getDest() == Address) { - Oper = &CopyInst->getAllOperands()[CopyAddrInst::Dest]; - return true; - } - Oper = &CopyInst->getAllOperands()[CopyAddrInst::Src]; - assert(!CopyInst->isTakeOfSrc() && "illegal deinitialization"); - return false; - } - bool visitStoreInst(StoreInst *Store) { - Oper = &Store->getAllOperands()[StoreInst::Dest]; - assert(Oper->get() == Address && "illegal store of an address"); - return true; - } - bool visitExistentialMetatypeInst(ExistentialMetatypeInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitInjectEnumAddrInst(InjectEnumAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitLoadInst(LoadInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitOpenExistentialAddrInst(OpenExistentialAddrInst *UserInst) { - Oper = &UserInst->getOperandRef(); - return false; - } - bool visitDestroyAddrInst(DestroyAddrInst *UserInst) { - llvm_unreachable("illegal deinitialization"); - } - bool visitUncheckedTakeEnumDataAddrInst( - UncheckedTakeEnumDataAddrInst *UserInst) { - llvm_unreachable("illegal deinitialization"); - } - bool visitUncheckedRefCastAddrInst( - UncheckedRefCastAddrInst *UserInst) { - if (UserInst->getDest() == Address) { - Oper = &UserInst->getAllOperands()[UncheckedRefCastAddrInst::Dest]; - } - return true; - } - bool visitDebugValueInst(DebugValueInst *UserInst) { - if (UserInst->hasAddrVal()) { - Oper = &UserInst->getOperandRef(); - return false; - } - return true; - } - bool visitSILInstruction(SILInstruction *UserInst) { - return false; - } -}; - -class CopyForwarding { - // Per-function state. - PostOrderAnalysis *PostOrder; - DominanceAnalysis *DomAnalysis; - RCIdentityAnalysis *RCIAnalysis; - bool HasChanged; - bool HasChangedCFG; - - // --- Per copied-def state --- - - // Transient state for the current Def valid during forwardCopiesOf. - SILValue CurrentDef; - - // Is the addressed defined by CurrentDef ever loaded from? - // This indicates that lifetime of any transitively referenced objects lives - // beyond the value's immediate uses. - bool IsSrcLoadedFrom; - - // Does the address defined by CurrentDef have unrecognized uses of a - // nontrivial value stored at its address? - bool HasUnknownStoredValue; - - bool HasForwardedToCopy; - SmallPtrSet SrcUserInsts; - SmallPtrSet SrcDebugValueInsts; - SmallVector TakePoints; - SmallVector DestroyPoints; - SmallPtrSet DeadInBlocks; - - // --- Per copy_addr state --- - CopyAddrInst *CurrentCopy = nullptr; - - class CopySrcUserVisitor : public AddressUserVisitor { - CopyForwarding &CPF; - public: - CopySrcUserVisitor(CopyForwarding &CPF) : CPF(CPF) {} - - virtual bool visitNormalUse(SILInstruction *user) override { - if (isa(user)) - CPF.IsSrcLoadedFrom = true; - - if (SILValue storedValue = getStoredValue(user)) { - if (!CPF.markStoredValueUsers(storedValue)) - CPF.HasUnknownStoredValue = true; - } - - // Bail on multiple uses in the same instruction to avoid complexity. - return CPF.SrcUserInsts.insert(user).second; - } - virtual bool visitTake(CopyAddrInst *take) override { - if (take->getSrc() == take->getDest()) - return false; - - CPF.TakePoints.push_back(take); - return true; - } - virtual bool visitDestroy(DestroyAddrInst *destroy) override { - CPF.DestroyPoints.push_back(destroy); - return true; - } - virtual bool visitDebugValue(DebugValueInst *debugValue) override { - return CPF.SrcDebugValueInsts.insert(debugValue).second; - } - }; - -public: - CopyForwarding(PostOrderAnalysis *PO, DominanceAnalysis *DA, - RCIdentityAnalysis *RCIAnalysis) - : PostOrder(PO), DomAnalysis(DA), RCIAnalysis(RCIAnalysis), - HasChanged(false), HasChangedCFG(false), - IsSrcLoadedFrom(false), HasUnknownStoredValue(false), - HasForwardedToCopy(false), CurrentCopy(nullptr) {} - - void reset(SILFunction *F) { - // Don't hoist destroy_addr globally in transparent functions. Avoid cloning - // destroy_addr instructions and splitting critical edges before mandatory - // diagnostic passes. For example, PredictableMemOps can no longer remove - // some alloc_stack cases after global destroy hoisting. CopyForwarding will - // be reapplied after the transparent function is inlined at which point - // global hoisting will be done. - if (HasChangedCFG) { - // We are only invalidating the analysis that we use internally. - // We'll invalidate the analysis that are used by other passes at the end. - DomAnalysis->invalidate(F, SILAnalysis::InvalidationKind::FunctionBody); - PostOrder->invalidate(F, SILAnalysis::InvalidationKind::FunctionBody); - RCIAnalysis->invalidate(F, SILAnalysis::InvalidationKind::FunctionBody); - } - CurrentDef = SILValue(); - IsSrcLoadedFrom = false; - HasUnknownStoredValue = false; - HasForwardedToCopy = false; - SrcUserInsts.clear(); - SrcDebugValueInsts.clear(); - TakePoints.clear(); - DestroyPoints.clear(); - DeadInBlocks.clear(); - CurrentCopy = nullptr; - } - - bool hasChanged() const { return HasChanged; } - bool hasChangedCFG() const { return HasChangedCFG; } - - /// Return true if CurrentDef has been forwarded through one copy into - /// another. This means we should iterate. - bool hasForwardedToCopy() const { return HasForwardedToCopy; } - - void forwardCopiesOf(SILValue Def, SILFunction *F); - -protected: - bool propagateCopy(CopyAddrInst *CopyInst); - CopyAddrInst *findCopyIntoDeadTemp( - CopyAddrInst *destCopy, - SmallVectorImpl &debugInstsToDelete); - bool forwardDeadTempCopy(CopyAddrInst *destCopy); - bool forwardPropagateCopy(); - bool backwardPropagateCopy(); - - bool isSourceDeadAtCopy(); - - typedef llvm::SmallSetVector UserVector; - bool doesCopyDominateDestUsers(const UserVector &DirectDestUses); - - bool markStoredValueUsers(SILValue storedValue); -}; - -class CopyDestUserVisitor : public AddressUserVisitor { - SmallPtrSetImpl &DestUsers; - -public: - CopyDestUserVisitor(SmallPtrSetImpl &DestUsers) - : DestUsers(DestUsers) {} - - virtual bool visitNormalUse(SILInstruction *user) override { - // Bail on multiple uses in the same instruction to avoid complexity. - return DestUsers.insert(user).second; - } - virtual bool visitTake(CopyAddrInst *take) override { - return DestUsers.insert(take).second; - } - virtual bool visitDestroy(DestroyAddrInst *destroy) override { - return DestUsers.insert(destroy).second; - } - virtual bool visitDebugValue(DebugValueInst *debugValue) override { - return DestUsers.insert(debugValue).second; - } -}; -} // end anonymous namespace - -/// Attempt to forward, then backward propagate this copy. -/// -/// The caller has already proven that lifetime of the value being copied ends -/// at the copy. (Either it is a [take] or is immediately destroyed). -/// -/// -/// If the forwarded copy is not an [init], then insert a destroy of the copy's -/// dest. -bool CopyForwarding:: -propagateCopy(CopyAddrInst *CopyInst) { - if (!EnableCopyForwarding) - return false; - - // CopyForwarding should be split into per-def-state vs. per-copy-state, but - // this hack is good enough for a pass that's going away "soon". - struct RAIISetCurrentCopy { - CopyAddrInst *&CurrentCopy; - - RAIISetCurrentCopy(CopyAddrInst *&CurrentCopy, CopyAddrInst *CopyInst) - : CurrentCopy(CurrentCopy) { - assert(!CurrentCopy); - CurrentCopy = CopyInst; - } - ~RAIISetCurrentCopy() { - CurrentCopy = nullptr; - } - }; - RAIISetCurrentCopy setCurrentCopy(CurrentCopy, CopyInst); - - // Handle copy-of-copy without analyzing uses. - // Assumes that CurrentCopy->getSrc() is dead after CurrentCopy. - assert(CurrentCopy->isTakeOfSrc()); - if (forwardDeadTempCopy(CurrentCopy)) { - HasChanged = true; - ++NumDeadTemp; - return true; - } - - if (forwardPropagateCopy()) { - LLVM_DEBUG(llvm::dbgs() << " Forwarding Copy:" << *CurrentCopy); - if (!CurrentCopy->isInitializationOfDest()) { - // Replace the original copy with a destroy. - SILBuilderWithScope(CurrentCopy) - .createDestroyAddr(CurrentCopy->getLoc(), CurrentCopy->getDest()); - } - swift::salvageStoreDebugInfo(CurrentCopy, CurrentCopy->getSrc(), - CurrentCopy->getDest()); - CurrentCopy->eraseFromParent(); - HasChanged = true; - ++NumCopyForward; - return true; - } - // Forward propagation failed. Attempt to backward propagate. - if (CurrentCopy->isInitializationOfDest() && backwardPropagateCopy()) { - LLVM_DEBUG(llvm::dbgs() << " Reversing Copy:" << *CurrentCopy); - swift::salvageStoreDebugInfo(CurrentCopy, CurrentCopy->getDest(), - CurrentCopy->getSrc()); - CurrentCopy->eraseFromParent(); - HasChanged = true; - ++NumCopyBackward; - return true; - } - return false; -} - -/// Find a copy into an otherwise dead temporary: -/// -/// The given copy is copying out of the temporary -/// copy_addr %temp, %dest -/// -/// Precondition: The lifetime of %temp ends at `destCopy` -/// (%temp is CurrentDef). -/// -/// Find a previous copy: -/// copy_addr %src, %temp -/// -/// Such that it is safe to forward its source into the source of -/// `destCopy`. i.e. `destCopy` can be safely rewritten as: -/// copy_addr %src, %dest -/// -/// Otherwise return nullptr. No instructions are harmed in this analysis. -/// -/// This can be checked with a simple instruction walk that ends at: -/// - an intervening instruction that may write to memory -/// - a use of the temporary, %temp -/// -/// Unlike the forward and backward propagation that finds all use points, this -/// handles copies of address projections. By conservatively checking all -/// intervening instructions, it avoids the need to analyze projection paths. -CopyAddrInst *CopyForwarding::findCopyIntoDeadTemp( - CopyAddrInst *destCopy, - SmallVectorImpl &debugInstsToDelete) { - auto tmpVal = destCopy->getSrc(); - assert(tmpVal == CurrentDef); - assert(isIdentifiedSourceValue(tmpVal)); - - for (auto II = destCopy->getIterator(), IB = destCopy->getParent()->begin(); - II != IB;) { - --II; - SILInstruction *UserInst = &*II; - if (auto *srcCopy = dyn_cast(UserInst)) { - if (srcCopy->getDest() == tmpVal) - return srcCopy; - } - // 'SrcUserInsts' consists of all users of the 'temp' - if (SrcUserInsts.count(UserInst)) - return nullptr; - - // Collect all debug_value w/ address value instructions between temp to - // dest copy and src to temp copy. - // On success, these debug_value instructions should be deleted. - if (isa(UserInst)) { - // 'SrcDebugValueInsts' consists of all the debug users of 'temp' - if (SrcDebugValueInsts.count(UserInst)) - debugInstsToDelete.push_back(UserInst); - continue; - } - - if (UserInst->mayWriteToMemory()) - return nullptr; - } - return nullptr; -} - -/// Forward a copy into a dead temporary as identified by -/// `findCopyIntoDeadTemp`. -/// -/// Returns true if the copy was successfully forwarded. -/// -/// Old SIL: -/// copy_addr %src, %temp -/// copy_addr %temp, %dest -/// -/// New SIL: -/// copy_addr %src, %dest -/// -/// Precondition: `srcCopy->getDest()` == `destCopy->getSrc()` -/// Precondition: %src is unused between srcCopy and destCopy. -/// Precondition: The lifetime of %temp ends immediate after `destCopy`. -/// -/// Postcondition: -/// - `srcCopy` is erased. -/// - Any initial value in %temp is destroyed at `srcCopy` position. -/// - %temp is uninitialized following `srcCopy` and subsequent instruction -/// attempts to destroy this uninitialized value. -bool CopyForwarding:: -forwardDeadTempCopy(CopyAddrInst *destCopy) { - SmallVector debugInstsToDelete; - auto *srcCopy = findCopyIntoDeadTemp(CurrentCopy, debugInstsToDelete); - if (!srcCopy) - return false; - - LLVM_DEBUG(llvm::dbgs() << " Temp Copy:" << *srcCopy - << " to " << *destCopy); - - assert(srcCopy->getDest() == destCopy->getSrc()); - // This pattern can be trivially folded without affecting %temp destroys: - // copy_addr [...] %src, [init] %temp - // copy_addr [take] %temp, [...] %dest - - // If copy into temp is not initializing, add a destroy: - // - copy_addr %src, %temp - // + destroy %temp - if (!srcCopy->isInitializationOfDest()) { - SILBuilderWithScope(srcCopy) - .createDestroyAddr(srcCopy->getLoc(), srcCopy->getDest()); - } - - // Salvage debug values before deleting them. - swift::salvageStoreDebugInfo(srcCopy, srcCopy->getSrc(), srcCopy->getDest()); - - // Delete all dead debug_value instructions - for (auto *deadDebugUser : debugInstsToDelete) { - deadDebugUser->eraseFromParent(); - } - - // `destCopy` is a take. It's safe to simply rewrite destCopy. - destCopy->setSrc(srcCopy->getSrc()); - destCopy->setIsTakeOfSrc(srcCopy->isTakeOfSrc()); - srcCopy->eraseFromParent(); - return true; -} - -/// Check that the lifetime of %src ends at the copy and is not reinitialized -/// thereafter with a new value. -bool CopyForwarding::isSourceDeadAtCopy() { - // A single copy_addr [take] %Src. - if (TakePoints.size() == 1 && DestroyPoints.empty() && SrcUserInsts.empty()) - return true; - - if (TakePoints.empty() && DestroyPoints.size() == 1 && - SrcUserInsts.size() == 1) { - assert(*SrcUserInsts.begin() == CurrentCopy); - return true; - } - // For now just check for a single copy_addr that destroys its source. - return false; -} - -/// Check that all immediate users of the destination address of the copy are -/// dominated by the copy. There is no path around copy that could initialize -/// %dest with a different value. -bool CopyForwarding::doesCopyDominateDestUsers( - const UserVector &DirectDestUsers) { - DominanceInfo *DT = DomAnalysis->get(CurrentCopy->getFunction()); - for (auto *user : DirectDestUsers) { - // Check dominance of the parent blocks. - if (!DT->properlyDominates(CurrentCopy, user)) - return false; - } - return true; -} - -// Return true if all users were recognized. -// -// To find all SSA users of storedValue, we first find the RC root, then search -// past any instructions that may propagate the reference. -bool CopyForwarding::markStoredValueUsers(SILValue storedValue) { - auto *F = storedValue->getFunction(); - - if (storedValue->getType().isTrivial(*F)) - return true; - - // Find the RC root, peeking past things like struct_extract. - RCIdentityFunctionInfo *RCI = RCIAnalysis->get(F); - SILValue root = RCI->getRCIdentityRoot(storedValue); - - SmallVector users; - RCI->getRCUsers(root, users); - - for (SILInstruction *user : users) { - // Recognize any uses that have no results as normal uses. They cannot - // transitively propagate a reference. - if (user->getResults().empty()) { - continue; - } - // Recognize full applies as normal uses. They may transitively retain, but - // the caller cannot rely on that. - if (FullApplySite::isa(user)) { - continue; - } - // A single-valued use is nontransitive if its result is trivial. - if (auto *SVI = dyn_cast(user)) { - if (SVI->getType().isTrivial(*F)) { - continue; - } - } - // Conservatively treat everything else as potentially transitively - // retaining the stored value. - LLVM_DEBUG(llvm::dbgs() << " Cannot reduce lifetime. May retain " - << storedValue - << " at: " << *user << "\n"); - return false; - } - return true; -} - -/// Returns the associated dealloc_stack if \p ASI has a single dealloc_stack. -/// Usually this is the case, but the optimizations may generate something like: -/// %1 = alloc_stack -/// if (...) { -/// dealloc_stack %1 -/// } else { -/// dealloc_stack %1 -/// } -static DeallocStackInst *getSingleDealloc(AllocStackInst *ASI) { - return ASI->getSingleDeallocStack(); -} - -/// Perform forward copy-propagation. Find a set of uses that the given copy can -/// forward to and replace them with the copy's source. -/// -/// We must only replace uses of this copy's value. To do this, we search -/// forward in the current block from the copy that initializes the value to the -/// point of deinitialization. Typically, this will be a point at which the -/// value is passed as an 'in' argument: -/// \code -/// %copy = alloc_stack $T -/// ... -/// CurrentBlock: -/// copy_addr %arg to [init] %copy : $*T -/// ... -/// %ret = apply %callee(%copy) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () -/// \endcode -/// -/// If the last use (deinit) is a copy, replace it with a destroy+copy[init]. -/// -/// The caller has already guaranteed that the lifetime of the copy's source -/// ends at this copy. The copy is a [take]. -bool CopyForwarding::forwardPropagateCopy() { - - SILValue CopyDest = CurrentCopy->getDest(); - // Require the copy dest to be a simple alloc_stack. This ensures that all - // instructions that may read from the destination address depend on CopyDest. - if (!isa(CopyDest)) - return false; - - // Record all direct dest uses. Forward propagation doesn't care if they are - // projections or propagate the address in any way--their operand only needs - // to be substituted with the copy's source. - UserVector DirectDestUsers; - for (auto *Use : CopyDest->getUses()) { - auto *UserInst = Use->getUser(); - if (UserInst == CurrentCopy) - continue; - - if (isa(UserInst)) - continue; - - // Bail on multiple uses in the same instruction so that AnalyzeForwardUse - // does not need to deal with it. - if (!DirectDestUsers.insert(UserInst)) - return false; - } - // Looking at - // - // copy_addr %Src, [init] %Dst - // - // We can reuse %Src if it is dead after the copy and not reinitialized. To - // know that we can safely replace all uses of %Dst with source we must know - // that it is uniquely named and cannot be accessed outside of the function - // (an alloc_stack instruction qualifies for this, an inout parameter does - // not). Additionally, we must know that all accesses to %Dst further on must - // have had this copy on their path (there might be reinitialization of %Dst - // later, but there must not be a path around this copy that reads from %Dst). - if (isSourceDeadAtCopy() && doesCopyDominateDestUsers(DirectDestUsers)) { - SILValue CopySrc = CurrentCopy->getSrc(); - // Replace all uses of Dest with a use of Src. - for (SILInstruction *user : DirectDestUsers) { - for (Operand &oper : user->getAllOperands()) { - if (oper.get() != CopyDest) - continue; - - // Rewrite both read and writes of CopyDest as CopySrc. - oper.set(CopySrc); - } - if (isa(user)) - HasForwardedToCopy = true; - } - // The caller will Remove the destroy_addr of %src. - assert((DestroyPoints.empty() || - (!CurrentCopy->isTakeOfSrc() && DestroyPoints.size() == 1)) && - "Must only have one destroy"); - - // The caller will remove the copy_addr. - return true; - } - - SILInstruction *DefDealloc = nullptr; - if (auto *ASI = dyn_cast(CurrentDef)) { - DefDealloc = getSingleDealloc(ASI); - if (!DefDealloc) { - LLVM_DEBUG(llvm::dbgs() << " Skipping copy" << *CurrentCopy - << " stack address has multiple uses.\n"); - return false; - } - } - - // Scan forward recording all operands that use CopyDest until we see the - // next deinit of CopyDest. - SmallVector ValueUses; - auto SI = CurrentCopy->getIterator(), SE = CurrentCopy->getParent()->end(); - for (++SI; SI != SE; ++SI) { - SILInstruction *UserInst = &*SI; - // If we see another use of Src, then the source location is reinitialized - // before the Dest location is deinitialized. So we really need the copy. - if (SrcUserInsts.count(UserInst)) { - LLVM_DEBUG(llvm::dbgs() << " Skipping copy" << *CurrentCopy - << " source used by" << *UserInst); - return false; - } - if (UserInst == DefDealloc) { - LLVM_DEBUG(llvm::dbgs() << " Skipping copy" << *CurrentCopy - << " dealloc_stack before dest use.\n"); - return false; - } - // Early check to avoid scanning unrelated instructions. - if (!DirectDestUsers.count(UserInst)) - continue; - - AnalyzeForwardUse AnalyzeUse(CopyDest); - bool seenDeinitOrStore = AnalyzeUse.visit(UserInst); - if (AnalyzeUse.Oper) - ValueUses.push_back(AnalyzeUse.Oper); - - // If this is a deinit or store, we're done searching. - if (seenDeinitOrStore) - break; - - // If this non-deinit instruction wasn't fully analyzed, bail-out. - if (!AnalyzeUse.Oper) - return false; - } - if (SI == SE) - return false; - - // Convert a reinitialization of this address into a destroy, followed by an - // initialization. Replacing a copy with a destroy+init is not by itself - // profitable. However, it does allow eliminating the earlier copy, and we may - // later be able to eliminate this initialization copy. - if (auto Copy = dyn_cast(&*SI)) { - if (Copy->getDest() == CopyDest) { - assert(!Copy->isInitializationOfDest() && "expected a deinit"); - - DestroyAddrInst *Destroy = - SILBuilderWithScope(Copy).createDestroyAddr(Copy->getLoc(), CopyDest); - Copy->setIsInitializationOfDest(IsInitialization); - - assert(ValueUses.back()->getUser() == Copy && "bad value use"); - ValueUses.back() = &Destroy->getOperandRef(); - } - } - // Now that a deinit was found, it is safe to substitute all recorded uses - // with the copy's source. - for (auto *Oper : ValueUses) { - Oper->set(CurrentCopy->getSrc()); - if (isa(Oper->getUser())) - HasForwardedToCopy = true; - } - return true; -} - -/// Given an address defined by 'Def', find the object root and all direct uses, -/// not including: -/// - 'Def' itself -/// - Transitive uses of 'Def' (listed elsewhere in DestUserInsts) -/// -/// i.e. If Def is returned directly, RootUserInsts will be empty. -/// -/// Return nullptr when the root != Def, and root has unrecognized uses. -/// -/// If the returned root is not 'Def' itself, then 'Def' must be an address -/// projection that can be trivially rematerialized with the root as its -/// operand. -static ValueBase * -findAddressRootAndUsers(ValueBase *Def, - SmallPtrSetImpl &RootUserInsts) { - switch (Def->getKind()) { - default: - return Def; - case ValueKind::InitEnumDataAddrInst: - case ValueKind::InitExistentialAddrInst: - auto InitInst = cast(Def); - SILValue InitRoot = InitInst->getOperand(0); - - CopyDestUserVisitor visitor(RootUserInsts); - if (!visitAddressUsers(InitRoot, InitInst, visitor)) - return nullptr; - return InitRoot; - } -} - -/// Perform backward copy-propagation. Find the initialization point of the -/// copy's source and replace the initializer's address with the copy's dest. -bool CopyForwarding::backwardPropagateCopy() { - - SILValue CopySrc = CurrentCopy->getSrc(); - ValueBase *CopyDestDef = CurrentCopy->getDest(); - - SmallPtrSet DestUserInsts; - CopyDestUserVisitor visitor(DestUserInsts); - if (!visitAddressUsers(CopyDestDef, CurrentCopy, visitor)) - return false; - - // RootUserInsts will contain any users of the same object not covered by - // DestUserInsts. - SmallPtrSet RootUserInsts; - ValueBase *CopyDestRoot = findAddressRootAndUsers(CopyDestDef, RootUserInsts); - if (!CopyDestRoot) - return false; - - // Require the copy dest value to be identified by this address. This ensures - // that all instructions that may write to destination address depend on - // CopyDestRoot. - if (!isIdentifiedDestValue(CopyDestRoot)) - return false; - - // Scan backward recording all operands that use CopySrc until we see the - // most recent init of CopySrc. - bool seenInit = false; - bool seenCopyDestDef = false; - // ValueUses records the uses of CopySrc in reverse order. - SmallVector ValueUses; - SmallVector DebugValueInstsToDelete; - auto SI = CurrentCopy->getIterator(), SE = CurrentCopy->getParent()->begin(); - while (SI != SE) { - --SI; - SILInstruction *UserInst = &*SI; - if (UserInst == CopyDestDef->getDefiningInstruction()) - seenCopyDestDef = true; - - // If we see another use of Dest, then Dest is live after the Src location - // is initialized, so we really need the copy. - if (UserInst == CopyDestRoot->getDefiningInstruction() - || DestUserInsts.count(UserInst) - || RootUserInsts.count(UserInst)) { - if (DebugValueInst::hasAddrVal(UserInst)) { - DebugValueInstsToDelete.push_back(UserInst); - continue; - } - LLVM_DEBUG(llvm::dbgs() << " Skipping copy" << *CurrentCopy - << " dest used by " << *UserInst); - return false; - } - // Early check to avoid scanning unrelated instructions. - if (!SrcUserInsts.count(UserInst) - && !(isa(UserInst) - && SrcDebugValueInsts.count(UserInst))) - continue; - - AnalyzeBackwardUse AnalyzeUse(CopySrc); - seenInit = AnalyzeUse.visit(UserInst); - // If this use cannot be analyzed, then abort. - if (!AnalyzeUse.Oper) - return false; - // Otherwise record the operand with the earliest use last in the list. - ValueUses.push_back(AnalyzeUse.Oper); - // If this is an init, we're done searching. - if (seenInit) - break; - } - if (!seenInit) - return false; - - for (auto *DVAI : DebugValueInstsToDelete) - DVAI->eraseFromParent(); - - // Convert a reinitialization of this address into a destroy, followed by an - // initialization. Replacing a copy with a destroy+init is not by itself - // profitable. However, it does allow us to eliminate the later copy, and the - // init copy may be eliminated later. - if (auto Copy = dyn_cast(&*SI)) { - if (Copy->getDest() == CopySrc && !Copy->isInitializationOfDest()) { - SILBuilderWithScope(Copy).createDestroyAddr(Copy->getLoc(), CopySrc); - Copy->setIsInitializationOfDest(IsInitialization); - } - } - // Rematerialize the projection if needed by simply moving it. - if (seenCopyDestDef) { - CopyDestDef->getDefiningInstruction()->moveBefore(&*SI); - } - // Now that an init was found, it is safe to substitute all recorded uses - // with the copy's dest. - for (auto *Oper : ValueUses) { - if (auto *SI = dyn_cast(Oper->getUser())) { - HasForwardedToCopy = true; - // This instruction gets "replaced", so we need to salvage its previous - // debug info. - swift::salvageStoreDebugInfo(SI, SI->getSrc(), SI->getDest()); - } - Oper->set(CurrentCopy->getDest()); - } - return true; -} - -/// Perform CopyForwarding on the current Def. -void CopyForwarding::forwardCopiesOf(SILValue Def, SILFunction *F) { - reset(F); - CurrentDef = Def; - LLVM_DEBUG(llvm::dbgs() << "Analyzing copies of Def: " << Def); - CopySrcUserVisitor visitor(*this); - if (!visitAddressUsers(Def, nullptr, visitor)) - return; - - // Forward any copies that implicitly destroy CurrentDef. - for (auto *CopyInst : TakePoints) { - propagateCopy(CopyInst); - } -} - -//===----------------------------------------------------------------------===// -// CopyForwardingPass -//===----------------------------------------------------------------------===// - -namespace { -#ifndef NDEBUG -static llvm::cl::opt ForwardStart("copy-forward-start", - llvm::cl::init(0), llvm::cl::Hidden); -static llvm::cl::opt ForwardStop("copy-forward-stop", - llvm::cl::init(-1), llvm::cl::Hidden); -#endif - -class CopyForwardingPass : public SILFunctionTransform -{ - void run() override { - if (!EnableCopyForwarding) - return; - - // This pass assumes that the ownership lifetime of a value in a memory - // locations can be determined by analyzing operations on the memory - // address. However, in non-OSSA code, this is not guaranteed. For example, - // this is valid non-OSSA SIL: - // - // bb0(%0 : $AnyObject): - // %alloc1 = alloc_stack $AnyObject - // store %0 to %objaddr : $*AnyObject - // %ref = load %objaddr : $*AnyObject - // %alloc2 = alloc_stack $ObjWrapper - // # The in-memory reference is destroyed before retaining the loaded ref. - // copy_addr [take] %alloc1 to [init] %alloc2 : $*ObjWrapper - // retain_value %ref : $AnyObject - // destroy_addr %alloc2 : $*ObjWrapper - if (!getFunction()->hasOwnership()) - return; - - LLVM_DEBUG(llvm::dbgs() << "Copy Forwarding in Func " - << getFunction()->getName() << "\n"); - - // Collect a set of identified objects (@in arg or alloc_stack) that are - // copied in this function. - llvm::SmallSetVector CopiedDefs; - for (auto &BB : *getFunction()) - for (auto II = BB.begin(), IE = BB.end(); II != IE; ++II) { - if (auto *CopyInst = dyn_cast(&*II)) { - SILValue Def = CopyInst->getSrc(); - if (isIdentifiedSourceValue(Def)) - CopiedDefs.insert(Def); - else { - LLVM_DEBUG(llvm::dbgs() << " Skipping Def: " << Def - << " not an argument or local var!\n"); - } - } - } - - // Perform Copy Forwarding. - if (CopiedDefs.empty()) - return; - - auto *PO = getAnalysis(); - auto *DA = getAnalysis(); - auto *RCIA = getAnalysis(); - auto Forwarding = CopyForwarding(PO, DA, RCIA); - - for (SILValue Def : CopiedDefs) { -#ifndef NDEBUG - static unsigned NumDefs = 0; - ++NumDefs; - if ((int)NumDefs < ForwardStart || NumDefs >= (unsigned)ForwardStop) - continue; -#endif - // Iterate to forward through chains of copies. - do { - Forwarding.forwardCopiesOf(Def, getFunction()); - } while (Forwarding.hasForwardedToCopy()); - } - if (Forwarding.hasChangedCFG()) { - // We've split critical edges so we can't preserve CFG. - invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); - } else { - invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions); - } - } -}; - -} // end anonymous namespace - -SILTransform *swift::createCopyForwarding() { - return new CopyForwardingPass(); -} diff --git a/test/DebugInfo/copyforward.sil b/test/DebugInfo/copyforward.sil deleted file mode 100644 index 438dc0bd0f710..0000000000000 --- a/test/DebugInfo/copyforward.sil +++ /dev/null @@ -1,84 +0,0 @@ -// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all -sil-print-debuginfo -copy-forwarding %s | %FileCheck %s -sil_stage canonical - -import Builtin -import Swift - -sil_scope 1 { loc "backward.swift":66:24 parent @backward : $@convention(method) <τ_0_0> (@inout CollectionOfOne<τ_0_0>.Iterator) -> @out Optional<τ_0_0> } -sil_scope 2 { loc "backward.swift":67:9 parent 1 } - -// CHECK-LABEL: sil {{.+}} @backward -sil [serialized] [ossa] @backward : $@convention(method) (@inout CollectionOfOne.Iterator) -> @out Optional { -[%0: write v**] -[%1: read s0.v**, write s0.v**, copy s0.v**, destroy s0.v**] -[global: read,write,copy,destroy,allocate,deinit_barrier] -bb0(%0 : $*Optional, %1 : $*CollectionOfOne.Iterator): - debug_value %1 : $*CollectionOfOne.Iterator, var, name "self", argno 1, expr op_deref, loc "backward.swift":66:24, scope 1 - %3 = alloc_stack [lexical] [var_decl] $Optional, let, name "result", loc "backward.swift":67:9, scope 2 - %4 = struct_element_addr %1 : $*CollectionOfOne.Iterator, #CollectionOfOne.Iterator._elements, loc "backward.swift":67:18, scope 1 - - // CHECK: debug_value %{{[0-9]+}} : $*Optional, let, (name "result", loc "backward.swift":67:9), expr op_deref, loc "backward.swift":67:18, scope 2 - - copy_addr %4 to [init] %3 : $*Optional, loc "backward.swift":67:18, scope 1 - %6 = alloc_stack $Optional, loc "backward.swift":68:17, scope 2 - inject_enum_addr %6 : $*Optional, #Optional.none!enumelt, loc "backward.swift":68:17, scope 2 - %8 = struct_element_addr %1 : $*CollectionOfOne.Iterator, #CollectionOfOne.Iterator._elements, loc "backward.swift":68:15, scope 2 - copy_addr [take] %6 to %8 : $*Optional, loc "backward.swift":68:15, scope 2 - dealloc_stack %6 : $*Optional, loc "backward.swift":68:17, scope 2 - copy_addr [take] %3 to [init] %0 : $*Optional, loc "backward.swift":69:12, scope 2 - dealloc_stack %3 : $*Optional, loc "backward.swift":70:3, scope 2 - %13 = tuple (), loc "backward.swift":70:3, scope 2 - return %13 : $(), loc "backward.swift":69:5, scope 2 -} // end sil function 'backward' - -sil_scope 10 { loc "tempcopy.swift":714:27 parent @tempcopy : $@convention(method) <τ_0_0 where τ_0_0 : Strideable, τ_0_0.Stride : SignedInteger> (@in PartialRangeFrom<τ_0_0>) -> @out PartialRangeFrom<τ_0_0>.Iterator } -sil_scope 12 { loc "tempcopy.swift":715:12 parent 10 } - -// CHECK-LABEL: sil {{.+}} @tempcopy -sil [serialized] [ossa] @tempcopy : $@convention(method) (@in PartialRangeFrom) -> @out PartialRangeFrom.Iterator { -[%0: write v**] -[%1: read v**, write v**, copy s0.v**, destroy v**] -[global: read,write,copy,destroy,allocate,deinit_barrier] -bb0(%0 : $*PartialRangeFrom.Iterator, %1 : $*PartialRangeFrom): - debug_value %1 : $*PartialRangeFrom, let, name "self", argno 1, expr op_deref, loc "tempcopy.swift":714:27, scope 10 // id: %2 - %3 = struct_element_addr %1 : $*PartialRangeFrom, #PartialRangeFrom.lowerBound, loc "tempcopy.swift":715:31, scope 10 // user: %5 - %4 = alloc_stack $Bound, loc "tempcopy.swift":715:31, scope 10 // users: %8, %6, %10, %5 - copy_addr [take] %3 to [init] %4 : $*Bound, loc "tempcopy.swift":715:31, scope 10 // id: %5 - // CHECK: debug_value %{{[0-9]+}} : $*Bound, let, (name "_current", loc "tempcopy.swift":696:17 - // CHECK-SAME: expr op_deref, loc "tempcopy.swift":715:31 - debug_value %4 : $*Bound, let, name "_current", expr op_deref, loc "tempcopy.swift":696:17, scope 12 // id: %6 - %7 = struct_element_addr %0 : $*PartialRangeFrom.Iterator, #PartialRangeFrom.Iterator._current, loc "tempcopy.swift":696:50, scope 12 // user: %8 - copy_addr [take] %4 to [init] %7 : $*Bound, loc "tempcopy.swift":696:50, scope 12 // id: %8 - %9 = tuple (), loc * "tempcopy.swift":696:61, scope 12 - dealloc_stack %4 : $*Bound, loc "tempcopy.swift":715:41, scope 10 // id: %10 - %11 = tuple (), loc "tempcopy.swift":716:3, scope 10 // user: %12 - return %11 : $(), loc "tempcopy.swift":715:5, scope 10 // id: %12 -} // end sil function 'tempcopy' - -sil @f_in : $@convention(thin) (@in T) -> () - -// CHECK-LABEL: sil hidden [ossa] @forward_takeinit : -sil hidden [ossa] @forward_takeinit : $@convention(thin) (@in T) -> () { -bb0(%0 : $*T): - // CHECK: debug_value %0 : $*T, let, (name "hello",{{.+}} expr op_deref - %l1 = alloc_stack $T, let, name "hello" - copy_addr [take] %0 to [init] %l1 : $*T - %f1 = function_ref @f_in : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - %c1 = apply %f1(%l1) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - dealloc_stack %l1 : $*T - %r1 = tuple () - return %r1 : $() -} - -// CHECK-LABEL: sil {{.+}} @forward : -sil [serialized] [ossa] @forward : $@convention(thin) (@in T, Builtin.RawPointer) -> () { -bb0(%0 : $*T, %1 : $Builtin.RawPointer): - // CHECK: debug_value %0 : $*T, var, (name "value",{{.+}} expr op_deref - %2 = alloc_stack [lexical] [var_decl] $T, var, name "value" - copy_addr [take] %0 to [init] %2 : $*T - %5 = pointer_to_address %1 : $Builtin.RawPointer to [strict] $*T - copy_addr [take] %2 to [init] %5 : $*T - dealloc_stack %2 : $*T - %8 = tuple () - return %8 : $() -} // end sil function 'forward' diff --git a/test/SILOptimizer/assemblyvision_remark/cast_remarks.swift b/test/SILOptimizer/assemblyvision_remark/cast_remarks.swift index 811e7443b66bb..2753aa41c64ce 100644 --- a/test/SILOptimizer/assemblyvision_remark/cast_remarks.swift +++ b/test/SILOptimizer/assemblyvision_remark/cast_remarks.swift @@ -39,8 +39,6 @@ public func forcedCast4(_ ns: NS, _ ns2: NS) -> T { var x = ns x = ns2 return x as! T // expected-remark @:12 {{unconditional runtime cast of value with type 'NS' to 'T'}} - // expected-note @-5:44 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast(_ ns: NS) -> T? { @@ -74,8 +72,6 @@ public func condCast4(_ ns: NS, _ ns2: NS) -> T? { var x = ns x = ns2 return x as? T // expected-remark @:12 {{conditional runtime cast of value with type 'NS' to 'T'}} - // expected-note @-5:42 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast5(_ ns: NS) -> T? { @@ -246,8 +242,6 @@ public func forcedCast4(_ ns: Existential1, _ ns2: Existential1) -> Existential2 var x = ns x = ns2 return x as! Existential2 // expected-remark @:12 {{unconditional runtime cast of value with type 'any Existential1' to 'any Existential2'}} - // expected-note @-5:47 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast(_ ns: Existential1) -> Existential2? { @@ -282,8 +276,6 @@ public func condCast4(_ ns: Existential1, _ ns2: Existential1) -> Existential2? var x = ns x = ns2 return x as? Existential2 // expected-remark @:12 {{conditional runtime cast of value with type 'any Existential1' to 'any Existential2'}} - // expected-note @-5:45 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast5(_ ns: Existential1) -> Existential2? { diff --git a/test/SILOptimizer/assemblyvision_remark/cast_remarks_objc.swift b/test/SILOptimizer/assemblyvision_remark/cast_remarks_objc.swift index f2c3ade1152ef..7d687ad6eb58c 100644 --- a/test/SILOptimizer/assemblyvision_remark/cast_remarks_objc.swift +++ b/test/SILOptimizer/assemblyvision_remark/cast_remarks_objc.swift @@ -35,8 +35,6 @@ public func forcedCast4(_ ns: NS, _ ns2: NS) -> T { var x = ns x = ns2 return x as! T // expected-remark @:12 {{unconditional runtime cast of value with type 'NS' to 'T'}} - // expected-note @-5:44 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast(_ ns: NS) -> T? { @@ -64,8 +62,6 @@ public func condCast4(_ ns: NS, _ ns2: NS) -> T? { var x = ns x = ns2 return x as? T // expected-remark @:12 {{conditional runtime cast of value with type 'NS' to 'T'}} - // expected-note @-5:42 {{of 'ns2'}} - // expected-note @-4:7 {{of 'x'}} } public func condCast5(_ ns: NS) -> T? { diff --git a/test/SILOptimizer/copyforward.sil b/test/SILOptimizer/copyforward.sil deleted file mode 100644 index 48a233b5c8505..0000000000000 --- a/test/SILOptimizer/copyforward.sil +++ /dev/null @@ -1,57 +0,0 @@ -// RUN: %target-sil-opt -sil-print-types -enforce-exclusivity=none -enable-sil-verify-all %s -copy-forwarding -enable-copyforwarding | %FileCheck %s - -// CopyForwarding currently only runs on OSSA. This file only contains -// tests that will break is CopyForwarding were to run on non-OSSA SIL. - -sil_stage canonical - -import Builtin -import Swift - -struct ObjWrapper { - var obj: AnyObject -} - -// If CopyForwarding (with destroy-hoisting) runs on non-OSSA SIL it -// may result in a use-after-free in this case. -// -// In this example, some other pass, such as retain-sinking, has -// already moved an object's retain (bb1) below the destroy of the -// memory from which the object was originally loaded (copy_addr -// [take] in bb0). This seems crazy but still follows non-OSSA SIL -// rules. -// -// If destroy hoisting reorders the retain and destroy_addr in bb1, -// then the object passed as a guaranteed argument will be incorrectly -// destroyed. -// CHECK-LABEL: sil @testDestroyHoistOverRetain : $@convention(thin) (@guaranteed AnyObject) -> () { -// CHECK: bb1: -// CHECK: retain_value %{{.*}} : $AnyObject -// CHECK: destroy_addr %{{.*}} : $*ObjWrapper -// CHECK-LABEL: } // end sil function 'testDestroyHoistOverRetain' -sil @testDestroyHoistOverRetain : $@convention(thin) (@guaranteed AnyObject) -> () { -bb0(%0 : $AnyObject): - %alloc1 = alloc_stack $ObjWrapper - %objaddr = struct_element_addr %alloc1 : $*ObjWrapper, #ObjWrapper.obj - store %0 to %objaddr : $*AnyObject - %ref = load %objaddr : $*AnyObject - %alloc2 = alloc_stack $ObjWrapper - // The location loaded from is destroyed here with no corresponding - // retain. - copy_addr [take] %alloc1 to [init] %alloc2 : $*ObjWrapper - cond_br undef, bb1, bb2 -bb1: - // This retain and destroy are not obviously related but need to - // execute in this order to cancel each other out. - retain_value %ref : $AnyObject - destroy_addr %alloc2 : $*ObjWrapper - br bb3 -bb2: - copy_addr [take] %alloc2 to [init] %alloc1 : $*ObjWrapper - br bb3 -bb3: - dealloc_stack %alloc2 : $*ObjWrapper - dealloc_stack %alloc1 : $*ObjWrapper - %99 = tuple () - return %99 : $() -} diff --git a/test/SILOptimizer/copyforward_ossa.sil b/test/SILOptimizer/copyforward_ossa.sil deleted file mode 100644 index 70e4fa746bf05..0000000000000 --- a/test/SILOptimizer/copyforward_ossa.sil +++ /dev/null @@ -1,516 +0,0 @@ -// RUN: %target-sil-opt -sil-print-types -enable-sil-verify-all %s -copy-forwarding -enable-copyforwarding -allow-critical-edges=false | %FileCheck %s - -// This is the ossa version of CopyForwarding tests -sil_stage raw - -import Builtin -import Swift - -class AClass {} - -struct NonTrivialStruct { - var cls : AClass -} - -sil @get_nontrivialstruct : $@convention(thin) () -> @out NonTrivialStruct -sil @use_aclass : $@convention(thin) (@in_guaranteed AClass) -> () - -sil @f_in : $@convention(thin) (@in T) -> () -sil @f_in_guaranteed : $@convention(thin) (@in_guaranteed T) -> () -sil @f_out : $@convention(thin) () -> @out T -sil @f_owned : $@convention(thin) (@owned T) -> () - -// CHECK-LABEL: sil hidden [ossa] @forward_takeinit : -// CHECK-NOT: copy_addr -// CHECK-NOT: destroy_addr -// CHECK-LABEL: } // end sil function 'forward_takeinit' -sil hidden [ossa] @forward_takeinit : $@convention(thin) (@in T) -> () { -bb0(%0 : $*T): - debug_value %0 : $*T, expr op_deref - %l1 = alloc_stack $T - copy_addr [take] %0 to [init] %l1 : $*T - %f1 = function_ref @f_in : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - %c1 = apply %f1(%l1) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - dealloc_stack %l1 : $*T - %r1 = tuple () - return %r1 : $() -} - -// CHECK-LABEL: sil hidden [ossa] @forward_takenoinit : -// CHECK-NOT: copy_addr -// CHECK: destroy_addr -// CHECK-LABEL: } // end sil function 'forward_takenoinit' -sil hidden [ossa] @forward_takenoinit : $@convention(thin) (@in T) -> () { -bb0(%0 : $*T): - debug_value %0 : $*T, expr op_deref - %l1 = alloc_stack $T - %f1 = function_ref @f_out : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c1 = apply %f1(%l1) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - copy_addr [take] %0 to %l1 : $*T - %f2 = function_ref @f_in : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - %c2 = apply %f2(%l1) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - dealloc_stack %l1 : $*T - %r1 = tuple () - return %r1 : $() -} - -// CHECK-LABEL: sil hidden [ossa] @backward_noinit : -// CHECK: copy_addr -// CHECK: destroy_addr -// CHECK-LABEL: } // end sil function 'backward_noinit' -sil hidden [ossa] @backward_noinit : $@convention(thin) () -> @out T { -bb0(%0 : $*T): - %l1 = alloc_stack $T - %f1 = function_ref @f_out : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c1 = apply %f1(%0) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c2 = apply %f1(%l1) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - copy_addr %l1 to %0 : $*T - destroy_addr %l1 : $*T - dealloc_stack %l1 : $*T - %t = tuple () - return %t : $() -} - - -// CHECK-LABEL: sil hidden [ossa] @backward_takeinit : -// CHECK-NOT: copy_addr -// CHECK-NOT: destroy_addr -// CHECK-LABEL: } // end sil function 'backward_takeinit' -sil hidden [ossa] @backward_takeinit : $@convention(thin) () -> @out T { -bb0(%0 : $*T): - %l1 = alloc_stack $T - %f1 = function_ref @f_out : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c1 = apply %f1(%l1) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - debug_value %l1 : $*T, expr op_deref - copy_addr [take] %l1 to [init] %0 : $*T - debug_value %0 : $*T, expr op_deref - dealloc_stack %l1 : $*T - %t = tuple () - return %t : $() -} - -// CHECK-LABEL: sil hidden [ossa] @backward_takenoinit : -// CHECK: copy_addr -// CHECK-NOT: destroy_addr -// CHECK-LABEL: } // end sil function 'backward_takenoinit' -sil hidden [ossa] @backward_takenoinit : $@convention(thin) () -> @out T { -bb0(%0 : $*T): - %l1 = alloc_stack $T - %f1 = function_ref @f_out : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c1 = apply %f1(%0) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - %c2 = apply %f1(%l1) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - copy_addr [take] %l1 to %0 : $*T - dealloc_stack %l1 : $*T - %t = tuple () - return %t : $() -} - -// CHECK-LABEL: sil hidden [ossa] @make_addronly : -// CHECK-NOT: copy_addr -// CHECK-LABEL: } // end sil function 'make_addronly' -sil hidden [ossa] @make_addronly : $@convention(thin) () -> @out T { -bb0(%0 : $*T): - %1 = alloc_stack $T, let, name "t" // users: %3, %4, %5 - %2 = function_ref @f_out : $@convention(thin) <τ_0_0> () -> @out τ_0_0 // user: %3 - %3 = apply %2(%1) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - copy_addr [take] %1 to [init] %0 : $*T // id: %4 - dealloc_stack %1 : $*T // id: %5 - %6 = tuple () // user: %7 - return %6 : $() // id: %7 -} - -sil [ossa] @_TFSq4someU__fMGSqQ__FQ_GSqQ__ : $@convention(thin) <τ_0_0> (@in τ_0_0, @thin Optional<τ_0_0>.Type) -> @out Optional<τ_0_0> - -sil [ossa] @_TFsoi2neU__FTGSqQ__Vs26_OptionalNilComparisonType_Sb : $@convention(thin) <τ_0_0> (@in Optional<τ_0_0>, _OptionalNilComparisonType) -> Bool - -// CHECK-LABEL: sil hidden [ossa] @option_init : -// CHECK: alloc_stack -// CHECK: alloc_stack -// CHECK: alloc_stack -// CHECK: copy_addr -// CHECK: copy_addr -// CHECK-NOT: copy_addr -// CHECK: alloc_stack -// CHECK: alloc_stack -// CHECK: copy_addr -// CHECK-NOT: copy_addr -// CHECK: alloc_stack -// CHECK-NOT: copy_addr -// CHECK: copy_addr -// CHECK-NOT: copy_addr -// CHECK: alloc_stack -// CHECK-NOT: copy_addr -// CHECK-LABEL: } // end sil function 'option_init' -sil hidden [ossa] @option_init : $@convention(thin) (@in AnyObject) -> () { -bb0(%0 : $*AnyObject): - %g0 = alloc_stack $IteratorOverOne // 831 - %s0 = struct_element_addr %g0 : $*IteratorOverOne, #IteratorOverOne._elements - - %l0 = alloc_stack $Optional - // function_ref Swift.Optional.some (Swift.Optional.Type)(A) -> Swift.Optional - %f0 = function_ref @_TFSq4someU__fMGSqQ__FQ_GSqQ__ : $@convention(thin) <τ_0_0> (@in τ_0_0, @thin Optional<τ_0_0>.Type) -> @out Optional<τ_0_0> - %t0 = metatype $@thin Optional.Type - %i0 = apply %f0(%l0, %0, %t0) : $@convention(thin) <τ_0_0> (@in τ_0_0, @thin Optional<τ_0_0>.Type) -> @out Optional<τ_0_0> - - %g1 = alloc_stack $IteratorOverOne // 850 - %s1 = struct_element_addr %g1 : $*IteratorOverOne, #IteratorOverOne._elements - // We can't backward propagate this yet because we can't analyze struct_element_addr copy dest. - copy_addr [take] %l0 to [init] %s1 : $*Optional - // We ignore this copy because its Def is used by struct_element_addr - copy_addr [take] %g1 to [init] %g0 : $*IteratorOverOne - - %l1 = alloc_stack $Optional // 869 - - %l2 = alloc_stack $Optional // 873 - // We ignore this copy because its Def is used by struct_element_addr - copy_addr %s0 to [init] %l2 : $*Optional - - %l3 = alloc_stack $Optional // 877 - %o1 = enum $Optional, #Optional.none!enumelt - store %o1 to [init] %l3 : $*Optional - // We can't backward propagate this yet because we can't analyze struct_element_addr copy dest. - copy_addr [take] %l3 to %s0 : $*Optional - dealloc_stack %l3 : $*Optional - // We can't forward propagate this because l2 is deallocated, but we can backward propagate l1. - copy_addr [take] %l2 to [init] %l1 : $*Optional - dealloc_stack %l2 : $*Optional - %l4 = alloc_stack $Optional // 889 - %o2 = load [copy] %l1 : $*Optional - store %o2 to [init] %l4 : $*Optional - %s5 = struct $_OptionalNilComparisonType () - - %f1 = function_ref @_TFsoi2neU__FTGSqQ__Vs26_OptionalNilComparisonType_Sb : $@convention(thin) <τ_0_0> (@in Optional<τ_0_0>, _OptionalNilComparisonType) -> Bool - %c5 = apply %f1(%l4, %s5) : $@convention(thin) <τ_0_0> (@in Optional<τ_0_0>, _OptionalNilComparisonType) -> Bool - dealloc_stack %l4 : $*Optional - destroy_addr %l1 : $*Optional - dealloc_stack %l1 : $*Optional - dealloc_stack %g1 : $*IteratorOverOne - dealloc_stack %l0 : $*Optional - destroy_addr %g0 : $*IteratorOverOne - dealloc_stack %g0 : $*IteratorOverOne - %p0 = tuple () - return %p0 : $() -} - -// Check that destroy is not hoisted above users of transitively related object -// -// CHECK-LABEL: sil hidden [ossa] @load_nontrivial : -// CHECK: load [copy] %0 : $*Optional -// CHECK-NOT: destroy_addr -// CHECK: unchecked_enum_data %{{.*}} : $Optional -// CHECK-NOT: destroy_addr -// CHECK: apply -// CHECK: destroy_addr -// CHECK-LABEL: } // end sil function 'load_nontrivial' -sil hidden [ossa] @load_nontrivial : $@convention(thin) () -> () { -bb0: - %v0 = alloc_stack $Optional - %v1 = alloc_stack $Optional - - %f0 = function_ref @f_out : $@convention(thin) () -> @out A - %c0 = apply %f0(%v0) : $@convention(thin) <τ_0_0> () -> @out τ_0_0 - - copy_addr %v0 to [init] %v1 : $*Optional - - %f1 = function_ref @f_in : $@convention(thin) (@in A) -> () - %c1 = apply %f1(%v1) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () - - dealloc_stack %v1 : $*Optional - %l1 = load [copy] %v0 : $*Optional - %d1 = unchecked_enum_data %l1 : $Optional, #Optional.some!enumelt - %f2 = function_ref @f_owned : $@convention(thin) (@owned A) -> () - %c2 = apply %f2(%d1) : $@convention(thin) <τ_0_0> (@owned τ_0_0) -> () - destroy_addr %v0 : $*Optional - %34 = tuple () - dealloc_stack %v0 : $*Optional - return %34 : $() -} - -sil @use: $@convention(thin) (@inout T) -> () - -// We currently don't handle reasoning about multiple copy_addr instructions at -// once. With the current logic we must not optimize this case (we would have to -// prove that we can replace both copy_addr to be able to optimize). - -// CHECK-LABEL: sil [ossa] @not_dominated_uses : -// CHECK: alloc_stack -// CHECK: cond_br -// CHECK: bb1 -// CHECK: copy_addr -// CHECK: apply -// CHECK: br bb3 -// CHECK: bb2 -// CHECK: copy_addr -// CHECK: apply -// CHECK: br bb3 -// CHECK: bb3 -// CHECK: apply -// CHECK: destroy_addr -// CHECK-LABEL: } // end sil function 'not_dominated_uses' -sil [ossa] @not_dominated_uses: $@convention(thin) (@in Optional, @in Optional, Bool) -> () { -bb0(%0 : $*Optional, %1 : $*Optional, %3 : $Bool): - %4 = alloc_stack $Optional - %5 = struct_extract %3 : $Bool, #Bool._value - %f = function_ref @use : $@convention(thin) (@inout T2) -> () - cond_br %5, bb1, bb2 - -bb1: - copy_addr [take] %0 to [init] %4 : $*Optional - %r1 = apply %f>(%4) : $@convention(thin) (@inout T2) -> () - destroy_addr %1 : $*Optional - br bb3 - -bb2: - copy_addr [take] %1 to [init] %4 : $*Optional - %r2 = apply %f>(%4) : $@convention(thin) (@inout T2) -> () - destroy_addr %0 : $*Optional - br bb3 - -bb3: - %r3 = apply %f>(%4) : $@convention(thin) (@inout T2) -> () - destroy_addr %4 : $*Optional - dealloc_stack %4 : $*Optional - %13 = tuple() - return %13 : $() -} - -public struct S { - @_hasStorage var f: T { get set } - @_hasStorage var g: T { get set } - init(f: T, g: T) -} - -// Test a dead copy that initializes a stack local. -// CHECK-LABEL: sil [ossa] @deadtemp : -// CHECK: %[[G:.*]] = struct_element_addr %0 : $*S, #S.g -// CHECK-NOT: copy_addr -// CHECK: debug_value %[[G:.*]] : $*T, expr op_deref -// CHECK: %[[F:.*]] = struct_element_addr %0 : $*S, #S.f -// CHECK: copy_addr %[[G]] to %[[F]] : $*T -// CHECK-LABEL: } // end sil function 'deadtemp' -sil [ossa] @deadtemp : $@convention(thin) (@inout S) -> () { -bb0(%0 : $*S): - %1 = struct_element_addr %0 : $*S, #S.g - %2 = alloc_stack $T - copy_addr %1 to [init] %2 : $*T - debug_value %2 : $*T, expr op_deref - %4 = struct_element_addr %0 : $*S, #S.f - copy_addr [take] %2 to %4 : $*T - dealloc_stack %2 : $*T - %7 = tuple () - return %7 : $() -} - -// Test assigning into a stack local. -// CHECK-LABEL: sil [ossa] @deadtemp_assign : -// CHECK: %[[G:.*]] = struct_element_addr %0 : $*S, #S.g -// CHECK-NOT: copy_addr -// CHECK: %[[F:.*]] = struct_element_addr %0 : $*S, #S.f -// CHECK: copy_addr %[[G]] to %[[F]] : $*T -// CHECK-LABEL: } // end sil function 'deadtemp_assign' -sil [ossa] @deadtemp_assign : $@convention(thin) (@inout S) -> () { -bb0(%0 : $*S): - %1 = struct_element_addr %0 : $*S, #S.g - %2 = alloc_stack $T - copy_addr %1 to [init] %2 : $*T - %4 = struct_element_addr %0 : $*S, #S.f - copy_addr [take] %2 to %4 : $*T - dealloc_stack %2 : $*T - %7 = tuple () - return %7 : $() -} - -// Test a dead copy that initializes a stack local, -// taking the source, and initializing the destination. -// CHECK-LABEL: sil [ossa] @deadtemp_take_init : -// CHECK-NOT: copy_addr -// CHECK: copy_addr [take] %1 to [init] %0 : $*T -// CHECK-LABEL: } // end sil function 'deadtemp_take_init' -sil [ossa] @deadtemp_take_init : $@convention(thin) (@in T) -> @out T { -bb0(%0 : $*T, %1 : $*T): - %2 = alloc_stack $T - copy_addr [take] %1 to [init] %2 : $*T - copy_addr [take] %2 to [init] %0 : $*T - dealloc_stack %2 : $*T - %7 = tuple () - return %7 : $() -} - -struct ObjWrapper { - var obj: AnyObject -} - -// Test that backward copy propagation does not interfere with the previous -// value of the copy's destination. The `load` is a use of the `alloc` value, -// but not a direct use. Since it occurs between the initialization of `temp` -// and the copy from temp into `alloc`, the copy into `alloc` cannot be backward -// propagated. -// Swift CI: resilience bot seg faults in stdlib/RangeReplaceable.swift.gyb. -// -// CHECK-LABEL: sil [ossa] @testLoadDestroy : $@convention(thin) (@in_guaranteed ObjWrapper, @in_guaranteed ObjWrapper) -> () { -// CHECK: bb0(%0 : $*ObjWrapper, %1 : $*ObjWrapper): -// CHECK: [[ALLOC:%.*]] = alloc_stack $ObjWrapper, var, name "o" -// CHECK: copy_addr %0 to [init] [[ALLOC]] : $*ObjWrapper -// CHECK: [[ELT_ADDR:%.*]] = struct_element_addr [[ALLOC]] : $*ObjWrapper, #ObjWrapper.obj -// CHECK: [[TEMP:%.*]] = alloc_stack $ObjWrapper -// CHECK: copy_addr %1 to [init] [[TEMP]] : $*ObjWrapper -// CHECK: [[LD:%.*]] = load [copy] [[ELT_ADDR]] : $*AnyObject -// CHECK: destroy_value [[LD]] : $AnyObject -// CHECK: destroy_addr [[ALLOC]] : $*ObjWrapper -// CHECK: destroy_addr [[TEMP]] : $*ObjWrapper -// CHECK: dealloc_stack [[TEMP]] : $*ObjWrapper -// CHECK: dealloc_stack [[ALLOC]] : $*ObjWrapper -// CHECK: %{{.*}} = tuple () -// CHECK: return %{{.*}} : $() -// CHECK-LABEL: } // end sil function 'testLoadDestroy' -sil [ossa] @testLoadDestroy : $@convention(thin) (@in_guaranteed ObjWrapper, @in_guaranteed ObjWrapper) -> () { -bb(%0 : $*ObjWrapper, %1 : $*ObjWrapper): - // Fully initialize a new stack var to arg0. - %alloc = alloc_stack $ObjWrapper, var, name "o" - copy_addr %0 to [init] %alloc : $*ObjWrapper - %objadr = struct_element_addr %alloc : $*ObjWrapper, #ObjWrapper.obj - - // Fully initialize a temporary to arg1. - // Rewriting this to %alloc would alias with the subsequent load. - %temp = alloc_stack $ObjWrapper - copy_addr %1 to [init] %temp : $*ObjWrapper - - // Load and release an reference from arg0 inside the stack var. - %obj = load [copy] %objadr : $*AnyObject - destroy_value %obj : $AnyObject - destroy_addr %alloc : $*ObjWrapper - // Move `temp` copy of arg1 into the stack var. - copy_addr [take] %temp to [init] %alloc : $*ObjWrapper - - destroy_addr %alloc : $*ObjWrapper - dealloc_stack %temp : $*ObjWrapper - dealloc_stack %alloc : $*ObjWrapper - %74 = tuple () - return %74 : $() -} - -// Helper for multipleUse -sil [ossa] @multipleArg : $@convention(thin) (@in_guaranteed T, @in_guaranteed T) -> () { -bb0(%0 : $*T, %1 : $*T): - %r1 = tuple () - return %r1 : $() -} - -// Test a corner case of forward copy propagation in which simple substitution -// does not work (the source is reinitialized) and need to propagate to an -// instruction with multiple uses of the source. -// CHECK-LABEL: sil hidden [ossa] @multipleUse : $@convention(thin) (@in T, @in T) -> () { -// CHECK: bb0(%0 : $*T, %1 : $*T): -// CHECK: [[A:%.*]] = alloc_stack $T -// CHECK: copy_addr [take] %0 to [init] [[A]] : $*T -// CHECK: [[F:%.*]] = function_ref @multipleArg : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> () -// CHECK: [[C:%.*]] = apply [[F]]([[A]], [[A]]) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> () -// CHECK: destroy_addr [[A]] : $*T -// CHECK: dealloc_stack [[A]] : $*T -// CHECK: copy_addr [take] %1 to [init] %0 : $*T -// CHECK: %{{.*}} = tuple () -// CHECK: return %{{.*}} : $() -// CHECK-LABEL: } // end sil function 'multipleUse' -sil hidden [ossa] @multipleUse : $@convention(thin) (@in T, @in T) -> () { -bb0(%0 : $*T, %1 : $*T): - %l1 = alloc_stack $T - copy_addr [take] %0 to [init] %l1 : $*T - %f1 = function_ref @multipleArg : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> () - %c1 = apply %f1(%l1, %l1) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> () - destroy_addr %l1 : $*T - dealloc_stack %l1 : $*T - // Reinitialize copy's source to avoid a fast isSourceDeadAtCopy check. - copy_addr [take] %1 to [init] %0 : $*T - destroy_addr %0 : $*T - %r1 = tuple () - return %r1 : $() -} - -// rdar://problem/43888666 -// https://github.com/apple/swift/issues/51046 -// Memory leak after switch in release configuration -// -// CHECK-LABEL: sil [ossa] @testGlobalHoistToStoredValue : $@convention(thin) (@owned AClass, @inout AClass) -> () { -// CHECK: bb0(%0 : @owned $AClass, %1 : $*AClass): -// CHECK-NEXT: [[LOCAL:%.*]] = alloc_stack $AClass -// CHECK-NEXT: [[COPY:%.*]] = copy_value %0 -// CHECK-NEXT: store %0 to [init] [[LOCAL]] : $*AClass -// CHECK-NEXT: copy_addr [[LOCAL]] to %1 : $*AClass -// CHECK-NEXT: store [[COPY]] to [assign] %1 : $*AClass -// CHECK-NEXT: br bb1 -// CHECK: bb1: -// CHECK-NEXT: destroy_addr [[LOCAL]] -// CHECK-NEXT: dealloc_stack [[LOCAL]] : $*AClass -// CHECK-NEXT: tuple () -// CHECK-NEXT: return -// CHECK-LABEL: } // end sil function 'testGlobalHoistToStoredValue' -sil [ossa] @testGlobalHoistToStoredValue : $@convention(thin) (@owned AClass, @inout AClass) -> () { -bb0(%obj : @owned $AClass, %ptr : $*AClass ): - %local = alloc_stack $AClass - %copy = copy_value %obj : $AClass - store %obj to [init] %local : $*AClass - copy_addr %local to %ptr : $*AClass - store %copy to [assign] %ptr : $*AClass - br bb1 -bb1: - destroy_addr %local : $*AClass - dealloc_stack %local : $*AClass - %v = tuple () - return %v : $() -} - -// CHECK-LABEL: sil [ossa] @test_dynamic_lifetime : -// CHECK: [[STK:%.*]] = alloc_stack [dynamic_lifetime] $NonTrivialStruct -// CHECK: copy_addr [take] {{.*}} to [init] [[STK]] : $*NonTrivialStruct -// CHECK-LABEL: } // end sil function 'test_dynamic_lifetime' -sil [ossa] @test_dynamic_lifetime : $@convention(thin) () -> @out NonTrivialStruct { -bb0(%0 : $*NonTrivialStruct): - %1 = alloc_stack $Builtin.Int1 - %2 = alloc_stack [dynamic_lifetime] $NonTrivialStruct - %3 = integer_literal $Builtin.Int1, 0 - store %3 to [trivial] %1 : $*Builtin.Int1 - br bb1 - -bb1: - %6 = load [trivial] %1 : $*Builtin.Int1 - cond_br %6, bb2, bb3 - -bb2: - destroy_addr %2 : $*NonTrivialStruct - br bb4 - -bb3: - br bb4 - -bb4: - %11 = integer_literal $Builtin.Int1, -1 - store %11 to [trivial] %1 : $*Builtin.Int1 - %13 = alloc_stack $NonTrivialStruct - %14 = function_ref @get_nontrivialstruct : $@convention(thin) () -> @out NonTrivialStruct - %15 = apply %14(%13) : $@convention(thin) () -> @out NonTrivialStruct - copy_addr [take] %13 to [init] %2 : $*NonTrivialStruct - dealloc_stack %13 : $*NonTrivialStruct - %18 = struct_element_addr %2 : $*NonTrivialStruct, #NonTrivialStruct.cls - %19 = function_ref @use_aclass : $@convention(thin) (@in_guaranteed AClass) -> () - apply %19(%18) : $@convention(thin) (@in_guaranteed AClass) -> () - cond_br undef, bb5, bb9 - -bb5: - br bb10 - -bb9: - br bb10 - -bb10: - cond_br undef, bb11, bb12 - -bb11: - br bb1 - -bb12: - copy_addr [take] %2 to [init] %0 : $*NonTrivialStruct - dealloc_stack %2 : $*NonTrivialStruct - %33 = tuple () - dealloc_stack %1 : $*Builtin.Int1 - return %33 : $() -} diff --git a/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil b/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil index 878d1e4795b14..089138754aa2a 100644 --- a/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil +++ b/test/SILOptimizer/sil_combiner_concrete_prop_all_args.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -sil-print-types -wmo -enable-sil-verify-all %s -inline -sil-combine -generic-specializer -allocbox-to-stack -copy-forwarding -lower-aggregate-instrs -mem2reg -devirtualizer -inline -dead-arg-signature-opt -dce | %FileCheck %s +// RUN: %target-sil-opt -sil-print-types -wmo -enable-sil-verify-all %s -inline -sil-combine -generic-specializer -allocbox-to-stack -lower-aggregate-instrs -mem2reg -devirtualizer -inline -dead-arg-signature-opt -dce | %FileCheck %s import Builtin import Swift import SwiftShims