Skip to content

Commit 3207592

Browse files
committed
Canonicalize stores in the CanonicalizeInstruction utility.
This is the complement to load canonicalization. Although store canonicalization is not required before diagnostics, it should be defined in the same utility.
1 parent 558cc63 commit 3207592

File tree

4 files changed

+134
-74
lines changed

4 files changed

+134
-74
lines changed

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,6 @@ class SILCombiner :
235235
SILInstruction *visitUpcastInst(UpcastInst *UCI);
236236
SILInstruction *optimizeLoadFromStringLiteral(LoadInst *LI);
237237
SILInstruction *visitLoadInst(LoadInst *LI);
238-
SILInstruction *visitStoreInst(StoreInst *si);
239238
SILInstruction *visitIndexAddrInst(IndexAddrInst *IA);
240239
SILInstruction *visitAllocStackInst(AllocStackInst *AS);
241240
SILInstruction *visitAllocRefInst(AllocRefInst *AR);

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -586,79 +586,6 @@ SILInstruction *SILCombiner::optimizeLoadFromStringLiteral(LoadInst *LI) {
586586
return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), str[index]);
587587
}
588588

589-
SILInstruction *SILCombiner::visitStoreInst(StoreInst *si) {
590-
auto *f = si->getFunction();
591-
assert(f->getConventions().useLoweredAddresses() &&
592-
"These optimizations assume that opaque values are not enabled");
593-
594-
// (store (struct_element_addr addr) object)
595-
// ->
596-
// (store addr (struct object))
597-
598-
// If our store's destination is not a struct_element_addr, bail early.
599-
auto *sea = dyn_cast<StructElementAddrInst>(si->getDest());
600-
if (!sea)
601-
return nullptr;
602-
603-
// Ok, we have at least one struct_element_addr. Canonicalize the underlying
604-
// store.
605-
Builder.setInsertionPoint(si);
606-
SILLocation loc = si->getLoc();
607-
608-
auto &mod = si->getModule();
609-
SILValue result = si->getSrc();
610-
SILValue iterAddr = sea->getOperand();
611-
SILValue storeAddr;
612-
while (true) {
613-
SILType iterAddrType = iterAddr->getType();
614-
615-
// If our aggregate has unreferenced storage then we can never prove if it
616-
// actually has a single field.
617-
if (iterAddrType.aggregateHasUnreferenceableStorage())
618-
break;
619-
620-
auto *decl = iterAddrType.getStructOrBoundGenericStruct();
621-
assert(
622-
!decl->isResilient(mod.getSwiftModule(), f->getResilienceExpansion()) &&
623-
"This code assumes resilient structs can not have fragile fields. If "
624-
"this assert is hit, this has been changed. Please update this code.");
625-
626-
// NOTE: If this is ever changed to support enums, we must check for address
627-
// only types here. For structs we do not have to check since a single
628-
// element struct with a loadable element can never be address only. We
629-
// additionally do not have to worry about our input value being address
630-
// only since we are storing into it.
631-
auto props = decl->getStoredProperties();
632-
if (std::next(props.begin()) != props.end())
633-
break;
634-
635-
// Update the store location now that we know it is safe.
636-
storeAddr = iterAddr;
637-
638-
// Otherwise, create the struct.
639-
result = Builder.createStruct(loc, iterAddrType.getObjectType(), result);
640-
641-
// See if we have another struct_element_addr we can strip off. If we don't
642-
// then this as much as we can promote.
643-
sea = dyn_cast<StructElementAddrInst>(sea->getOperand());
644-
if (!sea)
645-
break;
646-
iterAddr = sea->getOperand();
647-
}
648-
649-
// If we failed to create any structs, bail.
650-
if (result == si->getSrc())
651-
return nullptr;
652-
653-
// Then create a new store, storing the value into the relevant computed
654-
// address.
655-
Builder.createStore(loc, result, storeAddr,
656-
StoreOwnershipQualifier::Unqualified);
657-
658-
// Then eliminate the original store.
659-
return eraseInstFromFunction(*si);
660-
}
661-
662589
SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
663590
// (load (upcast-ptr %x)) -> (upcast-ref (load %x))
664591
Builder.setCurrentDebugScope(LI->getDebugScope());

lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,84 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
309309
return killInstAndIncidentalUses(loadInst, nextII, pass);
310310
}
311311

312+
// Given a store within a single property struct, recursively form the parent
313+
// struct values and promote the store to the outer struct type.
314+
//
315+
// (store (struct_element_addr %base) object)
316+
// ->
317+
// (store %base (struct object))
318+
static SILBasicBlock::iterator
319+
broadenSingleElementStores(StoreInst *storeInst,
320+
CanonicalizeInstruction &pass) {
321+
// Keep track of the next iterator after any newly added or to-be-deleted
322+
// instructions. This must be valid regardless of whether the pass immediately
323+
// deletes the instructions or simply records them for later deletion.
324+
auto nextII = std::next(storeInst->getIterator());
325+
326+
// Bail if the store's destination is not a struct_element_addr.
327+
auto *sea = dyn_cast<StructElementAddrInst>(storeInst->getDest());
328+
if (!sea)
329+
return nextII;
330+
331+
auto *f = storeInst->getFunction();
332+
333+
// Continue up the struct_element_addr chain, as long as each struct only has
334+
// a single property, creating StoreInsts along the way.
335+
SILBuilderWithScope builder(storeInst);
336+
337+
SILValue result = storeInst->getSrc();
338+
SILValue baseAddr = sea->getOperand();
339+
SILValue storeAddr;
340+
while (true) {
341+
SILType baseAddrType = baseAddr->getType();
342+
343+
// If our aggregate has unreferenced storage then we can never prove if it
344+
// actually has a single field.
345+
if (baseAddrType.aggregateHasUnreferenceableStorage())
346+
break;
347+
348+
auto *decl = baseAddrType.getStructOrBoundGenericStruct();
349+
assert(
350+
!decl->isResilient(f->getModule().getSwiftModule(),
351+
f->getResilienceExpansion()) &&
352+
"This code assumes resilient structs can not have fragile fields. If "
353+
"this assert is hit, this has been changed. Please update this code.");
354+
355+
// NOTE: If this is ever changed to support enums, we must check for address
356+
// only types here. For structs we do not have to check since a single
357+
// element struct with a loadable element can never be address only. We
358+
// additionally do not have to worry about our input value being address
359+
// only since we are storing into it.
360+
auto props = decl->getStoredProperties();
361+
if (std::next(props.begin()) != props.end())
362+
break;
363+
364+
// Update the store location now that we know it is safe.
365+
storeAddr = baseAddr;
366+
367+
// Otherwise, create the struct.
368+
result = builder.createStruct(storeInst->getLoc(),
369+
baseAddrType.getObjectType(), result);
370+
371+
// See if we have another struct_element_addr we can strip off. If we don't
372+
// then this as much as we can promote.
373+
sea = dyn_cast<StructElementAddrInst>(sea->getOperand());
374+
if (!sea)
375+
break;
376+
baseAddr = sea->getOperand();
377+
}
378+
// If we failed to create any structs, bail.
379+
if (result == storeInst->getSrc())
380+
return nextII;
381+
382+
// Store the new struct-wrapped value into the final base address.
383+
builder.createStore(storeInst->getLoc(), result, storeAddr,
384+
storeInst->getOwnershipQualifier());
385+
386+
// Erase the original store.
387+
return killInstruction(storeInst, nextII, pass);
388+
}
389+
312390
//===----------------------------------------------------------------------===//
313391
// Top-Level Entry Point
314392
//===----------------------------------------------------------------------===//
@@ -321,6 +399,9 @@ CanonicalizeInstruction::canonicalize(SILInstruction *inst) {
321399
if (auto *loadInst = dyn_cast<LoadInst>(inst))
322400
return splitAggregateLoad(loadInst, *this);
323401

402+
if (auto *storeInst = dyn_cast<StoreInst>(inst))
403+
return broadenSingleElementStores(storeInst, *this);
404+
324405
// Skip ahead.
325406
return std::next(inst->getIterator());
326407
}

test/SILOptimizer/silgen_cleanup.sil

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ import SwiftShims
1111
// CHECK: bb0([[IN:%[0-9]+]] : $*UInt8):
1212
// CHECK-NEXT: [[IN_GEP:%[0-9]+]] = struct_element_addr [[IN]] : $*UInt8, #UInt8._value
1313
// CHECK-NEXT: [[IN_LOADED:%[0-9]+]] = load [trivial] [[IN_GEP]] : $*Builtin.Int8
14+
// CHECK-NEXT: [[LITERAL:%[0-9]+]] = integer_literal $Builtin.Int8, 1
15+
// CHECK-NEXT: [[UINT8:%.*]] = struct $UInt8 ([[LITERAL]] : $Builtin.Int8)
16+
// CHECK-NEXT: store [[UINT8]] to [trivial] [[IN]] : $*UInt8
1417
// CHECK-NEXT: return [[IN_LOADED]] : $Builtin.Int8
1518
sil [ossa] @struct_extract_load_to_load_struct_element_addr : $@convention(thin) (@inout UInt8) -> (Builtin.Int8) {
1619
bb0(%0 : $*UInt8):
1720
%1 = load [trivial] %0 : $*UInt8
21+
%2 = integer_literal $Builtin.Int8, 1
22+
%3 = struct_element_addr %0 : $*UInt8, #UInt8._value
23+
store %2 to [trivial] %3 : $*Builtin.Int8
1824
%5 = struct_extract %1 : $UInt8, #UInt8._value
1925
return %5 : $Builtin.Int8
2026
}
@@ -23,10 +29,16 @@ bb0(%0 : $*UInt8):
2329
// CHECK: bb0([[IN:%[0-9]+]] : $*(Builtin.Int8, Builtin.Int8)):
2430
// CHECK-NEXT: [[IN_GEP:%[0-9]+]] = tuple_element_addr [[IN]] : $*(Builtin.Int8, Builtin.Int8), 0
2531
// CHECK-NEXT: [[IN_LOADED:%[0-9]+]] = load [trivial] [[IN_GEP]] : $*Builtin.Int8
32+
// CHECK-NEXT: [[LITERAL:%[0-9]+]] = integer_literal $Builtin.Int8, 1
33+
// CHECK-NEXT: [[IN_STORE_GEP:%[0-9]+]] = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
34+
// CHECK-NEXT: store [[LITERAL]] to [trivial] [[IN_STORE_GEP]] : $*Builtin.Int8
2635
// CHECK-NEXT: return [[IN_LOADED]] : $Builtin.Int8
2736
sil [ossa] @tuple_extract_load_to_load_tuple_element_addr : $@convention(thin) (@inout (Builtin.Int8, Builtin.Int8)) -> (Builtin.Int8) {
2837
bb0(%0 : $*(Builtin.Int8, Builtin.Int8)):
2938
%1 = load [trivial] %0 : $*(Builtin.Int8, Builtin.Int8)
39+
%2 = integer_literal $Builtin.Int8, 1
40+
%3 = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
41+
store %2 to [trivial] %3 : $*Builtin.Int8
3042
%5 = tuple_extract %1 : $(Builtin.Int8, Builtin.Int8), 0
3143
return %5 : $Builtin.Int8
3244
}
@@ -36,12 +48,18 @@ bb0(%0 : $*(Builtin.Int8, Builtin.Int8)):
3648
// CHECK-LABEL: sil [ossa] @multiple_use_struct_extract_load_to_load_struct_element_addr
3749
// CHECK: bb0([[IN:%[0-9]+]] : $*UInt8):
3850
// CHECK-NEXT: load
51+
// CHECK-NEXT: integer_literal
52+
// CHECK-NEXT: struct
53+
// CHECK-NEXT: store
3954
// CHECK-NEXT: struct_extract
4055
// CHECK-NEXT: tuple
4156
// CHECK-NEXT: return
4257
sil [ossa] @multiple_use_struct_extract_load_to_load_struct_element_addr : $@convention(thin) (@inout UInt8) -> (UInt8, Builtin.Int8) {
4358
bb0(%0 : $*UInt8):
4459
%1 = load [trivial] %0 : $*UInt8
60+
%2 = integer_literal $Builtin.Int8, 1
61+
%3 = struct_element_addr %0 : $*UInt8, #UInt8._value
62+
store %2 to [trivial] %3 : $*Builtin.Int8
4563
%5 = struct_extract %1 : $UInt8, #UInt8._value
4664
%6 = tuple (%1 : $UInt8, %5 : $Builtin.Int8)
4765
return %6 : $(UInt8, Builtin.Int8)
@@ -52,12 +70,18 @@ bb0(%0 : $*UInt8):
5270
// CHECK-LABEL: sil [ossa] @multiple_use_tuple_extract_load_to_load_tuple_element_addr
5371
// CHECK: bb0
5472
// CHECK-NEXT: load
73+
// CHECK-NEXT: integer_literal
74+
// CHECK-NEXT: tuple_element_addr
75+
// CHECK-NEXT: store
5576
// CHECK-NEXT: tuple_extract
5677
// CHECK-NEXT: tuple
5778
// CHECK-NEXT: return
5879
sil [ossa] @multiple_use_tuple_extract_load_to_load_tuple_element_addr : $@convention(thin) (@inout (Builtin.Int8, Builtin.Int8)) -> ((Builtin.Int8, Builtin.Int8), Builtin.Int8) {
5980
bb0(%0 : $*(Builtin.Int8, Builtin.Int8)):
6081
%1 = load [trivial] %0 : $*(Builtin.Int8, Builtin.Int8)
82+
%2 = integer_literal $Builtin.Int8, 1
83+
%3 = tuple_element_addr %0 : $*(Builtin.Int8, Builtin.Int8), 0
84+
store %2 to [trivial] %3 : $*Builtin.Int8
6185
%5 = tuple_extract %1 : $(Builtin.Int8, Builtin.Int8), 0
6286
%6 = tuple (%1 : $(Builtin.Int8, Builtin.Int8), %5 : $Builtin.Int8)
6387
return %6 : $((Builtin.Int8, Builtin.Int8), Builtin.Int8)
@@ -115,3 +139,32 @@ bb0(%0 : $*X1):
115139
%result = tuple (%a : $Int, %copy1 : $AnyObject, %copy2 : $AnyObject)
116140
return %result : $(Int, AnyObject, AnyObject)
117141
}
142+
143+
struct X2 {
144+
@_hasStorage @_hasInitialValue var obj: AnyObject { get set }
145+
}
146+
147+
struct X3 {
148+
@_hasStorage @_hasInitialValue var x2: X2 { get set }
149+
}
150+
151+
// CHECK-LABEL: sil private [ossa] @testStoreNontrivial : $@convention(thin) (@inout X3, @guaranteed AnyObject) -> () {
152+
// CHECK-LABEL: bb0(%0 : $*X3, %1 : @guaranteed $AnyObject):
153+
// CHECK: [[CP:%.*]] = copy_value %1 : $AnyObject
154+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] %0 : $*X3
155+
// CHECK: [[X2:%.*]] = struct $X2 ([[CP]] : $AnyObject)
156+
// CHECK: [[X3:%.*]] = struct $X3 ([[X2]] : $X2)
157+
// CHECK: store [[X3]] to [assign] [[ACCESS]] : $*X3
158+
// CHECK: end_access [[ACCESS]] : $*X3
159+
// CHECK-LABEL: } // end sil function 'testStoreNontrivial'
160+
sil private [ossa] @testStoreNontrivial : $@convention(thin) (@inout X3, @guaranteed AnyObject) -> () {
161+
bb0(%0 : $*X3, %1 : $AnyObject):
162+
%4 = copy_value %1 : $AnyObject
163+
%5 = begin_access [modify] [unknown] %0 : $*X3
164+
%6 = struct_element_addr %5 : $*X3, #X3.x2
165+
%7 = struct_element_addr %6 : $*X2, #X2.obj
166+
store %4 to [assign] %7 : $*AnyObject
167+
end_access %5 : $*X3
168+
%12 = tuple ()
169+
return %12 : $()
170+
}

0 commit comments

Comments
 (0)