-
Notifications
You must be signed in to change notification settings - Fork 17
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
Support detecting const-eval vs non-const-eval in a const fn
#7
Comments
This comment has been minimized.
This comment has been minimized.
There is a solution proposed for C++20 (I am not sure if it has been merged - EDIT: It was accepted LEWG and sent to LWG and CWG) called Having to choose between whether code can be const evaluated or whether it performs good at run-time is a weird spot to be in as a programmer. I don't know whether this problem is worth solving, but the problem is real. The C++ solution, doesn't feel right, but I don't have any alternatives either. |
@gnzlbg gave us a great summary on irc. Here's the gist:
|
Note that we can "just" add such a |
When fn bar(x: i32) -> i32 { x * 2 }
const fn foo(x: i32) -> i32 {
if unsafe { is_constant_evaluated() } {
x * 2
} else {
// unsafe required because `bar` is
// not a `const fn` (hence not `const`-safe):
unsafe { bar(x) }
}
} it could attempt to evaluate both branches, and compare that both produce the same result, which is the only effect that both branches can have. If the |
We have changed course. Discussion is in https://github.com/rust-lang/rust/pull/64683/files/223c832b3894fe6ce6d61d4f459f0aa827bec264#discussion_r327153517 The TLDR is that we'd create an intrinsic pub fn const_eval_select<ARG, F, G, RET>(arg: ARG, called_in_const: F, called_at_rt: G) -> RET; that calls |
How about a language-level const fn foo(x: i32) -> i32 {
if const {
x * 2
} else {
unsafe { bar(x) }
}
} Similar to how ISO NB comments tried to change C++20's |
I think we race-conditioned on posting. See my above post for a solution that is much simpler to implement, since it doesn't pollute the const part of a function with the non-const part. |
Add `const_eval_select` intrinsic Adds an intrinsic that calls a given function when evaluated at compiler time, but generates a call to another function when called at runtime. See rust-lang/const-eval#7 for previous discussion. r? `@oli-obk.`
What are the next steps that need to happen towards stabilizing a feature like this? It would be very nice for |
I don't think we would want to stabilize |
We should probably open an issue in the stdarch repository to kick off the discussion on whether most (all?) intrinsics that are "just math" should get this treatment. |
Here's an example of how to wrap |
After going down a very deep rabbit hole it seems ADX and MULX can't currently be emitted as intrinsics due to an LLVM bug: rust-lang/stdarch#666 It seems the best path forward is probably higher-level wrappers we could use instead: rust-lang/rust#85532 ...and ideally those could be Miri supported / const friendly. All that said, there are SIMD operations we'd potentially like to use in a const context as well, so I guess I'll keep my eye on rust-lang/miri#662 |
AFAIK there are plans for some high-level portable SIMD API; probably that would be the most suitable entry point for CTFE Regarding rust-lang/miri#662; it is not clear if const_eval_select will help Miri -- after all, Miri is evaluating runtime code, not const code. The portable SIMD API is again the nicest solution here, since it only needs a fairly small number of intrinsics that we can just implement in Miri (some of them we already have, in fact). |
we can "just" rename |
Regarding In the cases where we (prospectively) want to use |
Not exactly. The problem is that you cannot have non-const and const code within the same function, you need to have separate functions, so what I would imagine would work is x86_feature_select!(the_feature, non_const_fn, const_fn, (first_arg: Foo, second_arg:Bar) ->ReturnType) which expands to fn foobar(first_arg: Foo, second_arg: Bar) -> ReturnType {
if is_x86_feature_detected!(the_feature) {
non_const_fn(first_arg, second_arg)
} else {
const_fn(first_arg, second_arg)
}
}
const_eval_select((first_arg, second_arg), const_fn, foobar) This should work right now. |
I noticed |
I don't quite know what you mean? The standard library calls |
So it's callable from a pub fn const_eval_select<ARG: Tuple, F, G, RET>( |
It's a super special magic intrinsic. And highly unstable. ;) |
Also, intrinsics are never |
Any hopes for a stable API for this sort of thing in the future? As an example use case beyond SIMD: I would like to use In |
Hopes? Yeah for sure.^^ We even had an RFC that was meant to pave the way: rust-lang/rfcs#3352. However, that didn't go anywhere, so a new approach is needed. I don't think currently anyone is pushing for it. Personally I'm considering const_mut_refs to be higher priority so that's the big const-eval feature I am currently pushing towards. The current interface is also rather awkward, with how it forces you to write two functions to select between. It's really just a hack, and we'd probably want something nicer. |
const fn
const fn
const fn
There are multiple thousand intrinsics for various platforms which users might want to use to speed up their code. It is unrealistic to implement all of these (although a certain subgroup like https://github.com/rust-lang-nursery/stdsimd/blob/05c2f61c384e2097a3a4c648344114fc4ac983be/coresimd/simd_llvm.rs seem to be manageable.
@rkruppe and @gnzlbg mentioned (with discomfort) a way to "runtime" detect whether we're in const eval or runtime (which would get completely optimized during codegen). While this would be trivial to implement, it seems very unfortunate to have to resolve to this.
There's precedent in c++ for such an escape hatch.
I am not very happy that we even need that many intrinsics outside the libstd and don't quite understand why these can't be optimizations that "just happen". Requiring users to "fix" optimizer deficiencies by giving them multiple thousand ways to gain a few cycles seems suboptimal.
It's perfectly fine not to support calling e.g. an interrupt register fiddling intrinsic at compile-time (there's no use for doing that, or it could be a noop in many cases). But when/if performance intrinsics are sprinkled onto code used by many users and there's an algorithm that works both at compile-time and runtime and could be optimized, it seems very unfortunate to scrap compile-time evaluability.
The text was updated successfully, but these errors were encountered: