diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt index 319323a00f9..1cb0130076f 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt @@ -8,6 +8,7 @@ swift_compiler_sources(Optimizer SimplifyAllocRefDynamic.swift + SimplifyAllocStack.swift SimplifyApply.swift SimplifyBeginAndLoadBorrow.swift SimplifyBeginCOWMutation.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyAllocStack.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyAllocStack.swift new file mode 100644 index 00000000000..63dddd3050b --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyAllocStack.swift @@ -0,0 +1,298 @@ +//===--- SimplifyAllocStack.swift -----------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import SIL +import AST + +extension AllocStackInst : Simplifiable, SILCombineSimplifiable { + func simplify(_ context: SimplifyContext) { + if optimizeEnum(context) { + return + } + _ = optimizeExistential(context) + } +} + +private extension AllocStackInst { + /// Replaces an alloc_stack of an enum by an alloc_stack of the payload if only one enum case (with payload) + /// is stored to that location. + /// + /// For example: + /// ``` + /// %0 = alloc_stack $Optional + /// %1 = init_enum_data_addr %loc + /// store %2 to %1 + /// ... + /// %3 = unchecked_take_enum_data_addr %0 + /// %4 = load %3 + /// ``` + /// is transformed to + /// ``` + /// %0 = alloc_stack $T + /// store %2 to %0 + /// ... + /// %4 = load %0 + /// ``` + func optimizeEnum(_ context: SimplifyContext) -> Bool { + guard let (payloadType, isSingleInitTakePair) = getEnumInfo() else { + return false + } + + let builder = Builder(before: self, context) + let newAlloc = builder.createAllocStack(payloadType, + hasDynamicLifetime: hasDynamicLifetime, + isLexical: isLexical, + isFromVarDecl: isFromVarDecl, + usesMoveableValueDebugInfo: usesMoveableValueDebugInfo) + let oldAllocType = type + if let varInfo = debugVariable { + builder.createDebugValue(value: Undef.get(type: oldAllocType, context), debugVariable: varInfo) + } + self.replace(with: newAlloc, context) + + for use in newAlloc.uses { + switch use.instruction { + case let iea as InjectEnumAddrInst: + context.erase(instruction: iea) + case let da as DestroyAddrInst: + if isSingleInitTakePair { + // It's not possible that the enum has a payload at the destroy_addr, because it must have already + // been taken by the take of the single init-take pair. + // We _have_ to remove the destroy_addr, because we also remove all inject_enum_addrs which might + // inject a payload-less case before the destroy_addr. + // Otherwise the enum payload can still be valid at the destroy_addr, so we have to keep the destroy_addr. + // Just replace the enum with the payload (and because it's not a singleInitTakePair, we can be sure + // that the enum cannot have any other case than the payload case). + context.erase(instruction: da) + } + case let ieda as InitEnumDataAddrInst: + ieda.replace(with: newAlloc, context) + case let uteda as UncheckedTakeEnumDataAddrInst: + uteda.replace(with: newAlloc, context) + case is DeallocStackInst: + break + case let dv as DebugValueInst: + // TODO: Add support for op_enum_fragment + dv.operand.set(to: Undef.get(type: oldAllocType, context), context) + default: + fatalError("unexpected alloc_stack user"); + } + } + return true + } + + func getEnumInfo() -> (payloadType: Type, isSingleInitTakePair: Bool)? { + if !type.isEnum { + return nil + } + var numInits = 0 + var numTakes = 0 + var initBlock: BasicBlock? = nil + var takeBlock: BasicBlock? = nil + var caseIndex: Int? = nil + var payloadType: Type? = nil + for use in uses { + switch use.instruction { + case is DestroyAddrInst, + is DeallocStackInst, + is DebugValueInst, + // We'll check init_enum_addr below. + is InjectEnumAddrInst: + break + case let ieda as InitEnumDataAddrInst: + if let previouslyFoundCase = caseIndex, previouslyFoundCase != ieda.caseIndex { + return nil + } + caseIndex = ieda.caseIndex + assert(payloadType == nil || payloadType! == ieda.type) + payloadType = ieda.type + numInits += 1 + initBlock = ieda.parentBlock + case let uted as UncheckedTakeEnumDataAddrInst: + if let previouslyFoundCase = caseIndex, previouslyFoundCase != uted.caseIndex { + return nil + } + caseIndex = uted.caseIndex + numTakes += 1 + takeBlock = uted.parentBlock + default: + return nil + } + } + + guard let caseIndex, let payloadType else { + return nil + } + + // If the enum has a single init-take pair in a single block, we know that the enum cannot contain any + // valid payload outside that init-take pair. + // + // This also means that we can ignore any inject_enum_addr of another enum case, because this can only + // inject a case without a payload. + if numInits == 1 && numTakes == 1 && initBlock == takeBlock { + return (payloadType, isSingleInitTakePair: true) + } + // No single init-take pair: We cannot ignore inject_enum_addrs with a mismatching case. + if uses.users(ofType: InjectEnumAddrInst.self).contains(where: { $0.caseIndex != caseIndex}) { + return nil + } + return (payloadType, isSingleInitTakePair: false) + } + + /// Replaces an alloc_stack of an existential by an alloc_stack of the concrete type. + /// + /// For example: + /// ``` + /// %0 = alloc_stack $any P + /// %1 = init_existential_addr %0, $T + /// use %1 + /// ``` + /// is transformed to + /// ``` + /// %0 = alloc_stack $T + /// use %0 + /// ``` + /// + /// Also, if the alloc_stack is already an opened existential and the concrete type is known, + /// replace it as well: + /// ``` + /// %0 = metatype $@thick T.Type + /// %1 = init_existential_metatype %0, $@thick any P.Type + /// %2 = open_existential_metatype %1 : $@thick any P.Type to $@thick (@opened("X", P) Self).Type + /// ... + /// %3 = alloc_stack $@opened("X", any P) Self + /// use %3 + /// ``` + /// is transformed to + /// ``` + /// ... + /// %3 = alloc_stack $T + /// use %3 + /// ``` + func optimizeExistential(_ context: SimplifyContext) -> Bool { + guard type.astType.isExistential || type.astType.isExistentialArchetype, + let concreteFormalType = getConcreteTypeOfExistential() + else { + return false + } + + let builder = Builder(before: self, context) + let newAlloc = builder.createAllocStack(concreteFormalType.loweredType(in: parentFunction), + hasDynamicLifetime: hasDynamicLifetime, + isLexical: isLexical, + isFromVarDecl: isFromVarDecl, + usesMoveableValueDebugInfo: usesMoveableValueDebugInfo) + for use in uses { + switch use.instruction { + case let dea as DeinitExistentialAddrInst: + context.erase(instruction: dea) + case let iea as InitExistentialAddrInst: + if iea.type != newAlloc.type { + // We need a cast if the concrete type of the init_existential_addr is itself an opened existential + // for which we know the concrete type (which is differnt). + let builder = Builder(before: iea, context) + let addrCast = builder.createUncheckedAddrCast(from: newAlloc, to: iea.type) + iea.replace(with: addrCast, context) + } else { + iea.replace(with: newAlloc, context) + } + case let oea as OpenExistentialAddrInst: + assert(oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty) + oea.replace(with: newAlloc, context) + case let cab as CheckedCastAddrBranchInst: + let builder = Builder(before: cab, context) + builder.createCheckedCastAddrBranch( + source: newAlloc, sourceFormalType: concreteFormalType, + destination: cab.destination, targetFormalType: cab.targetFormalType, + consumptionKind: cab.consumptionKind, + successBlock: cab.successBlock, failureBlock: cab.failureBlock) + context.erase(instruction: cab) + case let ucca as UnconditionalCheckedCastAddrInst: + let builder = Builder(before: ucca, context) + builder.createUnconditionalCheckedCastAddr( + source: newAlloc, sourceFormalType: concreteFormalType, + destination: ucca.destination, targetFormalType: ucca.targetFormalType) + context.erase(instruction: ucca) + default: + use.set(to: newAlloc, context) + } + } + context.erase(instruction: self) + return true + } + + // Returns the concrete type of this alloc_stack if known. + // Assuming that its type is either an existential or an opened existential. + private func getConcreteTypeOfExistential() -> CanonicalType? { + var initExistential: InitExistentialAddrInst? = nil + var requiresLegalFormalType = false + + for use in uses { + switch use.instruction { + case is DestroyAddrInst, + is DeinitExistentialAddrInst, + is DeallocStackInst, + is DebugValueInst: + break + case let oea as OpenExistentialAddrInst: + if !oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty { + return nil + } + case let iea as InitExistentialAddrInst: + if initExistential != nil { + return nil + } + initExistential = iea + case is CheckedCastAddrBranchInst, is UnconditionalCheckedCastAddrInst: + // To construct a new cast instruction we need a formal type. + requiresLegalFormalType = true + fallthrough + case is UncheckedAddrCastInst: + if use != use.instruction.operands[0] { + return nil + } + default: + return nil + } + } + let concreteType: CanonicalType + if let initExistential { + assert(self.type.astType.isExistential) + if let cft = initExistential.concreteTypeOfDependentExistentialArchetype { + // Case 1: We will replace the alloc_stack of an existential with the concrete type. + // `alloc_stack $any P` -> `alloc_stack $ConcreteType` + concreteType = cft + } else { + // The instruction which defines the existential archetype must dominate the alloc_stack, because + // after the transformation the alloc_stack will use the existential archetype. + for openArchetypeOp in initExistential.typeDependentOperands { + if !openArchetypeOp.value.definingInstruction!.dominatesInSameBlock(self) { + return nil + } + } + // Case 2: We will replace the alloc_stack of an existential with the existential archetype. + // `alloc_stack $any P` -> `alloc_stack $@opened("...")` + concreteType = initExistential.type.astType + } + } else if self.type.astType.isExistentialArchetype, let cft = self.concreteTypeOfDependentExistentialArchetype { + // Case 3: We will replace the alloc_stack of an existential archetype with the concrete type: + // `alloc_stack $@opened("...")` -> `alloc_stack $ConcreteType` + concreteType = cft + } else { + return nil + } + if requiresLegalFormalType && !concreteType.isLegalFormalType { + return nil + } + return concreteType + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift index 8a1b83d379c..cd0376ef708 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift @@ -121,6 +121,7 @@ private func registerSwiftPasses() { registerForSILCombine(UncheckedEnumDataInst.self, { run(UncheckedEnumDataInst.self, $0) }) registerForSILCombine(WitnessMethodInst.self, { run(WitnessMethodInst.self, $0) }) registerForSILCombine(UnconditionalCheckedCastInst.self, { run(UnconditionalCheckedCastInst.self, $0) }) + registerForSILCombine(AllocStackInst.self, { run(AllocStackInst.self, $0) }) registerForSILCombine(ApplyInst.self, { run(ApplyInst.self, $0) }) registerForSILCombine(TryApplyInst.self, { run(TryApplyInst.self, $0) }) diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 6a962d2e05e..1ae0ff3c36a 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -540,6 +540,7 @@ SWIFT_SILCOMBINE_PASS(PointerToAddressInst) SWIFT_SILCOMBINE_PASS(TypeValueInst) SWIFT_SILCOMBINE_PASS(UncheckedEnumDataInst) SWIFT_SILCOMBINE_PASS(WitnessMethodInst) +SWIFT_SILCOMBINE_PASS_WITH_LEGACY(AllocStackInst) SWIFT_SILCOMBINE_PASS_WITH_LEGACY(UnconditionalCheckedCastInst) SWIFT_SILCOMBINE_PASS_WITH_LEGACY(ApplyInst) SWIFT_SILCOMBINE_PASS_WITH_LEGACY(TryApplyInst) diff --git a/lib/SILOptimizer/SILCombiner/SILCombiner.h b/lib/SILOptimizer/SILCombiner/SILCombiner.h index 1fedf420b39..4f8692c6ad2 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombiner.h +++ b/lib/SILOptimizer/SILCombiner/SILCombiner.h @@ -256,7 +256,6 @@ public: SILInstruction *visitIndexAddrInst(IndexAddrInst *IA); bool optimizeStackAllocatedEnum(AllocStackInst *AS); - SILInstruction *visitAllocStackInst(AllocStackInst *AS); SILInstruction *visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI); SILInstruction *visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI); SILInstruction *visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI); diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp index 90ccbf4066d..c74e83be000 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp @@ -321,21 +321,7 @@ namespace { /// A SILInstruction visitor that analyzes alloc stack values for dead live /// range and promotion opportunities. /// -/// init_existential_addr instructions behave like memory allocation within the -/// allocated object. We can promote the init_existential_addr allocation into a -/// dedicated allocation. -/// -/// We detect this pattern -/// %0 = alloc_stack $LogicValue -/// %1 = init_existential_addr %0 : $*LogicValue, $*Bool -/// ... -/// use of %1 -/// ... -/// destroy_addr %0 : $*LogicValue -/// dealloc_stack %0 : $*LogicValue -/// -/// At the same we time also look for dead alloc_stack live ranges that are only -/// copied into. +/// We look for dead alloc_stack live ranges that are only copied into. /// /// %0 = alloc_stack /// copy_addr %src, %0 @@ -347,22 +333,6 @@ struct AllocStackAnalyzer : SILInstructionVisitor { /// Do all of the users of the alloc stack allow us to perform optimizations. bool LegalUsers = true; - - /// If we saw an init_existential_addr in the use list of the alloc_stack, - /// this is the init_existential_addr. We are conservative in the face of - /// having multiple init_existential_addr. In such a case, we say that the use - /// list of the alloc_stack does not allow for optimizations to occur. - InitExistentialAddrInst *IEI = nullptr; - - /// If we saw an open_existential_addr in the use list of the alloc_stack, - /// this is the open_existential_addr. We are conservative in the case of - /// multiple open_existential_addr. In such a case, we say that the use list - /// of the alloc_stack does not allow for optimizations to occur. - OpenExistentialAddrInst *OEI = nullptr; - - /// Did we see any copies into the alloc stack. - bool HaveSeenCopyInto = false; - public: AllocStackAnalyzer(AllocStackInst *ASI) : ASI(ASI) {} @@ -402,26 +372,7 @@ public: void visitDeinitExistentialAddrInst(DeinitExistentialAddrInst *I) {} void visitDeallocStackInst(DeallocStackInst *I) {} - void visitInitExistentialAddrInst(InitExistentialAddrInst *I) { - // If we have already seen an init_existential_addr, we cannot - // optimize. This is because we only handle the single init_existential_addr - // case. - if (IEI || HaveSeenCopyInto) { - LegalUsers = false; - return; - } - IEI = I; - } - void visitOpenExistentialAddrInst(OpenExistentialAddrInst *I) { - // If we have already seen an open_existential_addr, we cannot - // optimize. This is because we only handle the single open_existential_addr - // case. - if (OEI) { - LegalUsers = false; - return; - } - // Make sure that the open_existential does not have any uses except // destroy_addr. for (auto *Use : getNonDebugUses(I)) { @@ -430,22 +381,13 @@ public: return; } } - - OEI = I; } void visitCopyAddrInst(CopyAddrInst *I) { - if (IEI) { - LegalUsers = false; - return; - } - // Copies into the alloc_stack live range are safe. if (I->getDest() == ASI) { - HaveSeenCopyInto = true; return; } - LegalUsers = false; } }; @@ -482,156 +424,7 @@ static bool somethingIsRetained(SILInstruction *from, AllocStackInst *alloc) { return false; } -/// Replaces an alloc_stack of an enum by an alloc_stack of the payload if only -/// one enum case (with payload) is stored to that location. -/// -/// For example: -/// -/// %loc = alloc_stack $Optional -/// %payload = init_enum_data_addr %loc -/// store %value to %payload -/// ... -/// %take_addr = unchecked_take_enum_data_addr %loc -/// %l = load %take_addr -/// -/// is transformed to -/// -/// %loc = alloc_stack $T -/// store %value to %loc -/// ... -/// %l = load %loc -bool SILCombiner::optimizeStackAllocatedEnum(AllocStackInst *AS) { - EnumDecl *enumDecl = AS->getType().getEnumOrBoundGenericEnum(); - if (!enumDecl) - return false; - - EnumElementDecl *element = nullptr; - unsigned numInits =0; - unsigned numTakes = 0; - SILBasicBlock *initBlock = nullptr; - SILBasicBlock *takeBlock = nullptr; - SILType payloadType; - - // First step: check if the stack location is only used to hold one specific - // enum case with payload. - for (auto *use : AS->getUses()) { - SILInstruction *user = use->getUser(); - switch (user->getKind()) { - case SILInstructionKind::DestroyAddrInst: - case SILInstructionKind::DeallocStackInst: - case SILInstructionKind::InjectEnumAddrInst: - // We'll check init_enum_addr below. - break; - case SILInstructionKind::DebugValueInst: - if (DebugValueInst::hasAddrVal(user)) - break; - return false; - case SILInstructionKind::InitEnumDataAddrInst: { - auto *ieda = cast(user); - auto *el = ieda->getElement(); - if (element && el != element) - return false; - element = el; - assert(!payloadType || payloadType == ieda->getType()); - payloadType = ieda->getType(); - numInits++; - initBlock = user->getParent(); - break; - } - case SILInstructionKind::UncheckedTakeEnumDataAddrInst: { - auto *el = cast(user)->getElement(); - if (element && el != element) - return false; - element = el; - numTakes++; - takeBlock = user->getParent(); - break; - } - default: - return false; - } - } - if (!element || !payloadType) - return false; - - // If the enum has a single init-take pair in a single block, we know that - // the enum cannot contain any valid payload outside that init-take pair. - // - // This also means that we can ignore any inject_enum_addr of another enum - // case, because this can only inject a case without a payload. - bool singleInitTakePair = - (numInits == 1 && numTakes == 1 && initBlock == takeBlock); - if (!singleInitTakePair) { - // No single init-take pair: We cannot ignore inject_enum_addrs with a - // mismatching case. - for (auto *use : AS->getUses()) { - if (auto *inject = dyn_cast(use->getUser())) { - if (inject->getElement() != element) - return false; - } - } - } - - // Second step: replace the enum alloc_stack with a payload alloc_stack. - Builder.setCurrentDebugScope(AS->getDebugScope()); - auto *newAlloc = Builder.createAllocStack( - AS->getLoc(), payloadType, {}, AS->hasDynamicLifetime(), IsNotLexical, - IsNotFromVarDecl, DoesNotUseMoveableValueDebugInfo, true); - if (auto varInfo = AS->getVarInfo()) { - // TODO: Add support for op_enum_fragment - // For now, we can't represent this variable correctly, so we drop it. - Builder.createDebugValue(AS->getLoc(), SILUndef::get(AS), *varInfo); - } - - while (!AS->use_empty()) { - Operand *use = *AS->use_begin(); - SILInstruction *user = use->getUser(); - switch (user->getKind()) { - case SILInstructionKind::InjectEnumAddrInst: - eraseInstFromFunction(*user); - break; - case SILInstructionKind::DestroyAddrInst: - if (singleInitTakePair) { - // It's not possible that the enum has a payload at the destroy_addr, - // because it must have already been taken by the take of the - // single init-take pair. - // We _have_ to remove the destroy_addr, because we also remove all - // inject_enum_addrs which might inject a payload-less case before - // the destroy_addr. - eraseInstFromFunction(*user); - } else { - // The enum payload can still be valid at the destroy_addr, so we have - // to keep the destroy_addr. Just replace the enum with the payload - // (and because it's not a singleInitTakePair, we can be sure that the - // enum cannot have any other case than the payload case). - use->set(newAlloc); - } - break; - case SILInstructionKind::DeallocStackInst: - use->set(newAlloc); - break; - case SILInstructionKind::InitEnumDataAddrInst: - case SILInstructionKind::UncheckedTakeEnumDataAddrInst: { - auto *svi = cast(user); - svi->replaceAllUsesWith(newAlloc); - eraseInstFromFunction(*svi); - break; - } - case SILInstructionKind::DebugValueInst: - // TODO: Add support for op_enum_fragment - use->set(SILUndef::get(AS)); - break; - default: - llvm_unreachable("unexpected alloc_stack user"); - } - } - return true; -} - -SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) { - if (optimizeStackAllocatedEnum(AS)) - return nullptr; - +SILInstruction *SILCombiner::legacyVisitAllocStackInst(AllocStackInst *AS) { // If we are testing SILCombine and we are asked not to eliminate // alloc_stacks, just return. if (DisableAllocStackOpts) @@ -645,65 +438,6 @@ SILInstruction *SILCombiner::visitAllocStackInst(AllocStackInst *AS) { if (!Analyzer.LegalUsers) return nullptr; - InitExistentialAddrInst *IEI = Analyzer.IEI; - OpenExistentialAddrInst *OEI = Analyzer.OEI; - - // If the only users of the alloc_stack are alloc, destroy and - // init_existential_addr then we can promote the allocation of the init - // existential. - // Be careful with open archetypes, because they cannot be moved before - // their definitions. - if (IEI && !OEI && - !IEI->getLoweredConcreteType().hasOpenedExistential()) { - Builder.setCurrentDebugScope(AS->getDebugScope()); - auto varInfo = AS->getVarInfo(); - if (varInfo) { - if (varInfo->Type == AS->getElementType()) { - varInfo->Type = {}; // Lower the variable's type too. - } else { - // Cannot salvage the variable, its type has changed and its expression - // cannot be rewritten. - Builder.createDebugValue(AS->getLoc(), SILUndef::get(AS), *varInfo); - varInfo = {}; - } - } - auto *ConcAlloc = Builder.createAllocStack( - AS->getLoc(), IEI->getLoweredConcreteType(), varInfo); - IEI->replaceAllUsesWith(ConcAlloc); - eraseInstFromFunction(*IEI); - - for (auto UI = AS->use_begin(), UE = AS->use_end(); UI != UE;) { - auto *Op = *UI; - ++UI; - if (auto *DA = dyn_cast(Op->getUser())) { - Builder.setInsertionPoint(DA); - Builder.createDestroyAddr(DA->getLoc(), ConcAlloc); - eraseInstFromFunction(*DA); - continue; - } - - if (isa(Op->getUser())) { - eraseInstFromFunction(*Op->getUser()); - continue; - } - - if (!isa(Op->getUser())) - continue; - - auto *DS = cast(Op->getUser()); - Builder.setInsertionPoint(DS); - Builder.createDeallocStack(DS->getLoc(), ConcAlloc); - eraseInstFromFunction(*DS); - } - - return eraseInstFromFunction(*AS); - } - - // If we have a live 'live range' or a live range that we have not sen a copy - // into, bail. - if (!Analyzer.HaveSeenCopyInto || IEI) - return nullptr; - // Otherwise remove the dead live range that is only copied into. // // TODO: Do we not remove purely dead live ranges here? Seems like we should. diff --git a/test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift b/test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift index 8fac9168a9b..94fa6e7d629 100644 --- a/test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift +++ b/test/Concurrency/Runtime/async_task_locals_isolated_deinit.swift @@ -51,6 +51,7 @@ actor ActorNoOp { self.group = group self.probe = Probe(expectedNumber: expectedNumber, group: group) self.probe.probeExpectedExecutor = self.unownedExecutor + _fixLifetime(self) } isolated deinit { diff --git a/test/SILOptimizer/devirt_protocol_method_invocations.swift b/test/SILOptimizer/devirt_protocol_method_invocations.swift index 3ab29974178..25a9162be3c 100644 --- a/test/SILOptimizer/devirt_protocol_method_invocations.swift +++ b/test/SILOptimizer/devirt_protocol_method_invocations.swift @@ -140,6 +140,9 @@ public func test_devirt_protocol_extension_method_invocation_with_self_return_ty // CHECK: return [[T1]] // CHECK: sil @$s34devirt_protocol_method_invocations14testExMetatypeSiyF +// CHECK: [[T0:%.*]] = integer_literal +// CHECK: [[T2:%.*]] = struct $Int ([[T0]] : {{.*}}) +// CHECK: return [[T2]] : $Int @inline(never) public func test_devirt_protocol_method_invocation(_ c: C) -> Int { diff --git a/test/SILOptimizer/existential_specializer_soletype.sil b/test/SILOptimizer/existential_specializer_soletype.sil index 432777686f9..814f3566298 100644 --- a/test/SILOptimizer/existential_specializer_soletype.sil +++ b/test/SILOptimizer/existential_specializer_soletype.sil @@ -166,7 +166,7 @@ sil @$s6testProtocolMethod : $@convention(method) <τ_0_0 where τ_0_0 : BasePro // Verify that the optimization was not performance and that we don't hang as a result. // CHECK-LABEL: sil hidden @$s6test1_ConcreteInitExistential : $@convention(method) (@in any SubProtocol) -> () { -// CHECK: [[E:%.*]] = init_existential_addr %{{.*}} : $*any SubProtocol, $@opened("{{.*}}", any SubProtocol) Self +// CHECK: [[E:%.*]] = alloc_stack $@opened("{{.*}}", any SubProtocol) Self // CHECK: apply %{{.*}}<@opened("{{.*}}", any SubProtocol) Self>([[E]], %{{.*}}) : $@convention(method) <τ_0_0 where τ_0_0 : BaseProtocol> (@in_guaranteed τ_0_0) -> @out τ_0_0 // CHECK-LABEL: } // end sil function '$s6test1_ConcreteInitExistential' sil hidden @$s6test1_ConcreteInitExistential : $@convention(method) (@in SubProtocol) -> () { diff --git a/test/SILOptimizer/functionsigopts_attrs.swift b/test/SILOptimizer/functionsigopts_attrs.swift index 9ac9db6d651..20dabb754fe 100644 --- a/test/SILOptimizer/functionsigopts_attrs.swift +++ b/test/SILOptimizer/functionsigopts_attrs.swift @@ -43,9 +43,10 @@ public func caller_guaranteed_eagerMove(s: S) { callee_guaranteed_eagerMove(s) } -// CHECK-LABEL: sil {{.*}}@$s4main22callee_owned_eagerMoveyyAA1P_pnFTf4e_nTf4g_n : {{.*}}{ -// CHECK: {{bb[0-9]+}}({{%[^,]+}} : @_eagerMove $ -// CHECK-LABEL: } // end sil function '$s4main22callee_owned_eagerMoveyyAA1P_pnFTf4e_nTf4g_n' +// TODO: update the test. Some exitential->generic specialization is happening, too. +// xCHECK-LABEL: sil {{.*}}@$s4main22callee_owned_eagerMoveyyAA1P_pnFTf4e_nTf4g_n : {{.*}}{ +// xCHECK: {{bb[0-9]+}}({{%[^,]+}} : @_eagerMove $ +// xCHECK-LABEL: } // end sil function '$s4main22callee_owned_eagerMoveyyAA1P_pnFTf4e_nTf4g_n' @inline(never) func callee_owned_eagerMove(@_eagerMove _ p: __owned P) { p.foo() diff --git a/test/SILOptimizer/sil_combine.sil b/test/SILOptimizer/sil_combine.sil index 3d86d95d246..c0a9262fd12 100644 --- a/test/SILOptimizer/sil_combine.sil +++ b/test/SILOptimizer/sil_combine.sil @@ -196,6 +196,7 @@ sil @dead_use_of_alloc_stack : $@convention(thin) () -> () { bb0: %1 = alloc_stack $((), (), ()) %2 = tuple_element_addr %1 : $*((), (), ()), 0 + fix_lifetime %2 dealloc_stack %1 : $*((), (), ()) %3 = tuple () return %3 : $() @@ -3516,9 +3517,9 @@ sil @any_to_object : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> @own // CHECK-LABEL: sil @dont_crash_when_propagating_existentials // CHECK: [[EM:%[0-9]+]] = init_existential_metatype %0 -// CHECK: [[S:%[0-9]+]] = alloc_stack $Any +// CHECK: [[S:%[0-9]+]] = alloc_stack $C // CHECK: [[M:%[0-9]+]] = open_existential_metatype [[EM]] -// CHECK: [[E:%[0-9]+]] = init_existential_addr [[S]] +// CHECK: [[E:%[0-9]+]] = unchecked_addr_cast [[S]] // CHECK: store [[M]] to [[E]] // CHECK: apply {{%[0-9]+}}<(@opened("5F99B72C-EC40-11EA-9534-8C8590A6A134", AnyObject) Self).Type>([[E]]) // CHECK: } // end sil function 'dont_crash_when_propagating_existentials' @@ -3790,8 +3791,6 @@ bb0(%0 : $B): // CHECK-LABEL: sil @mark_dependence_trivial_address_base : // CHECK: bb0( // CHECK-NEXT: strong_retain -// CHECK-NEXT: alloc_stack $Int -// CHECK-NEXT: dealloc_stack // CHECK-NEXT: return // CHECK: } // end sil function 'mark_dependence_trivial_address_base' sil @mark_dependence_trivial_address_base : $@convention(thin) (@guaranteed B) -> @owned B { diff --git a/test/SILOptimizer/sil_combine_cast_foldings.sil b/test/SILOptimizer/sil_combine_cast_foldings.sil index fbcb5a16b54..42acede3fbc 100644 --- a/test/SILOptimizer/sil_combine_cast_foldings.sil +++ b/test/SILOptimizer/sil_combine_cast_foldings.sil @@ -125,9 +125,7 @@ protocol P {} // CHECK-LABEL: sil @remove_dead_checked_cast // CHECK: bb0(%0 : $*AnyObject): -// CHECK-NEXT: alloc_stack // CHECK-NEXT: destroy_addr %0 -// CHECK-NEXT: dealloc_stack // CHECK-NEXT: tuple // CHECK-NEXT: return sil @remove_dead_checked_cast : $@convention(thin) (@in AnyObject) -> () { diff --git a/test/SILOptimizer/sil_combine_cast_foldings_ossa.sil b/test/SILOptimizer/sil_combine_cast_foldings_ossa.sil index 079dab02a4f..70a7a62d047 100644 --- a/test/SILOptimizer/sil_combine_cast_foldings_ossa.sil +++ b/test/SILOptimizer/sil_combine_cast_foldings_ossa.sil @@ -145,9 +145,7 @@ protocol P {} // // CHECK-LABEL: sil [ossa] @remove_dead_checked_cast : // CHECK: bb0(%0 : $*AnyObject): -// CHECK-NEXT: alloc_stack // CHECK-NEXT: destroy_addr %0 -// CHECK-NEXT: dealloc_stack // CHECK-NEXT: tuple // CHECK-NEXT: return // CHECK: } // end sil function 'remove_dead_checked_cast' diff --git a/test/SILOptimizer/sil_combine_concrete_existential.sil b/test/SILOptimizer/sil_combine_concrete_existential.sil index a8cbcda7c4e..6f14c4cd039 100644 --- a/test/SILOptimizer/sil_combine_concrete_existential.sil +++ b/test/SILOptimizer/sil_combine_concrete_existential.sil @@ -664,8 +664,7 @@ sil @callee2 : $@convention(thin) <τ_0_0 where τ_0_0 : SubscriptionViewControl // CHECK: bb0([[ARG:%.*]] : $any ResourceKitProtocol, [[ARG2:%.*]] : $ViewController): // CHECK: [[T1:%.*]] = metatype $@thick SubscriptionViewControllerBuilder.Type // CHECK: [[T2:%.*]] = open_existential_ref [[ARG]] : $any ResourceKitProtocol to $@opened("E4D92D2A-8893-11EA-9C89-ACDE48001122", any ResourceKitProtocol) Self -// CHECK: [[T3:%.*]] = alloc_stack $any SubscriptionViewControllerDelegate -// CHECK: [[T4:%.*]] = init_existential_addr [[T3]] : $*any SubscriptionViewControllerDelegate, $@opened("E4D92D2A-8893-11EA-9C89-ACDE48001122", any ResourceKitProtocol) Self +// CHECK: [[T4:%.*]] = alloc_stack $@opened("E4D92D2A-8893-11EA-9C89-ACDE48001122" // CHECK: store [[T2]] to [[T4]] : $*@opened("E4D92D2A-8893-11EA-9C89-ACDE48001122", any ResourceKitProtocol) Self // CHECK: [[T5:%.*]] = function_ref @callee2 : $@convention(thin) <τ_0_0 where τ_0_0 : SubscriptionViewControllerDelegate> (@in τ_0_0, @thick SubscriptionViewControllerBuilder.Type) -> @owned SubscriptionViewControllerBuilder // CHECK: [[T6:%.*]] = alloc_stack $@opened("E4D92D2A-8893-11EA-9C89-ACDE48001122", any ResourceKitProtocol) Self diff --git a/test/SILOptimizer/sil_combine_concrete_existential_noncopyable.swift b/test/SILOptimizer/sil_combine_concrete_existential_noncopyable.swift index 061a7d5081d..fc3fb239638 100644 --- a/test/SILOptimizer/sil_combine_concrete_existential_noncopyable.swift +++ b/test/SILOptimizer/sil_combine_concrete_existential_noncopyable.swift @@ -11,7 +11,7 @@ public struct S: P { } // CHECK-LABEL: sil @$s44sil_combine_concrete_existential_noncopyable1gyyF : $@convention(thin) () -> () { -// CHECK: [[S_ADDR:%.*]] = alloc_stack $S +// CHECK: [[S_ADDR:%.*]] = alloc_stack [var_decl] $S // CHECK: [[INIT_FN:%.*]] = function_ref @$s44sil_combine_concrete_existential_noncopyable1SVACycfC : $@convention(method) (@thin S.Type) -> S // CHECK: [[S:%.*]] = apply [[INIT_FN]]({{%.*}}) : $@convention(method) (@thin S.Type) -> S // CHECK: store [[S]] to [[S_ADDR]] diff --git a/test/SILOptimizer/sil_combine_concrete_existential_ossa.swift b/test/SILOptimizer/sil_combine_concrete_existential_ossa.swift index a6e49a982b8..cc226b8cdd5 100644 --- a/test/SILOptimizer/sil_combine_concrete_existential_ossa.swift +++ b/test/SILOptimizer/sil_combine_concrete_existential_ossa.swift @@ -138,3 +138,37 @@ public func testExtensionProtocolComposition(c: C_PQ) { let pp: P & Q = c pp.witnessComposition() } + + +public protocol ProtoA { } + +public protocol ProtoB: ProtoA { + init() +} + +func createB(_: T.Type) -> T { + T.init() +} + +func takesA(_ type: T.Type) -> T? { + if let bType = T.self as? ProtoB.Type { + return createB(bType) as? T + } + return nil +} + +public struct SomeStruct: ProtoB { + var x = 27 + public init() {} +} + +// CHECK-LABEL: sil @$s37sil_combine_concrete_existential_ossa16createSomeStructAA0gH0VSgyF : +// CHECK: [[L:%.*]] = integer_literal $Builtin.Int64, 27 +// CHECK-NEXT: [[I:%.*]] = struct $Int ([[L]] : $Builtin.Int64) +// CHECK-NEXT: [[S:%.*]] = struct $SomeStruct ([[I]] : $Int) +// CHECK-NEXT: [[O:%.*]] = enum $Optional, #Optional.some!enumelt, [[S]] : $SomeStruct +// CHECK-NEXT: return [[O]] : $Optional +// CHECK: } // end sil function '$s37sil_combine_concrete_existential_ossa16createSomeStructAA0gH0VSgyF' +public func createSomeStruct() -> SomeStruct? { + return takesA(SomeStruct.self) +} diff --git a/test/SILOptimizer/sil_combine_ossa.sil b/test/SILOptimizer/sil_combine_ossa.sil index 9a276f9187d..16ae0637131 100644 --- a/test/SILOptimizer/sil_combine_ossa.sil +++ b/test/SILOptimizer/sil_combine_ossa.sil @@ -253,6 +253,7 @@ sil [ossa] @dead_use_of_alloc_stack : $@convention(thin) () -> () { bb0: %1 = alloc_stack $((), (), ()) %2 = tuple_element_addr %1 : $*((), (), ()), 0 + fix_lifetime %2 dealloc_stack %1 : $*((), (), ()) %3 = tuple () return %3 : $() @@ -4027,9 +4028,9 @@ sil [ossa] @any_to_object : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) // CHECK-LABEL: sil [ossa] @dont_crash_when_propagating_existentials // CHECK: [[EM:%[0-9]+]] = init_existential_metatype %0 -// CHECK: [[S:%[0-9]+]] = alloc_stack $Any +// CHECK: [[S:%[0-9]+]] = alloc_stack $C // CHECK: [[M:%[0-9]+]] = open_existential_metatype [[EM]] -// CHECK: [[E:%[0-9]+]] = init_existential_addr [[S]] +// CHECK: [[E:%[0-9]+]] = unchecked_addr_cast [[S]] // CHECK: store [[M]] to [trivial] [[E]] // CHECK: apply {{%[0-9]+}}<(@opened("5F99B72C-EC40-11EA-9534-8C8590A6A134", AnyObject) Self).Type>([[E]]) // CHECK: } // end sil function 'dont_crash_when_propagating_existentials' diff --git a/test/SILOptimizer/simplify_alloc_stack.sil b/test/SILOptimizer/simplify_alloc_stack.sil new file mode 100644 index 00000000000..9631ce6693a --- /dev/null +++ b/test/SILOptimizer/simplify_alloc_stack.sil @@ -0,0 +1,348 @@ +// RUN: %target-sil-opt %s -simplification -simplify-instruction=alloc_stack | %FileCheck %s + +import Swift +import Builtin + +public class C {} + +public struct S {} + +protocol P { +} + +public struct T: P { + let c: C + let s: S +} + +public struct T1: P { + let x: Int +} + +public struct T2: P { + let x: Int +} + +enum MP { + case A(S) + case B(S) +} + +public enum X { + case none + case some(T) +} + +sil [ossa] @take_s : $@convention(thin) (@in S) -> () +sil [ossa] @take_t : $@convention(thin) (@in T) -> () +sil [ossa] @use_mp : $@convention(thin) (@in_guaranteed MP) -> () + +// CHECK-LABEL: sil [ossa] @expand_alloc_stack_of_enum1 : +// CHECK: [[A:%[0-9]+]] = alloc_stack $S +// CHECK: bb1: +// CHECK-NEXT: store %0 to [trivial] [[A]] +// CHECK: bb2: +// CHECK-NEXT: store %0 to [trivial] [[A]] +// CHECK: bb3: +// CHECK: apply {{%[0-9]+}}([[A]]) +// CHECK: } // end sil function 'expand_alloc_stack_of_enum1' +sil [ossa] @expand_alloc_stack_of_enum1 : $@convention(method) (S) -> () { +bb0(%0 : $S): + %1 = alloc_stack $MP + cond_br undef, bb1, bb2 + +bb1: + %2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %2 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + br bb3 + +bb2: + %3 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %3 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + br bb3 + +bb3: + %7 = unchecked_take_enum_data_addr %1 : $*MP, #MP.A!enumelt + %8 = function_ref @take_s : $@convention(thin) (@in S) -> () + %9 = apply %8(%7) : $@convention(thin) (@in S) -> () + dealloc_stack %1 : $*MP + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @expand_alloc_stack_of_enum_without_take : +// CHECK: [[A:%[0-9]+]] = alloc_stack $S +// CHECK: bb1: +// CHECK-NEXT: store %0 to [trivial] [[A]] +// CHECK: bb2: +// CHECK-NEXT: store %0 to [trivial] [[A]] +// CHECK: bb3: +// CHECK: destroy_addr [[A]] +// CHECK: } // end sil function 'expand_alloc_stack_of_enum_without_take' +sil [ossa] @expand_alloc_stack_of_enum_without_take : $@convention(method) (S) -> () { +bb0(%0 : $S): + %1 = alloc_stack $MP + cond_br undef, bb1, bb2 + +bb1: + %2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %2 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + br bb3 + +bb2: + %3 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %3 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + br bb3 + +bb3: + destroy_addr %1 : $*MP + dealloc_stack %1 : $*MP + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @expand_alloc_stack_of_enum_multiple_cases : +// CHECK: [[A:%[0-9]+]] = alloc_stack $T +// CHECK: bb1: +// CHECK-NEXT: [[COPY_ARG:%.*]] = copy_value %0 +// CHECK-NEXT: store [[COPY_ARG]] to [init] [[A]] +// CHECK: apply {{%[0-9]+}}([[A]]) +// CHECK: bb2: +// CHECK-NEXT: br bb3 +// CHECK: bb3: +// CHECK: } // end sil function 'expand_alloc_stack_of_enum_multiple_cases' +sil [ossa] @expand_alloc_stack_of_enum_multiple_cases : $@convention(method) (@guaranteed T) -> () { +bb0(%0 : @guaranteed $T): + %1 = alloc_stack $X + cond_br undef, bb1, bb2 + +bb1: + %2 = init_enum_data_addr %1 : $*X, #X.some!enumelt + %0a = copy_value %0 : $T + store %0a to [init] %2 : $*T + inject_enum_addr %1 : $*X, #X.some!enumelt + %7 = unchecked_take_enum_data_addr %1 : $*X, #X.some!enumelt + %8 = function_ref @take_t : $@convention(thin) (@in T) -> () + %9 = apply %8(%7) : $@convention(thin) (@in T) -> () + br bb3 + +bb2: + inject_enum_addr %1 : $*X, #X.none!enumelt + destroy_addr %1 : $*X + br bb3 + +bb3: + dealloc_stack %1 : $*X + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_expand_alloc_stack_of_enum_multiple_cases : +// CHECK: alloc_stack $MP +// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_multiple_cases' +sil [ossa] @dont_expand_alloc_stack_of_enum_multiple_cases : $@convention(method) (S) -> () { +bb0(%0 : $S): + %1 = alloc_stack $MP + cond_br undef, bb1, bb2 + +bb1: + %2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %2 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + br bb3 + +bb2: + %3 = init_enum_data_addr %1 : $*MP, #MP.B!enumelt + store %0 to [trivial] %3 : $*S + inject_enum_addr %1 : $*MP, #MP.B!enumelt + br bb3 + +bb3: + destroy_addr %1 : $*MP + dealloc_stack %1 : $*MP + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_expand_alloc_stack_of_enum_multiple_cases2 : +// CHECK: alloc_stack $X +// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_multiple_cases2' +sil [ossa] @dont_expand_alloc_stack_of_enum_multiple_cases2 : $@convention(method) (@guaranteed T) -> () { +bb0(%0 : @guaranteed $T): + %1 = alloc_stack $X + cond_br undef, bb1, bb2 + +bb1: + %2 = init_enum_data_addr %1 : $*X, #X.some!enumelt + %0a = copy_value %0 : $T + store %0a to [init] %2 : $*T + inject_enum_addr %1 : $*X, #X.some!enumelt + br bb3 + +bb2: + inject_enum_addr %1 : $*X, #X.none!enumelt + br bb3 + +bb3: + destroy_addr %1 : $*X + dealloc_stack %1 : $*X + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_expand_alloc_stack_of_enum_unknown_use : +// CHECK: alloc_stack $MP +// CHECK: } // end sil function 'dont_expand_alloc_stack_of_enum_unknown_use' +sil [ossa] @dont_expand_alloc_stack_of_enum_unknown_use : $@convention(method) (S) -> () { +bb0(%0 : $S): + %1 = alloc_stack $MP + %2 = init_enum_data_addr %1 : $*MP, #MP.A!enumelt + store %0 to [trivial] %2 : $*S + inject_enum_addr %1 : $*MP, #MP.A!enumelt + %8 = function_ref @use_mp : $@convention(thin) (@in_guaranteed MP) -> () + %9 = apply %8(%1) : $@convention(thin) (@in_guaranteed MP) -> () + destroy_addr %1 : $*MP + dealloc_stack %1 : $*MP + %11 = tuple () + return %11 : $() +} + +// CHECK-LABEL: sil [ossa] @replace_archetype : +// CHECK: [[S:%.*]] = alloc_stack $T +// CHECK: debug_value [[S]] +// CHECK: unchecked_addr_cast [[S]] +// CHECK: destroy_addr [[S]] +// CHECK: } // end sil function 'replace_archetype' +sil [ossa] @replace_archetype : $@convention(thin) (@owned T) -> () { +bb0(%0 : @owned $T): + %1 = metatype $@thick T.Type + %2 = init_existential_metatype %1, $@thick P.Type + %3 = open_existential_metatype %2 to $@thick (@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self).Type + %4 = alloc_stack $@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self + debug_value %4, var, name "x" + %5 = unchecked_addr_cast %4 to $*T + store %0 to [init] %5 + destroy_addr %4 + dealloc_stack %4 + %r = tuple () + return %r +} + +// CHECK-LABEL: sil [ossa] @replace_existential_with_concrete_type1 : +// CHECK: [[S:%.*]] = alloc_stack $T +// CHECK-NOT: init_existential_addr +// CHECK-NOT: open_existential_addr +// CHECK: destroy_addr [[S]] +// CHECK: } // end sil function 'replace_existential_with_concrete_type1' +sil [ossa] @replace_existential_with_concrete_type1 : $@convention(thin) (@owned T) -> () { +bb0(%0 : @owned $T): + %5 = alloc_stack $any P + %6 = init_existential_addr %5, $T + store %0 to [init] %6 + %8 = open_existential_addr mutable_access %5 to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122", P) Self + destroy_addr %8 + dealloc_stack %5 + %r = tuple () + return %r +} + +// CHECK-LABEL: sil [ossa] @replace_existential_with_concrete_type2 : +// CHECK: [[S:%.*]] = alloc_stack $T +// CHECK-NOT: init_existential_addr +// CHECK-NOT: open_existential_addr +// CHECK: store +// CHECK: unconditional_checked_cast_addr T in [[S]] to T in %0 +// CHECK: destroy_addr [[S]] +// CHECK: } // end sil function 'replace_existential_with_concrete_type2' +sil [ossa] @replace_existential_with_concrete_type2 : $@convention(thin) (@owned T) -> @out T { +bb0(%0 : $*T, %1 : @owned $T): + %2 = metatype $@thick T.Type + %3 = init_existential_metatype %2, $@thick P.Type + %4 = open_existential_metatype %3 to $@thick (@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self).Type + %5 = alloc_stack $any P + %6 = init_existential_addr %5, $(@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self) + %7 = unchecked_addr_cast %6 to $*T + store %1 to [init] %7 + unconditional_checked_cast_addr any P in %5 to T in %0 + %8 = open_existential_addr mutable_access %5 to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122", P) Self + destroy_addr %8 + dealloc_stack %5 + %r = tuple () + return %r +} + +// CHECK-LABEL: sil [ossa] @replace_existential_with_archetype : +// CHECK: [[S:%.*]] = alloc_stack $@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", any P) Self +// CHECK-NOT: init_existential_addr +// CHECK-NOT: open_existential_addr +// CHECK: checked_cast_addr_br take_always @opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", any P) Self in [[S]] to T in %0, bb1, bb2 +// CHECK: } // end sil function 'replace_existential_with_archetype' +sil [ossa] @replace_existential_with_archetype : $@convention(thin) (@owned T, @thick P.Type) -> @out T { +bb0(%0 : $*T, %1 : @owned $T, %2 : $@thick P.Type): + %3 = open_existential_metatype %2 to $@thick (@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self).Type + %4 = alloc_stack $any P + %5 = init_existential_addr %4, $(@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self) + %6 = unchecked_addr_cast %5 to $*T + store %1 to [init] %6 + checked_cast_addr_br take_always any P in %4 to T in %0, bb1, bb2 +bb1: + dealloc_stack %4 + %r = tuple () + return %r +bb2: + dealloc_stack %4 + unreachable +} + +// CHECK-LABEL: sil [ossa] @dont_replace_archetype_not_dominating : +// CHECK: alloc_stack $any P +// CHECK: init_existential_addr +// CHECK: open_existential_addr +// CHECK: } // end sil function 'dont_replace_archetype_not_dominating' +sil [ossa] @dont_replace_archetype_not_dominating : $@convention(thin) (@owned T, @thick P.Type) -> () { +bb0(%0 : @owned $T, %1 : $@thick P.Type): + %4 = alloc_stack $any P + %3 = open_existential_metatype %1 to $@thick (@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self).Type + %5 = init_existential_addr %4, $(@opened("82105EE0-DCB0-11E5-865D-C8E0EB309913", P) Self) + %6 = unchecked_addr_cast %5 to $*T + store %0 to [init] %6 + %8 = open_existential_addr mutable_access %4 to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122", P) Self + destroy_addr %8 + dealloc_stack %4 + %r = tuple () + return %r +} + +// CHECK-LABEL: sil [ossa] @dont_replace_multiple_inits : +// CHECK: [[S:%.*]] = alloc_stack $any P +// CHECK: init_existential_addr [[S]], $T1 +// CHECK: init_existential_addr [[S]], $T2 +// CHECK: open_existential_addr +// CHECK: } // end sil function 'dont_replace_multiple_inits' +sil [ossa] @dont_replace_multiple_inits : $@convention(thin) (T1, T2) -> () { +bb0(%0 : $T1, %1 : $T2): + %5 = alloc_stack $any P + cond_br undef, bb1, bb2 + +bb1: + %6 = init_existential_addr %5, $T1 + store %0 to [trivial] %6 + br bb3 + +bb2: + %9 = init_existential_addr %5, $T2 + store %1 to [trivial] %9 + br bb3 + +bb3: + %8 = open_existential_addr mutable_access %5 to $*@opened("83DE9694-7315-11E8-955C-ACDE48001122", P) Self + destroy_addr %8 + dealloc_stack %5 + %r = tuple () + return %r +} +