From 25f17dd8254588300f7bc431d586f3b7c4ee2df1 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 29 Jan 2025 22:22:56 +0100 Subject: [PATCH 1/2] Optimize `ToString` implementation for integers --- library/alloc/src/string.rs | 48 +++++++++++++++++++++++++++++++++++++ library/core/src/fmt/num.rs | 28 +++++++++++++++------- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 9a161d057db09..1a0b3b43eb698 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2824,7 +2824,54 @@ impl SpecToString for bool { } } +macro_rules! impl_to_string { + ($($signed:ident, $unsigned:ident,)*) => { + $( + #[cfg(not(no_global_oom_handling))] + #[cfg(not(feature = "optimize_for_size"))] + impl SpecToString for $signed { + #[inline] + fn spec_to_string(&self) -> String { + const SIZE: usize = $signed::MAX.ilog(10) as usize + 1; + let mut buf = [core::mem::MaybeUninit::::uninit(); SIZE]; + // Only difference between signed and unsigned are these 8 lines. + let mut out; + if *self < 0 { + out = String::with_capacity(SIZE + 1); + out.push('-'); + } else { + out = String::with_capacity(SIZE); + } + + out.push_str(self.unsigned_abs()._fmt(&mut buf)); + out + } + } + #[cfg(not(no_global_oom_handling))] + #[cfg(not(feature = "optimize_for_size"))] + impl SpecToString for $unsigned { + #[inline] + fn spec_to_string(&self) -> String { + const SIZE: usize = $unsigned::MAX.ilog(10) as usize + 1; + let mut buf = [core::mem::MaybeUninit::::uninit(); SIZE]; + + self._fmt(&mut buf).to_string() + } + } + )* + } +} + +impl_to_string! { + i8, u8, + i16, u16, + i32, u32, + i64, u64, + isize, usize, +} + #[cfg(not(no_global_oom_handling))] +#[cfg(feature = "optimize_for_size")] impl SpecToString for u8 { #[inline] fn spec_to_string(&self) -> String { @@ -2844,6 +2891,7 @@ impl SpecToString for u8 { } #[cfg(not(no_global_oom_handling))] +#[cfg(feature = "optimize_for_size")] impl SpecToString for i8 { #[inline] fn spec_to_string(&self) -> String { diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index 4467b37bd4510..ba30518d70bc2 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -208,7 +208,11 @@ macro_rules! impl_Display { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "optimize_for_size"))] { - self._fmt(true, f) + const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1; + // Buffer decimals for $unsigned with right alignment. + let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; + + f.pad_integral(true, "", self._fmt(&mut buf)) } #[cfg(feature = "optimize_for_size")] { @@ -222,7 +226,11 @@ macro_rules! impl_Display { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(not(feature = "optimize_for_size"))] { - return self.unsigned_abs()._fmt(*self >= 0, f); + const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1; + // Buffer decimals for $unsigned with right alignment. + let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; + + f.pad_integral(*self >= 0, "", self.unsigned_abs()._fmt(&mut buf)) } #[cfg(feature = "optimize_for_size")] { @@ -233,10 +241,13 @@ macro_rules! impl_Display { #[cfg(not(feature = "optimize_for_size"))] impl $unsigned { - fn _fmt(self, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { - const MAX_DEC_N: usize = $unsigned::MAX.ilog(10) as usize + 1; - // Buffer decimals for $unsigned with right alignment. - let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; + #[doc(hidden)] + #[unstable( + feature = "fmt_internals", + reason = "specialized method meant to only be used by `SpecToString` implementation", + issue = "none" + )] + pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::]) -> &'a str { // Count the number of bytes in buf that are not initialized. let mut offset = buf.len(); // Consume the least-significant decimals from a working copy. @@ -301,13 +312,12 @@ macro_rules! impl_Display { // SAFETY: All buf content since offset is set. let written = unsafe { buf.get_unchecked(offset..) }; // SAFETY: Writes use ASCII from the lookup table exclusively. - let as_str = unsafe { + unsafe { str::from_utf8_unchecked(slice::from_raw_parts( MaybeUninit::slice_as_ptr(written), written.len(), )) - }; - f.pad_integral(is_nonnegative, "", as_str) + } } })* From 1ef7585c9ef976211b897e22fad6ae9aa2c2f415 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 30 Jan 2025 11:05:34 +0100 Subject: [PATCH 2/2] Update weirdly failing ui tests --- tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs | 2 +- tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs | 2 +- .../equal-pointers-unequal/exposed-provenance/inline2.rs | 2 +- .../codegen/equal-pointers-unequal/exposed-provenance/zero.rs | 2 +- .../codegen/equal-pointers-unequal/strict-provenance/inline2.rs | 2 +- .../ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs b/tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs index 9a1ace86e4db8..5ec3c7cbdf55b 100644 --- a/tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs +++ b/tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs @@ -23,7 +23,7 @@ fn main() { let v = 0; &v as *const _ as usize }; - assert_eq!(a.to_string(), b.to_string()); + assert_eq!(format!("{a}"), format!("{b}")); assert_eq!(format!("{}", a == b), "true"); assert_eq!(format!("{}", cmp_in(a, b)), "true"); assert_eq!(format!("{}", cmp(a, b)), "true"); diff --git a/tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs b/tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs index d1aa95a9a569d..731c5b67882bd 100644 --- a/tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs +++ b/tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs @@ -21,7 +21,7 @@ fn main() { // It's not zero, which means `a` and `b` are not equal. assert_ne!(i, 0); // But it looks like zero... - assert_eq!(i.to_string(), "0"); + assert_eq!(format!("{i}"), "0"); // ...and now it *is* zero? assert_eq!(i, 0); // So `a` and `b` are equal after all? diff --git a/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs b/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs index f128e1bb0841a..94739708ab8bd 100644 --- a/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs +++ b/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs @@ -25,7 +25,7 @@ fn main() { let v = 0; ptr::from_ref(&v).expose_provenance() }; - assert_eq!(a.to_string(), b.to_string()); + assert_eq!(format!("{a}"), format!("{b}")); assert_eq!(format!("{}", a == b), "true"); assert_eq!(format!("{}", cmp_in(a, b)), "true"); assert_eq!(format!("{}", cmp(a, b)), "true"); diff --git a/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs b/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs index 7ccff8d0848e9..b7824f53d77f8 100644 --- a/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs +++ b/tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs @@ -23,7 +23,7 @@ fn main() { // It's not zero, which means `a` and `b` are not equal. assert_ne!(i, 0); // But it looks like zero... - assert_eq!(i.to_string(), "0"); + assert_eq!(format!("{i}"), "0"); // ...and now it *is* zero? assert_eq!(i, 0); // So `a` and `b` are equal after all? diff --git a/tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs b/tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs index 0414879804a5a..0f838af1fb190 100644 --- a/tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs +++ b/tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs @@ -25,7 +25,7 @@ fn main() { let v = 0; ptr::from_ref(&v).addr() }; - assert_eq!(a.to_string(), b.to_string()); + assert_eq!(format!("{a}"), format!("{b}")); assert_eq!(format!("{}", a == b), "true"); assert_eq!(format!("{}", cmp_in(a, b)), "true"); assert_eq!(format!("{}", cmp(a, b)), "true"); diff --git a/tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs b/tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs index d963e45e4cdf2..20ed991ed3d66 100644 --- a/tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs +++ b/tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs @@ -23,7 +23,7 @@ fn main() { // It's not zero, which means `a` and `b` are not equal. assert_ne!(i, 0); // But it looks like zero... - assert_eq!(i.to_string(), "0"); + assert_eq!(format!("{i}"), "0"); // ...and now it *is* zero? assert_eq!(i, 0); // So `a` and `b` are equal after all?