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:
Erik Eckstein
2025-02-27 20:43:31 +01:00
parent 5572c832e1
commit 46035305aa
18 changed files with 702 additions and 286 deletions

View File

@@ -8,6 +8,7 @@
swift_compiler_sources(Optimizer
SimplifyAllocRefDynamic.swift
SimplifyAllocStack.swift
SimplifyApply.swift
SimplifyBeginAndLoadBorrow.swift
SimplifyBeginCOWMutation.swift

View File

@@ -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
}
}

View File

@@ -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) })

View File

@@ -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)

View File

@@ -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);

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) -> () {

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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) -> () {

View File

@@ -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'

View File

@@ -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

View File

@@ -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]]

View File

@@ -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)
}

View File

@@ -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'

View 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
}