Add intrinsics for integer minimum and maximum - #161081
Conversation
| #[miri::fallback_is_spec] | ||
| pub const fn integer_min<T: Copy + [const] PartialOrd>(a: T, b: T) -> T { | ||
| if a < b { a } else { b } | ||
| } |
There was a problem hiding this comment.
cc @RalfJung because I said miri::fallback_is_spec
There was a problem hiding this comment.
Odd, why did the bot not ping me...
... ah, because it's a draft. :)
There was a problem hiding this comment.
Yeah, because I know my odds of getting a working PR first push are about 2% on a good day 🙃
EDIT: oh, and also I managed to spell it wrong 🤦
There was a problem hiding this comment.
LGTM apart from the comment nit. :)
This comment has been minimized.
This comment has been minimized.
0392eb2 to
2560466
Compare
This comment has been minimized.
This comment has been minimized.
2a35cda to
56bf46d
Compare
|
Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr
cc @bjorn3 Any special-casing of Miri in the standard library requires review. cc @rust-lang/miri
cc @rust-lang/miri |
|
r? @clarfonthey rustbot has assigned @clarfonthey. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
Given that LLVM can collapse all the mess this probably won't show much of a difference, but might as well make sure it's at least not worse somehow |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Add intrinsics for integer minimum and maximum
| #[rustc_nounwind] | ||
| #[rustc_intrinsic] | ||
| #[miri::intrinsic_fallback_is_spec] | ||
| pub const fn integer_min<T: Copy + [const] PartialOrd>(a: T, b: T) -> T { |
There was a problem hiding this comment.
The name says integer but the type signature does not. If there are constraints on the type that go beyond the signature, please spell them out in the doc comment.
There was a problem hiding this comment.
Do you have thoughts about doing it via a trait?
Like we have FloatPrimitive I could add IntegerPrimitive and bound it that way.
That exists for fallbacks more than for just type checking, though, so I don't know if it's worth doing here vs just documenting it.
Edit: Oh, actually, I think I might as well do that because then the Ord requirement can come from that trait instead of listing it out.
There was a problem hiding this comment.
Done, with both a bound and a doc-comment update.
|
I'm happy with this and would be fine merging once you make whatever fallback changes you were mentioning. I'm not on the compiler team, but just from the perspective of optimising the giant pile of operations we have in libstd, adding more intrinsics for "obvious" primitives like this is fine, even if they're technically redundant. For example, #161069 which also fell under my review recommended adding some potential additional reasoning for |
|
|
||
| other => { | ||
| tcx.dcx().emit_err(UnrecognizedIntrinsicFunction { span, name: other }); | ||
| tcx.dcx().emit_err(UnrecognizedIntrinsicFunction { span, name: other, file: file!() }); |
There was a problem hiding this comment.
I can definitely tell that this was motivated by you forgetting which file this was when you added this intrinsic. :p
There was a problem hiding this comment.
Yeah, this is where span_bug! might be better than emit_err. ;)
I don't know why we bother with "pretty" errors for intrinsic misuse anyway...
|
Curious, the build didn't get queued |
This comment has been minimized.
This comment has been minimized.
|
@rust-timer build 8764a9b |
|
Auto build was cancelled. Cancelled workflows: The next pull request likely to be tested is #162028. |
This comment has been minimized.
This comment has been minimized.
Add intrinsics for integer minimum and maximum
I got inspired to do this when looking at `SliceOrd::compare` where I was reminded that `if a < b { a } else { b }` isn't great in MIR since it takes 4 BBs. Looking at the codegen side, it turns out we currently emit [42 lines of LLVM-IR including 4 `alloca`s](https://rust.godbolt.org/z/ha4h4nT3r) for `u16::max` (pre-optimization), which is also unnecessarily bad†.
But both LLVM and Cranelift have dedicated things for min & max:
- https://llvm.org/docs/LangRef.html#llvm-umax-intrinsic
- https://docs.rs/cranelift-codegen/latest/cranelift_codegen/ir/trait.InstBuilder.html#method.smin
so let's just use those directly!
This actually wouldn't have been worth doing originally, but a couple of things have happened to change that:
- Back in 1.0 there was only `cmp::min` & `cmp::max`, so there was no place to actually do this at all, but in 2017 they were added to `Ord` as overridable things #25663 (comment)
- LLVM originally used icmp+select for these, not a dedicated construct, but then added one and as of 2022 the intrinsic is fully usable https://www.npopov.com/2022/12/20/This-year-in-LLVM-2022.html#integer-minmax-intrinsics
- Before we had intrinsic fallback this would have been more annoying to support everywhere -- GCC, [128-bit numbers on cg_clif](bytecodealliance/wasmtime#13790), CTFE, anything out-of-tree -- but now that we can write the obvious fallback we don't need to worry about that.
- The intrinsic would have helped less when it forced extra BBs and `alloca`s in codegen anyway, but [now](rust-lang/compiler-team#970) we can keep the result in SSA without needing to make it a primitive.
† Admittedly we could clean up the gratuitous badness there without needing an intrinsic, but I like doing the intrinsic anyway because that's the only way to avoid it always being stuck in the non-SSA path from the multi-BB assignments. Even if we made it inlineable, GVN and such will still just give up on seeing the `x = if a < b { a } else { b }` because it's multiple assignments to the same Local, which is non-ideal for something primitive-like.
|
💔 Test for b59ba1f failed: CI. Failed job:
|
|
(As an observer trying to understand the Rust build system, is this kind of failure known/expected to happen sometimes? Segfaulting on installing sccache seems like a very strange way to fail) |
|
A job failed! Check out the build log: (web) (plain enhanced) (plain) Click to see the possible cause of the failure (guessed by this bot) |
|
There are lots of spurious issues with GitHub actions, unfortunately. Whenever they come up, we just retry the job and hope it doesn't happen again. |
|
@bors try jobs=x86_64-msvc-1 |
This comment has been minimized.
This comment has been minimized.
Add intrinsics for integer minimum and maximum try-job: x86_64-msvc-1
|
@bors retry |
This comment has been minimized.
This comment has been minimized.
|
A job failed! Check out the build log: (web) (plain enhanced) (plain) Click to see the possible cause of the failure (guessed by this bot) |
|
Finished benchmarking commit (be4b6a9): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Our benchmarks found a performance regression caused by this PR. Next Steps:
@rustbot label: +perf-regression Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 0.1%, secondary -1.0%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary 0.1%, secondary 1.4%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeResults (primary 0.2%, secondary 0.2%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Bootstrap: 477.635s -> 474.007s (-0.76%) |
| (sym::integer_max, true) => "llvm.smax", | ||
| (sym::integer_min, false) => "llvm.umin", | ||
| (sym::integer_min, true) => "llvm.smin", | ||
| _ => bug!(), |
There was a problem hiding this comment.
is this another unreachable!()?
There was a problem hiding this comment.
Yeah, the compiler uses it to display ICEs (internal compiler errors). But here unreachable!() would also work.
|
perf triage: Some compile time impact was expected and justfied in #161081 (comment) The post-merge result doesn't match the pre-merge run, but I assume that's also somewhat expected based on that comment justification. @rustbot label: +perf-regression-triaged |
View all comments
I got inspired to do this when looking at
SliceOrd::comparewhere I was reminded thatif a < b { a } else { b }isn't great in MIR since it takes 4 BBs. Looking at the codegen side, it turns out we currently emit 42 lines of LLVM-IR including 4allocas foru16::max(pre-optimization), which is also unnecessarily bad†.But both LLVM and Cranelift have dedicated things for min & max:
so let's just use those directly!
This actually wouldn't have been worth doing originally, but a couple of things have happened to change that:
cmp::min&cmp::max, so there was no place to actually do this at all, but in 2017 they were added toOrdas overridable things Tracking issue for Ord::{min, max} #25663 (comment)allocas in codegen anyway, but now we can keep the result in SSA without needing to make it a primitive.† Admittedly we could clean up the gratuitous badness there without needing an intrinsic, but I like doing the intrinsic anyway because that's the only way to avoid it always being stuck in the non-SSA path from the multi-BB assignments. Even if we made it inlineable, GVN and such will still just give up on seeing the
x = if a < b { a } else { b }because it's multiple assignments to the same Local, which is non-ideal for something primitive-like.Done without LLMs.