diff --git a/include/swift/AST/SemanticAttrs.def b/include/swift/AST/SemanticAttrs.def index 81615e37d9c1..944d56b9348d 100644 --- a/include/swift/AST/SemanticAttrs.def +++ b/include/swift/AST/SemanticAttrs.def @@ -51,6 +51,7 @@ SEMANTICS_ATTR(ARRAY_GET_ELEMENT_ADDRESS, "array.get_element_address") SEMANTICS_ATTR(ARRAY_INIT, "array.init") SEMANTICS_ATTR(ARRAY_INIT_EMPTY, "array.init.empty") SEMANTICS_ATTR(ARRAY_MAKE_MUTABLE, "array.make_mutable") +SEMANTICS_ATTR(ARRAY_END_MUTATION, "array.end_mutation") SEMANTICS_ATTR(ARRAY_MUTATE_UNKNOWN, "array.mutate_unknown") SEMANTICS_ATTR(ARRAY_PROPS_IS_NATIVE_TYPE_CHECKED, "array.props.isNativeTypeChecked") SEMANTICS_ATTR(ARRAY_RESERVE_CAPACITY_FOR_APPEND, "array.reserve_capacity_for_append") @@ -67,6 +68,8 @@ SEMANTICS_ATTR(OPTIMIZE_SIL_SPECIALIZE_GENERIC_PARTIAL_NEVER, "optimize.sil.specialize.generic.partial.never") SEMANTICS_ATTR(OPTIMIZE_SIL_SPECIALIZE_GENERIC_SIZE_NEVER, "optimize.sil.specialize.generic.size.never") +SEMANTICS_ATTR(OPTIMIZE_SIL_SPECIALIZE_OWNED2GUARANTEE_NEVER, + "optimize.sil.specialize.owned2guarantee.never") SEMANTICS_ATTR(OSLOG_MESSAGE_INIT_INTERPOLATION, "oslog.message.init_interpolation") SEMANTICS_ATTR(OSLOG_MESSAGE_INIT_STRING_LITERAL, "oslog.message.init_stringliteral") diff --git a/include/swift/SILOptimizer/Analysis/ArraySemantic.h b/include/swift/SILOptimizer/Analysis/ArraySemantic.h index 0d6c51b729c9..67b715680479 100644 --- a/include/swift/SILOptimizer/Analysis/ArraySemantic.h +++ b/include/swift/SILOptimizer/Analysis/ArraySemantic.h @@ -31,6 +31,7 @@ enum class ArrayCallKind { kGetElement, kGetElementAddress, kMakeMutable, + kEndMutation, kMutateUnknown, kReserveCapacityForAppend, kWithUnsafeMutableBufferPointer, @@ -42,7 +43,8 @@ enum class ArrayCallKind { // before this comment. kArrayInit, kArrayUninitialized, - kArrayUninitializedIntrinsic + kArrayUninitializedIntrinsic, + kArrayFinalizeIntrinsic }; /// Return true is the given function is an array semantics call. @@ -78,6 +80,8 @@ class ArraySemanticsCall { ArraySemanticsCall(SILValue V, StringRef semanticName, bool matchPartialName); + ArraySemanticsCall() : SemanticsCall(nullptr) {} + /// Can we hoist this call. bool canHoist(SILInstruction *To, DominanceInfo *DT) const; diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 3b2c9eacde8e..b18748cb603d 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -120,6 +120,8 @@ PASS(CopyForwarding, "copy-forwarding", "Copy Forwarding to Remove Redundant Copies") PASS(CopyPropagation, "copy-propagation", "Copy propagation to Remove Redundant SSA Copies") +PASS(COWOpts, "cow-opts", + "Optimize COW operations") PASS(Differentiation, "differentiation", "Automatic Differentiation") PASS(EpilogueARCMatcherDumper, "sil-epilogue-arc-dumper", diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index b51e15605b17..9e8ffa340805 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -128,7 +128,8 @@ SILValue swift::stripCastsWithoutMarkDependence(SILValue V) { auto K = V->getKind(); if (isRCIdentityPreservingCast(K) || - K == ValueKind::UncheckedTrivialBitCastInst) { + K == ValueKind::UncheckedTrivialBitCastInst || + K == ValueKind::EndCOWMutationInst) { V = cast(V)->getOperand(0); continue; } @@ -308,7 +309,8 @@ bool swift::onlyAffectsRefCount(SILInstruction *user) { } bool swift::mayCheckRefCount(SILInstruction *User) { - return isa(User) || isa(User); + return isa(User) || isa(User) || + isa(User); } bool swift::isSanitizerInstrumentation(SILInstruction *Instruction) { diff --git a/lib/SIL/Utils/Projection.cpp b/lib/SIL/Utils/Projection.cpp index 2881469102ea..849e5ebcd95a 100644 --- a/lib/SIL/Utils/Projection.cpp +++ b/lib/SIL/Utils/Projection.cpp @@ -371,10 +371,13 @@ Optional ProjectionPath::getProjectionPath(SILValue Start, auto Iter = End; while (Start != Iter) { - Projection AP(Iter); - if (!AP.isValid()) - break; - P.Path.push_back(AP); + // end_cow_mutation is not a projection, but we want to "see through" it. + if (!isa(Iter)) { + Projection AP(Iter); + if (!AP.isValid()) + break; + P.Path.push_back(AP); + } Iter = cast(*Iter).getOperand(0); } diff --git a/lib/SILOptimizer/Analysis/ArraySemantic.cpp b/lib/SILOptimizer/Analysis/ArraySemantic.cpp index c4d47098c296..abfd4510a4cb 100644 --- a/lib/SILOptimizer/Analysis/ArraySemantic.cpp +++ b/lib/SILOptimizer/Analysis/ArraySemantic.cpp @@ -33,12 +33,14 @@ ArrayCallKind swift::getArraySemanticsKind(SILFunction *f) { .StartsWith("array.init", ArrayCallKind::kArrayInit) .Case("array.uninitialized", ArrayCallKind::kArrayUninitialized) .Case("array.uninitialized_intrinsic", ArrayCallKind::kArrayUninitializedIntrinsic) + .Case("array.finalize_intrinsic", ArrayCallKind::kArrayFinalizeIntrinsic) .Case("array.check_subscript", ArrayCallKind::kCheckSubscript) .Case("array.check_index", ArrayCallKind::kCheckIndex) .Case("array.get_count", ArrayCallKind::kGetCount) .Case("array.get_capacity", ArrayCallKind::kGetCapacity) .Case("array.get_element", ArrayCallKind::kGetElement) .Case("array.make_mutable", ArrayCallKind::kMakeMutable) + .Case("array.end_mutation", ArrayCallKind::kEndMutation) .Case("array.get_element_address", ArrayCallKind::kGetElementAddress) .Case("array.mutate_unknown", ArrayCallKind::kMutateUnknown) @@ -101,10 +103,12 @@ bool swift::ArraySemanticsCall::isValidSignature() { } case ArrayCallKind::kCheckSubscript: { // Int, Bool, Self - if (SemanticsCall->getNumArguments() != 3 || - !SemanticsCall->getArgument(0)->getType().isTrivial(*F)) + unsigned numArgs = SemanticsCall->getNumArguments(); + if (numArgs != 2 && numArgs != 3) + return false; + if (!SemanticsCall->getArgument(0)->getType().isTrivial(*F)) return false; - if (!SemanticsCall->getArgument(1)->getType().isTrivial(*F)) + if (numArgs == 3 && !SemanticsCall->getArgument(1)->getType().isTrivial(*F)) return false; auto SelfConvention = FnTy->getSelfParameter().getConvention(); return SelfConvention == ParameterConvention::Direct_Guaranteed || @@ -324,27 +328,26 @@ bool swift::ArraySemanticsCall::canHoist(SILInstruction *InsertBefore, // Not implemented yet. return false; - case ArrayCallKind::kCheckSubscript: { - auto IsNativeArg = getArrayPropertyIsNativeTypeChecked(); - ArraySemanticsCall IsNative(IsNativeArg, - "array.props.isNativeTypeChecked", true); - if (!IsNative) { - // Do we have a constant parameter? - auto *SI = dyn_cast(IsNativeArg); - if (!SI) - return false; - if (!isa(SI->getOperand(0))) + case ArrayCallKind::kCheckSubscript: + if (SILValue IsNativeArg = getArrayPropertyIsNativeTypeChecked()) { + ArraySemanticsCall IsNative(IsNativeArg, + "array.props.isNativeTypeChecked", true); + if (!IsNative) { + // Do we have a constant parameter? + auto *SI = dyn_cast(IsNativeArg); + if (!SI) + return false; + if (!isa(SI->getOperand(0))) + return false; + } else if (!IsNative.canHoist(InsertBefore, DT)) + // Otherwise, we must be able to hoist the function call. return false; - } else if (!IsNative.canHoist(InsertBefore, DT)) - // Otherwise, we must be able to hoist the function call. - return false; - + } return canHoistArrayArgument(SemanticsCall, getSelf(), InsertBefore, DT); - } - case ArrayCallKind::kMakeMutable: { + case ArrayCallKind::kMakeMutable: + case ArrayCallKind::kEndMutation: return canHoistArrayArgument(SemanticsCall, getSelf(), InsertBefore, DT); - } } // End switch. return false; @@ -448,9 +451,8 @@ ApplyInst *swift::ArraySemanticsCall::hoistOrCopy(SILInstruction *InsertBefore, hoistOrCopySelf(SemanticsCall, InsertBefore, DT, LeaveOriginal); SILValue NewArrayProps; - if (Kind == ArrayCallKind::kCheckSubscript) { + if (SILValue IsNativeArg = getArrayPropertyIsNativeTypeChecked()) { // Copy the array.props argument call. - auto IsNativeArg = getArrayPropertyIsNativeTypeChecked(); ArraySemanticsCall IsNative(IsNativeArg, "array.props.isNativeTypeChecked", true); if (!IsNative) { @@ -492,8 +494,8 @@ ApplyInst *swift::ArraySemanticsCall::hoistOrCopy(SILInstruction *InsertBefore, return Call; } - case ArrayCallKind::kMakeMutable: { - assert(!LeaveOriginal && "Copying not yet implemented"); + case ArrayCallKind::kMakeMutable: + case ArrayCallKind::kEndMutation: { // Hoist the call. auto Call = hoistOrCopyCall(SemanticsCall, InsertBefore, LeaveOriginal, DT); return Call; @@ -515,14 +517,15 @@ void swift::ArraySemanticsCall::removeCall() { switch (getKind()) { default: break; - case ArrayCallKind::kCheckSubscript: { - // Remove all uses with the empty tuple (). - auto EmptyDep = SILBuilderWithScope(SemanticsCall) - .createStruct(SemanticsCall->getLoc(), - SemanticsCall->getType(), {}); - SemanticsCall->replaceAllUsesWith(EmptyDep); - } - break; + case ArrayCallKind::kCheckSubscript: + if (!SemanticsCall->getType().isVoid()){ + // Remove all uses with the empty tuple (). + auto EmptyDep = SILBuilderWithScope(SemanticsCall) + .createStruct(SemanticsCall->getLoc(), + SemanticsCall->getType(), {}); + SemanticsCall->replaceAllUsesWith(EmptyDep); + } + break; case ArrayCallKind::kGetElement: { // Remove the matching isNativeTypeChecked and check_subscript call. ArraySemanticsCall IsNative(getTypeCheckedArgument(), @@ -552,11 +555,13 @@ SILValue swift::ArraySemanticsCall::getArrayPropertyIsNativeTypeChecked() const { switch (getKind()) { case ArrayCallKind::kCheckSubscript: - return SemanticsCall->getArgument(1); + if (SemanticsCall->getNumArguments() == 3) + return SemanticsCall->getArgument(1); + return SILValue(); case ArrayCallKind::kGetElement: return getTypeCheckedArgument(); default: - llvm_unreachable("Must have an array.props argument"); + return SILValue(); } } @@ -569,6 +574,7 @@ bool swift::ArraySemanticsCall::doesNotChangeArray() const { case ArrayCallKind::kGetCount: case ArrayCallKind::kGetCapacity: case ArrayCallKind::kGetElement: + case ArrayCallKind::kEndMutation: return true; } } diff --git a/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp b/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp index 5b02943c33a8..cbe9cd0db444 100644 --- a/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/EscapeAnalysis.cpp @@ -2285,6 +2285,7 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I, case SILInstructionKind::SelectValueInst: analyzeSelectInst(cast(I), ConGraph); return; + case SILInstructionKind::EndCOWMutationInst: case SILInstructionKind::StructInst: case SILInstructionKind::TupleInst: case SILInstructionKind::EnumInst: { diff --git a/lib/SILOptimizer/Analysis/SimplifyInstruction.cpp b/lib/SILOptimizer/Analysis/SimplifyInstruction.cpp index 4c67b9d6fd06..c7be441ba87d 100644 --- a/lib/SILOptimizer/Analysis/SimplifyInstruction.cpp +++ b/lib/SILOptimizer/Analysis/SimplifyInstruction.cpp @@ -62,6 +62,7 @@ namespace { SILValue visitUncheckedBitwiseCastInst(UncheckedBitwiseCastInst *UBCI); SILValue visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *UTBCI); + SILValue visitEndCOWMutationInst(EndCOWMutationInst *ECM); SILValue visitThinFunctionToPointerInst(ThinFunctionToPointerInst *TFTPI); SILValue visitPointerToThinFunctionInst(PointerToThinFunctionInst *PTTFI); SILValue visitBeginAccessInst(BeginAccessInst *BAI); @@ -329,6 +330,21 @@ visitUnconditionalCheckedCastInst(UnconditionalCheckedCastInst *UCCI) { return SILValue(); } +/// If the only use of a cast is a destroy, just destroy the cast operand. +static SILValue simplifyDeadCast(SingleValueInstruction *Cast) { + for (Operand *op : Cast->getUses()) { + switch (op->getUser()->getKind()) { + case SILInstructionKind::DestroyValueInst: + case SILInstructionKind::StrongReleaseInst: + case SILInstructionKind::StrongRetainInst: + break; + default: + return SILValue(); + } + } + return Cast->getOperand(0); +} + SILValue InstSimplifier:: visitUncheckedRefCastInst(UncheckedRefCastInst *OPRI) { @@ -351,7 +367,8 @@ visitUncheckedRefCastInst(UncheckedRefCastInst *OPRI) { if (OPRI->getOperand()->getType() == OPRI->getType()) return OPRI->getOperand(); - return SILValue(); + // (destroy_value (unchecked_ref_cast x)) -> destroy_value x + return simplifyDeadCast(OPRI); } SILValue @@ -375,7 +392,8 @@ SILValue InstSimplifier::visitUpcastInst(UpcastInst *UI) { if (URCI->getOperand()->getType() == UI->getType()) return URCI->getOperand(); - return SILValue(); + // (destroy_value (upcast x)) -> destroy_value x + return simplifyDeadCast(UI); } #define LOADABLE_REF_STORAGE(Name, ...) \ @@ -410,6 +428,11 @@ visitUncheckedTrivialBitCastInst(UncheckedTrivialBitCastInst *UTBCI) { return SILValue(); } +SILValue InstSimplifier::visitEndCOWMutationInst(EndCOWMutationInst *ECM) { + // (destroy_value (end_cow_mutation x)) -> destroy_value x + return simplifyDeadCast(ECM); +} + SILValue InstSimplifier:: visitUncheckedBitwiseCastInst(UncheckedBitwiseCastInst *UBCI) { diff --git a/lib/SILOptimizer/FunctionSignatureTransforms/OwnedToGuaranteedTransform.cpp b/lib/SILOptimizer/FunctionSignatureTransforms/OwnedToGuaranteedTransform.cpp index f0e927613920..e0b8f831b40c 100644 --- a/lib/SILOptimizer/FunctionSignatureTransforms/OwnedToGuaranteedTransform.cpp +++ b/lib/SILOptimizer/FunctionSignatureTransforms/OwnedToGuaranteedTransform.cpp @@ -13,6 +13,7 @@ #define DEBUG_TYPE "fso-owned-to-guaranteed-transform" #include "FunctionSignatureOpts.h" #include "swift/SIL/DebugUtils.h" +#include "swift/AST/SemanticAttrs.h" #include "llvm/Support/CommandLine.h" using namespace swift; @@ -258,6 +259,9 @@ void FunctionSignatureTransform::OwnedToGuaranteedAddResultRelease( bool FunctionSignatureTransform::OwnedToGuaranteedAnalyze() { if (FSODisableOwnedToGuaranteed) return false; + SILFunction *F = TransformDescriptor.OriginalFunction; + if (F->hasSemanticsAttr(semantics::OPTIMIZE_SIL_SPECIALIZE_OWNED2GUARANTEE_NEVER)) + return false; const bool Result = OwnedToGuaranteedAnalyzeResults(); const bool Params = OwnedToGuaranteedAnalyzeParameters(); diff --git a/lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp b/lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp index a6a0fef10d3b..fc4ca58f5659 100644 --- a/lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp +++ b/lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp @@ -288,6 +288,7 @@ static bool isNonMutatingArraySemanticCall(SILInstruction *Inst) { case ArrayCallKind::kGetCapacity: case ArrayCallKind::kGetElement: case ArrayCallKind::kGetElementAddress: + case ArrayCallKind::kEndMutation: return true; case ArrayCallKind::kMakeMutable: case ArrayCallKind::kMutateUnknown: @@ -296,6 +297,7 @@ static bool isNonMutatingArraySemanticCall(SILInstruction *Inst) { case ArrayCallKind::kArrayInit: case ArrayCallKind::kArrayUninitialized: case ArrayCallKind::kArrayUninitializedIntrinsic: + case ArrayCallKind::kArrayFinalizeIntrinsic: case ArrayCallKind::kAppendContentsOf: case ArrayCallKind::kAppendElement: return false; diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index ef5d63181638..a51a9123aee0 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -366,6 +366,7 @@ void addFunctionPasses(SILPassPipelinePlan &P, P.addRedundantLoadElimination(); } + P.addCOWOpts(); P.addPerformanceConstantPropagation(); // Remove redundant arguments right before CSE and DCE, so that CSE and DCE // can cleanup redundant and dead instructions. @@ -514,6 +515,7 @@ static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) { P.addLICM(); // Run loop unrolling after inlining and constant propagation, because loop // trip counts may have became constant. + P.addLICM(); P.addLoopUnroll(); } @@ -595,6 +597,7 @@ static void addLateLoopOptPassPipeline(SILPassPipelinePlan &P) { P.addAccessEnforcementReleaseSinking(); P.addAccessEnforcementOpts(); P.addLICM(); + P.addCOWOpts(); // Simplify CFG after LICM that creates new exit blocks P.addSimplifyCFG(); // LICM might have added new merging potential by hoisting diff --git a/lib/SILOptimizer/SILCombiner/SILCombiner.h b/lib/SILOptimizer/SILCombiner/SILCombiner.h index 8a7435272e26..508fa6e324a9 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombiner.h +++ b/lib/SILOptimizer/SILCombiner/SILCombiner.h @@ -183,6 +183,7 @@ class SILCombiner : SILInstruction *visitPointerToAddressInst(PointerToAddressInst *PTAI); SILInstruction *visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI); SILInstruction *visitUncheckedRefCastInst(UncheckedRefCastInst *URCI); + SILInstruction *visitEndCOWMutationInst(EndCOWMutationInst *URCI); SILInstruction *visitUncheckedRefCastAddrInst(UncheckedRefCastAddrInst *URCI); SILInstruction *visitBridgeObjectToRefInst(BridgeObjectToRefInst *BORI); SILInstruction *visitUnconditionalCheckedCastInst( diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp index e370a62e16e8..86081cf98d94 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp @@ -264,6 +264,26 @@ SILCombiner::visitUncheckedRefCastInst(UncheckedRefCastInst *URCI) { return nullptr; } +SILInstruction *SILCombiner::visitEndCOWMutationInst(EndCOWMutationInst *ECM) { + + // Remove a cast if it's only used by an end_cow_mutation. + // + // (end_cow_mutation (upcast X)) -> (end_cow_mutation X) + // (end_cow_mutation (unchecked_ref_cast X)) -> (end_cow_mutation X) + SILValue op = ECM->getOperand(); + if (!isa(op) && !isa(op)) + return nullptr; + if (!op->hasOneUse()) + return nullptr; + + SingleValueInstruction *refCast = cast(op); + auto *newECM = Builder.createEndCOWMutation(ECM->getLoc(), + refCast->getOperand(0)); + ECM->replaceAllUsesWith(refCast); + refCast->setOperand(0, newECM); + refCast->moveAfter(newECM); + return eraseInstFromFunction(*ECM); +} SILInstruction * SILCombiner::visitBridgeObjectToRefInst(BridgeObjectToRefInst *BORI) { diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp index 0dcb55ce8eca..2d79a39f6d9e 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp @@ -699,6 +699,7 @@ static bool isZeroLoadFromEmptyCollection(LoadInst *LI) { case ValueKind::UpcastInst: case ValueKind::RawPointerToRefInst: case ValueKind::AddressToPointerInst: + case ValueKind::EndCOWMutationInst: addr = cast(addr)->getOperand(0); break; default: diff --git a/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp b/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp index eb790a3dc3c0..846037d58f65 100644 --- a/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp +++ b/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp @@ -135,13 +135,24 @@ bool ArrayAllocation::recursivelyCollectUses(ValueBase *Def) { } // Check array semantic calls. - if (auto apply = dyn_cast(User)) { + if (auto *apply = dyn_cast(User)) { ArraySemanticsCall ArrayOp(apply); - if (ArrayOp && ArrayOp.doesNotChangeArray()) { - if (ArrayOp.getKind() == ArrayCallKind::kGetCount) + switch (ArrayOp.getKind()) { + case ArrayCallKind::kNone: + return false; + case ArrayCallKind::kGetCount: CountCalls.insert(ArrayOp); - continue; + break; + case ArrayCallKind::kArrayFinalizeIntrinsic: + if (!recursivelyCollectUses(apply)) + return false; + break; + default: + if (!ArrayOp.doesNotChangeArray()) + return false; + break; } + continue; } // An operation that escapes or modifies the array value. diff --git a/lib/SILOptimizer/Transforms/ArrayElementValuePropagation.cpp b/lib/SILOptimizer/Transforms/ArrayElementValuePropagation.cpp index 8a24b9d41cf6..3b31bdd2edaa 100644 --- a/lib/SILOptimizer/Transforms/ArrayElementValuePropagation.cpp +++ b/lib/SILOptimizer/Transforms/ArrayElementValuePropagation.cpp @@ -138,20 +138,24 @@ bool ArrayAllocation::recursivelyCollectUses(ValueBase *Def) { // Check array semantic calls. ArraySemanticsCall ArrayOp(User); - if (ArrayOp) { - if (ArrayOp.getKind() == ArrayCallKind::kAppendContentsOf) { + switch (ArrayOp.getKind()) { + case ArrayCallKind::kNone: + return false; + case ArrayCallKind::kAppendContentsOf: AppendContentsOfCalls.push_back(ArrayOp); - continue; - } else if (ArrayOp.getKind() == ArrayCallKind::kGetElement) { + break; + case ArrayCallKind::kGetElement: GetElementCalls.insert(ArrayOp); - continue; - } else if (ArrayOp.doesNotChangeArray()) { - continue; - } + break; + case ArrayCallKind::kArrayFinalizeIntrinsic: + if (!recursivelyCollectUses(cast(User))) + return false; + break; + default: + if (ArrayOp.doesNotChangeArray()) + break; + return false; } - - // An operation that escapes or modifies the array value. - return false; } return true; } diff --git a/lib/SILOptimizer/Transforms/CMakeLists.txt b/lib/SILOptimizer/Transforms/CMakeLists.txt index 30868a98f205..b5cfb49ac39d 100644 --- a/lib/SILOptimizer/Transforms/CMakeLists.txt +++ b/lib/SILOptimizer/Transforms/CMakeLists.txt @@ -8,6 +8,7 @@ target_sources(swiftSILOptimizer PRIVATE ArrayCountPropagation.cpp ArrayElementValuePropagation.cpp AssumeSingleThreaded.cpp + COWOpts.cpp CSE.cpp ConditionForwarding.cpp CopyForwarding.cpp diff --git a/lib/SILOptimizer/Transforms/COWOpts.cpp b/lib/SILOptimizer/Transforms/COWOpts.cpp new file mode 100644 index 000000000000..de13369819e7 --- /dev/null +++ b/lib/SILOptimizer/Transforms/COWOpts.cpp @@ -0,0 +1,277 @@ +//===--- COWOpts.cpp - Optimize COW operations ----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 +// +//===----------------------------------------------------------------------===// +// +// This pass optimizes begin_cow_mutation and end_cow_mutation patterns. +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "cow-opts" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILBuilder.h" +#include "llvm/Support/Debug.h" + +using namespace swift; + +namespace { + +/// Constant folds the uniqueness result of begin_cow_mutation instructions. +/// +/// If it can be proved that the buffer argument is uniquely referenced, the +/// uniqueness result is replaced with a constant boolean "true". +/// For example: +/// +/// \code +/// %buffer = end_cow_mutation %mutable_buffer +/// // ... +/// // %buffer does not escape here +/// // ... +/// (%is_unique, %mutable_buffer2) = begin_cow_mutation %buffer +/// cond_br %is_unique, ... +/// \endcode +/// +/// is replaced with +/// +/// \code +/// %buffer = end_cow_mutation [keep_unique] %mutable_buffer +/// // ... +/// (%not_used, %mutable_buffer2) = begin_cow_mutation %buffer +/// %true = integer_literal 1 +/// cond_br %true, ... +/// \endcode +/// +/// Note that the keep_unique flag is set on the end_cow_mutation because the +/// code now relies on that the buffer is really uniquely referenced. +/// +/// The optimization can also handle def-use chains between end_cow_mutation and +/// begin_cow_mutation which involve phi-arguments. +/// +/// An additional peephole optimization is performed: if the begin_cow_mutation +/// is the only use of the end_cow_mutation, the whole pair of instructions +/// is eliminated. +/// +class COWOptsPass : public SILFunctionTransform { +public: + COWOptsPass() {} + + void run() override; + +private: + using InstructionSet = SmallPtrSet; + using VoidPointerSet = SmallPtrSet; + + AliasAnalysis *AA = nullptr; + + bool optimizeBeginCOW(BeginCOWMutationInst *BCM); + + static void collectEscapePoints(SILValue v, + InstructionSet &escapePoints, + VoidPointerSet &handled); +}; + +void COWOptsPass::run() { + SILFunction *F = getFunction(); + if (!F->shouldOptimize()) + return; + + LLVM_DEBUG(llvm::dbgs() << "*** RedundantPhiElimination on function: " + << F->getName() << " ***\n"); + + AA = PM->getAnalysis(); + + bool changed = false; + for (SILBasicBlock &block : *F) { + auto iter = block.begin(); + while (iter != block.end()) { + SILInstruction *inst = &*iter++; + if (auto *beginCOW = dyn_cast(inst)) + changed |= optimizeBeginCOW(beginCOW); + } + } + + if (changed) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } +} + +bool COWOptsPass::optimizeBeginCOW(BeginCOWMutationInst *BCM) { + VoidPointerSet handled; + SmallVector workList; + SmallPtrSet endCOWMutationInsts; + + // Collect all end_cow_mutation instructions, used by the begin_cow_mutation, + // looking through block phi-arguments. + workList.push_back(BCM->getOperand()); + while (!workList.empty()) { + SILValue v = workList.pop_back_val(); + if (SILPhiArgument *arg = dyn_cast(v)) { + if (handled.insert(arg).second) { + SmallVector incomingVals; + if (!arg->getIncomingPhiValues(incomingVals)) + return false; + for (SILValue incomingVal : incomingVals) { + workList.push_back(incomingVal); + } + } + } else if (auto *ECM = dyn_cast(v)) { + endCOWMutationInsts.insert(ECM); + } else { + return false; + } + } + + // Collect all uses of the end_cow_instructions, where the buffer can + // potentially escape. + handled.clear(); + InstructionSet potentialEscapePoints; + for (EndCOWMutationInst *ECM : endCOWMutationInsts) { + collectEscapePoints(ECM, potentialEscapePoints, handled); + } + + if (!potentialEscapePoints.empty()) { + // Now, this is the complicated part: check if there is an escape point + // within the liverange between the end_cow_mutation(s) and + // begin_cow_mutation. + // + // For store instructions we do a little bit more: only count a store as an + // escape if there is a (potential) load from the same address within the + // liverange. + handled.clear(); + SmallVector instWorkList; + SmallVector potentialLoadInsts; + llvm::DenseSet storeAddrs; + + // This is a simple worklist-based backward dataflow analysis. + // Start at the initial begin_cow_mutation and go backward. + instWorkList.push_back(BCM); + + while (!instWorkList.empty()) { + SILInstruction *inst = instWorkList.pop_back_val(); + for (;;) { + if (potentialEscapePoints.count(inst) != 0) { + if (auto *store = dyn_cast(inst)) { + // Don't immediately bail on a store instruction. Instead, remember + // it and check if it interfers with any (potential) load. + storeAddrs.insert(store->getDest()); + } else { + return false; + } + } + if (inst->mayReadFromMemory()) + potentialLoadInsts.push_back(inst); + + // An end_cow_mutation marks the begin of the liverange. It's the end + // point of the dataflow analysis. + auto *ECM = dyn_cast(inst); + if (ECM && endCOWMutationInsts.count(ECM) != 0) + break; + + if (inst == &inst->getParent()->front()) { + for (SILBasicBlock *pred : inst->getParent()->getPredecessorBlocks()) { + if (handled.insert(pred).second) + instWorkList.push_back(pred->getTerminator()); + } + break; + } + + inst = &*std::prev(inst->getIterator()); + } + } + + // Check if there is any (potential) load from a memory location where the + // buffer is stored to. + if (!storeAddrs.empty()) { + // Avoid quadratic behavior. Usually this limit is not exceeded. + if (storeAddrs.size() * potentialLoadInsts.size() > 128) + return false; + for (SILInstruction *load : potentialLoadInsts) { + for (SILValue storeAddr : storeAddrs) { + if (!AA || AA->mayReadFromMemory(load, storeAddr)) + return false; + } + } + } + } + + // Replace the uniqueness result of the begin_cow_mutation with an integer + // literal of "true". + SILBuilderWithScope B(BCM); + auto *IL = B.createIntegerLiteral(BCM->getLoc(), + BCM->getUniquenessResult()->getType(), 1); + BCM->getUniquenessResult()->replaceAllUsesWith(IL); + + // Try the peephole optimization: remove an end_cow_mutation/begin_cow_mutation + // pair completely if the begin_cow_mutation is the only use of + // end_cow_mutation. + if (auto *singleEndCOW = dyn_cast(BCM->getOperand())) { + assert(endCOWMutationInsts.size() == 1 && + *endCOWMutationInsts.begin() == singleEndCOW); + if (singleEndCOW->hasOneUse()) { + BCM->getBufferResult()->replaceAllUsesWith(singleEndCOW->getOperand()); + BCM->eraseFromParent(); + singleEndCOW->eraseFromParent(); + return true; + } + } + + for (EndCOWMutationInst *ECM : endCOWMutationInsts) { + // This is important for other optimizations: The code is now relying on + // the buffer to be unique. + ECM->setKeepUnique(); + } + + return true; +} + +void COWOptsPass::collectEscapePoints(SILValue v, + InstructionSet &escapePoints, + VoidPointerSet &handled) { + if (!handled.insert(v.getOpaqueValue()).second) + return; + + for (Operand *use : v->getUses()) { + SILInstruction *user = use->getUser(); + switch (user->getKind()) { + case SILInstructionKind::BeginCOWMutationInst: + case SILInstructionKind::RefElementAddrInst: + case SILInstructionKind::RefTailAddrInst: + break; + case SILInstructionKind::BranchInst: + collectEscapePoints(cast(user)->getArgForOperand(use), + escapePoints, handled); + break; + case SILInstructionKind::CondBranchInst: + collectEscapePoints(cast(user)->getArgForOperand(use), + escapePoints, handled); + break; + case SILInstructionKind::StructInst: + case SILInstructionKind::TupleInst: + case SILInstructionKind::UncheckedRefCastInst: + collectEscapePoints(cast(user), + escapePoints, handled); + break; + default: + // Everything else is considered to be a potential escape of the buffer. + escapePoints.insert(user); + } + } +} + +} // end anonymous namespace + +SILTransform *swift::createCOWOpts() { + return new COWOptsPass(); +} + diff --git a/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp b/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp index 350db1805b13..fff5fafdde1f 100644 --- a/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp +++ b/lib/SILOptimizer/Transforms/DeadObjectElimination.cpp @@ -412,7 +412,8 @@ recursivelyCollectInteriorUses(ValueBase *DefInst, // Lifetime endpoints that don't allow the address to escape. if (isa(User) || - isa(User)) { + isa(User) || + isa(User)) { AllUsers.insert(User); continue; } @@ -445,13 +446,13 @@ recursivelyCollectInteriorUses(ValueBase *DefInst, continue; } // Recursively follow projections. - if (auto ProjInst = dyn_cast(User)) { - ProjectionIndex PI(ProjInst); + if (auto *svi = dyn_cast(User)) { + ProjectionIndex PI(svi); if (PI.isValid()) { IndexTrieNode *ProjAddrNode = AddressNode; bool ProjInteriorAddr = IsInteriorAddress; - if (Projection::isAddressProjection(ProjInst)) { - if (isa(ProjInst)) { + if (Projection::isAddressProjection(svi)) { + if (isa(svi)) { // Don't support indexing within an interior address. if (IsInteriorAddress) return false; @@ -466,13 +467,19 @@ recursivelyCollectInteriorUses(ValueBase *DefInst, // Don't expect to extract values once we've taken an address. return false; } - if (!recursivelyCollectInteriorUses(ProjInst, + if (!recursivelyCollectInteriorUses(svi, ProjAddrNode->getChild(PI.Index), ProjInteriorAddr)) { return false; } continue; } + ArraySemanticsCall AS(svi); + if (AS.getKind() == swift::ArrayCallKind::kArrayFinalizeIntrinsic) { + if (!recursivelyCollectInteriorUses(svi, AddressNode, IsInteriorAddress)) + return false; + continue; + } } // Otherwise bail. LLVM_DEBUG(llvm::dbgs() << " Found an escaping use: " << *User); diff --git a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp b/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp index e7bfef35bbb4..06968a86b32a 100644 --- a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp +++ b/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp @@ -159,6 +159,7 @@ static bool isRLEInertInstruction(SILInstruction *Inst) { case SILInstructionKind::CondFailInst: case SILInstructionKind::IsEscapingClosureInst: case SILInstructionKind::IsUniqueInst: + case SILInstructionKind::EndCOWMutationInst: case SILInstructionKind::FixLifetimeInst: case SILInstructionKind::EndAccessInst: case SILInstructionKind::SetDeallocatingInst: diff --git a/lib/SILOptimizer/Utils/ConstExpr.cpp b/lib/SILOptimizer/Utils/ConstExpr.cpp index 9716ec28085f..01a36e1f8907 100644 --- a/lib/SILOptimizer/Utils/ConstExpr.cpp +++ b/lib/SILOptimizer/Utils/ConstExpr.cpp @@ -45,6 +45,8 @@ enum class WellKnownFunction { ArrayInitEmpty, // Array._allocateUninitializedArray AllocateUninitializedArray, + // Array._endMutation + EndArrayMutation, // Array.append(_:) ArrayAppendElement, // String.init() @@ -71,6 +73,8 @@ static llvm::Optional classifyFunction(SILFunction *fn) { return WellKnownFunction::ArrayInitEmpty; if (fn->hasSemanticsAttr(semantics::ARRAY_UNINITIALIZED_INTRINSIC)) return WellKnownFunction::AllocateUninitializedArray; + if (fn->hasSemanticsAttr(semantics::ARRAY_END_MUTATION)) + return WellKnownFunction::EndArrayMutation; if (fn->hasSemanticsAttr(semantics::ARRAY_APPEND_ELEMENT)) return WellKnownFunction::ArrayAppendElement; if (fn->hasSemanticsAttr(semantics::STRING_INIT_EMPTY)) @@ -946,6 +950,17 @@ ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply, resultType, allocator)); return None; } + case WellKnownFunction::EndArrayMutation: { + // This function has the following signature in SIL: + // (@inout Array) -> () + assert(conventions.getNumParameters() == 1 && + conventions.getNumDirectSILResults() == 0 && + conventions.getNumIndirectSILResults() == 0 && + "unexpected Array._endMutation() signature"); + + // _endMutation is a no-op. + return None; + } case WellKnownFunction::ArrayAppendElement: { // This function has the following signature in SIL: // (@in Element, @inout Array) -> () diff --git a/test/Driver/opt-remark.swift b/test/Driver/opt-remark.swift index 0c3978eab4e8..95facf952c5d 100644 --- a/test/Driver/opt-remark.swift +++ b/test/Driver/opt-remark.swift @@ -3,6 +3,8 @@ // RUN: %target-swiftc_driver -O -Rpass=sil-inliner %s -o %t/throwaway 2>&1 | %FileCheck -check-prefix=REMARK_PASSED %s // RUN: %target-swiftc_driver -O -Rpass-missed=sil-inliner %s -o %t/throwaway 2>&1 | %FileCheck -check-prefix=REMARK_MISSED %s +// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib + // DEFAULT-NOT: remark: func big() { @@ -39,11 +41,11 @@ func small() { func foo() { // REMARK_MISSED-NOT: remark: {{.*}} inlined - // REMARK_MISSED: opt-remark.swift:44:2: remark: Not profitable to inline function "throwaway.big()" (cost = {{.*}}, benefit = {{.*}}) + // REMARK_MISSED: opt-remark.swift:46:2: remark: Not profitable to inline function "throwaway.big()" (cost = {{.*}}, benefit = {{.*}}) // REMARK_MISSED-NOT: remark: {{.*}} inlined big() // REMARK_PASSED-NOT: remark: Not profitable - // REMARK_PASSED: opt-remark.swift:48:3: remark: "throwaway.small()" inlined into "throwaway.foo()" (cost = {{.*}}, benefit = {{.*}}) + // REMARK_PASSED: opt-remark.swift:50:3: remark: "throwaway.small()" inlined into "throwaway.foo()" (cost = {{.*}}, benefit = {{.*}}) // REMARK_PASSED-NOT: remark: Not profitable small() } diff --git a/test/IRGen/multithread_module.swift b/test/IRGen/multithread_module.swift index 83eec8c2d3a1..895e710cc965 100644 --- a/test/IRGen/multithread_module.swift +++ b/test/IRGen/multithread_module.swift @@ -9,6 +9,7 @@ // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out | %FileCheck %s // REQUIRES: executable_test +// REQUIRES: optimized_stdlib,swift_stdlib_no_asserts // Test compilation of a module in multi-threaded compilation. diff --git a/test/IRGen/objc_protocol_extended_method_types.swift b/test/IRGen/objc_protocol_extended_method_types.swift index 87db4b883d49..3b23b810e7ec 100644 --- a/test/IRGen/objc_protocol_extended_method_types.swift +++ b/test/IRGen/objc_protocol_extended_method_types.swift @@ -5,6 +5,9 @@ // REQUIRES: OS=macosx // REQUIRES: objc_interop +// TODO: fix test for unoptimized stdlib with asserts +// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib + import Foundation diff --git a/test/IRGen/upcast.sil b/test/IRGen/upcast.sil index 365ea261e198..809f696179ad 100644 --- a/test/IRGen/upcast.sil +++ b/test/IRGen/upcast.sil @@ -2,19 +2,20 @@ // Make sure that we are able to lower upcast addresses. -// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} swiftcc void @upcast_test(%T6upcast1DC** nocapture dereferenceable({{.*}}) %0) {{.*}} { +// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} swiftcc %T6upcast1CC* @upcast_test(%T6upcast1DC** nocapture dereferenceable({{.*}}) %0) {{.*}} { // CHECK: entry: -// CHECK-NEXT: bitcast %T6upcast1DC** {{%[0-0]+}} to %T6upcast1CC** -// CHECK-NEXT: ret void +// CHECK-NEXT: [[A:%[0-9]+]] = bitcast %T6upcast1DC** {{%[0-0]+}} to %T6upcast1CC** +// CHECK-NEXT: [[C:%[0-9]+]] = load %T6upcast1CC*, %T6upcast1CC** [[A]] +// CHECK-NEXT: ret %T6upcast1CC* [[C]] class C {} sil_vtable C {} class D : C {} sil_vtable D {} -sil @upcast_test : $@convention(thin) (@inout D) -> () { +sil @upcast_test : $@convention(thin) (@inout D) -> C { bb0(%0 : $*D): %1 = upcast %0 : $*D to $*C - %33 = tuple() - return %33 : $() + %2 = load %1 : $*C + return %2 : $C } diff --git a/test/SILOptimizer/array_count_propagation.sil b/test/SILOptimizer/array_count_propagation.sil index 4cae662c6a56..1defa16de572 100644 --- a/test/SILOptimizer/array_count_propagation.sil +++ b/test/SILOptimizer/array_count_propagation.sil @@ -29,6 +29,7 @@ sil [_semantics "array.uninitialized"] @adoptStorage : $@convention(thin) (@owne sil [_semantics "array.get_count"] @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt sil [_semantics "array.get_element"] @getElement : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt sil [_semantics "array.uninitialized"] @allocateUninitialized : $@convention(thin) (MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) +sil [_semantics "array.finalize_intrinsic"] @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray sil [_semantics "array.init"] @initRepeatedValueCount : $@convention(thin) (@in MyInt, MyInt, @thin MyArray.Type) -> @owned MyArray sil [_semantics "array.init"] @initEmpty : $@convention(thin) (@thin MyArray.Type) -> @owned MyArray @@ -50,9 +51,11 @@ bb0: %7 = tuple_extract %6 : $(MyArray, UnsafeMutablePointer), 0 %8 = tuple_extract %6 : $(MyArray, UnsafeMutablePointer), 1 debug_value %7 : $MyArray + %f = function_ref @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray + %a = apply %f(%7) : $@convention(thin) (@owned MyArray) -> @owned MyArray %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt - %10 = apply %9(%7) : $@convention(method) (@guaranteed MyArray) -> MyInt - %12 = struct_extract %7 : $MyArray, #MyArray._buffer + %10 = apply %9(%a) : $@convention(method) (@guaranteed MyArray) -> MyInt + %12 = struct_extract %a : $MyArray, #MyArray._buffer %13 = struct_extract %12 : $_MyArrayBuffer, #_MyArrayBuffer._storage %14 = struct_extract %13 : $_MyBridgeStorage, #_MyBridgeStorage.rawValue strong_release %14 : $Builtin.BridgeObject diff --git a/test/SILOptimizer/array_element_propagation.sil b/test/SILOptimizer/array_element_propagation.sil index adf7ac72861a..136a8c0282fa 100644 --- a/test/SILOptimizer/array_element_propagation.sil +++ b/test/SILOptimizer/array_element_propagation.sil @@ -34,6 +34,7 @@ sil [_semantics "array.get_element"] @getElement2 : $@convention(method) (MyInt, sil @unknown_array_use : $@convention(method) (@guaranteed MyArray) -> MyBool sil [_semantics "array.uninitialized"] @arrayAdoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin Array.Type) -> @owned (Array, UnsafeMutablePointer) sil @arrayInit : $@convention(method) (@thin Array.Type) -> @owned Array +sil [_semantics "array.finalize_intrinsic"] @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray sil [_semantics "array.append_contentsOf"] @arrayAppendContentsOf : $@convention(method) (@owned Array, @inout Array) -> () // CHECK-LABEL: sil @propagate01 @@ -88,35 +89,37 @@ sil @propagate01 : $@convention(thin) () -> () { %19 = integer_literal $Builtin.Int64, 2 %20 = struct $MyInt (%19 : $Builtin.Int64) store %20 to %18 : $*MyInt - %23 = struct_extract %7 : $MyArray, #MyArray._buffer + %f = function_ref @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray + %a = apply %f(%7) : $@convention(thin) (@owned MyArray) -> @owned MyArray + %23 = struct_extract %a : $MyArray, #MyArray._buffer %24 = struct_extract %23 : $_MyArrayBuffer, #_MyArrayBuffer._storage %25 = struct_extract %24 : $_MyBridgeStorage, #_MyBridgeStorage.rawValue %26 = alloc_stack $MyInt - debug_value %7 : $MyArray + debug_value %a : $MyArray %27 = function_ref @hoistableIsNativeTypeChecked : $@convention(method) (@guaranteed MyArray) -> MyBool - %28 = apply %27(%7) : $@convention(method) (@guaranteed MyArray) -> MyBool + %28 = apply %27(%a) : $@convention(method) (@guaranteed MyArray) -> MyBool debug_value %28 : $MyBool // id: %104 %29 = function_ref @checkSubscript : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken - %30 = apply %29(%12, %28, %7) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken + %30 = apply %29(%12, %28, %a) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken debug_value %30 : $_MyDependenceToken %31 = function_ref @getElement : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt - %32 = apply %31(%26, %12, %28, %30, %7) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt + %32 = apply %31(%26, %12, %28, %30, %a) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt %35 = alloc_stack $MyInt debug_value %16 : $MyInt - debug_value %7 : $MyArray + debug_value %a : $MyArray debug_value %28 : $MyBool strong_retain %25 : $Builtin.BridgeObject - %36 = apply %29(%16, %28, %7) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken + %36 = apply %29(%16, %28, %a) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken debug_value %36 : $_MyDependenceToken - %37 = apply %31(%35, %16, %28, %36, %7) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt + %37 = apply %31(%35, %16, %28, %36, %a) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt strong_release %25 : $Builtin.BridgeObject %44 = alloc_stack $MyInt - debug_value %7 : $MyArray + debug_value %a : $MyArray debug_value %28 : $MyBool strong_retain %25 : $Builtin.BridgeObject - %45 = apply %29(%20, %28, %7) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken + %45 = apply %29(%20, %28, %a) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> _MyDependenceToken debug_value %45 : $_MyDependenceToken - %46 = apply %31(%44, %20, %28, %45, %7) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt + %46 = apply %31(%44, %20, %28, %45, %a) : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray) -> @out MyInt strong_release %25 : $Builtin.BridgeObject %52 = tuple () dealloc_stack %44 : $*MyInt diff --git a/test/SILOptimizer/cow_opts.sil b/test/SILOptimizer/cow_opts.sil new file mode 100644 index 000000000000..c3b8d9d8d820 --- /dev/null +++ b/test/SILOptimizer/cow_opts.sil @@ -0,0 +1,158 @@ +// RUN: %target-sil-opt %s -cow-opts | %FileCheck %s + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +final class Buffer { + @_hasStorage var i: Int { get set } + init() +} + +sil @unknown : $@convention(thin) (@guaranteed Buffer) -> () + +// CHECK-LABEL: sil @test_complete_removal +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: [[T:%[0-9]+]] = tuple ([[I]] : $Builtin.Int1, %0 : $Buffer) +// CHECK: return [[T]] +// CHECK: } // end sil function 'test_complete_removal' +sil @test_complete_removal : $@convention(thin) (@owned Buffer) -> (Builtin.Int1, @owned Buffer) { +bb0(%0 : $Buffer): + %e = end_cow_mutation %0 : $Buffer + (%u, %b) = begin_cow_mutation %e : $Buffer + %t = tuple (%u : $Builtin.Int1, %b : $Buffer) + return %t : $(Builtin.Int1, Buffer) +} + +// CHECK-LABEL: sil @test_simple +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: ({{.*}}, [[B:%[0-9]+]]) = begin_cow_mutation +// CHECK: [[T:%[0-9]+]] = tuple ({{.*}}, [[I]] : $Builtin.Int1, [[B]] : $Buffer) +// CHECK: return [[T]] +// CHECK: } // end sil function 'test_simple' +sil @test_simple : $@convention(thin) (@owned Buffer) -> (Int, Builtin.Int1, @owned Buffer) { +bb0(%0 : $Buffer): + %e = end_cow_mutation %0 : $Buffer + %addr = ref_element_addr [immutable] %e : $Buffer, #Buffer.i + %i = load %addr : $*Int + (%u, %b) = begin_cow_mutation %e : $Buffer + %t = tuple (%i : $Int, %u : $Builtin.Int1, %b : $Buffer) + return %t : $(Int, Builtin.Int1, Buffer) +} + +// CHECK-LABEL: sil @test_store +// CHECK: end_cow_mutation +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: begin_cow_mutation +// CHECK: return [[I]] +// CHECK: } // end sil function 'test_store' +sil @test_store : $@convention(thin) (@inout Buffer) -> Builtin.Int1 { +bb0(%0 : $*Buffer): + %l = load %0 : $*Buffer + %e = end_cow_mutation %l : $Buffer + store %e to %0 : $*Buffer + (%u, %b) = begin_cow_mutation %e : $Buffer + store %b to %0 : $*Buffer + return %u : $Builtin.Int1 +} + +// CHECK-LABEL: sil @test_store_and_load +// CHECK: end_cow_mutation +// CHECK: ([[U:%[0-9]+]], {{.*}}) = begin_cow_mutation +// CHECK: return [[U]] +// CHECK: } // end sil function 'test_store_and_load' +sil @test_store_and_load : $@convention(thin) (@inout Buffer) -> Builtin.Int1 { +bb0(%0 : $*Buffer): + %l = load %0 : $*Buffer + %e = end_cow_mutation %l : $Buffer + store %e to %0 : $*Buffer + %l2 = load %0 : $*Buffer + %f = function_ref @unknown : $@convention(thin) (@guaranteed Buffer) -> () + apply %f(%l2) : $@convention(thin) (@guaranteed Buffer) -> () + (%u, %b) = begin_cow_mutation %e : $Buffer + store %b to %0 : $*Buffer + return %u : $Builtin.Int1 +} + +// CHECK-LABEL: sil @test_store_and_load_outside_liverange +// CHECK: end_cow_mutation +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: begin_cow_mutation +// CHECK: return [[I]] +// CHECK: } // end sil function 'test_store_and_load_outside_liverange' +sil @test_store_and_load_outside_liverange : $@convention(thin) (@inout Buffer) -> Builtin.Int1 { +bb0(%0 : $*Buffer): + %l = load %0 : $*Buffer + %e = end_cow_mutation %l : $Buffer + store %e to %0 : $*Buffer + (%u, %b) = begin_cow_mutation %e : $Buffer + %l2 = load %0 : $*Buffer + %f = function_ref @unknown : $@convention(thin) (@guaranteed Buffer) -> () + apply %f(%l2) : $@convention(thin) (@guaranteed Buffer) -> () + store %b to %0 : $*Buffer + return %u : $Builtin.Int1 +} + +// CHECK-LABEL: sil @test_loop +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: [[B:%[0-9]+]] = end_cow_mutation +// CHECK: [[T:%[0-9]+]] = tuple ([[I]] : $Builtin.Int1, [[B]] : $Buffer) +// CHECK: return [[T]] +// CHECK: } // end sil function 'test_loop' +sil @test_loop : $@convention(thin) (@owned Buffer) -> (Builtin.Int1, @owned Buffer) { +bb0(%0 : $Buffer): + %e = end_cow_mutation %0 : $Buffer + br bb1(%e : $Buffer) +bb1(%a : $Buffer): + (%u, %b) = begin_cow_mutation %a : $Buffer + %e2 = end_cow_mutation %b : $Buffer + cond_br undef, bb1(%e2 : $Buffer), bb2 +bb2: + %t = tuple (%u : $Builtin.Int1, %e2 : $Buffer) + return %t : $(Builtin.Int1, Buffer) +} + +// CHECK-LABEL: sil @test_escape_in_loop +// CHECK: ([[U:%[0-9]+]], {{.*}}) = begin_cow_mutation +// CHECK: [[B:%[0-9]+]] = end_cow_mutation +// CHECK: [[T:%[0-9]+]] = tuple ([[U]] : $Builtin.Int1, [[B]] : $Buffer) +// CHECK: return [[T]] +// CHECK: } // end sil function 'test_escape_in_loop' +sil @test_escape_in_loop : $@convention(thin) (@owned Buffer) -> (Builtin.Int1, @owned Buffer) { +bb0(%0 : $Buffer): + %f = function_ref @unknown : $@convention(thin) (@guaranteed Buffer) -> () + %e = end_cow_mutation %0 : $Buffer + br bb1(%e : $Buffer) +bb1(%a : $Buffer): + (%u, %b) = begin_cow_mutation %a : $Buffer + %e2 = end_cow_mutation %b : $Buffer + apply %f(%e2) : $@convention(thin) (@guaranteed Buffer) -> () + cond_br undef, bb1(%e2 : $Buffer), bb2 +bb2: + %t = tuple (%u : $Builtin.Int1, %e2 : $Buffer) + return %t : $(Builtin.Int1, Buffer) +} + +// CHECK-LABEL: sil @test_escape_outside_loop +// CHECK: [[I:%[0-9]+]] = integer_literal $Builtin.Int1, -1 +// CHECK: [[B:%[0-9]+]] = end_cow_mutation +// CHECK: [[T:%[0-9]+]] = tuple ([[I]] : $Builtin.Int1, [[B]] : $Buffer) +// CHECK: return [[T]] +// CHECK: } // end sil function 'test_escape_outside_loop' +sil @test_escape_outside_loop : $@convention(thin) (@owned Buffer) -> (Builtin.Int1, @owned Buffer) { +bb0(%0 : $Buffer): + %f = function_ref @unknown : $@convention(thin) (@guaranteed Buffer) -> () + %e = end_cow_mutation %0 : $Buffer + br bb1(%e : $Buffer) +bb1(%a : $Buffer): + (%u, %b) = begin_cow_mutation %a : $Buffer + %e2 = end_cow_mutation %b : $Buffer + cond_br undef, bb1(%e2 : $Buffer), bb2 +bb2: + apply %f(%e2) : $@convention(thin) (@guaranteed Buffer) -> () + %t = tuple (%u : $Builtin.Int1, %e2 : $Buffer) + return %t : $(Builtin.Int1, Buffer) +} + diff --git a/test/SILOptimizer/cse.sil b/test/SILOptimizer/cse.sil index 410d517635f1..52f65b49001b 100644 --- a/test/SILOptimizer/cse.sil +++ b/test/SILOptimizer/cse.sil @@ -617,21 +617,18 @@ bb0(%0 : $FakeOptional): class C {} class D : C { } -// CHECK-LABEL: sil @test1cse : $@convention(thin) (C) -> () { -// CHECK: unchecked_ref_cast +// CHECK-LABEL: sil @test1cse +// CHECK: [[C:%[0-9]+]] = unchecked_ref_cast // CHECK-NOT: unchecked_ref_cast -// CHECK: strong_release -// CHECK: strong_release -// CHECK: return -sil @test1cse : $@convention(thin) (C) -> () { +// CHECK: [[T:%[0-9]+]] = tuple ([[C]] : $Builtin.NativeObject, [[C]] : $Builtin.NativeObject) +// CHECK: return [[T]] +sil @test1cse : $@convention(thin) (C) -> (Builtin.NativeObject, Builtin.NativeObject) { bb0(%0 : $C): strong_retain %0 : $C %1 = unchecked_ref_cast %0 : $C to $Builtin.NativeObject %2 = unchecked_ref_cast %0 : $C to $Builtin.NativeObject - strong_release %1 : $Builtin.NativeObject - strong_release %2 : $Builtin.NativeObject - %5 = tuple() - return %5 : $() + %5 = tuple(%1 : $Builtin.NativeObject, %2 : $Builtin.NativeObject) + return %5 : $(Builtin.NativeObject, Builtin.NativeObject) } // CHECK-LABEL: sil @test2cse : $@convention(thin) (C) -> () { @@ -704,23 +701,20 @@ bb0(%0 : $*Builtin.Int8): } // CHECK-LABEL: sil @cse_unchecked_ref_cast -// CHECK: unchecked_ref_cast +// CHECK: [[C:%[0-9]+]] = unchecked_ref_cast // CHECK-NOT: unchecked_ref_cast -// CHECK: strong_release -// CHECK: strong_release -// CHECK: return -sil @cse_unchecked_ref_cast : $@convention(thin) (@owned B, Builtin.Int1) -> () { +// CHECK: [[T:%[0-9]+]] = tuple ([[C]] : $Builtin.NativeObject, [[C]] : $Builtin.NativeObject) +// CHECK: return [[T]] +sil @cse_unchecked_ref_cast : $@convention(thin) (@owned B, Builtin.Int1) -> (Builtin.NativeObject, Builtin.NativeObject) { bb0(%0: $B, %1: $Builtin.Int1): %5 = unchecked_ref_cast %0 : $B to $Builtin.NativeObject - strong_release %5 : $Builtin.NativeObject cond_br %1, bb1, bb2 bb1: br bb2 bb2: %21 = unchecked_ref_cast %0 : $B to $Builtin.NativeObject - strong_release %21 : $Builtin.NativeObject - %32 = tuple () - return %32 : $() + %32 = tuple(%5 : $Builtin.NativeObject, %21 : $Builtin.NativeObject) + return %32 : $(Builtin.NativeObject, Builtin.NativeObject) } // CHECK-LABEL: sil @cse_raw_pointer_to_ref diff --git a/test/SILOptimizer/dead_array_elim.sil b/test/SILOptimizer/dead_array_elim.sil index 620dbb5cec86..5d05d554ab50 100644 --- a/test/SILOptimizer/dead_array_elim.sil +++ b/test/SILOptimizer/dead_array_elim.sil @@ -25,6 +25,8 @@ sil [_semantics "array.uninitialized_intrinsic"] @allocArray : $@convention(thin sil [_semantics "array.uninitialized"] @adoptStorageSpecialiedForInt : $@convention(method) (@guaranteed _ContiguousArrayStorage, Builtin.Word, @thin Array.Type) -> (@owned Array, UnsafeMutablePointer) +sil [_semantics "array.finalize_intrinsic"] @finalize : $@convention(thin) (@owned Array) -> @owned Array + // CHECK-LABEL: sil @deadarrayWithAdoptStorage : $@convention(thin) () -> () { // CHECK-NOT: alloc_ref // CHECK-NOT: strong_release @@ -36,10 +38,16 @@ bb0: %7 = metatype $@thin Array.Type %8 = function_ref @adoptStorageSpecialiedForInt : $@convention(method) (@guaranteed _ContiguousArrayStorage, Builtin.Word, @thin Array.Type) -> (@owned Array, UnsafeMutablePointer) %9 = apply %8(%6, %0, %7) : $@convention(method) (@guaranteed _ContiguousArrayStorage, Builtin.Word, @thin Array.Type) -> (@owned Array, UnsafeMutablePointer) - strong_release %6 : $_ContiguousArrayStorage %10 = tuple_extract %9 : $(Array, UnsafeMutablePointer), 0 %11 = tuple_extract %9 : $(Array, UnsafeMutablePointer), 1 %12 = struct_extract %11 : $UnsafeMutablePointer, #UnsafeMutablePointer._rawValue + %f = function_ref @finalize : $@convention(thin) (@owned Array) -> @owned Array + %a = apply %f(%10) : $@convention(thin) (@owned Array) -> @owned Array + fix_lifetime %a : $Array + %13 = struct_extract %a : $Array, #Array._buffer + %14 = struct_extract %13 : $_ArrayBuffer, #_ArrayBuffer._storage + %15 = struct_extract %14 : $_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue + strong_release %15 : $Builtin.BridgeObject %9999 = tuple() return %9999 : $() } diff --git a/test/SILOptimizer/generic_specialization_loops_detection_with_loops.swift b/test/SILOptimizer/generic_specialization_loops_detection_with_loops.swift index 30d8f319fbf2..5c1bf0233dd8 100644 --- a/test/SILOptimizer/generic_specialization_loops_detection_with_loops.swift +++ b/test/SILOptimizer/generic_specialization_loops_detection_with_loops.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-frontend -O -emit-sil -enforce-exclusivity=unchecked -Xllvm -sil-print-generic-specialization-loops -Xllvm -sil-print-generic-specialization-info %s 2>&1 | %FileCheck --check-prefix=CHECK %s +// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib + // Check that the generic specializer does not hang a compiler by // creating and infinite loop of generic specializations. diff --git a/test/SILOptimizer/hello-world.swift b/test/SILOptimizer/hello-world.swift index 32009c597ec8..29a29f8681db 100644 --- a/test/SILOptimizer/hello-world.swift +++ b/test/SILOptimizer/hello-world.swift @@ -1,6 +1,6 @@ // RUN: rm -rf %t && mkdir -p %t/stats // RUN: %target-swift-frontend -emit-sil -stats-output-dir %t/stats %s -o /dev/null // RUN: %{python} %utils/process-stats-dir.py --evaluate 'NumSILGenFunctions < 10' %t/stats -// RUN: %{python} %utils/process-stats-dir.py --evaluate 'NumSILOptFunctions < 10' %t/stats +// RUN: %{python} %utils/process-stats-dir.py --evaluate 'NumSILOptFunctions < 20' %t/stats print("Hello world") diff --git a/test/SILOptimizer/licm_exclusivity.swift b/test/SILOptimizer/licm_exclusivity.swift index 1354f4caf93a..7ada653d6745 100644 --- a/test/SILOptimizer/licm_exclusivity.swift +++ b/test/SILOptimizer/licm_exclusivity.swift @@ -4,7 +4,7 @@ // RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -Xllvm -debug-only=sil-licm -whole-module-optimization %s 2>&1 | %FileCheck %s --check-prefix=TESTLICMWMO // RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -whole-module-optimization %s | %FileCheck %s --check-prefix=TESTSILWMO -// REQUIRES: optimized_stdlib,asserts +// REQUIRES: optimized_stdlib,asserts,swift_stdlib_no_asserts // REQUIRES: PTRSIZE=64 // TESTLICM-LABEL: Processing loops in {{.*}}run_ReversedArray{{.*}} diff --git a/test/SILOptimizer/redundant_load_elim.sil b/test/SILOptimizer/redundant_load_elim.sil index 684c7a1890df..633a72cd588c 100644 --- a/test/SILOptimizer/redundant_load_elim.sil +++ b/test/SILOptimizer/redundant_load_elim.sil @@ -157,6 +157,19 @@ bb0(%0 : $AB): return %5 : $Int // id: %15 } +// CHECK-LABEL: sil hidden @load_forward_across_end_cow_mutation +// CHECK-NOT: = load +// CHECK: return %1 +sil hidden @load_forward_across_end_cow_mutation : $@convention(thin) (@owned AB, Int) -> Int { +bb0(%0 : $AB, %1 : $Int): + %2 = ref_element_addr %0 : $AB, #AB.value + store %1 to %2 : $*Int + %4 = end_cow_mutation %0 : $AB + %5 = ref_element_addr %4 : $AB, #AB.value + %6 = load %5 : $*Int + return %6 : $Int +} + // CHECK-LABEL: sil hidden @redundant_load_across_fixlifetime_inst // CHECK: = load // CHECK-NOT: = load diff --git a/test/SILOptimizer/sil_combine.sil b/test/SILOptimizer/sil_combine.sil index 0b28ba548b13..cfb4377c2934 100644 --- a/test/SILOptimizer/sil_combine.sil +++ b/test/SILOptimizer/sil_combine.sil @@ -809,6 +809,44 @@ bb0(%0 : $C3): return %2 : $C1 } +// CHECK-LABEL: sil @dead_upcast +// CHECK: bb0 +// CHECK-NEXT: strong_retain %0 +// CHECK-NEXT: strong_release %0 +// CHECK-NEXT: return +sil @dead_upcast : $@convention(thin) (C2) -> C2 { +bb0(%0 : $C2): + %1 = upcast %0 : $C2 to $C1 + strong_retain %1 : $C1 + strong_release %0 : $C2 + return %0 : $C2 +} + +// CHECK-LABEL: sil @dead_unchecked_ref_cast +// CHECK: bb0 +// CHECK-NEXT: strong_retain %0 +// CHECK-NEXT: strong_release %0 +// CHECK-NEXT: return +sil @dead_unchecked_ref_cast : $@convention(thin) (C1) -> C1 { +bb0(%0 : $C1): + %1 = unchecked_ref_cast %0 : $C1 to $C2 + strong_retain %1 : $C2 + strong_release %0 : $C1 + return %0 : $C1 +} + +// CHECK-LABEL: sil @dead_end_cow_mutation +// CHECK: bb0 +// CHECK-NEXT: strong_retain %0 +// CHECK-NEXT: strong_release %0 +// CHECK-NEXT: return +sil @dead_end_cow_mutation : $@convention(thin) (C1) -> C1 { +bb0(%0 : $C1): + %1 = end_cow_mutation %0 : $C1 + strong_retain %1 : $C1 + strong_release %0 : $C1 + return %0 : $C1 +} struct XS { var m: Int @@ -2294,14 +2332,13 @@ bb0(%0 : $B, %1 : $@sil_unowned B, %2 : $AnyObject, %3: $@sil_unmanaged AnyObjec // CHECK-NOT: open_existential_ref // CHECK: unchecked_ref_cast [[Ref]] -sil @collapse_existential_pack_unpack_unchecked_ref_cast : $@convention(thin) (MyClass) -> () { +sil @collapse_existential_pack_unpack_unchecked_ref_cast : $@convention(thin) (MyClass) -> Builtin.NativeObject { bb0(%0: $MyClass): %1 = init_existential_ref %0 : $MyClass : $MyClass, $AnyObject %2 = open_existential_ref %1 : $AnyObject to $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987") AnyObject %3 = unchecked_ref_cast %2 : $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987") AnyObject to $Builtin.NativeObject strong_retain %3: $Builtin.NativeObject - %5 = tuple () - return %5 : $() + return %3 : $Builtin.NativeObject } // CHECK-LABEL: sil @collapse_existential_pack_unpack_ref_to_raw_pointer @@ -2531,15 +2568,12 @@ sil @alloc_ref_dynamic_with_metatype_genneric : $() -> () { // CHECK-NOT: alloc_ref_dynamic // CHECK-NEXT: [[R:%[0-9]+]] = alloc_ref $E // CHECK-NEXT: [[C:%[0-9]+]] = upcast [[R]] : $E to $B -// CHECK-NEXT: strong_release [[C]] -// CHECK: return -sil @alloc_ref_dynamic_with_upcast_metatype : $() -> () { +// CHECK-NEXT: return [[C]] +sil @alloc_ref_dynamic_with_upcast_metatype : $() -> B { %1 = metatype $@thick E.Type %2 = upcast %1 : $@thick E.Type to $@thick B.Type %3 = alloc_ref_dynamic %2 : $@thick B.Type, $B - strong_release %3 : $B - %4 = tuple() - return %4 : $() + return %3 : $B } // CHECK-LABEL: @alloc_ref_dynamic_after_successful_checked_cast_br @@ -2570,8 +2604,7 @@ bb3 (%10: $Builtin.Int32): // CHECK: bb1 // CHECK-NOT: alloc_ref_dynamic // CHECK: [[R:%[0-9]+]] = alloc_ref $E -// CHECK-NEXT: [[C:%[0-9]+]] = upcast [[R]] : $E to $B -// CHECK-NEXT: strong_release [[C]] +// CHECK-NEXT: strong_release [[R]] sil @alloc_ref_dynamic_upcast_after_successful_checked_cast_br : $(@thick B.Type) -> Builtin.Int32 { bb0(%1 : $@thick B.Type): checked_cast_br [exact] %1 : $@thick B.Type to E.Type, bb1, bb2 diff --git a/test/SILOptimizer/stack_promotion_array_literal.swift b/test/SILOptimizer/stack_promotion_array_literal.swift index 1f6b4e2c2a35..3e5d3c890905 100644 --- a/test/SILOptimizer/stack_promotion_array_literal.swift +++ b/test/SILOptimizer/stack_promotion_array_literal.swift @@ -7,12 +7,12 @@ // CHECK-LABEL: sil @{{.*}}testit // CHECK: alloc_ref [stack] [tail_elems -public func testit(_ N: Int) { +public func testit(_ N: Int, _ x: Int) { for _ in 0..