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

Missed optimization between range parameter metadata and assumes #123278

Closed
scottmcm opened this issue Jan 17, 2025 · 5 comments
Closed

Missed optimization between range parameter metadata and assumes #123278

scottmcm opened this issue Jan 17, 2025 · 5 comments
Assignees
Labels
llvm:optimizations missed-optimization question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!

Comments

@scottmcm
Copy link

scottmcm commented Jan 17, 2025

Now that range parameter metadata exists (🎉) I'm trying to remove some of our assumes that rustc outputs which should no longer be necessary (rust-lang/rust#135610).

That works for almost all of our tests in https://github.com/rust-lang/rust/blob/master/tests/codegen/transmute-optimized.rs , but one.

We end up, after optimizations, still getting

define noundef range(i8 1, 4) i8 @ordering_transmute_onetwothree(i8 noundef returned range(i8 -1, 2) %x) unnamed_addr #2 {
start:
  %0 = icmp ne i8 %x, 0
  tail call void @llvm.assume(i1 %0)
  %1 = icmp ult i8 %x, 4
  tail call void @llvm.assume(i1 %1)
  ret i8 %x
}

That input range is [-1, 2) and those assumes are a range [1, 4), so it ought to simplify to just ret i8 1, but it doesn't.

Alive2 proof that it would be legal: https://alive2.llvm.org/ce/z/GucQsJ -- and legal even without the range on the return value.

Maybe this is somehow related to the x uge 1 being turned into x ne 0, and thus it not noticing there's a range? Or maybe it's something about the wrap-around?

SEO: rust transmute range bounds


As an aside, I'd love to emit these as range assume operand bundles instead of icmps, but AFAICT those don't exist yet, so I'm stuck with the icmps for now 🙁

bors added a commit to rust-lang-ci/rust that referenced this issue Jan 17, 2025
Emit fewer `assume`s now that we have `range` metadata on parameters

We still need the `assume` for the *target* type's range, but we no longer need it for the *source* type's range.

Admittedly there's one test not properly handled by LLVM today, but it's synthetic, so I'd still be fine doing this and just updating the test once LLVM fixes the bug (llvm/llvm-project#123278).  All the other optimization tests still pass.

Hopefully this means less crud for LLVM to churn through in `opt` builds...
@dtcxzyw
Copy link
Member

dtcxzyw commented Jan 17, 2025

I cannot reproduce this issue: https://godbolt.org/z/aeh996njM It should be simplified by CVP/SCCP.

BTW, if the range is known in the frontend, you can convert it into the canonical form (X + offset) upred C instead of two comparisons. For example, you should emit assume(x - 1 u< 3) for [1, 4). See also ConstantRange::getEquivalentICmp(CmpInst::Predicate &Pred, APInt &RHS, APInt &Offset).

@scottmcm
Copy link
Author

I cannot reproduce this issue

On nightly there's a another assume for the input that's a duplicate of the range metadata, which is how that passes.

Adding -C no-prepopulate-passes to your link I get https://godbolt.org/z/K5c55dP5q

define noundef range(i8 1, 4) i8 @ordering_transmute_onetwothree(i8 noundef range(i8 -1, 2) %x) unnamed_addr {
start:
  %x.dbg.spill = alloca [1 x i8], align 1
  store i8 %x, ptr %x.dbg.spill, align 1
  %0 = icmp uge i8 %x, -1
  %1 = icmp ule i8 %x, 1
  %2 = or i1 %0, %1
  call void @llvm.assume(i1 %2)
  %3 = icmp uge i8 %x, 1
  call void @llvm.assume(i1 %3)
  %4 = icmp ule i8 %x, 3
  call void @llvm.assume(i1 %4)
  ret i8 %x
}

Where it's that first of the three assumes that enables the optimization, even though that assume should be a complete duplicate of the range(i8 -1, 2).

And sorry for not providing a repro link earlier, but here's one with opt trunk: https://llvm.godbolt.org/z/c4jascGah

For example, you should emit assume(x - 1 u< 3) for [1, 4).

Oh, if that's better I'll absolutely do that. Thanks!

@dtcxzyw
Copy link
Member

dtcxzyw commented Jan 17, 2025

I am trying to avoid breaking assume((icmp X, C1) && (icmp X, C2)) into two assumptions in InstCombine.

// Canonicalize assume(a && b) -> assume(a); assume(b);
// Note: New assumption intrinsics created here are registered by
// the InstCombineIRInserter object.
FunctionType *AssumeIntrinsicTy = II->getFunctionType();
Value *AssumeIntrinsic = II->getCalledOperand();
Value *A, *B;
if (match(IIOperand, m_LogicalAnd(m_Value(A), m_Value(B)))) {
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic, A, OpBundles,
II->getName());
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic, B, II->getName());
return eraseInstFromFunction(*II);
}
// assume(!(a || b)) -> assume(!a); assume(!b);
if (match(IIOperand, m_Not(m_LogicalOr(m_Value(A), m_Value(B))))) {
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic,
Builder.CreateNot(A), OpBundles, II->getName());
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic,
Builder.CreateNot(B), II->getName());
return eraseInstFromFunction(*II);
}

@dtcxzyw dtcxzyw self-assigned this Jan 17, 2025
@scottmcm
Copy link
Author

scottmcm commented Jan 17, 2025

Update: I locally changed how rustc emits those assumes, and it fixed the test! 🎉

Now I get

define noundef range(i8 1, 4) i8 @ordering_transmute_onetwothree(i8 noundef range(i8 -1, 2) %x) unnamed_addr #2 {
start:
  %0 = add nsw i8 %x, -1
  %1 = icmp ult i8 %0, 3
  tail call void @llvm.assume(i1 %1)
  ret i8 1
}

And the compiler need to do less work to emit these in the first place, so beautiful.

Thank you, @dtcxzyw! I appreciate your help ❤

Up to you if you want to keep this open or close it as "frontend doing it wrong".

@dtcxzyw
Copy link
Member

dtcxzyw commented Jan 17, 2025

I am trying to avoid breaking assume((icmp X, C1) && (icmp X, C2)) into two assumptions in InstCombine.

llvm-project/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp

Lines 3164 to 3183 in 7253c6f

// Canonicalize assume(a && b) -> assume(a); assume(b);
// Note: New assumption intrinsics created here are registered by
// the InstCombineIRInserter object.
FunctionType *AssumeIntrinsicTy = II->getFunctionType();
Value *AssumeIntrinsic = II->getCalledOperand();
Value *A, *B;
if (match(IIOperand, m_LogicalAnd(m_Value(A), m_Value(B)))) {
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic, A, OpBundles,
II->getName());
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic, B, II->getName());
return eraseInstFromFunction(*II);
}
// assume(!(a || b)) -> assume(!a); assume(!b);
if (match(IIOperand, m_Not(m_LogicalOr(m_Value(A), m_Value(B))))) {
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic,
Builder.CreateNot(A), OpBundles, II->getName());
Builder.CreateCall(AssumeIntrinsicTy, AssumeIntrinsic,
Builder.CreateNot(B), II->getName());
return eraseInstFromFunction(*II);
}

Confirmed that it doesn't help :(

@dtcxzyw dtcxzyw closed this as not planned Won't fix, can't repro, duplicate, stale Jan 17, 2025
@EugeneZelenko EugeneZelenko added the question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead! label Jan 17, 2025
bors added a commit to rust-lang-ci/rust that referenced this issue Jan 17, 2025
Emit fewer `assume`s now that we have `range` metadata on parameters

We still need the `assume` for the *target* type's range, but we no longer need it for the *source* type's range.

The first stab at this regressed a test, but thanks to good advice in llvm/llvm-project#123278 (comment) the second commit here changes how we emit these range assertions to the form that LLVM apparently likes better (and, conveniently, is easier to emit too) which got that test passing again 🎉

Hopefully this means less crud for LLVM to churn through in `opt` builds...
bors added a commit to rust-lang-ci/rust that referenced this issue Jan 18, 2025
Update our range `assume`s to the format that LLVM prefers

I found out in llvm/llvm-project#123278 (comment) that the way I started emitting the `assume`s in rust-lang#109993 was suboptimal, and as seen in that LLVM issue the way we're doing it -- with two `assume`s sometimes -- can at times lead to CVP/SCCP not realize what's happening because one of them turns into a `ne` instead of conveying a range.

So this updates how it's emitted from
```
assume( x >= LOW );
assume( x <= HIGH );
```
to
```
assume( (x - LOW) <= (HIGH - LOW) );
```
so that we don't need multiple `icmp`s nor multiple `assume`s for a single value.
bors added a commit to rust-lang-ci/rust that referenced this issue Jan 22, 2025
Update our range `assume`s to the format that LLVM prefers

I found out in llvm/llvm-project#123278 (comment) that the way I started emitting the `assume`s in rust-lang#109993 was suboptimal, and as seen in that LLVM issue the way we're doing it -- with two `assume`s sometimes -- can at times lead to CVP/SCCP not realize what's happening because one of them turns into a `ne` instead of conveying a range.

So this updates how it's emitted from
```
assume( x >= LOW );
assume( x <= HIGH );
```
or
```
// (for ranges that wrap the range)
assume( (x <= LOW) | (x >= HIGH) );
```
to
```
assume( (x - LOW) <= (HIGH - LOW) );
```
so that we don't need multiple `icmp`s nor multiple `assume`s for a single value, and both wrappping and non-wrapping ranges emit the same shape.

(And we don't bother emitting the subtraction if `LOW` is zero, since that's trivial for us to check too.)
github-actions bot pushed a commit to rust-lang/miri that referenced this issue Jan 23, 2025
Update our range `assume`s to the format that LLVM prefers

I found out in llvm/llvm-project#123278 (comment) that the way I started emitting the `assume`s in #109993 was suboptimal, and as seen in that LLVM issue the way we're doing it -- with two `assume`s sometimes -- can at times lead to CVP/SCCP not realize what's happening because one of them turns into a `ne` instead of conveying a range.

So this updates how it's emitted from
```
assume( x >= LOW );
assume( x <= HIGH );
```
or
```
// (for ranges that wrap the range)
assume( (x <= LOW) | (x >= HIGH) );
```
to
```
assume( (x - LOW) <= (HIGH - LOW) );
```
so that we don't need multiple `icmp`s nor multiple `assume`s for a single value, and both wrappping and non-wrapping ranges emit the same shape.

(And we don't bother emitting the subtraction if `LOW` is zero, since that's trivial for us to check too.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:optimizations missed-optimization question A question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!
Projects
None yet
Development

No branches or pull requests

4 participants