Closed
Description
These used to require if
to work, but are forwarded to llvm intrinsics nowadays (#58003). We can now implement them as const eval intrinsics, too.
Impl instructions:
- add to
- remove
unsafe
for calls to the intrinsic - add to
- make functions calling the intrinsic
const fn
- Implement in
rust/src/librustc_mir/interpret/intrinsics.rs
Line 102 in 79d8a0f
read_immediate
calls, a call tobinary_op_imm
with arguments similar to the other add/sub intrinsics, in case the overflow bool in the return value is false, write the actual value to the destination like in . In case the overflow bool is true, compute the min or max value of the type like inrust/src/librustc_mir/interpret/operator.rs
Line 217 in 79d8a0f
dest
. - Write some tests that use the saturating methods in constants
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
pmccarter commentedon Jan 31, 2019
I'd really like to do this!
oli-obk commentedon Feb 1, 2019
It's all yours! If anything is unclear, don't hesitate to ask.
pmccarter commentedon Feb 1, 2019
Thanks! I have some questions, please let me know if I'm misunderstanding anything...
Is it correct that the point of this ticket is to avoid code generation (LLVM intrinsics) by evaluating during compilation (const eval intrinsics), where previously LLVM branching code was generated? Is there already compiler logic to detect non immediate arguments/terms so that the LLVM code is generated even after the functions are whitelisted as const?
I can't find any compiler code where calls to n.saturating_add(m) are wrapped in unsafe blocks, and why would they be? Also, are the implementation guidelines saying that all functions with calls to these two functions can be marked as const? Why would that be, irregardless of other statements in the function?
nikic commentedon Feb 1, 2019
Not quite. For normal code, the LLVM intrinsics will still be generated (at which point LLVM may evaluate them during optimization, but that's a different matter). Making the intrinsics const just means that they can be used in contexts where the result must be evaluated during compilation. It allows you to write something like
const N = A.saturating_add(B);
.You need to separate two things here: The
saturating_add
methods on numeric types likeu32
, and thesaturating_add
intrinsic incore::intrinsics
. By default all such intrinsics are unsafe, but forsaturating_add
in particular there is no reason for it to be. (I think this is already the case -- though maybe this has to be marked separate for const handling.)This refers to the functions that call the intrinsics, which should just be the
saturing_add
/saturating_sub
methods incore::num
(such as https://github.com/rust-lang/rust/blob/master/src/libcore/num/mod.rs#L885). Those are the only functions that should be marked asconst
.RalfJung commentedon Feb 1, 2019
No. This is to make the CTFE engine in Rust support these intrinsics. When you write
const FOO: T = bar;
orstatic FOO: T = bar;
, Rust has to evaluatebar
at compile-time, that's called CTFE (compile-time function evaluation). And the point of this issue is to enablebar
to usesaturating_add
, to make code like this work:CTFE is very different from const propagation, which is an optimization that opportunistically will try to evaluate code at compile-time. Const propagation is performed by LLVM, rustc does not have to do anything for it to happen.
pmccarter commentedon Feb 4, 2019
Thanks for the help guys.
Currently the num module saturating_add() wrapper only uses the intrinsic for stage 1+, so the function can't just be marked as const because of the non const checked_add() call in the stage 0 compiler. Looks like other intrinsic calls don't need the stage guards so not sure what the issue is. Does this need to wait for the bootstrapping compile version to advance or something?
Also not sure about the special // ~> comments...
nikic commentedon Feb 4, 2019
@pmccarter I believe the way to handle this would be to move the cfg to cover the whole function. You'd have a stage0 variant that is non-const and also uses the old checked_add based implementation, and you'd have a not(stage0) variant that is const and uses the saturating_add intrinsic based implementation.
So basically instead of
you'd have
saturating_add
andsaturating_sub
const
functions #58246