Skip to content

LazyValueInfo::getConstantRangeAtUse returns incorrect range for shift instruction #60629

Closed
llvm/llvm-project-release-prs
#301
@d-makogon

Description

@d-makogon

Reproducer: https://godbolt.org/z/ez4xncxs8
Recently CorrelatedValuePropagation pass started using LVI::getConstantRangeAtUse which is expected to return more precise result than the previously used LVI::getConstantRange. This was introduced in 5c38c6a by @nikic.
With this patch applied, I get a miscompilation on the above reproducer.
Unfortunately, alive2 was unable to prove that this is a miscompilation.

So I'll explain here why the transform perfomed is incorrect. So in the test function we have a loop which takes backedge exactly one time.
It executes ashr %iv, %arg on 1st iteration, so it actually is ashr 127, %arg. Let's assume that the %arg value is 1. Then %shift is equal to 63. The second time we enter the loop block, shifted.iv would be equal to shift which is 63. Then we'd have %iv equal to 128 and %loop_cond would be false, so we'd branch to exit and return retval which is equal to shifted.iv which is 63.

In the IR after CVP pass, we replace the shifted.iv input which was shift with its first operand (which is iv) and erases the shift instruction. Now let's analyze the return value of the function - it would still be shifted.iv, but it would be equal to 128 because no shift would be done.

So we get a different return value from the function.

The faulting transform is made in the following piece of code in CorrelatedValuePropagation.cpp:

static bool processAShr(BinaryOperator *SDI, LazyValueInfo *LVI) {
  ...
  ConstantRange LRange = LVI->getConstantRangeAtUse(SDI->getOperandUse(0));
  unsigned OrigWidth = SDI->getType()->getIntegerBitWidth();
  ConstantRange NegOneOrZero =
      ConstantRange(APInt(OrigWidth, (uint64_t)-1, true), APInt(OrigWidth, 1));
  if (NegOneOrZero.contains(LRange)) {
    // ashr of -1 or 0 never changes the value, so drop the whole instruction
    ++NumAShrsRemoved;
    SDI->replaceAllUsesWith(SDI->getOperand(0));
    SDI->eraseFromParent();
    return true;
  }

So it calculates the range of the first operand of the shift, which is iv, using LVI::getConstantRangeAtUse. And this function returns empty-set for iv and this is incorrect. Then we check whether [-1, 1) contains the operand range which is empty and we obviously get a positive result and thus erase the shift.

So I tried debugging LVI::getConstantRangeAtUse, but couldn't quickly figure out how to solve it.
Here is what I got:

  1. When called on iv use in shift, it calls LVI::getConstantRange which returns [-2147483648,128).
  2. It traverses transitive uses of the value and tries to infer more precise range based on where the value is used. In the example we only have PHI users of the value, for which we call getEdgeValueLocal to get the value range on the edge from loop block to exit and it returns [128,-2147483648).
  3. It then intersects [-2147483648,128) with [128,-2147483648) and this is why we get empty set.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions