Skip to content

Commit f22371b

Browse files
committed
Add SIL SinkAddressProjections utility to avoid address phis.
Enable this utility during jump-threading in SimplifyCFG. Ultimately, the SIL verifier should prevent all address-phis and we'll need to use this utility in a few more places. Fixes <rdar://problem/55320867> SIL verification failed: Unknown formal access pattern: storage
1 parent 00ac8d8 commit f22371b

File tree

4 files changed

+285
-26
lines changed

4 files changed

+285
-26
lines changed

include/swift/SILOptimizer/Utils/Local.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,46 @@ class BasicBlockCloner : public SILCloner<BasicBlockCloner> {
372372
}
373373
};
374374

375+
/// Sink address projections to their out-of-block uses. This is
376+
/// required after cloning a block and before calling
377+
/// updateSSAAfterCloning to avoid address-type phis.
378+
///
379+
/// This clones address projections at their use points, but does not
380+
/// mutate the block containing the projections.
381+
class SinkAddressProjections {
382+
// Projections ordered from last to first in the chain.
383+
SmallVector<SingleValueInstruction *, 4> projections;
384+
SmallSetVector<SILValue, 4> inBlockDefs;
385+
386+
public:
387+
/// Check for an address projection chain ending at \p inst. Return true if
388+
/// the given instruction is successfully analyzed.
389+
///
390+
/// If \p inst does not produce an address, then return
391+
/// true. getInBlockDefs() will contain \p inst if any of its
392+
/// (non-address) values are used outside its block.
393+
///
394+
/// If \p inst does produce an address, return true only of the
395+
/// chain of address projections within this block is clonable at
396+
/// their use sites. getInBlockDefs will return all non-address
397+
/// operands in the chain that are also defined in this block. These
398+
/// may require phis after cloning the projections.
399+
bool analyzeAddressProjections(SILInstruction *inst);
400+
401+
/// After analyzing projections, returns the list of (non-address) values
402+
/// defined in the same block as the projections which will have uses outside
403+
/// the block after cloning.
404+
ArrayRef<SILValue> getInBlockDefs() const {
405+
return inBlockDefs.getArrayRef();
406+
}
407+
/// Clone the chain of projections at their use sites.
408+
///
409+
/// Return true if anything was done.
410+
///
411+
/// getInBlockProjectionOperandValues() can be called before or after cloning.
412+
bool cloneProjections();
413+
};
414+
375415
/// Helper function to perform SSA updates in case of jump threading.
376416
void updateSSAAfterCloning(BasicBlockCloner &Cloner, SILBasicBlock *SrcBB,
377417
SILBasicBlock *DestBB);

lib/SILOptimizer/Transforms/SimplifyCFG.cpp

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,79 @@ static bool isUsedOutsideOfBlock(SILValue V, SILBasicBlock *BB) {
209209
return false;
210210
}
211211

212+
// Populate 'projections' with the chain of address projections leading
213+
// to and including 'inst'.
214+
//
215+
// Populate 'inBlockDefs' with all the non-address value definitions in
216+
// the block that will be used outside this block after projection sinking.
217+
//
218+
// Return true on success, even if projections is empty.
219+
bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) {
220+
projections.clear();
221+
inBlockDefs.clear();
222+
223+
SILBasicBlock *bb = inst->getParent();
224+
auto pushOperandVal = [&](SILValue def) {
225+
if (def->getParentBlock() != bb)
226+
return true;
227+
228+
if (!def->getType().isAddress()) {
229+
inBlockDefs.insert(def);
230+
return true;
231+
}
232+
if (auto *addressProj = dyn_cast<SingleValueInstruction>(def)) {
233+
if (addressProj->isTriviallyDuplicatable()) {
234+
projections.push_back(addressProj);
235+
return true;
236+
}
237+
}
238+
// Can't handle a multi-value or unclonable address producer.
239+
return false;
240+
};
241+
// Check the given instruction for any address-type results.
242+
for (auto result : inst->getResults()) {
243+
if (!isUsedOutsideOfBlock(result, bb))
244+
continue;
245+
if (!pushOperandVal(result))
246+
return false;
247+
}
248+
// Recurse upward through address projections.
249+
for (unsigned idx = 0; idx < projections.size(); ++idx) {
250+
// Only one address result/operand can be handled per instruction.
251+
if (projections.size() != idx + 1)
252+
return false;
253+
254+
for (SILValue operandVal : projections[idx]->getOperandValues())
255+
pushOperandVal(operandVal);
256+
}
257+
return true;
258+
}
259+
260+
// Clone the projections gathered by 'analyzeAddressProjections' at
261+
// their use site outside this block.
262+
bool SinkAddressProjections::cloneProjections() {
263+
if (projections.empty())
264+
return false;
265+
266+
SILBasicBlock *bb = projections.front()->getParent();
267+
SmallVector<Operand *, 4> usesToReplace;
268+
// Clone projections in last-to-first order.
269+
for (unsigned idx = 0; idx < projections.size(); ++idx) {
270+
auto *oldProj = projections[idx];
271+
assert(oldProj->getParent() == bb);
272+
usesToReplace.clear();
273+
for (Operand *use : oldProj->getUses()) {
274+
if (use->getUser()->getParent() != bb)
275+
usesToReplace.push_back(use);
276+
}
277+
for (Operand *use : usesToReplace) {
278+
auto *newProj = oldProj->clone(use->getUser());
279+
use->set(cast<SingleValueInstruction>(newProj));
280+
}
281+
}
282+
return true;
283+
}
284+
212285
/// Helper function to perform SSA updates in case of jump threading.
213286
void swift::updateSSAAfterCloning(BasicBlockCloner &Cloner,
214287
SILBasicBlock *SrcBB, SILBasicBlock *DestBB) {
@@ -1024,20 +1097,31 @@ bool SimplifyCFG::tryJumpThreading(BranchInst *BI) {
10241097
// If it looks potentially interesting, decide whether we *can* do the
10251098
// operation and whether the block is small enough to be worth duplicating.
10261099
int copyCosts = 0;
1027-
for (auto &Inst : *DestBB) {
1028-
copyCosts += getThreadingCost(&Inst);
1100+
SinkAddressProjections sinkProj;
1101+
for (auto ii = DestBB->begin(), ie = DestBB->end(); ii != ie;) {
1102+
copyCosts += getThreadingCost(&*ii);
10291103
if (ThreadingBudget <= copyCosts)
10301104
return false;
10311105

1032-
// We need to update ssa if a value is used outside the duplicated block.
1033-
if (!NeedToUpdateSSA) {
1034-
for (auto result : Inst.getResults()) {
1035-
if (isUsedOutsideOfBlock(result, DestBB)) {
1036-
NeedToUpdateSSA = true;
1037-
break;
1038-
}
1039-
}
1040-
}
1106+
// If this is an address projection with outside uses, sink it before
1107+
// checking for SSA update.
1108+
if (!sinkProj.analyzeAddressProjections(&*ii))
1109+
return false;
1110+
1111+
sinkProj.cloneProjections();
1112+
// After cloning check if any of the non-address defs in the cloned block
1113+
// (including the current instruction) now have uses outside the
1114+
// block. Do this even if nothing was cloned.
1115+
if (!sinkProj.getInBlockDefs().empty())
1116+
NeedToUpdateSSA = true;
1117+
1118+
auto nextII = std::next(ii);
1119+
recursivelyDeleteTriviallyDeadInstructions(
1120+
&*ii, false, [&nextII](SILInstruction *deadInst) {
1121+
if (deadInst->getIterator() == nextII)
1122+
++nextII;
1123+
});
1124+
ii = nextII;
10411125
}
10421126

10431127
// Don't jump thread through a potential header - this can produce irreducible

test/SILOptimizer/simplify_cfg.sil

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,24 +1425,24 @@ bb6(%r : $Int32):
14251425
// CHECK: bb0:
14261426
// CHECK-NEXT: cond_br undef, bb1, bb2
14271427
// CHECK: bb1:
1428-
// CHECK-NEXT: cond_br undef, bb3, bb4
1428+
// CHECK: enum $Optional<Int32>, #Optional.none!enumelt
1429+
// CHECK: br bb3
14291430
// CHECK: bb2:
1431+
// CHECK: integer_literal $Builtin.Int32, 0
1432+
// CHECK: enum $Optional<Int32>, #Optional.some!enumelt.1
1433+
// CHECK: br bb3
1434+
// CHECK: bb3
14301435
// CHECK: integer_literal {{.*}}, 27
1431-
// CHECK: br bb5
1432-
// CHECK: bb3:
1433-
// CHECK: br bb4
1434-
// CHECK: bb4:
14351436
// CHECK: integer_literal {{.*}}, 28
1436-
// CHECK: br bb5
1437-
// CHECK: bb5({{.*}}):
1438-
// CHECK-NEXT: return
1437+
// CHECK: return
14391438
sil @jumpthread_switch_enum4 : $@convention(thin) () -> Builtin.Int32 {
14401439
bb0:
1440+
%c0 = builtin "assert_configuration"() : $Builtin.Int32
14411441
cond_br undef, bb1, bb2
14421442

14431443
bb1:
14441444
%4 = enum $Optional<Int32>, #Optional.none!enumelt
1445-
cond_br undef, bb3(%4 : $Optional<Int32>), bb4(%4 : $Optional<Int32>)
1445+
cond_br undef, bb3(%4 : $Optional<Int32>), bb4(%4 : $Optional<Int32>, %c0 : $Builtin.Int32)
14461446

14471447
bb2:
14481448
%6 = integer_literal $Builtin.Int32, 0
@@ -1454,23 +1454,22 @@ bb2:
14541454
bb3(%10 : $Optional<Int32>):
14551455
// Some instruction which is not "trivial"
14561456
%c1 = builtin "assert_configuration"() : $Builtin.Int32
1457-
br bb4(%10 : $Optional<Int32>)
1458-
1457+
br bb4(%10 : $Optional<Int32>, %c1 : $Builtin.Int32)
14591458

1460-
bb4(%13 : $Optional<Int32>):
1459+
bb4(%13 : $Optional<Int32>, %carg1 : $Builtin.Int32):
14611460
switch_enum %13 : $Optional<Int32>, case #Optional.some!enumelt.1: bb5, case #Optional.none!enumelt: bb6
14621461

14631462
bb5:
14641463
%r1 = integer_literal $Builtin.Int32, 27
14651464
%c2 = builtin "assert_configuration"() : $Builtin.Int32
1466-
br bb7(%r1 : $Builtin.Int32)
1465+
br bb7(%r1 : $Builtin.Int32, %c2 : $Builtin.Int32)
14671466

14681467
bb6:
14691468
%r2 = integer_literal $Builtin.Int32, 28
14701469
%c3 = builtin "assert_configuration"() : $Builtin.Int32
1471-
br bb7(%r2 : $Builtin.Int32)
1470+
br bb7(%r2 : $Builtin.Int32, %c3 : $Builtin.Int32)
14721471

1473-
bb7(%r : $Builtin.Int32):
1472+
bb7(%r : $Builtin.Int32, %carg2 : $Builtin.Int32):
14741473
return %r : $Builtin.Int32
14751474
}
14761475

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// RUN: %target-sil-opt -enable-objc-interop -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s
2+
//
3+
// Test SimplifyCFG's handling of address-type phis. In other words, makes sure it doesn't create them at all!
4+
5+
sil_stage canonical
6+
7+
import Builtin
8+
import Swift
9+
import SwiftShims
10+
11+
class C {
12+
@_hasStorage @_hasInitialValue var field: Int { get set }
13+
@objc deinit
14+
init()
15+
}
16+
17+
sil @getC : $@convention(thin) () -> C
18+
19+
// Test that jump threading sinks a ref_element_addr, generating a
20+
// non-address phi for its operand.
21+
//
22+
// The retain on separate paths followed by a merged release, and
23+
// target block with a conditional branch are necessary just to get
24+
// jump threading to kick in.
25+
//
26+
// CHECK-LABEL: sil @testJumpThreadRefEltLoop : $@convention(thin) () -> () {
27+
// CHECK: bb0
28+
// CHECK: function_ref @getC : $@convention(thin) () -> C
29+
// CHECK: cond_br undef, bb1, bb2
30+
// CHECK: bb1:
31+
// CHECK: [[C1:%.*]] = apply %0() : $@convention(thin) () -> C
32+
// CHECK: strong_retain [[C1]] : $C
33+
// CHECK: strong_release [[C1]] : $C
34+
// CHECK: br bb3([[C1]] : $C)
35+
// CHECK: bb2:
36+
// CHECK: [[C2:%.*]] = apply %0() : $@convention(thin) () -> C
37+
// CHECK: strong_retain [[C2]] : $C
38+
// CHECK: strong_release [[C2]] : $C
39+
// CHECK: br bb3([[C2]] : $C)
40+
// CHECK: bb3([[PHI:%.*]] : $C):
41+
// CHECK: [[ADR:%.*]] = ref_element_addr [[PHI]] : $C, #C.field
42+
// CHECK: begin_access [read] [dynamic] [[ADR]] : $*Int
43+
// CHECK: load
44+
// CHECK: end_access
45+
// CHECK-LABEL: } // end sil function 'testJumpThreadRefEltLoop'
46+
sil @testJumpThreadRefEltLoop : $@convention(thin) () -> () {
47+
bb0:
48+
%f = function_ref @getC : $@convention(thin) () -> C
49+
cond_br undef, bb1, bb2
50+
51+
bb1:
52+
%c1 = apply %f() : $@convention(thin) ()->C
53+
strong_retain %c1 : $C
54+
br bb3(%c1 : $C)
55+
56+
bb2:
57+
%c2 = apply %f() : $@convention(thin) ()->C
58+
strong_retain %c2 : $C
59+
br bb3(%c2 : $C)
60+
61+
bb3(%arg : $C):
62+
strong_release %arg : $C
63+
%18 = ref_element_addr %arg : $C, #C.field
64+
br bb4
65+
66+
bb4:
67+
%19 = begin_access [read] [dynamic] %18 : $*Int
68+
%20 = load %19 : $*Int
69+
end_access %19 : $*Int
70+
cond_br undef, bb4, bb5
71+
72+
bb5:
73+
%z = tuple ()
74+
return %z : $()
75+
}
76+
77+
// Test that jump threading sinks a
78+
// ref_tail_addr->index_addr->struct_element_addr chain and generates
79+
// a phi for the index_addr's index operand.
80+
//
81+
// The retain on separate paths followed by a merged release, and
82+
// target block with a conditional branch are necessary just to get
83+
// jump threading to kick in.
84+
//
85+
// CHECK-LABEL: sil @testJumpThreadIndex : $@convention(thin) (__ContiguousArrayStorageBase, Builtin.Int64) -> Builtin.Int32 {
86+
// CHECK: bb0(%0 : $__ContiguousArrayStorageBase, %1 : $Builtin.Int64):
87+
// CHECK: cond_br undef, bb1, bb2
88+
// CHECK: bb1:
89+
// CHECK: apply
90+
// CHECK: strong_retain
91+
// CHECK: strong_release
92+
// CHECK: [[IDX1:%.*]] = builtin "truncOrBitCast_Int64_Word"(%1 : $Builtin.Int64) : $Builtin.Word
93+
// CHECK: br bb3([[IDX1]] : $Builtin.Word)
94+
// CHECK: bb2:
95+
// CHECK: apply
96+
// CHECK: strong_retain
97+
// CHECK: strong_release
98+
// CHECK: [[IDX2:%.*]] = builtin "truncOrBitCast_Int64_Word"(%1 : $Builtin.Int64) : $Builtin.Word
99+
// CHECK: br bb3([[IDX2]] : $Builtin.Word)
100+
// CHECK: bb3([[PHI:%.*]] : $Builtin.Word):
101+
// CHECK: [[TAIL:%.*]] = ref_tail_addr %0 : $__ContiguousArrayStorageBase, $Int32
102+
// CHECK: [[ELT:%.*]] = index_addr [[TAIL]] : $*Int32, %14 : $Builtin.Word
103+
// CHECK: [[ADR:%.*]] = struct_element_addr [[ELT]] : $*Int32, #Int32._value
104+
// CHECK: load [[ADR]] : $*Builtin.Int32
105+
// CHECK: cond_br undef, bb4, bb5
106+
// CHECK-LABEL: } // end sil function 'testJumpThreadIndex'
107+
sil @testJumpThreadIndex : $@convention(thin) (__ContiguousArrayStorageBase, Builtin.Int64) -> Builtin.Int32 {
108+
bb0(%0 : $__ContiguousArrayStorageBase, %1 : $Builtin.Int64):
109+
%f = function_ref @getC : $@convention(thin) () -> C
110+
cond_br undef, bb1, bb2
111+
112+
bb1:
113+
%c1 = apply %f() : $@convention(thin) ()->C
114+
strong_retain %c1 : $C
115+
br bb3(%c1 : $C)
116+
117+
bb2:
118+
%c2 = apply %f() : $@convention(thin) ()->C
119+
strong_retain %c2 : $C
120+
br bb3(%c2 : $C)
121+
122+
bb3(%arg : $C):
123+
strong_release %arg : $C
124+
%tail = ref_tail_addr %0 : $__ContiguousArrayStorageBase, $Int32
125+
%idx = builtin "truncOrBitCast_Int64_Word"(%1 : $Builtin.Int64) : $Builtin.Word
126+
%elt = index_addr %tail : $*Int32, %idx : $Builtin.Word
127+
%adr = struct_element_addr %elt : $*Int32, #Int32._value
128+
br bb4
129+
130+
bb4:
131+
%val = load %adr : $*Builtin.Int32
132+
cond_br undef, bb4, bb5
133+
134+
bb5:
135+
return %val : $Builtin.Int32
136+
}

0 commit comments

Comments
 (0)