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

Simple Option use, like in checked_sub, should optimize out fully in MIR #138544

Open
scottmcm opened this issue Mar 15, 2025 · 3 comments
Open
Labels
A-mir-opt Area: MIR optimizations C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-slow Issue: Problems and improvements with respect to performance of generated code. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.

Comments

@scottmcm
Copy link
Member

scottmcm commented Mar 15, 2025

Today, this bit of code: https://rust.godbolt.org/z/EfE68Meax

pub fn saturating_sub_at_home(lhs: u32, rhs: u32) -> u32 {
    u32::checked_sub(lhs, rhs).unwrap_or(0)
}

Doesn't MIR-opt as well as it ought, because the Option isn't completely optimized out:

    bb2: {
        StorageLive(_5);
        _5 = SubUnchecked(copy _1, copy _2);
        _3 = Option::<u32>::Some(move _5);
        StorageDead(_5);
        StorageDead(_4);
        StorageLive(_6);
        _6 = move ((_3 as Some).0: u32);
        _0 = move _6;
        StorageDead(_6);
        goto -> bb3;
    }

Even though it's clearly unnecessary to build-then-unwrap the option here.

This isn't "just" a GVN bug, because when GVN sees it it's not all together in one BB like this, and IIRC the Option local is also not SSA at the time, so it's reasonable that GVN doesn't currently address it. Yay phase ordering :/


Once #138545 lands, there will be tests in the repo further demonstrating this, at https://github.com/search?q=repo%3Arust-lang%2Frust%20138544%20%20path%3Atests%2Fmir-opt&type=code

@scottmcm scottmcm added the A-mir-opt Area: MIR optimizations label Mar 15, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Mar 15, 2025
@saethlin
Copy link
Member

Yay phase ordering :/

Is there a pass ordering that can make this optimizable?

@lolbinarycat lolbinarycat added I-slow Issue: Problems and improvements with respect to performance of generated code. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such labels Mar 16, 2025
@scottmcm
Copy link
Member Author

Is there a pass ordering that can make this optimizable?

Run everything a second time? 🙃

I think the problem is that it's a mix of stuff, since it requires both value propagation and CFG changes, so there's no obvious "oh just do this first" solution, at least not that I can see.

Good news is that at least if #138582 works it'll remove the codegen-side cost of not unwrapping it in MIR.

@FractalFir
Copy link
Contributor

I have been working on a pass that can remove those, as long as there is no other statement(besides NOP and storage) between the assigement and use.

It is a simple linear scan, so it should be pretty fast.

Right now, the biggest issue is posed by the StorageDead statement in the middle, since I need to check that the aggregate is not built from locals that "die" after that statement.

Consider MIR like this:

_2 = Option::<u32>::Some(move _1);
StorageDead(_1);
_3 =  move ((_2 as Some).0: u32);

Since local _1 is no longer alive in the second assigement, a transformation like this:

nop;
StorageDead(_1);
_3 =  move _1;

would be unsound. Going the other way posses the same problem with StorageLive's.

This is not a theoretical problem: this pattern occurs in float_to_exponential_common:

        _6 = Option::<usize>::Some(move _17);
        StorageDead(_17);
        StorageDead(_15);
        StorageDead(_14);
        StorageDead(_16);
        _7 = copy ((_6 as Some).0: usize);

I could go over all the locals referenced by the original aggregate, and check that the StorageDeads don't affect them, but that will not be enough.

Consider MIR like this:

_2 = &1
_3 = Option::<usize>::Some(move *(_2))
StorageDead(_1)
_4 =  move ((_3 as Some).0: u32);

Here, the operand move *(_2) is no longer valid, since it points to deallocated _1.

All of that makes getting rid of those simple uses of Option way harder.

compiler-errors added a commit to compiler-errors/rust that referenced this issue Mar 23, 2025
…-Simulacrum

Add MIR pre-codegen tests to track rust-lang#138544

I don't know how best to fix the problem yet, but wanted to check in some tests to demonstrate it and make sure that they get updated to keep it fixed if anyone does fix it 🙂

No code changes; just the tests for rust-lang#138544.
bors added a commit to rust-lang-ci/rust that referenced this issue Mar 23, 2025
…mpiler-errors

Rollup of 12 pull requests

Successful merges:

 - rust-lang#136040 (Remove unused trait BoundedSize)
 - rust-lang#138236 (uefi: Add OwnedEvent abstraction)
 - rust-lang#138293 (rustdoc: Gate unstable `doc(cfg())` predicates)
 - rust-lang#138509 (Add test to ensure no index out of bounds panic (rust-lang#135474))
 - rust-lang#138545 (Add MIR pre-codegen tests to track rust-lang#138544)
 - rust-lang#138631 (Update test for SGX now implementing `read_buf`)
 - rust-lang#138641 (Add unstable `--print=supported-crate-types` option)
 - rust-lang#138667 (std: uefi: fs: Implement mkdir)
 - rust-lang#138849 (doc: rename reference #create-a-configtoml to #create-a-bootstraptoml)
 - rust-lang#138854 (Fix ICE rust-lang#138415 for invalid extern function body)
 - rust-lang#138858 (Say which test failed the `COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS` assertion)
 - rust-lang#138861 (Tweak type flags, fix missing flags from coroutine kind ty)

Failed merges:

 - rust-lang#138755 ([rustdoc] Remove duplicated loop when computing doc cfgs)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this issue Mar 23, 2025
…mpiler-errors

Rollup of 12 pull requests

Successful merges:

 - rust-lang#136040 (Remove unused trait BoundedSize)
 - rust-lang#138236 (uefi: Add OwnedEvent abstraction)
 - rust-lang#138293 (rustdoc: Gate unstable `doc(cfg())` predicates)
 - rust-lang#138509 (Add test to ensure no index out of bounds panic (rust-lang#135474))
 - rust-lang#138545 (Add MIR pre-codegen tests to track rust-lang#138544)
 - rust-lang#138631 (Update test for SGX now implementing `read_buf`)
 - rust-lang#138641 (Add unstable `--print=supported-crate-types` option)
 - rust-lang#138667 (std: uefi: fs: Implement mkdir)
 - rust-lang#138849 (doc: rename reference #create-a-configtoml to #create-a-bootstraptoml)
 - rust-lang#138854 (Fix ICE rust-lang#138415 for invalid extern function body)
 - rust-lang#138858 (Say which test failed the `COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS` assertion)
 - rust-lang#138861 (Tweak type flags, fix missing flags from coroutine kind ty)

Failed merges:

 - rust-lang#138755 ([rustdoc] Remove duplicated loop when computing doc cfgs)

r? `@ghost`
`@rustbot` modify labels: rollup
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Mar 24, 2025
Rollup merge of rust-lang#138545 - scottmcm:more-option-tests, r=Mark-Simulacrum

Add MIR pre-codegen tests to track rust-lang#138544

I don't know how best to fix the problem yet, but wanted to check in some tests to demonstrate it and make sure that they get updated to keep it fixed if anyone does fix it 🙂

No code changes; just the tests for rust-lang#138544.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-mir-opt Area: MIR optimizations C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-slow Issue: Problems and improvements with respect to performance of generated code. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged.
Projects
None yet
Development

No branches or pull requests

5 participants