Skip to content

Commit 6bda828

Browse files
authored
Merge pull request swiftlang#31884 from eeckstein/phi-expansion
SILOptimizer: a new phi-argument expansion optimization.
2 parents 1de3d0d + ad99b9d commit 6bda828

File tree

6 files changed

+300
-5
lines changed

6 files changed

+300
-5
lines changed

include/swift/SIL/SILArgument.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ class SILArgument : public ValueBase {
9999
Bits.SILArgument.VOKind = static_cast<unsigned>(newKind);
100100
}
101101

102-
SILBasicBlock *getParent() { return parentBlock; }
103-
const SILBasicBlock *getParent() const { return parentBlock; }
102+
SILBasicBlock *getParent() const { return parentBlock; }
104103

105104
SILFunction *getFunction();
106105
const SILFunction *getFunction() const;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ PASS(PredictableDeadAllocationElimination, "predictable-deadalloc-elim",
260260
"Eliminate dead temporary allocations after diagnostics")
261261
PASS(RedundantPhiElimination, "redundant-phi-elimination",
262262
"Redundant Phi Block Argument Elimination")
263+
PASS(PhiExpansion, "phi-expansion",
264+
"Replace Phi arguments by their only used field")
263265
PASS(ReleaseDevirtualizer, "release-devirtualizer",
264266
"SIL release Devirtualization")
265267
PASS(RetainSinking, "retain-sinking",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ void addFunctionPasses(SILPassPipelinePlan &P,
351351
// Jump threading can expose opportunity for SILCombine (enum -> is_enum_tag->
352352
// cond_br).
353353
P.addJumpThreadSimplifyCFG();
354+
P.addPhiExpansion();
354355
P.addSILCombine();
355356
// SILCombine can expose further opportunities for SimplifyCFG.
356357
P.addSimplifyCFG();

lib/SILOptimizer/Transforms/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ target_sources(swiftSILOptimizer PRIVATE
2323
Outliner.cpp
2424
ObjectOutliner.cpp
2525
PerformanceInliner.cpp
26+
PhiArgumentOptimizations.cpp
2627
RedundantLoadElimination.cpp
2728
RedundantOverflowCheckRemoval.cpp
28-
RedundantPhiElimination.cpp
2929
ReleaseDevirtualizer.cpp
3030
SemanticARCOpts.cpp
3131
SILCodeMotion.cpp

lib/SILOptimizer/Transforms/RedundantPhiElimination.cpp renamed to lib/SILOptimizer/Transforms/PhiArgumentOptimizations.cpp

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- RedundantPhiElimination.cpp - Remove redundant phi arguments -----===//
1+
//===--- PhiArgumentOptimizations.cpp - phi argument optimizations --------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
//
13-
// This pass eliminates redundant basic block arguments.
13+
// This file contains optimizations for basic block phi arguments.
1414
//
1515
//===----------------------------------------------------------------------===//
1616

@@ -200,8 +200,186 @@ bool RedundantPhiEliminationPass::valuesAreEqual(SILValue val1, SILValue val2) {
200200
return true;
201201
}
202202

203+
/// Replaces struct phi-arguments by a struct field.
204+
///
205+
/// If only a single field of a struct phi-argument is used, replace the
206+
/// argument by the field value.
207+
///
208+
/// \code
209+
/// br bb(%str)
210+
/// bb(%phi):
211+
/// %f = struct_extract %phi, #Field // the only use of %phi
212+
/// use %f
213+
/// \endcode
214+
///
215+
/// is replaced with
216+
///
217+
/// \code
218+
/// %f = struct_extract %str, #Field
219+
/// br bb(%f)
220+
/// bb(%phi):
221+
/// use %phi
222+
/// \endcode
223+
///
224+
/// This also works if the phi-argument is in a def-use cycle.
225+
///
226+
/// TODO: Handle tuples (but this is not so important).
227+
///
228+
/// The PhiExpansionPass is not part of SimplifyCFG because
229+
/// * no other SimplifyCFG optimization depends on it.
230+
/// * compile time: it doesn't need to run every time SimplifyCFG runs.
231+
///
232+
class PhiExpansionPass : public SILFunctionTransform {
233+
public:
234+
PhiExpansionPass() {}
235+
236+
void run() override;
237+
238+
private:
239+
bool optimizeArg(SILPhiArgument *initialArg);
240+
};
241+
242+
void PhiExpansionPass::run() {
243+
SILFunction *F = getFunction();
244+
if (!F->shouldOptimize())
245+
return;
246+
247+
LLVM_DEBUG(llvm::dbgs() << "*** PhiReduction on function: "
248+
<< F->getName() << " ***\n");
249+
250+
bool changed = false;
251+
for (SILBasicBlock &block : *getFunction()) {
252+
for (auto argAndIdx : enumerate(block.getArguments())) {
253+
if (!argAndIdx.value()->isPhiArgument())
254+
continue;
255+
256+
unsigned idx = argAndIdx.index();
257+
258+
// Try multiple times on the same argument to handle nested structs.
259+
while (optimizeArg(cast<SILPhiArgument>(block.getArgument(idx)))) {
260+
changed = true;
261+
}
262+
}
263+
}
264+
265+
if (changed) {
266+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
267+
}
268+
}
269+
270+
bool PhiExpansionPass::optimizeArg(SILPhiArgument *initialArg) {
271+
llvm::SmallVector<const SILPhiArgument *, 8> collectedPhiArgs;
272+
llvm::SmallPtrSet<const SILPhiArgument *, 8> handled;
273+
collectedPhiArgs.push_back(initialArg);
274+
handled.insert(initialArg);
275+
276+
VarDecl *field = nullptr;
277+
SILType newType;
278+
Optional<SILLocation> loc;
279+
280+
// First step: collect all phi-arguments which can be transformed.
281+
unsigned workIdx = 0;
282+
while (workIdx < collectedPhiArgs.size()) {
283+
const SILArgument *arg = collectedPhiArgs[workIdx++];
284+
for (Operand *use : arg->getUses()) {
285+
SILInstruction *user = use->getUser();
286+
if (isa<DebugValueInst>(user))
287+
continue;
288+
289+
if (auto *extr = dyn_cast<StructExtractInst>(user)) {
290+
if (field && extr->getField() != field)
291+
return false;
292+
field = extr->getField();
293+
newType = extr->getType();
294+
loc = extr->getLoc();
295+
continue;
296+
}
297+
if (auto *branch = dyn_cast<BranchInst>(user)) {
298+
const SILPhiArgument *destArg = branch->getArgForOperand(use);
299+
assert(destArg);
300+
if (handled.insert(destArg).second)
301+
collectedPhiArgs.push_back(destArg);
302+
continue;
303+
}
304+
if (auto *branch = dyn_cast<CondBranchInst>(user)) {
305+
const SILPhiArgument *destArg = branch->getArgForOperand(use);
306+
307+
// destArg is null if the use is the condition and not a block argument.
308+
if (!destArg)
309+
return false;
310+
311+
if (handled.insert(destArg).second)
312+
collectedPhiArgs.push_back(destArg);
313+
continue;
314+
}
315+
// An unexpected use -> bail.
316+
return false;
317+
}
318+
}
319+
320+
if (!field)
321+
return false;
322+
323+
// Second step: do the transformation.
324+
for (const SILPhiArgument *arg : collectedPhiArgs) {
325+
SILBasicBlock *block = arg->getParent();
326+
SILArgument *newArg = block->replacePhiArgumentAndReplaceAllUses(
327+
arg->getIndex(), newType, arg->getOwnershipKind());
328+
329+
// First collect all users, then do the transformation.
330+
// We don't want to modify the use list while iterating over it.
331+
llvm::SmallVector<DebugValueInst *, 8> debugValueUsers;
332+
llvm::SmallVector<StructExtractInst *, 8> structExtractUsers;
333+
334+
for (Operand *use : newArg->getUses()) {
335+
SILInstruction *user = use->getUser();
336+
if (auto *dvi = dyn_cast<DebugValueInst>(user)) {
337+
debugValueUsers.push_back(dvi);
338+
continue;
339+
}
340+
if (auto *sei = dyn_cast<StructExtractInst>(user)) {
341+
structExtractUsers.push_back(sei);
342+
continue;
343+
}
344+
// Branches are handled below by handling incoming phi operands.
345+
assert(isa<BranchInst>(user) || isa<CondBranchInst>(user));
346+
}
347+
348+
for (DebugValueInst *dvi : debugValueUsers) {
349+
dvi->eraseFromParent();
350+
}
351+
for (StructExtractInst *sei : structExtractUsers) {
352+
sei->replaceAllUsesWith(sei->getOperand());
353+
sei->eraseFromParent();
354+
}
355+
356+
// "Move" the struct_extract to the predecessors.
357+
llvm::SmallVector<Operand *, 8> incomingOps;
358+
bool success = newArg->getIncomingPhiOperands(incomingOps);
359+
(void)success;
360+
assert(success && "could not get all incoming phi values");
361+
362+
for (Operand *op : incomingOps) {
363+
// Did we already handle the operand?
364+
if (op->get()->getType() == newType)
365+
continue;
366+
367+
SILInstruction *branchInst = op->getUser();
368+
SILBuilder builder(branchInst);
369+
auto *strExtract = builder.createStructExtract(loc.getValue(),
370+
op->get(), field, newType);
371+
op->set(strExtract);
372+
}
373+
}
374+
return true;
375+
}
376+
203377
} // end anonymous namespace
204378

205379
SILTransform *swift::createRedundantPhiElimination() {
206380
return new RedundantPhiEliminationPass();
207381
}
382+
383+
SILTransform *swift::createPhiExpansion() {
384+
return new PhiExpansionPass();
385+
}

test/SILOptimizer/phi-expansion.sil

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// RUN: %target-sil-opt %s -phi-expansion | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
import Swift
7+
import SwiftShims
8+
9+
struct Mystruct {
10+
@_hasStorage var i: Int { get set }
11+
@_hasStorage var j: Int { get set }
12+
init(i: Int)
13+
}
14+
15+
// CHECK-LABEL: sil @test_simple
16+
// CHECK: br bb3(%{{[0-9]*}} : $Int)
17+
// CHECK: bb3(%{{[0-9]*}} : $Int):
18+
// CHECK: } // end sil function 'test_simple'
19+
sil @test_simple : $@convention(thin) (Mystruct, Mystruct) -> Int {
20+
bb0(%0 : $Mystruct, %1 : $Mystruct):
21+
cond_br undef, bb1, bb2
22+
23+
bb1:
24+
br bb3(%0 : $Mystruct)
25+
26+
bb2:
27+
br bb3(%1 : $Mystruct)
28+
29+
30+
bb3(%5 : $Mystruct):
31+
%6 = struct_extract %5 : $Mystruct, #Mystruct.i
32+
return %6 : $Int
33+
}
34+
35+
// CHECK-LABEL: sil @test_multiple_struct_extracts
36+
// CHECK: br bb3(%{{[0-9]*}} : $Int)
37+
// CHECK: bb3(%{{[0-9]*}} : $Int):
38+
// CHECK: } // end sil function 'test_multiple_struct_extracts'
39+
sil @test_multiple_struct_extracts : $@convention(thin) (Mystruct, Mystruct) -> (Int, Int) {
40+
bb0(%0 : $Mystruct, %1 : $Mystruct):
41+
cond_br undef, bb1, bb2
42+
43+
bb1:
44+
br bb3(%0 : $Mystruct)
45+
46+
bb2:
47+
br bb3(%1 : $Mystruct)
48+
49+
50+
bb3(%5 : $Mystruct):
51+
%6 = struct_extract %5 : $Mystruct, #Mystruct.i
52+
%7 = struct_extract %5 : $Mystruct, #Mystruct.i
53+
%8 = tuple (%6 : $Int, %7 : $Int)
54+
return %8 : $(Int, Int)
55+
}
56+
57+
// CHECK-LABEL: sil @dont_transform_multiple_fields
58+
// CHECK: br bb3(%{{[0-9]*}} : $Mystruct)
59+
// CHECK: bb3(%{{[0-9]*}} : $Mystruct):
60+
// CHECK: } // end sil function 'dont_transform_multiple_fields'
61+
sil @dont_transform_multiple_fields : $@convention(thin) (Mystruct, Mystruct) -> (Int, Int) {
62+
bb0(%0 : $Mystruct, %1 : $Mystruct):
63+
cond_br undef, bb1, bb2
64+
65+
bb1:
66+
br bb3(%0 : $Mystruct)
67+
68+
bb2:
69+
br bb3(%1 : $Mystruct)
70+
71+
72+
bb3(%5 : $Mystruct):
73+
%6 = struct_extract %5 : $Mystruct, #Mystruct.i
74+
%7 = struct_extract %5 : $Mystruct, #Mystruct.j
75+
%8 = tuple (%6 : $Int, %7 : $Int)
76+
return %8 : $(Int, Int)
77+
}
78+
79+
// CHECK-LABEL: sil @test_loop_with_br
80+
// CHECK: br bb1(%{{[0-9]*}} : $Int)
81+
// CHECK: bb1(%{{[0-9]*}} : $Int):
82+
// CHECK: br bb1(%{{[0-9]*}} : $Int)
83+
// CHECK: } // end sil function 'test_loop_with_br'
84+
sil @test_loop_with_br : $@convention(thin) (Mystruct) -> Int {
85+
bb0(%0 : $Mystruct):
86+
br bb1(%0 : $Mystruct)
87+
88+
bb1(%2 : $Mystruct):
89+
%3 = struct_extract %2 : $Mystruct, #Mystruct.i
90+
cond_br undef, bb2, bb3
91+
92+
bb2:
93+
br bb1(%2 : $Mystruct)
94+
95+
bb3:
96+
return %3 : $Int
97+
}
98+
99+
// CHECK-LABEL: sil @test_loop_with_cond_br
100+
// CHECK: br bb1(%{{[0-9]*}} : $Int)
101+
// CHECK: bb1(%{{[0-9]*}} : $Int):
102+
// CHECK: cond_br undef, bb1(%{{[0-9]*}} : $Int), bb2
103+
// CHECK: } // end sil function 'test_loop_with_cond_br'
104+
sil @test_loop_with_cond_br : $@convention(thin) (Mystruct) -> Int {
105+
bb0(%0 : $Mystruct):
106+
br bb1(%0 : $Mystruct)
107+
108+
bb1(%2 : $Mystruct):
109+
%3 = struct_extract %2 : $Mystruct, #Mystruct.i
110+
cond_br undef, bb1(%2 : $Mystruct), bb2
111+
112+
bb2:
113+
return %3 : $Int
114+
}
115+

0 commit comments

Comments
 (0)