Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscompile, probably due to DAGCombiner turning SELECT into AND even if the SELECT operands may be poison #84653

Open
bjope opened this issue Mar 9, 2024 · 1 comment
Labels
llvm:SelectionDAG SelectionDAGISel as well miscompilation

Comments

@bjope
Copy link
Collaborator

bjope commented Mar 9, 2024

Given a test case like this:

define i16 @foo(i32 %x) {
  %cmp1 = icmp sgt i32 %x, 0
  %sub = sub nsw i32 2147483647, %x
  %cmp2 = icmp sgt i32 %x, %sub
  %r = select i1 %cmp1, i1 %cmp2, i1 false
  %zext = zext i1 %r to i16
  ret i16 %zext
}

we started to see miscompiles after improvements to ValueTracking/KnownBits in 17162b6

Here is a godbolt link using aarch64 as an example (need mattr=+cssc to get a legal SMAX for i32): https://godbolt.org/z/8zxa4YEs5

When using llc -debug-only=isel,dagcombine -mtriple=aarch64 -mattr=+cssc one can see that we get an initial DAG like this:

Initial selection DAG: %bb.0 'foo:'
SelectionDAG has 16 nodes:
  t0: ch,glue = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
    t5: i1 = setcc t2, Constant:i32<0>, setgt:ch
      t7: i32 = sub nsw Constant:i32<2147483647>, t2
    t8: i1 = setcc t2, t7, setgt:ch
  t10: i1 = select t5, t8, Constant:i1<0>
  t11: i16 = zero_extend t10
    t12: i32 = zero_extend t10
  t14: ch,glue = CopyToReg t0, Register:i32 $w0, t12
  t15: ch = AArch64ISD::RET_GLUE t14, Register:i32 $w0, t14:1

Then the DAGCombiner (foldBoolSelectToLogic) will do

Combining: t10: i1 = select t5, t8, Constant:i1<0>
 ... into: t16: i1 = and t5, t8
 
SelectionDAG has 14 nodes:
  t0: ch,glue = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
        t5: i1 = setcc t2, Constant:i32<0>, setgt:ch
          t7: i32 = sub nsw Constant:i32<2147483647>, t2
        t8: i1 = setcc t2, t7, setgt:ch
      t16: i1 = and t5, t8
    t12: i32 = zero_extend t16
  t14: ch,glue = CopyToReg t0, Register:i32 $w0, t12
  t15: ch = AArch64ISD::RET_GLUE t14, Register:i32 $w0, t14:1

which I think might be wrong depending on if we see such an AND as a logical/bitwise AND (or rather if it blocks poison or not).

What happens next is that that the AND is combined into a SMAX and a new SETCC:

SelectionDAG has 13 nodes:
  t0: ch,glue = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
          t7: i32 = sub nsw Constant:i32<2147483647>, t2
        t17: i32 = smax t7, Constant:i32<0>
      t19: i1 = setcc t17, t2, setlt:ch
    t12: i32 = zero_extend t19
  t14: ch,glue = CopyToReg t0, Register:i32 $w0, t12
  t15: ch = AArch64ISD::RET_GLUE t14, Register:i32 $w0, t14:1

And here it is clear that if the sub result in poison, then the result is poison as well.

On a side note. It is a bit sad that the last couple of years more and more things that can "produce poison" (such as nsw/nuw/nneg) are being exposed in the selection DAG, while I think we lack lots of documentation about what the rules are when it comes to dealing with poison in the selection DAG.

@bjope
Copy link
Collaborator Author

bjope commented Mar 10, 2024

I forgot to write that the difference we see, after the improvement in ValueTracking in 17162b6, is that the SMAX will be folded away.
So after type legalization we just get

Optimized type-legalized selection DAG: %bb.0 'foo:'
SelectionDAG has 10 nodes:
  t0: ch,glue = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
      t22: i32 = xor t2, Constant:i32<2147483647>
    t23: i32 = setcc t22, t2, setlt:ch
  t14: ch,glue = CopyToReg t0, Register:i32 $w0, t23
  t15: ch = AArch64ISD::RET_GLUE t14, Register:i32 $w0, t14:1

while we used to get

Optimized type-legalized selection DAG: %bb.0 'foo:'
SelectionDAG has 12 nodes:
  t0: ch,glue = EntryToken
  t2: i32,ch = CopyFromReg t0, Register:i32 %0
        t22: i32 = xor t2, Constant:i32<2147483647>
      t17: i32 = smax t22, Constant:i32<0>
    t23: i32 = setcc t17, t2, setlt:ch
  t14: ch,glue = CopyToReg t0, Register:i32 $w0, t23
  t15: ch = AArch64ISD::RET_GLUE t14, Register:i32 $w0, t14:1

The smax is needed to get correct behavior when the sub (later rewritten as xor) has signed wrap.

bjope added a commit to bjope/llvm-project that referenced this issue Mar 12, 2024
bjope added a commit to bjope/llvm-project that referenced this issue Mar 13, 2024
bjope added a commit to bjope/llvm-project that referenced this issue Mar 15, 2024
bjope added a commit to bjope/llvm-project that referenced this issue Mar 15, 2024
Just like for regular IR we need to treat SELECT as conditionally
blocking poison. So (unless the condition itself is poison) the
result is only poison if the selected true/false value is poison.
Thus, when doing DAG combines that turn SELECT into arithmetic/logical
operations (e.g. AND/OR) we need to make sure that the new operations
aren't more poisonous. One way to do that is to use FREEZE to make
sure the operands aren't posion.

This patch aims at fixing the kind of miscompiles reported in
  llvm#84653
and
  llvm#85190

Solution is to make sure that we insert FREEZE, if needed to make
the fold sound, when using the foldBoolSelectToLogic and
foldVSelectToSignBitSplatMask DAG combines.

This may result in some (hopefully minor) regressions since we lack
some ways to fold away the freeze (or due to isGuaranteedNotToBePoison
being too pessimistic). Focus in this patch is to just avoid
miscompiles, but I think some of the regressions can be avoided by
general improvements regarding poison/freeze handling in SelectionDAG.
bjope added a commit to bjope/llvm-project that referenced this issue Mar 20, 2024
The description of CopyFromReg in ISDOpcodes.h says that the input
valus is defined outside the scope of the current SelectionDAG. I
think that means that we basically can treat it as a FREEZE in the
sense that it can be seen as neither being undef nor poison.

Being able to fold freeze(CopyFromReg) into CopyFromReg seems
useful to avoid regressions if we start to introduce freeze
instruction in DAGCombiner/foldBoolSelectToLogic, e.g. to solve
llvm#84653

Things _not_ dealt with in this patch:
- Depending on calling convention an input argument can be passed
  also on the stack and not in a register. If it is allowed to treat
  an argument received in a register as not being poison, then I think
  we want to treat arguments received on the stack the same way. But
  then we need to attribute load instructions, or add explicit FREEZE
  when lowering formal arguments.
- A common pattern is that there is an AssertZext or AssertSext just
  after CopyFromReg. I think that if we treat CopyFromReg as never
  being poison, then it should be allowed to fold
     (freeze(AssertZext(CopyFromReg))) -> AssertZext(CopyFromReg))
bjope added a commit to bjope/llvm-project that referenced this issue Apr 9, 2024
bjope added a commit to bjope/llvm-project that referenced this issue Apr 9, 2024
Just like for regular IR we need to treat SELECT as conditionally
blocking poison. So (unless the condition itself is poison) the
result is only poison if the selected true/false value is poison.
Thus, when doing DAG combines that turn SELECT into arithmetic/logical
operations (e.g. AND/OR) we need to make sure that the new operations
aren't more poisonous. One way to do that is to use FREEZE to make
sure the operands aren't posion.

This patch aims at fixing the kind of miscompiles reported in
  llvm#84653
and
  llvm#85190

Solution is to make sure that we insert FREEZE, if needed to make
the fold sound, when using the foldBoolSelectToLogic and
foldVSelectToSignBitSplatMask DAG combines.

This may result in some (hopefully minor) regressions since we lack
some ways to fold away the freeze (or due to isGuaranteedNotToBePoison
being too pessimistic). Focus in this patch is to just avoid
miscompiles, but I think some of the regressions can be avoided by
general improvements regarding poison/freeze handling in SelectionDAG.
bjope added a commit to bjope/llvm-project that referenced this issue Apr 9, 2024
Allow pushing freeze through SETCC and SELECT_CC even if there are
multiple "maybe poison" operands. In the past we have limited it to
a single "maybe poison" operand, but it seems profitable to also
allow the multiple operand scenario.

One goal here is to avoid some regressions seen in review of
  llvm#84924
when solving the select->and miscompiles described in
  llvm#84653
bjope added a commit to bjope/llvm-project that referenced this issue Apr 24, 2024
The description of CopyFromReg in ISDOpcodes.h says that the input
valus is defined outside the scope of the current SelectionDAG. I
think that means that we basically can treat it as a FREEZE in the
sense that it can be seen as neither being undef nor poison.

Being able to fold freeze(CopyFromReg) into CopyFromReg seems
useful to avoid regressions if we start to introduce freeze
instruction in DAGCombiner/foldBoolSelectToLogic, e.g. to solve
llvm#84653

Things _not_ dealt with in this patch:
- Depending on calling convention an input argument can be passed
  also on the stack and not in a register. If it is allowed to treat
  an argument received in a register as not being poison, then I think
  we want to treat arguments received on the stack the same way. But
  then we need to attribute load instructions, or add explicit FREEZE
  when lowering formal arguments.
- A common pattern is that there is an AssertZext or AssertSext just
  after CopyFromReg. I think that if we treat CopyFromReg as never
  being poison, then it should be allowed to fold
     (freeze(AssertZext(CopyFromReg))) -> AssertZext(CopyFromReg))
bjope added a commit to bjope/llvm-project that referenced this issue Apr 26, 2024
The description of CopyFromReg in ISDOpcodes.h says that the input
valus is defined outside the scope of the current SelectionDAG. I
think that means that we basically can treat it as a FREEZE in the
sense that it can be seen as neither being undef nor poison.

Being able to fold freeze(CopyFromReg) into CopyFromReg seems
useful to avoid regressions if we start to introduce freeze
instruction in DAGCombiner/foldBoolSelectToLogic, e.g. to solve
llvm#84653

Things _not_ dealt with in this patch:
- Depending on calling convention an input argument can be passed
  also on the stack and not in a register. If it is allowed to treat
  an argument received in a register as not being poison, then I think
  we want to treat arguments received on the stack the same way. But
  then we need to attribute load instructions, or add explicit FREEZE
  when lowering formal arguments.
- A common pattern is that there is an AssertZext or AssertSext just
  after CopyFromReg. I think that if we treat CopyFromReg as never
  being poison, then it should be allowed to fold
     (freeze(AssertZext(CopyFromReg))) -> AssertZext(CopyFromReg))
bjope added a commit that referenced this issue Apr 26, 2024
bjope added a commit to bjope/llvm-project that referenced this issue Apr 26, 2024
Just like for regular IR we need to treat SELECT as conditionally
blocking poison. So (unless the condition itself is poison) the
result is only poison if the selected true/false value is poison.
Thus, when doing DAG combines that turn SELECT into arithmetic/logical
operations (e.g. AND/OR) we need to make sure that the new operations
aren't more poisonous. One way to do that is to use FREEZE to make
sure the operands aren't posion.

This patch aims at fixing the kind of miscompiles reported in
  llvm#84653
and
  llvm#85190

Solution is to make sure that we insert FREEZE, if needed to make
the fold sound, when using the foldBoolSelectToLogic and
foldVSelectToSignBitSplatMask DAG combines.

This may result in some (hopefully minor) regressions since we lack
some ways to fold away the freeze (or due to isGuaranteedNotToBePoison
being too pessimistic). Focus in this patch is to just avoid
miscompiles, but I think some of the regressions can be avoided by
general improvements regarding poison/freeze handling in SelectionDAG.
bjope added a commit to bjope/llvm-project that referenced this issue Apr 26, 2024
Allow pushing freeze through SETCC and SELECT_CC even if there are
multiple "maybe poison" operands. In the past we have limited it to
a single "maybe poison" operand, but it seems profitable to also
allow the multiple operand scenario.

One goal here is to avoid some regressions seen in review of
  llvm#84924
when solving the select->and miscompiles described in
  llvm#84653
bjope added a commit to bjope/llvm-project that referenced this issue May 28, 2024
Just like for regular IR we need to treat SELECT as conditionally
blocking poison. So (unless the condition itself is poison) the
result is only poison if the selected true/false value is poison.
Thus, when doing DAG combines that turn SELECT into arithmetic/logical
operations (e.g. AND/OR) we need to make sure that the new operations
aren't more poisonous. One way to do that is to use FREEZE to make
sure the operands aren't posion.

This patch aims at fixing the kind of miscompiles reported in
  llvm#84653
and
  llvm#85190

Solution is to make sure that we insert FREEZE, if needed to make
the fold sound, when using the foldBoolSelectToLogic and
foldVSelectToSignBitSplatMask DAG combines.

This may result in some (hopefully minor) regressions since we lack
some ways to fold away the freeze (or due to isGuaranteedNotToBePoison
being too pessimistic). Focus in this patch is to just avoid
miscompiles, but I think some of the regressions can be avoided by
general improvements regarding poison/freeze handling in SelectionDAG.
bjope added a commit to bjope/llvm-project that referenced this issue May 28, 2024
Allow pushing freeze through SETCC and SELECT_CC even if there are
multiple "maybe poison" operands. In the past we have limited it to
a single "maybe poison" operand, but it seems profitable to also
allow the multiple operand scenario.

One goal here is to avoid some regressions seen in review of
  llvm#84924
when solving the select->and miscompiles described in
  llvm#84653
bjope added a commit to bjope/llvm-project that referenced this issue May 29, 2024
Allow pushing freeze through SETCC and SELECT_CC even if there are
multiple "maybe poison" operands. In the past we have limited it to
a single "maybe poison" operand, but it seems profitable to also
allow the multiple operand scenario.

One goal here is to avoid some regressions seen in review of
  llvm#84924
when solving the select->and miscompiles described in
  llvm#84653
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:SelectionDAG SelectionDAGISel as well miscompilation
Projects
None yet
Development

No branches or pull requests

3 participants