This is an archive of the discontinued LLVM Phabricator instance.

[SimplifyCFG] Rewrite SinkThenElseCodeToEnd
ClosedPublic

Authored by jmolloy on Jul 8 2016, 9:11 AM.

Details

Summary

The new version has several advantages:

  1. (IMNSHO) it's more readable and neater
  2. It handles loads and stores properly
  3. It can handle any number of incoming blocks rather than just two. I'll be taking advantage of this in a followup patch.

With this change we can now finally sink load-modify-store idioms such as:

if (a)
  return *b += 3;
else
  return *b += 4;

=>

%z = load i32, i32* %y
%.sink = select i1 %a, i32 3, i32 4
%b = add i32 %z, %.sink
store i32 %b, i32* %y
ret i32 %b

When this works for switches it'll be even more powerful.

Diff Detail

Repository
rL LLVM

Event Timeline

jmolloy updated this revision to Diff 63242.Jul 8 2016, 9:11 AM
jmolloy retitled this revision from to [SimplifyCFG] Rewrite SinkThenElseCodeToEnd.
jmolloy updated this object.
jmolloy added reviewers: sanjoy, mcrosier.
jmolloy set the repository for this revision to rL LLVM.
jmolloy added a subscriber: llvm-commits.
mcrosier updated this object.Jul 8 2016, 9:17 AM
sanjoy requested changes to this revision.Jul 12 2016, 9:58 AM
sanjoy edited edge metadata.
sanjoy added inline comments.
lib/Transforms/Utils/SimplifyCFG.cpp
1263

Why not use Instruction::isIdenticalTo or Instruction::isIdenticalToWhenDefined?

1295

This is minor and optional to fix, but I'd write all of the any_of checks you have as all_of checks, I think that reads more natural -- "I want all of Insts to have this property, if not leave".

1299

Why not use llvm::any_of?

1301

Instead of having a dense block of code here, I'd piecemeal put the comments down here, like:

if (is not load or store)
  return may have side effects or mayreadorwritememory; // why?
...

also, why do you care about I->mayHaveSideEffects() || I->mayReadOrWriteMemory())?

1322

Can the number of operands vary between instructions in Inst? How about if they are all readnone calls with different # of arguments?

1324

Does areValuesTriviallySame take into account things like:

%a = load %x
*%x = 42;
%b = load %x

above %a and %b are trivially same, but won't compute the same value.

This revision now requires changes to proceed.Jul 12 2016, 9:58 AM
jmolloy marked 3 inline comments as done.Jul 15 2016, 3:46 AM
jmolloy added inline comments.
lib/Transforms/Utils/SimplifyCFG.cpp
1301

also, why do you care about I->mayHaveSideEffects() || I->mayReadOrWriteMemory())?

Primarily to keep this as NFC as possible. It now handles loads and stores, but I hadn't done an audit of the possible things that could go wrong if we allowed movement of sideeffect instructions.

I suppose this is probably fine. Would you like me to delete the restriction?

1322

This is enforced by Instruction::isSameOperationAs.

jmolloy updated this revision to Diff 64121.Jul 15 2016, 3:46 AM
jmolloy edited edge metadata.
jmolloy marked an inline comment as done.

Thanks Sanjoy for the review! I think all your comments should be addressed now with the exception of one.

Cheers,

James

jmolloy added a reviewer: hans.Aug 2 2016, 4:39 AM

Ping! Hans, do you have any comments on this?

hans edited edge metadata.Aug 2 2016, 1:56 PM

Looks good as far as I can tell. It would be good to hear if Sanjoy's happy with it too though, since he started reviewing it.

lib/Transforms/Utils/SimplifyCFG.cpp
1388

I know I'm oldfashioned, but I'd find this easier to read as a simple loop over Insts rather than all_of :-)

Since we want to return false for the cases below, we might as well do that directly rather than returning false in the lambda and then checking that return value.

1408

I think this would be easier to read as a for-loop too.

hans added a comment.Aug 2 2016, 2:21 PM

When this works for switches it'll be even more powerful.

Oh yes.

And can it work for function calls too? I.e. transforming

if (foo)
  x = f(1)
else
  x = f(2)

to

x = f(foo ? 1 : 2);
majnemer added inline comments.
lib/Transforms/Utils/SimplifyCFG.cpp
1388

I think the llvm:: is a little superfluous here.

1395

The comment says "change memory" but you are using mayReadOrWriteMemory.

Which is right?

1458

I think llvm:: here is not needed.

1509

Use llvm's all_of?

sanjoy added a comment.Aug 2 2016, 3:26 PM

Some comments inline (and apologies for the delay!).

lib/Transforms/Utils/SimplifyCFG.cpp
1322

I think this needs a comment describing what it does (especially the role of At0 and At1).

1337

Does this do the right thing for alloca instructions? If it does, can you please add a test case?

1415

The isa<PHINode>(U) seems repeated -- why not:

if (I0 is not store) {
  auto *PNUse = dyn_cast<>(*I0->user_begin());
  if (!PNUse || !all_of(Insts, [](I) { return *I->user_begin == PNUse; }))
    return false;
}
1420

Very minor, but I'd s/O/OI and s/E/OE.

1423

Does this work with token types (which cannot be PHI'ed)?

1444

Can't you use getPrevNode() here and earlier?

1470

You at least need to handle token types here.

jmolloy marked 10 inline comments as done.Aug 3 2016, 3:32 AM

Thanks all for the comments! Patch updated.

lib/Transforms/Utils/SimplifyCFG.cpp
1395

mayReadOrWriteMemory was intended to be a proxy for "sideeffects". mayHaveSideEffects() is better.

1423

The checks to isEHPad() catch all instructions that can take or return token types apart from catchret, which as a terminator can never be considered for sinking here.

1470

Tokens should never ever get here. I've added an assert to that effect.

jmolloy updated this revision to Diff 66637.Aug 3 2016, 3:49 AM
jmolloy edited edge metadata.
jmolloy removed rL LLVM as the repository for this revision.
jmolloy marked 2 inline comments as done.
majnemer added inline comments.Aug 3 2016, 8:49 AM
lib/Transforms/Utils/SimplifyCFG.cpp
1461

extra llvm::

1463

There are intrinsics which take an return tokens.

jmolloy updated this revision to Diff 66788.Aug 4 2016, 4:52 AM
jmolloy set the repository for this revision to rL LLVM.

Thanks again for the review; sorry I forgot to update the last diff to the very latest version so it didn't have all the changes I'd intended to publish.

I think all comments are now addressed.

sanjoy requested changes to this revision.Aug 5 2016, 6:49 PM
sanjoy edited edge metadata.

I think all comments are now addressed.

I think you missed two?

lib/Transforms/Utils/SimplifyCFG.cpp
1322

This one wasn't addressed.

1337

This one wasn't addressed.

This revision now requires changes to proceed.Aug 5 2016, 6:49 PM
jmolloy updated this revision to Diff 67072.Aug 6 2016, 3:59 AM
jmolloy edited edge metadata.
jmolloy marked an inline comment as done.

Thanks Sanjoy. Sorry for not addressing those; must have got a bit lost in the "comment soup".

Addressed, and after extra testing I've discovered we could be sinking GEPs and replacing an operand that must be constant with a variable (struct indexing) so I've fixed that and added a testcase.

jmolloy updated this revision to Diff 67073.Aug 6 2016, 4:03 AM
jmolloy edited edge metadata.
sanjoy accepted this revision.Aug 9 2016, 11:20 AM
sanjoy edited edge metadata.

lgtm with minor comments inline.

lib/Transforms/Utils/SimplifyCFG.cpp
1328

Stray / at EOL?

1369

Optional, but I'd extract out a WritesMemory lambda out and use it twice. I suspect the conditions will be more readable that way.

1442

Again, I personally would've extracted out a named SameAsI0 lambda here. That way the condition will read more naturally.

This revision is now accepted and ready to land.Aug 9 2016, 11:20 AM
jmolloy closed this revision.Aug 15 2016, 1:13 AM

Thanks Sanjoy - committed with your suggestions in r278660!

lib/Transforms/Utils/SimplifyCFG.cpp
1337

This is never called for alloca instructions. There is a known set of semantics-breaking instructions pruned out in line 1389.

sebpop added a subscriber: sebpop.Aug 30 2016, 6:28 AM

Hi James,

a big regression on a proprietary benchmark attracted my attention to your patch.
I need to do a bit more perf analysis to figure out why the regression happened.

Reading through the patch, I remembered my tries to make the CFG simplifier stronger
to catch a hoist testcase. I think there are serious limitations to the CFG simplifier, and
also because it is called a gazillion times (after every other CFG transform pass), it may
be quite cost prohibitive to implement anything stronger than what it currently does.

I was wondering whether you would consider extending GVN-hoist to sink instructions?

lib/Transforms/Utils/SimplifyCFG.cpp
1368

Have you considered implementing sinking in the GVN-hoist pass?
That would make sinking more powerful as it works on MemorySSA,
more general by handling switch stmts, and arbitrarily complex CFG.

Sinking candidates are already detected as equivalent expressions
by the hoister using value numbering. In some cases some expressions
can only be sinked because of side effects in the upper CFG: those are
good candidates to be sinked if there are no hazards below.

Hi Sebastien,

Sorry to hear this caused a regression; perhaps it's similar to https://llvm.org/bugs/show_bug.cgi?id=30188 ? There, improved SimplifyCFG confuses SROA.

SimplifyCFG's sinking logic was and is indeed dumb. It being run a million times is a dual-edged sword though - yes, complex algorithms are difficult to run in the time constraint but also you get many attempts at optimizing across the compilation pipeline. Code sinking is a really important optimization that I think really needs to be done multiple times during canonicalization as more opportunities are opened up (by CSE, InstCombine etc).

I think there's certainly an upside in implementing a better, higher runtime algorithm in something like GVN hoist, but I don't think that should replace the dumb logic in SimplifyCFG entirely. In fact, this rewrite makes it slightly less dumb at no extra algorithmic complexity, and the code you commented on (that is deliberately conservative) gets deleted in https://reviews.llvm.org/D23911 as it becomes no longer needed.

Cheers,

James