Skip to content

Refactor core::cmp::{smallest, largest} & add mir-opt test - #161275

Open
bushrat011899 wants to merge 2 commits into
rust-lang:mainfrom
bushrat011899:variadic_min_refactor
Open

Refactor core::cmp::{smallest, largest} & add mir-opt test#161275
bushrat011899 wants to merge 2 commits into
rust-lang:mainfrom
bushrat011899:variadic_min_refactor

Conversation

@bushrat011899

@bushrat011899 bushrat011899 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Tracking Issue: #160728

Description

Based on this feedback from the initial implementation, I've added a mir-opt test to highlight the amount of MIR added by using a variadic min implementation. To my knowledge, there isn't a way to actually test the MIR generated by the actual implementation in core, so I've replicated a smaller representative sample in a test instead. Please see GodBolt for a more detailed comparison between the (unoptimised) MIR for the current and splatted versions of min (max omitted since it's identical).

In short, the splat version does produce more MIR, especially for the additional trait implementations for larger tuple sizes. However, the added MIR almost exclusively comes from the indirection of calling a method on a trait, and not specifically the #[rustc_splat] variadic implementation. Because of this, it may be worth considering a MIR pass which could inline this kind of static dispatch.

From experimenting with this test, I also found a simpler way to implement smaller and larger. The most notable difference is instead of passing all arguments through the splat, I've pulled out the first two arguments so they aren't de/tupled. This does mean 1 argument smallest/largest no longer works, but I think that's actually preferable anyway, since it's just the identity function.


Notes

  • No AI tooling of any kind was used during the creation of this PR.

Remove support for 1 argument variant and adjust implementation to reduce MIR generated.
2-argument variant in release mode reduces down to the same function regardless, but the debug build is affected.
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 18, 2026
@rustbot rustbot added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Aug 18, 2026
@rustbot

rustbot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

r? @JonathanBrouwer

rustbot has assigned @JonathanBrouwer.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 75 candidates
  • Random selection from 16 candidates

@JonathanBrouwer

Copy link
Copy Markdown
Member

r? libs

@Mark-Simulacrum

Copy link
Copy Markdown
Member

show that at the rust level it folds down to the same thing

I'm wondering if it would be easier/better to show this on -Cno-prepopulate-passes LLVM IR? That should be able to do it without duplicating core's impl, as long as the impls in core are #[inline]d into the calling code. (I assume they would be since it's all generic code even without any explicit inlines?).

cc @scottmcm

Looking at the actual impl here, I guess the reason we're pulling out two tuple 'parts' is to simplify the common case of having smallest/largest with just two arguments passed? I'm wondering if we'd get better codegen in debug mode if we made this more explicitly a loop, by transmuting the (T, T, ...) into [T; N] and then going through Iterator::{min, max}? I'm pretty sure we don't guarantee enough about repr(Rust) to make that transmute always compile, but if it does, then I think it is a sound way to access the elements?

In particular I suspect doing it via min/max on the array makes it easier for LLVM to notice the loop and not lower to "forcefully" unrolled code... while hopefully not making the common (short) case much worse.

@scottmcm scottmcm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what we probably want here is a pre-codegen MIR test: https://github.com/rust-lang/rust/tree/main/tests/mir-opt/pre-codegen

Notably, those do run mir optimizations, so it should be fine to call cmp::min and cmp::smallest and see what they do. (Which, after #161081, should both just be "call the compiler intrinsic.)

If open-coded smallest will have more overhead in unoptimized, but I'm not actually that worried about it. We always ship the sysroot with mir optimizations, for example.

I mostly just want to see that the various method layers properly fold away thanks to the mir-opt-level=2 when people call the std methods. (This is similar to the tests that make sure that passing tiny closures to Option::map can get inlined so it's not gratuitously different from a manual if-let.)


TBH it might not even need any changes to the implementation, since https://rust.godbolt.org/z/cGPqao9jf is already looking pretty reasonable. (That nightly doesn't have the min intrinsic, though, so you'll see in a test here will probably be different.)

View changes since this review

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 31, 2026
@rustbot

rustbot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Comment thread library/core/src/cmp.rs
Comment on lines +2005 to +2007
v1: T,
v2: T,
#[rustc_splat] args: impl [const] SmallestArgs<T>,

@scottmcm scottmcm Aug 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know this worked with splat; cool!

My instinct would be to keep the 1-parameter version working, but to do the middle-ground here were it emphasizes that the 0-parameter version doesn't exist without understanding the trait impls. (If in future this could be #[rustc_splat] args: [T; N], for example, that'd be pretty sweet.)

So maybe something like

Suggested change
v1: T,
v2: T,
#[rustc_splat] args: impl [const] SmallestArgs<T>,
v: T,
#[rustc_splat] args: impl [const] SmallestArgs<T>,

instead?

View changes since the review

Comment thread library/core/src/cmp.rs
{
#[inline(always)] // improves unoptimised codegen
fn smallest(v1: T, v2: T, ($($x,)*): Self) -> T {
v1.min(v2)$(.min($x))*

@scottmcm scottmcm Aug 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and having this be

Suggested change
v1.min(v2)$(.min($x))*
v $(.min($x))*

without needing the separate detupling looks really elegant.

View changes since the review

@scottmcm

scottmcm commented Aug 31, 2026

Copy link
Copy Markdown
Member

I'm wondering if we'd get better codegen in debug mode if we made this more explicitly a loop

My instinct is that that'd be really really bad for the common cases, since we'd end up with the loop in MIR even for the 2-param case, which then wouldn't fold down (mir-opt doesn't have loop optimizations) and thus wouldn't be inlinable and would thus be overall worse.

To me by the time you're passing the N args separately, it's fine (maybe even good) to forcibly expand to N calls.

(Said otherwise, I think of this as "you don't have to write min(min(a, b), min(c, d)) any more", not as a replacement for [a, b, c, d].into_iter().min().unwrap().)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants