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

Stop forcing an alloc just because something looked at the discriminant #137503

Closed
scottmcm opened this issue Feb 24, 2025 · 3 comments · Fixed by #138391
Closed

Stop forcing an alloc just because something looked at the discriminant #137503

scottmcm opened this issue Feb 24, 2025 · 3 comments · Fixed by #138391
Assignees
Labels
A-codegen Area: Code generation T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@scottmcm
Copy link
Member

The rust code https://rust.godbolt.org/z/5Tvx8Pfqj

pub fn demo(x: Result<i32, u32>) -> isize {
    std::intrinsics::discriminant_value(&x)
}

gives MIR

fn demo(_1: Result<i32, u32>) -> isize {
    debug x => _1;
    let mut _0: isize;

    bb0: {
        _0 = discriminant(_1);
        return;
    }
}

for which the LLVM IR we emit look like

define noundef i64 @demo(i32 noundef range(i32 0, 2) %0, i32 noundef %1) unnamed_addr {
start:
  %x = alloca [8 x i8], align 4
  store i32 %0, ptr %x, align 4
  %2 = getelementptr inbounds i8, ptr %x, i64 4
  store i32 %1, ptr %2, align 4
  %3 = load i32, ptr %x, align 4, !range !3, !noundef !4
  %_0 = zext i32 %3 to i64
  ret i64 %_0
}

Now, LLVM's optimizer is certainly capable of cleaning that up, but it'd be nice if it never needed to in the first place -- especially in debug where the optimizer doesn't run. Since the tag is in %0 as an SSA value already, we could save a bunch of emitting work too.

I think this is caused because the Rvalue::Discriminant is a NonMutatingUseContext::Inspect, which ends up here:

| PlaceContext::NonMutatingUse(
NonMutatingUseContext::Inspect
| NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::FakeBorrow
| NonMutatingUseContext::RawBorrow
| NonMutatingUseContext::Projection,
) => {
self.locals[local] = LocalKind::Memory;
}

And thus it keeps the variable from being SSA even if it otherwise could.

I don't know what the right fix is. Maybe all the Inspects are fine with SSA, maybe discriminant should be a different kind of use, or maybe the analysis should special-case Rvalue::Discriminant.

@scottmcm scottmcm added the A-codegen Area: Code generation label Feb 24, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 24, 2025
@scottmcm
Copy link
Member Author

Or for a more realistic, less-minimized example: https://rust.godbolt.org/z/ae1KWMe1q

pub fn demo(x: Option<u32>) -> u32 {
    if let Some(x) = x { x } else { 0 }
}

that should keep the Option<u32> in SSA the whole time, but it doesn't:

define i32 @demo(i32 %0, i32 %1) unnamed_addr {
start:
  %_0 = alloca [4 x i8], align 4
  %x = alloca [8 x i8], align 4
  store i32 %0, ptr %x, align 4
  %2 = getelementptr inbounds i8, ptr %x, i64 4
  store i32 %1, ptr %2, align 4
  %3 = load i32, ptr %x, align 4
  %_2 = zext i32 %3 to i64
  %4 = icmp eq i64 %_2, 1
  br i1 %4, label %bb1, label %bb2

bb1:
  %5 = getelementptr inbounds i8, ptr %x, i64 4
  %x1 = load i32, ptr %5, align 4
  store i32 %x1, ptr %_0, align 4
  br label %bb3

bb2:
  store i32 0, ptr %_0, align 4
  br label %bb3

bb3:
  %6 = load i32, ptr %_0, align 4
  ret i32 %6

bb4:
  unreachable
}

(The return value being non-SSA is expected, but we shouldn't need to write x to an alloca.)

@saethlin saethlin added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Feb 24, 2025
@saethlin
Copy link
Member

saethlin commented Feb 24, 2025

In the general case this seems really hard to avoid, because the layout we compute is just that the discriminant has some layout at some offset. Perhaps we can special-case for where the value is all discriminant, or where the value is a ScalarPair and one element of the pair all discriminant?

This has goofy codegen too:

#[no_mangle]
pub fn demo(x: Option<()>) -> u32 {
    if let Some(x) = x { 1 } else { 0 }
}

@scottmcm
Copy link
Member Author

@saethlin Yeah, in the general case the place will be LocalKind::Memory anyway, and if that's the case the discriminant won't change anything. This only matters for things that are Immediate or ScalarPair anyway, as you say.

@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 24, 2025
@scottmcm scottmcm self-assigned this Mar 12, 2025
bors added a commit to rust-lang-ci/rust that referenced this issue Mar 12, 2025
Don't `alloca` just to look at a discriminant

Today we're making LLVM do a bunch of extra work when you match on trivial stuff like `Option<bool>` or `ControlFlow<u8>`.

This PR changes that so that simple types like `Option<u32>` or `Result<(), Box<Error>>` can stay as `OperandValue::ScalarPair` and we can still read the discriminant from them, rather than needing to write them into memory to have a `PlaceValue` just to get the discriminant out.

Fixes rust-lang#137503
@bors bors closed this as completed in addae07 Mar 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-codegen Area: Code generation T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants