Skip to content

feat: add const support for float rounding methods #141521

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

ruancomelli
Copy link
Contributor

Add const support for float rounding methods

This PR makes the following float rounding methods const:

  • f64::{floor, ceil, trunc, round, round_ties_even}
  • and the corresponding methods for f16, f32 and f128

Procedure

I followed c09ed3e as closely as I could in making float methods const, and also received great guidance from https://internals.rust-lang.org/t/const-rounding-methods-in-float-types/22957/3?u=ruancomelli.

Question regarding the "unstability" attributes

I did my best to apply the appropriate const stability attributes, but I would greatly appreciate feedback or corrections:

  • #[rustc_allow_const_fn_unstable(core_intrinsics)] in library/core/src/intrinsics/mod.rs
  • #[rustc_const_unstable(feature = "f16", issue = "116909")] in library/core/src/num/f16.rs
  • #[rustc_const_unstable(feature = "f128", issue = "116909")] in library/core/src/num/f128.rs
  • #[rustc_const_unstable(feature = "const_float_methods", issue = "130843")] in library/core/src/num/32.rs and library/core/src/num/f64.rs

Please let me know if any of these are incorrect or should be revised.

Alternative implementation

I implemented one intrinsic method (float_<...>_intrinsic) for each one of the rounding methods considered in this PR (floor, ceil, trunc, round and round_ties_even). This is similar to how other intrinsics are implemented in compiler/rustc_const_eval/src/interpret/intrinsics.rs.

However, I could have implemented a single one taking a rustc_apfloat::Round parameter:

    fn float_round_intrinsic<F>(
        &mut self,
        args: &[OpTy<'tcx, M::Provenance>],
        dest: &MPlaceTy<'tcx, M::Provenance>,
        mode: rustc_apfloat::Round
    ) -> InterpResult<'tcx, ()>
    where
        F: rustc_apfloat::Float + rustc_apfloat::FloatConvert<F> + Into<Scalar<M::Provenance>>,
    {
        let x: F = self.read_scalar(&args[0])?.to_float()?;
        let res = x.round_to_integral(mode).value;
        self.write_scalar(res, dest)?;
        interp_ok(())
    }

This is, of course, shorter than the current implementation. I'd be happy to change to this version if preferred.

Note

This is my first code contribution to the Rust project, so please let me know if I missed anything - I'd be more than happy to revise and learn more. Thank you for taking the time to review it!

@rustbot
Copy link
Collaborator

rustbot commented May 24, 2025

r? @ibraheemdev

rustbot has assigned @ibraheemdev.
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

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. 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 May 24, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 24, 2025

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

@RalfJung
Copy link
Member

However, I could have implemented a single one taking a rustc_apfloat::Round parameter:

Yes, that's a lot better I think. :)

@RalfJung
Copy link
Member

Note that your branch has conflicts with the master branch. Not sure how that happened given that the PR is fresh, but it means you'll have to rebase and resolve those conflicts.

@ruancomelli ruancomelli force-pushed the const-float-rounding branch from bed4525 to 372e4cd Compare May 24, 2025 21:49
@rustbot

This comment has been minimized.

@ruancomelli
Copy link
Contributor Author

Strange, CI is failing with intrinsic is not supported at compile-time for each one of the new methods.
I'm looking into that.

@ruancomelli
Copy link
Contributor Author

ruancomelli commented May 25, 2025

As far as I can see, the main difference between the newly const methods (e.g. floor) and the ones that already work (e.g. abs) is the attributes. So my current hypothesis is that I got them wrong.

Copy link
Contributor

@CAD97 CAD97 left a comment

Choose a reason for hiding this comment

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

AIUI, the reason you're hitting "intrinsic not supported at compile time" is that std currently gets built with the bootstrap compiler, which is then used when building the new compiler. So you need to have a #[cfg(bootstrap)] version of the function/intrinsic which isn't const and a #[cfg(not(bootstrap))] which is.

IIUC, we're in the process of flipping this so that the compiler gets built with the bootstrap std, and std always gets built with the new compiler. Once that switch happens, this won't be necessary anymore.

@RalfJung
Copy link
Member

AIUI, the reason you're hitting "intrinsic not supported at compile time" is that std currently gets built with the bootstrap compiler, which is then used when building the new compiler. So you need to have a #[cfg(bootstrap)] version of the function/intrinsic which isn't const and a #[cfg(not(bootstrap))] which is.

The problem occurs when building the tests. So you don't need to do any cfg in libcore itself, but the new tests in library/coretests need a #[cfg(not(bootstrap))].

Copy link
Member

Choose a reason for hiding this comment

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

No need to add two complete sets of tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @RalfJung - do you mean I should revert the changes to this file?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah -- you're good on test coverage with the other tests. I should look into removing this file entirely and having library tests for it instead.

@RalfJung
Copy link
Member

RalfJung commented May 25, 2025 via email

@rust-log-analyzer

This comment has been minimized.

@ruancomelli
Copy link
Contributor Author

Hey @RalfJung and @CAD97, I've addressed or replied to all of your review comments.

I've also created a tracking issue for this feature here: #141555.

I have two main open questions now.

First one is: should the rounding methods on f16 and f128 be gated behind #[rustc_const_unstable(feature = {"f16", "f128"}, issue = "116909")] (from #116909) or #[rustc_const_unstable(feature = "const_float_round_methods", issue = "141555")]? I think it should be the former one, but I'm not sure.

The second question: I added #![feature(const_float_round_methods)] to library/std/src/lib.rs, which was suggested by the compiler itself. Without it, ./x check results in the following errors:

error: `core::f32::math::floor` is not yet stable as a const fn
  --> library/std/src/f32.rs:50:9
   |
50 |         core::f32::math::floor(self)
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: add `#![feature(const_float_round_methods)]` to the crate attributes to enable
  --> library/std/src/lib.rs:433:1
   |
433+ #![feature(const_float_round_methods)]
   |

... and similar for the other rounding methods

error: could not compile `std` (lib) due to 12 previous errors

This is similar to what was done in the reference PR, even though they changed library/core/src/lib.rs (notice core and not std).
However, I'm not sure if this is the correct way to proceed. I read the dev guide book, but that question remained unanswered.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-llvm-19 failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
#18 exporting to docker image format
#18 sending tarball 20.1s done
#18 DONE 27.2s
##[endgroup]
Setting extra environment values for docker:  --env ENABLE_GCC_CODEGEN=1 --env GCC_EXEC_PREFIX=/usr/lib/gcc/
[CI_JOB_NAME=x86_64-gnu-llvm-19]
[CI_JOB_NAME=x86_64-gnu-llvm-19]
debug: `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` configured.
---
sccache: Listening on address 127.0.0.1:4226
##[group]Configure the build
configure: processing command line
configure: 
configure: build.configure-args := ['--build=x86_64-unknown-linux-gnu', '--llvm-root=/usr/lib/llvm-19', '--enable-llvm-link-shared', '--set', 'rust.randomize-layout=true', '--set', 'rust.thin-lto-import-instr-limit=10', '--set', 'build.print-step-timings', '--enable-verbose-tests', '--set', 'build.metrics', '--enable-verbose-configure', '--enable-sccache', '--disable-manage-submodules', '--enable-locked-deps', '--enable-cargo-native-static', '--set', 'rust.codegen-units-std=1', '--set', 'dist.compression-profile=balanced', '--dist-compression-formats=xz', '--set', 'rust.lld=false', '--disable-dist-src', '--release-channel=nightly', '--enable-debug-assertions', '--enable-overflow-checks', '--enable-llvm-assertions', '--set', 'rust.verify-llvm-ir', '--set', 'rust.codegen-backends=llvm,cranelift,gcc', '--set', 'llvm.static-libstdcpp', '--set', 'gcc.download-ci-gcc=true', '--enable-new-symbol-mangling']
configure: build.build          := x86_64-unknown-linux-gnu
configure: target.x86_64-unknown-linux-gnu.llvm-config := /usr/lib/llvm-19/bin/llvm-config
configure: llvm.link-shared     := True
configure: rust.randomize-layout := True
configure: rust.thin-lto-import-instr-limit := 10
---
---- num::f32::floor stdout ----
---- num::f32::floor stderr ----

thread 'main' panicked at coretests/tests/num/mod.rs:1041:1:
assertion failed: (0.0 as f32).floor().is_sign_negative()
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::panic
   3: coretests::num::f32::floor
---
---- num::f64_const::floor stdout ----
---- num::f64_const::floor stderr ----

thread 'main' panicked at coretests/tests/num/mod.rs:1077:1:
assertion failed: const { (0.0 as f64).floor().is_sign_negative() }
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::panic
   3: coretests::num::f64_const::floor
---
---- num::f32_const::floor stdout ----
---- num::f32_const::floor stderr ----

thread 'main' panicked at coretests/tests/num/mod.rs:1053:1:
assertion failed: const { (0.0 as f32).floor().is_sign_negative() }
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::panic
   3: coretests::num::f32_const::floor
---
---- num::f64::floor stdout ----
---- num::f64::floor stderr ----

thread 'main' panicked at coretests/tests/num/mod.rs:1065:1:
assertion failed: (0.0 as f64).floor().is_sign_negative()
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::panic
   3: coretests::num::f64::floor
---
    num::f64_const::floor

test result: FAILED. 1902 passed; 4 failed; 4 ignored; 0 measured; 0 filtered out; finished in 1.29s

error: test failed, to rerun pass `-p coretests --test coretests`
env -u RUSTC_WRAPPER CARGO_ENCODED_RUSTDOCFLAGS="-Csymbol-mangling-version=v0\u{1f}-Zrandomize-layout\u{1f}-Zunstable-options\u{1f}--check-cfg=cfg(bootstrap)\u{1f}--check-cfg=cfg(llvm_enzyme)\u{1f}-Dwarnings\u{1f}-Wrustdoc::invalid_codeblock_attributes\u{1f}--crate-version\u{1f}1.89.0-nightly\t(8f5a570ef\t2025-05-25)" CARGO_ENCODED_RUSTFLAGS="-Csymbol-mangling-version=v0\u{1f}-Zrandomize-layout\u{1f}-Zunstable-options\u{1f}--check-cfg=cfg(bootstrap)\u{1f}--check-cfg=cfg(llvm_enzyme)\u{1f}-Zmacro-backtrace\u{1f}-Csplit-debuginfo=off\u{1f}-Clink-arg=-L/usr/lib/llvm-19/lib\u{1f}-Cllvm-args=-import-instr-limit=10\u{1f}-Clink-args=-Wl,-z,origin\u{1f}-Clink-args=-Wl,-rpath,$ORIGIN/../lib\u{1f}-Alinker-messages\u{1f}--cap-lints=allow\u{1f}--cfg\u{1f}randomized_layouts" RUSTC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/cg_clif/dist/rustc-clif" RUSTDOC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/cg_clif/dist/rustdoc-clif" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/bin/cargo" "test" "--manifest-path" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/cg_clif/build/sysroot_tests/Cargo.toml" "--target-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage1-tools/cg_clif/build/sysroot_tests_target" "--locked" "--target" "x86_64-unknown-linux-gnu" "-p" "coretests" "-p" "alloctests" "--tests" "--" "-q" exited with status ExitStatus(unix_wait_status(25856))
Command has failed. Rerun with -v to see more details.
Build completed unsuccessfully in 0:24:26
  local time: Sun May 25 19:49:46 UTC 2025
  network time: Sun, 25 May 2025 19:49:46 GMT
##[error]Process completed with exit code 1.
Post job cleanup.

Copy link
Member

Choose a reason for hiding this comment

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

Could you add tests for fract here?

@RalfJung
Copy link
Member

RalfJung commented May 25, 2025

First one is: should the rounding methods on f16 and f128 be gated behind #[rustc_const_unstable(feature = {"f16", "f128"}, issue = "116909")] (from #116909) or #[rustc_const_unstable(feature = "const_float_round_methods", issue = "141555")]? I think it should be the former one, but I'm not sure.

Good question! The typical thing we do is to gate it behind the f16/f128 feature, but then also have a second commented out more specific feature gate:

#[rustc_const_unstable(feature = "f16", issue = "116909")]
// #[rustc_const_unstable(feature = "const_float_round_methods", issue = "141555")]
pub const fn trunc(self) -> self { ... }

The second question: I added #![feature(const_float_round_methods)] to library/std/src/lib.rs, which was suggested by the compiler itself.

Yes, that's fine.
Thanks for asking for confirmation. :)

@RalfJung
Copy link
Member

@rustbot author

@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 May 25, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 25, 2025

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

@@ -924,6 +924,96 @@ macro_rules! test_float {
assert!($inf.div_euclid($nan).is_nan());
assert!($nan.div_euclid($inf).is_nan());
}
#[test]
#[cfg(not(bootstrap))]
fn floor() {
Copy link
Member

@RalfJung RalfJung May 26, 2025

Choose a reason for hiding this comment

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

FWIW we already have floor tests in library/coretests/tests/floats/f*.rs (and same for the other rounding methods). We probably shouldn't duplicate tests, but also those tests can't easily access the "test the same thing in const and regular fn" infrastructure that we have here.

@tgross35 how would you like these tests to be organized? Is it okay to move them from tests/float/f*.rs to here?

Copy link
Member

Choose a reason for hiding this comment

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

It's honestly kind of strange to have float tests in num at all given that the core::num module hardly has any float stuff... but it is described as being for "numeric" types, not just integers. Also this is the wrong PR to move everything around.^^

@RalfJung
Copy link
Member

r? @RalfJung

@rustbot rustbot assigned RalfJung and unassigned ibraheemdev May 26, 2025
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.

6 participants