mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Optimizer: improve simplification of alloc_stack
* Reimplement most of the logic in Swift as an Instruction simplification and remove the old code from SILCombine
* support more cases of existential archetype replacements:
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
```
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
swift_compiler_sources(Optimizer
|
||||
SimplifyAllocRefDynamic.swift
|
||||
SimplifyAllocStack.swift
|
||||
SimplifyApply.swift
|
||||
SimplifyBeginAndLoadBorrow.swift
|
||||
SimplifyBeginCOWMutation.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<T>
|
||||
/// %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
|
||||
}
|
||||
}
|
||||
@@ -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) })
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<AllocStackAnalyzer> {
|
||||
|
||||
/// 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<T>
|
||||
/// %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<InitEnumDataAddrInst>(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<UncheckedTakeEnumDataAddrInst>(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<InjectEnumAddrInst>(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<SingleValueInstruction>(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<DestroyAddrInst>(Op->getUser())) {
|
||||
Builder.setInsertionPoint(DA);
|
||||
Builder.createDestroyAddr(DA->getLoc(), ConcAlloc);
|
||||
eraseInstFromFunction(*DA);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isa<DeinitExistentialAddrInst>(Op->getUser())) {
|
||||
eraseInstFromFunction(*Op->getUser());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isa<DeallocStackInst>(Op->getUser()))
|
||||
continue;
|
||||
|
||||
auto *DS = cast<DeallocStackInst>(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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) -> () {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) -> () {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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: ProtoB>(_: T.Type) -> T {
|
||||
T.init()
|
||||
}
|
||||
|
||||
func takesA<T: ProtoA>(_ 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<SomeStruct>, #Optional.some!enumelt, [[S]] : $SomeStruct
|
||||
// CHECK-NEXT: return [[O]] : $Optional<SomeStruct>
|
||||
// CHECK: } // end sil function '$s37sil_combine_concrete_existential_ossa16createSomeStructAA0gH0VSgyF'
|
||||
public func createSomeStruct() -> SomeStruct? {
|
||||
return takesA(SomeStruct.self)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
348
test/SILOptimizer/simplify_alloc_stack.sil
Normal file
348
test/SILOptimizer/simplify_alloc_stack.sil
Normal file
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user