Skip to content

Commit e6965e6

Browse files
committed
RedundantLoadElimination: optimize a load from a memory location which was written by copy_addr
For example: ``` %1 = alloc_stack $B copy_addr %0 to [init] %1 %3 = load [take] %1 dealloc_stack %1 ``` -> ``` %3 = load [copy] %0 ```
1 parent 1292f5f commit e6965e6

File tree

4 files changed

+245
-5
lines changed

4 files changed

+245
-5
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/RedundantLoadElimination.swift

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ private func replace(load: LoadInst, with availableValues: [AvailableValue], _ c
251251
var ssaUpdater = SSAUpdater(function: load.parentFunction,
252252
type: load.type, ownership: load.ownership, context)
253253

254-
for availableValue in availableValues {
254+
for availableValue in availableValues.replaceCopyAddrsWithLoadsAndStores(context) {
255255
let block = availableValue.instruction.parentBlock
256256
let availableValue = provideValue(for: load, from: availableValue, context)
257257
ssaUpdater.addAvailableValue(availableValue, in: block)
@@ -342,6 +342,8 @@ private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: Availa
342342
fatalError("unqualified store in ossa function?")
343343
}
344344
return valueToAdd
345+
case .viaCopyAddr:
346+
fatalError("copy_addr must be lowered before shrinking lifetime")
345347
}
346348
}
347349

@@ -380,39 +382,58 @@ private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue
380382
let valueToAdd = builder.createLoad(fromAddress: addr, ownership: .take)
381383
availableStore.trySplit(context)
382384
return valueToAdd
385+
case .viaCopyAddr:
386+
fatalError("copy_addr must be lowered before shrinking lifetime")
383387
}
384388
}
385389

386390
/// Either a `load` or `store` which is preceding the original load and provides the loaded value.
387391
private enum AvailableValue {
388392
case viaLoad(LoadInst)
389393
case viaStore(StoreInst)
394+
case viaCopyAddr(CopyAddrInst)
390395

391396
var value: Value {
392397
switch self {
393398
case .viaLoad(let load): return load
394399
case .viaStore(let store): return store.source
400+
case .viaCopyAddr: fatalError("copy_addr must be lowered")
395401
}
396402
}
397403

398404
var address: Value {
399405
switch self {
400-
case .viaLoad(let load): return load.address
401-
case .viaStore(let store): return store.destination
406+
case .viaLoad(let load): return load.address
407+
case .viaStore(let store): return store.destination
408+
case .viaCopyAddr(let copyAddr): return copyAddr.destination
402409
}
403410
}
404411

405412
var instruction: Instruction {
406413
switch self {
407-
case .viaLoad(let load): return load
408-
case .viaStore(let store): return store
414+
case .viaLoad(let load): return load
415+
case .viaStore(let store): return store
416+
case .viaCopyAddr(let copyAddr): return copyAddr
409417
}
410418
}
411419

412420
func getBuilderForProjections(_ context: FunctionPassContext) -> Builder {
413421
switch self {
414422
case .viaLoad(let load): return Builder(after: load, context)
415423
case .viaStore(let store): return Builder(before: store, context)
424+
case .viaCopyAddr: fatalError("copy_addr must be lowered")
425+
}
426+
}
427+
}
428+
429+
private extension Array where Element == AvailableValue {
430+
func replaceCopyAddrsWithLoadsAndStores(_ context: FunctionPassContext) -> [AvailableValue] {
431+
return map {
432+
if case .viaCopyAddr(let copyAddr) = $0 {
433+
return .viaStore(copyAddr.replaceWithLoadAndStore(context))
434+
} else {
435+
return $0
436+
}
416437
}
417438
}
418439
}
@@ -520,6 +541,16 @@ private struct InstructionScanner {
520541
potentiallyRedundantSubpath = precedingStorePath
521542
}
522543

544+
case let preceedingCopy as CopyAddrInst where preceedingCopy.canProvideValue:
545+
let copyPath = preceedingCopy.destination.constantAccessPath
546+
if copyPath.getMaterializableProjection(to: accessPath) != nil {
547+
availableValues.append(.viaCopyAddr(preceedingCopy))
548+
return .available
549+
}
550+
if accessPath.getMaterializableProjection(to: copyPath) != nil, potentiallyRedundantSubpath == nil {
551+
potentiallyRedundantSubpath = copyPath
552+
}
553+
523554
default:
524555
break
525556
}
@@ -606,3 +637,20 @@ private struct Liverange {
606637
return false
607638
}
608639
}
640+
641+
private extension CopyAddrInst {
642+
var canProvideValue: Bool {
643+
if !source.type.isLoadable(in: parentFunction) {
644+
// Although the original load's type is loadable (obviously), it can be projected-out
645+
// from the copy_addr's type which might be not loadable.
646+
return false
647+
}
648+
if !parentFunction.hasOwnership {
649+
if !isTakeOfSrc || !isInitializationOfDest {
650+
// For simplicity, bail if we would have to insert compensating retains and releases.
651+
return false
652+
}
653+
}
654+
return true
655+
}
656+
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,32 @@ extension CheckedCastAddrBranchInst {
863863
}
864864
}
865865

866+
extension CopyAddrInst {
867+
@discardableResult
868+
func replaceWithLoadAndStore(_ context: some MutatingContext) -> StoreInst {
869+
let loadOwnership: LoadInst.LoadOwnership
870+
let storeOwnership: StoreInst.StoreOwnership
871+
if parentFunction.hasOwnership {
872+
if source.type.isTrivial(in: parentFunction) {
873+
loadOwnership = .trivial
874+
storeOwnership = .trivial
875+
} else {
876+
loadOwnership = isTakeOfSrc ? .take : .copy
877+
storeOwnership = isInitializationOfDest ? .initialize : .assign
878+
}
879+
} else {
880+
loadOwnership = .unqualified
881+
storeOwnership = .unqualified
882+
}
883+
884+
let builder = Builder(before: self, context)
885+
let value = builder.createLoad(fromAddress: source, ownership: loadOwnership)
886+
let store = builder.createStore(source: value, destination: destination, ownership: storeOwnership)
887+
context.erase(instruction: self)
888+
return store
889+
}
890+
}
891+
866892
extension Type {
867893
/// True if a type can be expanded without a significant increase to code
868894
/// size.

test/SILOptimizer/redundant_load_elim.sil

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,3 +1341,48 @@ bb0(%0 : @guaranteed $COpt, %1 : $Int32):
13411341
}
13421342

13431343

1344+
// CHECK-LABEL: sil @copy_addr_copy_init :
1345+
// CHECK: copy_addr
1346+
// CHECK-NEXT: load
1347+
// CHECK-LABEL: } // end sil function 'copy_addr_copy_init'
1348+
sil @copy_addr_copy_init : $@convention(thin) (@in_guaranteed B, @inout B) -> @owned B {
1349+
bb0(%0 : $*B, %1 : $*B):
1350+
copy_addr %0 to [init] %1
1351+
%3 = load %1
1352+
return %3
1353+
}
1354+
1355+
// CHECK-LABEL: sil @copy_addr_copy_assign :
1356+
// CHECK: copy_addr
1357+
// CHECK-NEXT: load
1358+
// CHECK-LABEL: } // end sil function 'copy_addr_copy_assign'
1359+
sil @copy_addr_copy_assign : $@convention(thin) (@in_guaranteed B, @inout B) -> @owned B {
1360+
bb0(%0 : $*B, %1 : $*B):
1361+
copy_addr %0 to %1
1362+
%3 = load %1
1363+
return %3
1364+
}
1365+
1366+
// CHECK-LABEL: sil @copy_addr_take_init :
1367+
// CHECK: [[LD:%.*]] = load %0
1368+
// CHECK-NEXT: store [[LD]] to %1
1369+
// CHECK: return [[LD]]
1370+
// CHECK-LABEL: } // end sil function 'copy_addr_take_init'
1371+
sil @copy_addr_take_init : $@convention(thin) (@in B, @inout B) -> @owned B {
1372+
bb0(%0 : $*B, %1 : $*B):
1373+
copy_addr [take] %0 to [init] %1
1374+
%3 = load %1
1375+
return %3
1376+
}
1377+
1378+
// CHECK-LABEL: sil @copy_addr_take_assign :
1379+
// CHECK: copy_addr
1380+
// CHECK-NEXT: load
1381+
// CHECK-LABEL: } // end sil function 'copy_addr_take_assign'
1382+
sil @copy_addr_take_assign : $@convention(thin) (@in B, @inout B) -> @owned B {
1383+
bb0(%0 : $*B, %1 : $*B):
1384+
copy_addr [take] %0 to %1
1385+
%3 = load %1
1386+
return %3
1387+
}
1388+

test/SILOptimizer/redundant_load_elim_ossa.sil

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ struct F {
130130

131131
}
132132

133+
protocol P {}
134+
135+
struct ExistentialIntPair {
136+
var p: P
137+
var x: Int
138+
}
139+
133140
sil_global @total : $Int32
134141

135142
sil @use : $@convention(thin) (Builtin.Int32) -> ()
@@ -1531,3 +1538,117 @@ bb0(%0 : $*B):
15311538
return %8 : $()
15321539
}
15331540

1541+
// CHECK-LABEL: sil [ossa] @copy_addr_copy_init :
1542+
// CHECK: [[LD:%.*]] = load [copy] %0
1543+
// CHECK-NEXT: [[C:%.*]] = copy_value [[LD]]
1544+
// CHECK-NEXT: store [[LD]] to [init] %1
1545+
// CHECK: return [[C]]
1546+
// CHECK-LABEL: } // end sil function 'copy_addr_copy_init'
1547+
sil [ossa] @copy_addr_copy_init : $@convention(thin) (@in_guaranteed B, @inout B) -> @owned B {
1548+
bb0(%0 : $*B, %1 : $*B):
1549+
destroy_addr %1
1550+
copy_addr %0 to [init] %1
1551+
%3 = load [copy] %1
1552+
return %3
1553+
}
1554+
1555+
// CHECK-LABEL: sil [ossa] @copy_addr_copy_assign :
1556+
// CHECK: [[LD:%.*]] = load [copy] %0
1557+
// CHECK-NEXT: [[C:%.*]] = copy_value [[LD]]
1558+
// CHECK-NEXT: store [[LD]] to [assign] %1
1559+
// CHECK: return [[C]]
1560+
// CHECK-LABEL: } // end sil function 'copy_addr_copy_assign'
1561+
sil [ossa] @copy_addr_copy_assign : $@convention(thin) (@in_guaranteed B, @inout B) -> @owned B {
1562+
bb0(%0 : $*B, %1 : $*B):
1563+
copy_addr %0 to %1
1564+
%3 = load [copy] %1
1565+
return %3
1566+
}
1567+
1568+
// CHECK-LABEL: sil [ossa] @copy_addr_take_init :
1569+
// CHECK: [[LD:%.*]] = load [take] %0
1570+
// CHECK-NEXT: [[C:%.*]] = copy_value [[LD]]
1571+
// CHECK-NEXT: store [[LD]] to [init] %1
1572+
// CHECK: return [[C]]
1573+
// CHECK-LABEL: } // end sil function 'copy_addr_take_init'
1574+
sil [ossa] @copy_addr_take_init : $@convention(thin) (@in B, @inout B) -> @owned B {
1575+
bb0(%0 : $*B, %1 : $*B):
1576+
destroy_addr %1
1577+
copy_addr [take] %0 to [init] %1
1578+
%3 = load [copy] %1
1579+
return %3
1580+
}
1581+
1582+
// CHECK-LABEL: sil [ossa] @copy_addr_take_assign :
1583+
// CHECK: [[LD:%.*]] = load [take] %0
1584+
// CHECK-NEXT: [[C:%.*]] = copy_value [[LD]]
1585+
// CHECK-NEXT: store [[LD]] to [assign] %1
1586+
// CHECK: return [[C]]
1587+
// CHECK-LABEL: } // end sil function 'copy_addr_take_assign'
1588+
sil [ossa] @copy_addr_take_assign : $@convention(thin) (@in B, @inout B) -> @owned B {
1589+
bb0(%0 : $*B, %1 : $*B):
1590+
copy_addr [take] %0 to %1
1591+
%3 = load [copy] %1
1592+
return %3
1593+
}
1594+
1595+
// CHECK-LABEL: sil [ossa] @copy_addr_load_take :
1596+
// CHECK: [[LD:%.*]] = load [copy]
1597+
// CHECK-NOT: copy_addr
1598+
// CHECK: return [[LD]]
1599+
// CHECK-LABEL: } // end sil function 'copy_addr_load_take'
1600+
sil [ossa] @copy_addr_load_take : $@convention(thin) (@in_guaranteed B) -> @owned B {
1601+
bb0(%0 : $*B):
1602+
%1 = alloc_stack $B
1603+
copy_addr %0 to [init] %1
1604+
%3 = load [take] %1
1605+
dealloc_stack %1
1606+
return %3
1607+
}
1608+
1609+
// CHECK-LABEL: sil [ossa] @copy_addr_projection :
1610+
// CHECK: [[LD:%.*]] = load [trivial] %0
1611+
// CHECK-NEXT: [[A:%.*]] = struct_extract [[LD]]
1612+
// CHECK-NEXT: store [[LD]] to [trivial] %1
1613+
// CHECK: return [[A]]
1614+
// CHECK-LABEL: } // end sil function 'copy_addr_projection'
1615+
sil [ossa] @copy_addr_projection : $@convention(thin) (@in_guaranteed TwoField, @inout TwoField) -> Int {
1616+
bb0(%0 : $*TwoField, %1 : $*TwoField):
1617+
copy_addr %0 to %1
1618+
%3 = struct_element_addr %1, #TwoField.a
1619+
%4 = load [trivial] %3
1620+
return %4
1621+
}
1622+
1623+
// CHECK-LABEL: sil [ossa] @copy_addr_aggregate :
1624+
// CHECK: [[A_ADDR:%.*]] = struct_element_addr %2 : $*TwoField, #TwoField.a
1625+
// CHECK: [[A:%.*]] = load [trivial] %0
1626+
// CHECK: store [[A]] to [trivial] [[A_ADDR]]
1627+
// CHECK: [[B_ADDR:%.*]] = struct_element_addr %2 : $*TwoField, #TwoField.b
1628+
// CHECK: [[B:%.*]] = load [trivial] %1
1629+
// CHECK: store [[B]] to [trivial] [[B_ADDR]]
1630+
// CHECK: [[R:%.*]] = struct $TwoField ([[A]] : $Int, [[B]] : $Int)
1631+
// CHECK: return [[R]]
1632+
// CHECK-LABEL: } // end sil function 'copy_addr_aggregate'
1633+
sil [ossa] @copy_addr_aggregate : $@convention(thin) (@in_guaranteed Int, @in_guaranteed Int, @inout TwoField) -> TwoField {
1634+
bb0(%0 : $*Int, %1 : $*Int, %2 : $*TwoField):
1635+
%3 = struct_element_addr %2, #TwoField.a
1636+
copy_addr %0 to %3
1637+
%5 = struct_element_addr %2, #TwoField.b
1638+
copy_addr %1 to %5
1639+
%4 = load [trivial] %2
1640+
return %4
1641+
}
1642+
1643+
// CHECK-LABEL: sil [ossa] @copy_addr_not_loadable :
1644+
// CHECK: copy_addr
1645+
// CHECK: load
1646+
// CHECK-LABEL: } // end sil function 'copy_addr_not_loadable'
1647+
sil [ossa] @copy_addr_not_loadable : $@convention(thin) (@in_guaranteed ExistentialIntPair, @inout ExistentialIntPair) -> Int {
1648+
bb0(%0 : $*ExistentialIntPair, %1 : $*ExistentialIntPair):
1649+
copy_addr %0 to %1
1650+
%3 = struct_element_addr %1, #ExistentialIntPair.x
1651+
%4 = load [trivial] %3
1652+
return %4
1653+
}
1654+

0 commit comments

Comments
 (0)