LargeTypesReg2Mem: Peephole load followed by single user to reuse the load's address

For example the pattern:

```
%e = load %1 : SomeEnum
switch_enum %e
```

The switch_enum_addr should reuse the load's address operand instead of
creating a new location for the SSA value of %e.

While we are there also don't create a new stack location for extracting payload
in a switch_enum_addr case target basic block if that extracted payload is not
used. That is if the basic block arguments in all switch target blocks are
unused in the switch_enum version.

```
switch_enum %enum, case #Optional.some!enumelt: bb1,
                   case #Optional.none!enumelt: bb2

bb2(%payload : $Payload):
   // unused %payload value
```

rdar://156602951
This commit is contained in:
Arnold Schwaighofer
2025-08-06 13:48:38 -07:00
parent 4adccbdd90
commit 1e8e3b82df
3 changed files with 83 additions and 36 deletions

View File

@@ -3927,6 +3927,13 @@ void AddressAssignment::finish(DominanceInfo *dominance,
StackNesting::fixNesting(&currFn);
}
static bool isSoleUserOf(LoadInst *load, SILInstruction *next) {
if (!load->hasOneUse())
return false;
if(load->getSingleUse()->getUser() != next)
return false;
return true;
}
namespace {
class AssignAddressToDef : SILInstructionVisitor<AssignAddressToDef> {
friend SILVisitorBase<AssignAddressToDef>;
@@ -4036,6 +4043,14 @@ protected:
}
void visitLoadInst(LoadInst *load) {
// Forward the address of the load if its sole user immediately follows the
// load instructions.
if (isSoleUserOf(load, &*++load->getIterator())) {
assignment.markForDeletion(load);
assignment.mapValueToAddress(origValue, load->getOperand());
return;
}
auto builder = assignment.getBuilder(load->getIterator());
auto addr = assignment.createAllocStack(load->getType());
@@ -4464,22 +4479,35 @@ protected:
void visitSwitchEnumInst(SwitchEnumInst *sw) {
auto opdAddr = assignment.getAddressForValue(sw->getOperand());
{
// UncheckedTakeEnumDataAddr is destructive. If we have a used switch target
// block argument we need to provide for a destructible location for the
// UncheckedTakeEnumDataAddr.
SILValue destructibleAddress;
auto initDestructibleAddress = [&] () -> void{
if (destructibleAddress)
return;
auto addr = assignment.createAllocStack(sw->getOperand()->getType());
// UncheckedTakeEnumDataAddr is destructive.
// So we need to copy to keep the original address location valid.
auto builder = assignment.getBuilder(sw->getIterator());
builder.createCopyAddr(sw->getLoc(), opdAddr, addr, IsTake,
IsInitialization);
opdAddr = addr;
}
destructibleAddress = addr;
};
auto loc = sw->getLoc();
auto rewriteCase = [&](EnumElementDecl *caseDecl, SILBasicBlock *caseBB) {
// Nothing to do for unused case payloads.
if (caseBB->getArguments().size() == 0)
if (caseBB->getArguments().size() == 0 ||
caseBB->getArguments()[0]->use_empty()) {
if (caseBB->getArguments().size()) {
assignment.markBlockArgumentForDeletion(caseBB);
}
return;
}
initDestructibleAddress();
assert(caseBB->getArguments().size() == 1);
SILArgument *caseArg = caseBB->getArgument(0);
@@ -4488,8 +4516,10 @@ protected:
SILBuilder caseBuilder = assignment.getBuilder(caseBB->begin());
auto *caseAddr =
caseBuilder.createUncheckedTakeEnumDataAddr(loc, opdAddr, caseDecl,
caseArg->getType().getAddressType());
caseBuilder.createUncheckedTakeEnumDataAddr(loc, destructibleAddress,
caseDecl,
caseArg->getType().getAddressType());
if (assignment.isLargeLoadableType(caseArg->getType())) {
assignment.mapValueToAddress(caseArg, caseAddr);
assignment.markBlockArgumentForDeletion(caseBB);

View File

@@ -80,17 +80,14 @@ unwind:
// CHECK-LABEL: sil @use_yield_big : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK-NEXT: [[TEMP:%.*]] = alloc_stack $BigStruct
// CHECK-NEXT: [[TEMP2:%.*]] = alloc_stack $BigStruct
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[CORO:%.*]] = function_ref @test_yield_big : $@yield_once @convention(thin) () -> @yields @in_guaranteed BigStruct
// CHECK-NEXT: ([[ADDR:%.*]], [[TOKEN:%.*]]) = begin_apply [[CORO]]()
// CHECK-NEXT: copy_addr [take] [[ADDR]] to [init] [[TEMP]] : $*BigStruct
// CHECK-NEXT: copy_addr [take] [[TEMP]] to [init] [[TEMP2]] : $*BigStruct
// CHECK-NEXT: // function_ref
// CHECK-NEXT: [[USE:%.*]] = function_ref @use_big_struct : $@convention(thin) (@in_guaranteed BigStruct) -> ()
// CHECK-NEXT: apply [[USE]]([[TEMP2]])
// CHECK-NEXT: apply [[USE]]([[TEMP]])
// CHECK-NEXT: [[RET:%.*]] = end_apply [[TOKEN]] as $()
// CHECK-NEXT: dealloc_stack [[TEMP2]] : $*BigStruct
// CHECK-NEXT: dealloc_stack [[TEMP]] : $*BigStruct
// CHECK-NEXT: return [[RET]] : $()
sil @use_yield_big : $@convention(thin) () -> () {

View File

@@ -66,15 +66,12 @@ struct Small {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $X
// CHECK: %3 = alloc_stack $Optional<X>
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: %5 = init_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %1 to [init] %5 : $*X
// CHECK: %2 = alloc_stack $Optional<X>
// CHECK: %3 = init_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %1 to [init] %3 : $*X
// CHECK: inject_enum_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %0 to [init] %3 : $*Optional<X>
// CHECK: dealloc_stack %3 : $*Optional<X>
// CHECK: dealloc_stack %2 : $*X
// CHECK: copy_addr [take] %0 to [init] %2 : $*Optional<X>
// CHECK: dealloc_stack %2 : $*Optional<X>
// CHECK: dealloc_stack %1 : $*X
// CHECK: dealloc_stack %0 : $*Optional<X>
// CHECK: } // end sil function 'test1'
@@ -92,17 +89,21 @@ bb0:
return %t : $()
}
sil @useX : $@convention(thin) (X) -> ()
// CHECK: sil @test2 : $@convention(thin) () -> () {
// CHECK: bb0:
// CHECK: %0 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $Optional<X>
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = alloc_stack $Optional<X>
// CHECK: copy_addr [take] %2 to [init] %1 : $*Optional<X>
// CHECK: copy_addr [take] %1 to [init] %0 : $*Optional<X>
// CHECK: switch_enum_addr %0 : $*Optional<X>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
// CHECK: copy_addr [take] %2 to [init] %0 : $*Optional<X>
// CHECK: switch_enum_addr %2 : $*Optional<X>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
// CHECK: bb1:
// CHECK: %6 = unchecked_take_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: %5 = unchecked_take_enum_data_addr %0 : $*Optional<X>, #Optional.some!enumelt
// CHECK: copy_addr [take] %5 to [init] %1 : $*X
// CHECK: %7 = function_ref @useX : $@convention(thin) (@in_guaranteed X) -> ()
// CHECK: %8 = apply %7(%1) : $@convention(thin) (@in_guaranteed X) -> ()
// CHECK: br bb3
// CHECK: bb2:
@@ -110,7 +111,7 @@ bb0:
// CHECK: bb3:
// CHECK: dealloc_stack %2 : $*Optional<X>
// CHECK: dealloc_stack %1 : $*Optional<X>
// CHECK: dealloc_stack %1 : $*X
// CHECK: dealloc_stack %0 : $*Optional<X>
// CHECK: } // end sil function 'test2'
@@ -121,6 +122,8 @@ bb0:
switch_enum %2 : $Optional<X>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
bb1(%3: $X):
%f = function_ref @useX : $@convention(thin) (X) -> ()
apply %f(%3) : $@convention(thin) (X) -> ()
br bb3
bb2:
@@ -184,11 +187,9 @@ bb0:
// CHECK: sil @test5 : $@convention(thin) (@in (X, X)) -> () {
// CHECK: bb0(%0 : $*(X, X)):
// CHECK: %1 = alloc_stack $(X, X)
// CHECK: %2 = alloc_stack $X
// CHECK: copy_addr [take] %0 to [init] %1 : $*(X, X)
// CHECK: %4 = tuple_element_addr %1 : $*(X, X), 1
// CHECK: copy_addr [take] %4 to [init] %2 : $*X
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = tuple_element_addr %0 : $*(X, X), 1
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: } // end sil function 'test5'
sil @test5 : $@convention(thin) (@in (X, X)) -> () {
@@ -231,11 +232,9 @@ bb0:
// CHECK: sil @test7 : $@convention(thin) (@in Y) -> () {
// CHECK: bb0(%0 : $*Y):
// CHECK: %1 = alloc_stack $Y
// CHECK: %2 = alloc_stack $X
// CHECK: copy_addr [take] %0 to [init] %1 : $*Y
// CHECK: %4 = struct_element_addr %1 : $*Y, #Y.y1
// CHECK: copy_addr [take] %4 to [init] %2 : $*X
// CHECK: %1 = alloc_stack $X
// CHECK: %2 = struct_element_addr %0 : $*Y, #Y.y1
// CHECK: copy_addr [take] %2 to [init] %1 : $*X
// CHECK: } // end sil function 'test7'
sil @test7 : $@convention(thin) (@in Y) -> () {
@@ -339,8 +338,8 @@ bb0:
}
// CHECK: sil @test13
// CHECK: [[ADDR:%.*]] = unchecked_addr_cast %1 : $*X to $*Y
// CHECK: copy_addr [take] [[ADDR]] to [init] %2 : $*Y
// CHECK: [[ADDR:%.*]] = unchecked_addr_cast %2 : $*X to $*Y
// CHECK: copy_addr [take] [[ADDR]] to [init] %1 : $*Y
// CHECK: } // end sil function 'test13'
sil @test13 : $@convention(thin) (@in X) -> () {
bb0(%0 : $*X):
@@ -443,3 +442,24 @@ bb0(%0 : $String):
%13 = tuple ()
return %13 : $()
}
sil_global private @global : $Optional<X>
sil @test18: $@convention(thin) () -> () {
bb0:
%0 = global_addr @global : $*Optional<X>
%1 = begin_access [modify] [static] [no_nested_conflict] %0
%2 = load %1
switch_enum %2, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1
bb1:
%4 = integer_literal $Builtin.Int1, -1
cond_fail %4, "Unexpectedly found nil while unwrapping an Optional value"
unreachable
bb2(%7 : $X):
%8 = unchecked_take_enum_data_addr %1, #Optional.some!enumelt
end_access %1
%13 = tuple ()
return %13 : $()
}