This is an archive of the discontinued LLVM Phabricator instance.

[InstCombine] Add optimization to prevent poison from being propagated.
ClosedPublic

Authored by hyeongyukim on Jul 3 2021, 2:34 AM.

Details

Summary

In D104569, Freeze was inserted just before br to solve the branching on undef miscompilation problem.
But value analysis was being disturbed by added freeze.

v = load ptr
cond = freeze(icmp (and v, const), const')
br cond, ...

The case in which value analysis disturbed is as above.
By changing freeze to add immediately after load, value analysis will be successful again.

v = load ptr
freeze(icmp (and v, const), const')
=>
v = load ptr
v' = freeze v
icmp (and v', const), const'

In this patch, I propose the above optimization.
With this patch, the poison will not spread as the freeze is performed early.

Diff Detail

Event Timeline

hyeongyukim created this revision.Jul 3 2021, 2:34 AM
hyeongyukim requested review of this revision.Jul 3 2021, 2:34 AM
Herald added a project: Restricted Project. · View Herald TranscriptJul 3 2021, 2:34 AM
hyeongyukim edited the summary of this revision. (Show Details)Jul 3 2021, 2:34 AM
nikic added a comment.Jul 3 2021, 2:44 AM

This optimization is unnecessarily specific to the load + and + icmp combination. I would expect it to go something like this: If the freeze operand is a) one-use, b) does not produce poison, c) has all but one guaranteed-non-poison operands, then push the freeze through to the one operand that is not guaranteed-non-poison. Recursively, this will push the freeze first through the icmp and then the and, but also work for other instruction sequences.

This optimization is unnecessarily specific to the load + and + icmp combination. I would expect it to go something like this: If the freeze operand is a) one-use, b) does not produce poison, c) has all but one guaranteed-non-poison operands, then push the freeze through to the one operand that is not guaranteed-non-poison. Recursively, this will push the freeze first through the icmp and then the and, but also work for other instruction sequences.

That's a good point!
I will improve my patch to deal with more cases.

Is there any simple way to check if Operand doesn't produce Poison? @nikic

nikic added a comment.Jul 3 2021, 12:51 PM

@hyeongyukim canCreateUndefOrPoison()

Modified to try to push freeze through instructions that propagate but don't produce poison as far as possible.

nikic added inline comments.Jul 4 2021, 9:31 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3596

This is the wrong API to use here: You want isGuaranteedNotToBeUndefOrPoison() on the operands of the instruction.

getGuaranteedNonPoisonOps() is about which operands of an instruction would result in immediate UB if they are poison.

I modified it to use isGuaranteedNotToBeUndefOrPoison() instead of isGuaranteedNotToBeUndefOrPoison().

isGuaranteedNotToBePoison -> isGuaranteedNotToBeUndefOrPoison

Reformat Code.

xbolva00 added inline comments.
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3598

Address clang tidy warnings? Plus above.

nikic added inline comments.Jul 4 2021, 11:02 AM
llvm/lib/Analysis/ValueTracking.cpp
5347 ↗(On Diff #356384)

This change should no longer be needed.

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3598

This should be for (Use &U : Inst->operands()) or so.

3611

This is not generally safe. E.g. if Op1 is an invoke, you can't insert after it. I think for now you should insert this before Inst.

3613

What you're doing here is really a separate transform: You're canonicalizing from v to freeze v, if a freeze is present. This is presumably needed for your motivating case, but I think it would be better to handle it as an independent transform, as profitability is less obvious in this case. You also don't have any test coverage for this right now.

For now, I'd just do a replaceUse() for the single use.

Remove meaningless code, modify the location where freeze inserted and some refactoring.

hyeongyukim marked 5 inline comments as done.Jul 4 2021, 12:43 PM

Thank you for your detailed review!

lebedev.ri added inline comments.
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3580

This should go into a new function

3595
3598–3601
3604

These checks should be done first

nikic added inline comments.Jul 4 2021, 1:05 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3609

You should directly store the Use * rather than the Value * and then pass that to replaceUse(). Right now you're replacing some random use of the value.

lebedev.ri added inline comments.Jul 4 2021, 1:14 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3595

And actually, what do we want to do if there are several operands with the same Value?
Unless that is okay for us (we'd still only need a single freeze) and we want to rewrite them all,
i guess we need to record the operand index, not the value.

nikic added inline comments.Jul 4 2021, 1:23 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3595

This is why it should be Use * rather than Value *.

Just to be perfectly clear, this code should be Use *NotGuaranteedNonPoisonOperand = nullptr and then below NotGuaranteedNonPoisonOperand = &U and replaceUse(*NotGuaranteedNonPoisonOperand, FI).

Also maybe rename NotGuaranteedNonPoisonOperand to MaybePoisonOperand, it's a mouthful :)

Move code to seperate function.
Fixed the problems that Value* is used instead of Use*.

hyeongyukim marked 6 inline comments as done.Jul 4 2021, 2:03 PM
hyeongyukim added inline comments.
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3580

I named it PushFreezeToPreventPoisonFromPropagate, is there a more appropriate name?
Please recommend me a better name!

3595

TBH I didn't consider the difference between Use * and Value *, but I realized it thanks to the comments.

Almost there i think

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3533–3534

Early return please
Also, this needs a comment why we don't want to just adjust all the other uses.

3535

Ok, Optional isn't much better here than just storing a pointer, let's go back to that one.

3541–3542
3545–3546

Can we just drop freeze then?

3549

Not clang-formatted

hyeongyukim marked an inline comment as done.

Optional was removed and comments were added.

hyeongyukim marked 6 inline comments as done.Jul 4 2021, 2:29 PM

Fix test code.

I corrected it wrong while refactoring it. I'll fix it again.

lebedev.ri added inline comments.Jul 4 2021, 2:34 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3516–3568

I think it would be good to have a comment explaining why we don't do this iff the OrigOpInst has other uses.
We could just change them all to use OrigFI first. Why do we want to not do this?

3517–3519

Note that it was a question, i don't know what should we be doing here.

3519

You probably want = nullptr;

3523

This looks like the freeze will get the name from it's user.
I would expect that it would get the base name from the non-frozen value.

nikic added a comment.Jul 4 2021, 3:05 PM

Can you please add a test for freeze of phi? I expect this will fail because you can't insert an instruction before a phi. It's okay to simply bail on this case for now.

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3517–3519

Normally the freeze would get dropped by the basic isGuaranteedNotToBeUndefOrPoison() fold already. I think we can only hit this case due to recursion bailout (we effectively allow one more level of recursion here).

As it doesn't cut us anything to do so, just dropping the freeze here seems reasonable.

Another fixes with some comments.

hyeongyukim marked 2 inline comments as done.Jul 4 2021, 3:22 PM
hyeongyukim added inline comments.
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3516–3568

I add an example of a problem that occurs when there is more than one use of OrigOpInst.

Can you please add a test for freeze of phi? I expect this will fail because you can't insert an instruction before a phi. It's okay to simply bail on this case for now.

define i32 @test(i1 %cond, i32 *%ptrA) {
; CHECK-LABEL: @test(
; CHECK-NEXT:    br i1 [[COND:%.*]], label [[A:%.*]], label [[B:%.*]]
; CHECK:       A:
; CHECK-NEXT:    br label [[C:%.*]]
; CHECK:       B:
; CHECK-NEXT:    [[V:%.*]] = load i32, i32* [[PTR:%.*]], align 4
; CHECK-NEXT:    [[PHI_FR:%.*]] = freeze i32 [[V]]
; CHECK-NEXT:    br label [[C]]
; CHECK:       C:
; CHECK-NEXT:    [[Y:%.*]] = phi i32 [ 0, [[A]] ], [ [[PHI_FR]], [[B]] ]
; CHECK-NEXT:    ret i32 [[Y]]
;
  br i1 %cond, label %A, label %B
A:
  br label %C
B:
  %v = load i32, i32* %ptr
  br label %C
C:
  %y = phi i32 [0, %A], [%v, %B]
  %y.fr = freeze i32 %y
  ret i32 %y.fr
}

I tried it, and it seems to go to the label included in phi and add freeze.
@nikic Can you give me another example that could fail?

nikic added inline comments.Jul 5 2021, 12:16 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3575

@hyeongyukim Looks like phi nodes are not a problem because they're handled separately here. I'd suggest to add a assert(!isa<PHINode>(...)) check in your transform to make it obvious that it can't occur there.

Add assertion about PHINode

hyeongyukim marked an inline comment as done.Jul 5 2021, 12:35 AM
nikic added a comment.Jul 5 2021, 12:09 PM

It looks like you uploaded a partial diff in the last iteration.

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3517

I'd move the assert next to the insertBefore call, as that's the only place where this matters.

3517

guranteed -> guaranteed

3525

This doesn't look like a wrong transformation to me. Just a possibly non-profitable one, as we unnecessarily use a frozen value. (I agree that we shouldn't do this, at least not in this form, but the comment does not seem right.)

llvm/test/Transforms/InstCombine/freeze.ll
88–137

It would be good to add a test for not pushing freeze through add nuw %x, 1. (In the future, we could push freeze through by dropping poison flags.)

Edit comments and add test case.

change to whole diff

nikic accepted this revision.Jul 7 2021, 12:32 PM

LGTM

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3517

As the clang-tidy warning indicates, the method should be lowercase. Also Propagate -> Propagating?

This revision is now accepted and ready to land.Jul 7 2021, 12:32 PM

rename function

@lebedev.ri
Could you check this patch? Thanks

lebedev.ri added inline comments.Jul 8 2021, 12:30 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3519

Hm, why would multiple freeze would be inserted?
The transform i envisioned is:

%a = <whatever> %x
%a.frozen = freeze %a
%b = <whatever> %a.frozen
%c = <whatever> %a
 =>
%a = <whatever> %x
%a.frozen = freeze %a
%b = <whatever> %a.frozen
%c = <whatever> %a.frozen
 =>
%x.frozen = freeze %x
%a.frozen = <whatever> %x.frozen
%b = <whatever> %a.frozen
%c = <whatever> %a.frozen

My question being, why do we not to change other users of %a to use %a.frozen?

3563–3564

I do not expect that this assertion will hold, foldOpIntoPhi() has a number of bailouts.

hyeongyukim added inline comments.Jul 8 2021, 4:45 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3519

In terms of correctness, it is still fine.
However, %aand %a.frozen have different semantics, and the optimization performed in %a may not be performed in %a.frozen.
I thought it might not be good for the frozen values to spread too much, so single-use %a is considered only.

lebedev.ri added inline comments.Jul 8 2021, 4:51 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3519

That is what i thought, too, but that's not what the comment says.

Clarify comments.

hyeongyukim added inline comments.Jul 8 2021, 9:12 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3563–3564

Do you mean OrigOp can be PHINode even if we already tried foldOpIntoPhi()?
Then, is it correct to change the assertion to the below code?

if (is<PHINode>(OrigOp))
  return nullptr;
aqjune added inline comments.Jul 9 2021, 11:07 PM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3538

Maybe update this sentence as well? Insertion of multiple freezes isn't necessary, as discussed.

3563–3564

I think so - +1 for foldOpIntoPhi handling the freeze(phi) case.

Modify to return null if OrigOp is PHINode.

lebedev.ri added inline comments.Jul 10 2021, 4:50 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3536
While we could change the other users of OrigOp to use freeze(OrigOp), that potentially reduces their optimization potential, so let's only do this iff the OrigOp is only used by the freeze.
3538

The PHI bailout should go here.
Otherwise we will be wasting compile time.

Update comment.

lebedev.ri accepted this revision.Jul 10 2021, 5:30 AM

LG i guess.
Thanks.

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3556–3558

Oh, hm, actually i suspect this should go into instsimplify, without the artificial PHINode restriction..

3580–3581

Modify pushFreezeToPreventPoisonFromPropagating to return Instruction rather than Value.

lebedev.ri added inline comments.Jul 10 2021, 6:05 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3580–3581

I actually forgot to remove this comment, it doesn't make sense.

This revision was landed with ongoing or failed builds.Jul 10 2021, 8:41 PM
This revision was automatically updated to reflect the committed changes.
lebedev.ri added inline comments.Jul 27 2021, 8:46 AM
llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
3539

The one-use check may need refinement given freezeDominatedUses()