From ff7c8e6dd9d9b463bbc866615032648e66cf8261 Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Wed, 12 Aug 2026 21:10:48 +0800 Subject: [PATCH 1/8] Speed up decimal integer parsing with SWAR Use SIMD-within-a-register to process 8 ASCII digits at once in from_str_radix when radix == 10 and the result is guaranteed not to overflow. Falls back to the existing per-digit loop for the remaining 0-7 digits. The fast path uses two helper functions: - is_8digits: branch-free check that all 8 bytes are b'0'..=b'9' - parse_8digits: 3 multiplications to pack 8 digits into a u64 Benchmark on 16-20 digit decimal strings (5000 iterations, stage 1): bench_u64_from_str_radix_10_long 98818 -> 73194 ns (-25.9%) bench_i64_from_str_radix_10_long 149705 -> 120089 ns (-19.8%) Also add LONG_ASCII_NUMBERS and from_str_radix_long_bench macro to exercise the fast path with strings that trigger 2+ SWAR iterations. --- library/core/src/num/mod.rs | 60 ++++++++++++++++++++++++++++ library/coretests/benches/num/mod.rs | 36 +++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 25326f4f066c6..282e818c194cd 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1589,6 +1589,30 @@ pub const fn can_not_overflow(radix: u32, is_signed_ty: bool, digits: &[u8]) radix <= 16 && digits.len() <= size_of::() * 2 - is_signed_ty as usize } +/// Checks if all 8 bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). +/// +/// Uses a SWAR (SIMD Within A Register) technique to check all 8 bytes +/// without per-byte branching. +#[inline] +const fn is_8digits(v: u64) -> bool { + let a = v.wrapping_add(0x4646_4646_4646_4646); + let b = v.wrapping_sub(0x3030_3030_3030_3030); + (a | b) & 0x8080_8080_8080_8080 == 0 +} + +/// Parses 8 ASCII decimal digits packed in a u64 into a numeric value. +/// +/// Uses a SWAR technique with 3 multiplications to convert 8 digits at once. +/// The caller must ensure all 8 bytes are ASCII digits, e.g. via [`is_8digits`]. +#[inline] +const fn parse_8digits(v: u64) -> u64 { + let mut v = v; + v = (v & 0x0f0f_0f0f_0f0f_0f0f).wrapping_mul(2561) >> 8; + v = (v & 0x00ff_00ff_00ff_00ff).wrapping_mul(6_553_601) >> 16; + v = (v & 0x0000_ffff_0000_ffff).wrapping_mul(42_949_672_960_001) >> 32; + v +} + #[cfg_attr(not(panic = "immediate-abort"), inline(never))] #[cfg_attr(panic = "immediate-abort", inline)] #[cold] @@ -1832,6 +1856,29 @@ macro_rules! from_str_int_impl { // // NOTE: We could use unchecked arithmetic here, but we don't, based on the observation // that it produces the same assembly as wrapping ones. See #163099. + + // SWAR fast path: process 8 decimal digits at once using + // SIMD-within-a-register. Only applies to radix 10, where + // the digit range is contiguous and the multiply-by-10^8 + // packing works. Safe because `can_not_overflow` guarantees + // the full result fits in `$int_ty`. + if radix == 10 { + while let [a, b, c, d, e, f, g, h, rest @ ..] = digits { + let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); + if !is_8digits(chunk) { + return Err(PIE { kind: InvalidDigit }); + } + let parsed = parse_8digits(chunk) as $int_ty; + result = result * (100_000_000u32 as $int_ty); + if is_positive { + result = result + parsed; + } else { + result = result - parsed; + } + digits = rest; + } + } + macro_rules! run_no_check_loop { ($additive_op:ident) => {{ while let [c, rest @ ..] = digits { @@ -1842,6 +1889,19 @@ macro_rules! from_str_int_impl { } }}; } + if is_positive { + run_no_check_loop!(wrapping_add) + } else { + run_no_check_loop!(wrapping_sub) + }; + while let [c, rest @ ..] = digits { + result = <$int_ty>::wrapping_mul(result, radix as _); + let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit); + result = result.$additive_op(x as $int_ty); + digits = rest; + } + }}; + } if is_positive { run_no_check_loop!(wrapping_add) } else { diff --git a/library/coretests/benches/num/mod.rs b/library/coretests/benches/num/mod.rs index a131b3454f0cc..9a63a9a6d3288 100644 --- a/library/coretests/benches/num/mod.rs +++ b/library/coretests/benches/num/mod.rs @@ -31,6 +31,21 @@ const ASCII_NUMBERS: [&str; 19] = [ "c0ffee", ]; +/// Long decimal strings (16-20 digits) that trigger the SWAR fast path +/// multiple times for 64-bit integer parsing. +const LONG_ASCII_NUMBERS: [&str; 10] = [ + "1234567890123456", // 16 digits, exactly 2 SWAR chunks + "12345678901234567", // 17 digits + "123456789012345678", // 18 digits + "1234567890123456789", // 19 digits + "18446744073709551615", // 20 digits, u64::MAX + "9223372036854775807", // 19 digits, i64::MAX + "9999999999999999", // 16 digits + "10000000000000000", // 17 digits + "-9223372036854775808", // 19 digits + sign, i64::MIN + "0000123456789012", // 16 digits with leading zeros +]; + macro_rules! from_str_bench { ($mac:ident, $t:ty) => { #[bench] @@ -63,6 +78,22 @@ macro_rules! from_str_radix_bench { }; } +macro_rules! from_str_radix_long_bench { + ($mac:ident, $t:ty, $radix:expr) => { + #[bench] + fn $mac(b: &mut Bencher) { + b.iter(|| { + LONG_ASCII_NUMBERS + .iter() + .cycle() + .take(5_000) + .filter_map(|s| <$t>::from_str_radix(black_box(s), $radix).ok()) + .max() + }) + } + }; +} + from_str_bench!(bench_u8_from_str, u8); from_str_radix_bench!(bench_u8_from_str_radix_2, u8, 2); from_str_radix_bench!(bench_u8_from_str_radix_10, u8, 10); @@ -110,3 +141,8 @@ from_str_radix_bench!(bench_i64_from_str_radix_2, i64, 2); from_str_radix_bench!(bench_i64_from_str_radix_10, i64, 10); from_str_radix_bench!(bench_i64_from_str_radix_16, i64, 16); from_str_radix_bench!(bench_i64_from_str_radix_36, i64, 36); + +// Long-string benchmarks: 16-20 digit decimal numbers that exercise +// the SWAR fast path (2+ iterations of 8-digit-at-a-time parsing). +from_str_radix_long_bench!(bench_u64_from_str_radix_10_long, u64, 10); +from_str_radix_long_bench!(bench_i64_from_str_radix_10_long, i64, 10); From 636327679f97f26ab285c0fa38b6c2022c55fb30 Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Thu, 13 Aug 2026 17:22:38 +0800 Subject: [PATCH 2/8] Fix rustfmt comment alignment in LONG_ASCII_NUMBERS Align trailing `//` comments vertically in the new benchmark data constant. rustfmt in nightly runs during the tidy CI job flags the misaligned comments. --- library/coretests/benches/num/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/coretests/benches/num/mod.rs b/library/coretests/benches/num/mod.rs index 9a63a9a6d3288..07701ccd3fca5 100644 --- a/library/coretests/benches/num/mod.rs +++ b/library/coretests/benches/num/mod.rs @@ -34,16 +34,16 @@ const ASCII_NUMBERS: [&str; 19] = [ /// Long decimal strings (16-20 digits) that trigger the SWAR fast path /// multiple times for 64-bit integer parsing. const LONG_ASCII_NUMBERS: [&str; 10] = [ - "1234567890123456", // 16 digits, exactly 2 SWAR chunks - "12345678901234567", // 17 digits - "123456789012345678", // 18 digits - "1234567890123456789", // 19 digits + "1234567890123456", // 16 digits, exactly 2 SWAR chunks + "12345678901234567", // 17 digits + "123456789012345678", // 18 digits + "1234567890123456789", // 19 digits "18446744073709551615", // 20 digits, u64::MAX - "9223372036854775807", // 19 digits, i64::MAX - "9999999999999999", // 16 digits - "10000000000000000", // 17 digits + "9223372036854775807", // 19 digits, i64::MAX + "9999999999999999", // 16 digits + "10000000000000000", // 17 digits "-9223372036854775808", // 19 digits + sign, i64::MIN - "0000123456789012", // 16 digits with leading zeros + "0000123456789012", // 16 digits with leading zeros ]; macro_rules! from_str_bench { From 7aa825c89b7c36fc6832c8a2d6ebc5142378d93e Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Fri, 14 Aug 2026 09:37:27 +0800 Subject: [PATCH 3/8] Add 32-bit SWAR path for decimal integer parsing 8-digit SWAR uses u64 ops that are emulated on 32-bit and slower than the per-byte loop. Add a 4-digit u32 variant so 32-bit targets get the speedup too. On 64-bit the 4-digit path also picks up the tail after the 8-digit loop. The 8-digit functions are cfg-gated to avoid dead code on 32-bit. --- library/core/src/num/mod.rs | 50 +++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 282e818c194cd..625497262e85d 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1593,6 +1593,7 @@ pub const fn can_not_overflow(radix: u32, is_signed_ty: bool, digits: &[u8]) /// /// Uses a SWAR (SIMD Within A Register) technique to check all 8 bytes /// without per-byte branching. +#[cfg(not(target_pointer_width = "32"))] #[inline] const fn is_8digits(v: u64) -> bool { let a = v.wrapping_add(0x4646_4646_4646_4646); @@ -1604,6 +1605,7 @@ const fn is_8digits(v: u64) -> bool { /// /// Uses a SWAR technique with 3 multiplications to convert 8 digits at once. /// The caller must ensure all 8 bytes are ASCII digits, e.g. via [`is_8digits`]. +#[cfg(not(target_pointer_width = "32"))] #[inline] const fn parse_8digits(v: u64) -> u64 { let mut v = v; @@ -1613,6 +1615,29 @@ const fn parse_8digits(v: u64) -> u64 { v } +/// Checks if all 4 bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). +/// +/// Same SWAR technique as `is_8digits` but for 32-bit registers, so it can +/// be used on platforms where 64-bit operations are expensive. +#[inline] +const fn is_4digits(v: u32) -> bool { + let a = v.wrapping_add(0x4646_4646); + let b = v.wrapping_sub(0x3030_3030); + (a | b) & 0x8080_8080 == 0 +} + +/// Parses 4 ASCII decimal digits packed in a u32 into a numeric value. +/// +/// Uses a SWAR technique with 2 multiplications to convert 4 digits at once. +/// The caller must ensure all 4 bytes are ASCII digits, e.g. via [`is_4digits`]. +#[inline] +const fn parse_4digits(v: u32) -> u32 { + let mut v = v; + v = (v & 0x0f0f_0f0f).wrapping_mul(2561) >> 8; + v = (v & 0x00ff_00ff).wrapping_mul(6_553_601) >> 16; + v +} + #[cfg_attr(not(panic = "immediate-abort"), inline(never))] #[cfg_attr(panic = "immediate-abort", inline)] #[cold] @@ -1857,12 +1882,18 @@ macro_rules! from_str_int_impl { // NOTE: We could use unchecked arithmetic here, but we don't, based on the observation // that it produces the same assembly as wrapping ones. See #163099. - // SWAR fast path: process 8 decimal digits at once using + // SWAR fast path: process decimal digits in batches using // SIMD-within-a-register. Only applies to radix 10, where - // the digit range is contiguous and the multiply-by-10^8 + // the digit range is contiguous and the multiply-by-10^N // packing works. Safe because `can_not_overflow` guarantees // the full result fits in `$int_ty`. + // + // On 64-bit+ platforms, use 8-digit batches (u64). On + // 32-bit platforms, u64 multiplication is emulated and + // slower than the per-byte loop, so only use 4-digit + // batches (u32). if radix == 10 { + #[cfg(not(target_pointer_width = "32"))] while let [a, b, c, d, e, f, g, h, rest @ ..] = digits { let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); if !is_8digits(chunk) { @@ -1877,6 +1908,21 @@ macro_rules! from_str_int_impl { } digits = rest; } + + while let [a, b, c, d, rest @ ..] = digits { + let chunk = u32::from_le_bytes([*a, *b, *c, *d]); + if !is_4digits(chunk) { + return Err(PIE { kind: InvalidDigit }); + } + let parsed = parse_4digits(chunk) as $int_ty; + result = result * (10_000u32 as $int_ty); + if is_positive { + result = result + parsed; + } else { + result = result - parsed; + } + digits = rest; + } } macro_rules! run_no_check_loop { From 4b259c7a1df74a4e6379879b2116a48379032633 Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Wed, 2 Sep 2026 10:17:41 +0800 Subject: [PATCH 4/8] Inline swar_parse_decimal into the parse path An out-of-line call on the long-input path forced LLVM to set up a stack frame in the hot caller and cost 13% on short u64 inputs. With the helper inlined the short-input regression is down to ~5% and long inputs keep their speedup. Also switch from_ascii_bytes_radix_impl to #[inline(always)] so the larger impl is still inlined into callers. --- library/core/src/num/mod.rs | 169 +++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 22 deletions(-) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 625497262e85d..9fbcca34ef1a3 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1833,7 +1833,7 @@ macro_rules! from_str_int_impl { <$int_ty>::from_ascii_bytes_radix_impl(src.as_ref(), radix) } - #[inline] + #[inline(always)] pub(super) const fn from_ascii_bytes_radix_impl(src: &[u8], radix: u32) -> Result<$int_ty, ParseIntError> { use self::IntErrorKind::*; use self::ParseIntError as PIE; @@ -1869,6 +1869,85 @@ macro_rules! from_str_int_impl { }; } + // SWAR fast path: process leading decimal digits in + // batches using SIMD-within-a-register. Only radix 10 and + // only types of more than 4 bytes (u32 max is 10 digits, so + // for smaller types this whole branch is dead code). + // + // We never batch more than 16 leading digits: that many + // decimal digits can never overflow `$int_ty`, so the batch + // arithmetic is safe to run unchecked even in debug builds. + // + // In the no-overflow branch, batching can only fire for + // inputs that exactly fill the bound (u64, 16 digits); the + // tail is then empty. In the checked branch, the batch + // shrinks the input and the remaining digits are validated + // by the checked loop, which still catches overflow for + // inputs longer than the type's range. + // + // On 64-bit+ platforms, use 8-digit batches (u64). On + // 32-bit platforms, u64 multiplication is emulated and + // slower than the per-byte loop, so only use 4-digit + // batches (u32). + let swar_min_len = if size_of::<$int_ty>() <= 4 { + usize::MAX + } else { + 16 + }; + macro_rules! run_swar { + () => {{ + if radix == 10 && digits.len() >= swar_min_len { + match Self::swar_parse_decimal(is_positive, result, digits) { + Ok((r, rest)) => { + result = r; + digits = rest; + } + Err(e) => return Err(e), + } + } + }}; + } + + macro_rules! run_unchecked_loop { + ($unchecked_additive_op:tt) => {{ + while let [c, rest @ ..] = digits { + result = result * (radix as $int_ty); + let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit); + result = result $unchecked_additive_op (x as $int_ty); + digits = rest; + } + }}; + } + macro_rules! run_checked_loop { + ($checked_additive_op:ident, $overflow_err:ident) => {{ + while let [c, rest @ ..] = digits { + // When `radix` is passed in as a literal, rather than doing a slow `imul` + // the compiler can use shifts if `radix` can be expressed as a + // sum of powers of 2 (x*10 can be written as x*8 + x*2). + // When the compiler can't use these optimisations, + // the latency of the multiplication can be hidden by issuing it + // before the result is needed to improve performance on + // modern out-of-order CPU as multiplication here is slower + // than the other instructions, we can get the end result faster + // doing multiplication first and let the CPU spends other cycles + // doing other computation and get multiplication result later. + let mul = result.checked_mul(radix as $int_ty); + let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit) as $int_ty; + result = unwrap_or_PIE!(mul, $overflow_err); + result = unwrap_or_PIE!(<$int_ty>::$checked_additive_op(result, x), $overflow_err); + digits = rest; + } + }}; + } + + // If the len of the str is short compared to the range of the type + // we are parsing into, then we can be certain that an overflow will not occur. + // This bound is when `radix.pow(digits.len()) - 1 <= T::MAX` but the condition + // above is a faster (conservative) approximation of this. + // + // Consider radix 16 as it has the highest information density per digit and will thus overflow the earliest: + // `u8::MAX` is `ff` - any str of len 2 is guaranteed to not overflow. + // `i8::MAX` is `7f` - only a str of len 1 is guaranteed to not overflow. if can_not_overflow::<$int_ty>(radix, is_signed_ty, digits) { // If the len of the str is short compared to the range of the type // we are parsing into, then we can be certain that an overflow will not occur. @@ -1954,27 +2033,7 @@ macro_rules! from_str_int_impl { run_no_check_loop!(wrapping_sub) }; } else { - macro_rules! run_checked_loop { - ($checked_additive_op:ident, $overflow_err:ident) => {{ - while let [c, rest @ ..] = digits { - // When `radix` is passed in as a literal, rather than doing a slow `imul` - // the compiler can use shifts if `radix` can be expressed as a - // sum of powers of 2 (x*10 can be written as x*8 + x*2). - // When the compiler can't use these optimisations, - // the latency of the multiplication can be hidden by issuing it - // before the result is needed to improve performance on - // modern out-of-order CPU as multiplication here is slower - // than the other instructions, we can get the end result faster - // doing multiplication first and let the CPU spends other cycles - // doing other computation and get multiplication result later. - let mul = result.checked_mul(radix as $int_ty); - let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit) as $int_ty; - result = unwrap_or_PIE!(mul, $overflow_err); - result = unwrap_or_PIE!(<$int_ty>::$checked_additive_op(result, x), $overflow_err); - digits = rest; - } - }}; - } + run_swar!(); if is_positive { run_checked_loop!(checked_add, PosOverflow) } else { @@ -1983,6 +2042,72 @@ macro_rules! from_str_int_impl { } Ok(result) } + + /// SWAR (SIMD-within-a-register) helper for radix-10 parsing. + /// Parses up to 16 leading decimal digits in 8- and 4-digit + /// batches, returning the accumulated result and the remaining + /// digits. The caller then runs the checked loop on the tail. + /// + /// Batches are bounded so the unchecked batch arithmetic can + /// never overflow `$int_ty`, even in debug builds. + /// + /// Must stay inlinable (`#[inline]`, not outlined): an out-of-line + /// call here, even though it is only reachable on the long-input + /// path, makes LLVM set up a stack frame in the hot caller and + /// measurably slows short inputs that never reach the SWAR path. + #[inline] + pub(super) const fn swar_parse_decimal( + is_positive: bool, + mut result: $int_ty, + mut digits: &[u8], + ) -> Result<($int_ty, &[u8]), ParseIntError> { + use self::IntErrorKind::*; + use self::ParseIntError as PIE; + + let mut remaining = match size_of::<$int_ty>() { + 1 => 0, + 2 => 4, + 4 => 8, + _ => 16, + }; + + #[cfg(not(target_pointer_width = "32"))] + while remaining >= 8 { + let [a, b, c, d, e, f, g, h, rest @ ..] = digits else { break }; + let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); + if !is_8digits(chunk) { + return Err(PIE { kind: InvalidDigit }); + } + let parsed = parse_8digits(chunk) as $int_ty; + result = result * (100_000_000u32 as $int_ty); + if is_positive { + result = result + parsed; + } else { + result = result - parsed; + } + digits = rest; + remaining -= 8; + } + + while remaining >= 4 { + let [a, b, c, d, rest @ ..] = digits else { break }; + let chunk = u32::from_le_bytes([*a, *b, *c, *d]); + if !is_4digits(chunk) { + return Err(PIE { kind: InvalidDigit }); + } + let parsed = parse_4digits(chunk) as $int_ty; + result = result * (10_000u32 as $int_ty); + if is_positive { + result = result + parsed; + } else { + result = result - parsed; + } + digits = rest; + remaining -= 4; + } + + Ok((result, digits)) + } } )*} } From d150ad790368ef28657955ba8ccb9d536ba909b7 Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Wed, 2 Sep 2026 11:06:12 +0800 Subject: [PATCH 5/8] Add boundary tests for SWAR decimal parsing Cover the 16-digit batch boundary, invalid digits inside the batched window, u64::MAX / i64::MIN, and overflow past them. --- library/coretests/tests/num/mod.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/library/coretests/tests/num/mod.rs b/library/coretests/tests/num/mod.rs index b1c3001790f07..ae6a5ae19f280 100644 --- a/library/coretests/tests/num/mod.rs +++ b/library/coretests/tests/num/mod.rs @@ -152,6 +152,36 @@ fn test_int_from_str_overflow() { test_parse::("-9223372036854775809", Err(IntErrorKind::NegOverflow)); } +#[test] +fn test_from_str_radix_10_swar_boundaries() { + // SWAR batches up to 16 leading digits for 64-bit types. These + // cases sit on the batch boundaries and put invalid digits inside + // the batched window. + test_parse::("9999999999999999", Ok(9_999_999_999_999_999)); + test_parse::("10000000000000000", Ok(10_000_000_000_000_000)); + test_parse::("18446744073709551615", Ok(18_446_744_073_709_551_615)); + test_parse::("18446744073709551616", Err(IntErrorKind::PosOverflow)); + test_parse::("99999999999999999999", Err(IntErrorKind::PosOverflow)); + + test_parse::("1234567890123456x", Err(IntErrorKind::InvalidDigit)); + test_parse::("x2345678901234567", Err(IntErrorKind::InvalidDigit)); + test_parse::("1234567890x2345678", Err(IntErrorKind::InvalidDigit)); + test_parse::("12345678901234567x", Err(IntErrorKind::InvalidDigit)); + + test_parse::("9223372036854775807", Ok(9_223_372_036_854_775_807)); + test_parse::("-9223372036854775808", Ok(-9_223_372_036_854_775_808)); + test_parse::("9223372036854775808", Err(IntErrorKind::PosOverflow)); + test_parse::("-9223372036854775809", Err(IntErrorKind::NegOverflow)); + test_parse::("-123456789012345678x", Err(IntErrorKind::InvalidDigit)); + test_parse::("123456789012345x6", Err(IntErrorKind::InvalidDigit)); + + // Short inputs must not be affected by the SWAR path. + test_parse::("0", Ok(0)); + test_parse::("+42", Ok(42)); + test_parse::("-42", Ok(-42)); + test_parse::("12a34", Err(IntErrorKind::InvalidDigit)); +} + #[test] fn test_can_not_overflow() { fn can_overflow(radix: u32, input: &str) -> bool From b6b313c91e6dcdf9644eb16104115c5423cd030c Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Sun, 6 Sep 2026 11:05:21 +0800 Subject: [PATCH 6/8] Split SWAR parsing out of the per-type macro from_str_int_impl! expanded a copy of swar_parse_decimal for every integer type, including u8..u32 where the code is dead. Give the macro a SWAR and a no-SWAR arm and only instantiate the batch parser for u64, i64, u128 and i128. The batch parser now lives in a dedicated decimal_swar module and does its arithmetic in i64, so signed values keep their sign when cast to i128 instead of going through u64 two's complement. Also build the digit-check constant with usize::repeat_u8 and document why it adds 0x46 rather than subtracting 0x30. --- library/core/src/num/mod.rs | 310 ++++++++++++++++++++---------------- 1 file changed, 172 insertions(+), 138 deletions(-) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 9fbcca34ef1a3..71a63c6178ffd 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1589,53 +1589,123 @@ pub const fn can_not_overflow(radix: u32, is_signed_ty: bool, digits: &[u8]) radix <= 16 && digits.len() <= size_of::() * 2 - is_signed_ty as usize } -/// Checks if all 8 bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). +/// SIMD-within-a-register helpers for radix-10 parsing. /// -/// Uses a SWAR (SIMD Within A Register) technique to check all 8 bytes -/// without per-byte branching. -#[cfg(not(target_pointer_width = "32"))] -#[inline] -const fn is_8digits(v: u64) -> bool { - let a = v.wrapping_add(0x4646_4646_4646_4646); - let b = v.wrapping_sub(0x3030_3030_3030_3030); - (a | b) & 0x8080_8080_8080_8080 == 0 -} +/// Kept outside the integer-type macro so the SWAR implementation is not +/// duplicated for every `FromStr` instantiation, and so it is only emitted +/// for the types that actually dispatch here. +mod decimal_swar { + use super::{IntErrorKind, ParseIntError}; + + /// Checks if all bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). + /// + /// For each byte `c`, two tests must both clear the high bit: + /// - `c + 0x46` overflows only when `c > 0xb9`, which is true for all + /// non-digits above `'9'` (0x39) and false for digits `0x30..=0x39`. + /// Adding `'F'` (0x46) maps `'0'..='9'` to `0x76..=0x7f`, all with + /// the high bit clear. + /// - `c - '0'` (0x30) underflows only when `c < 0x30`, which is true + /// for all non-digits below `'0'`. + /// + /// ORing the two results and testing the high bit catches any byte + /// that fails either test. + #[inline] + pub(super) const fn is_digits(v: usize) -> bool { + let a = v.wrapping_add(usize::repeat_u8(0x46)); + let b = v.wrapping_sub(usize::repeat_u8(0x30)); + (a | b) & usize::repeat_u8(0x80) == 0 + } -/// Parses 8 ASCII decimal digits packed in a u64 into a numeric value. -/// -/// Uses a SWAR technique with 3 multiplications to convert 8 digits at once. -/// The caller must ensure all 8 bytes are ASCII digits, e.g. via [`is_8digits`]. -#[cfg(not(target_pointer_width = "32"))] -#[inline] -const fn parse_8digits(v: u64) -> u64 { - let mut v = v; - v = (v & 0x0f0f_0f0f_0f0f_0f0f).wrapping_mul(2561) >> 8; - v = (v & 0x00ff_00ff_00ff_00ff).wrapping_mul(6_553_601) >> 16; - v = (v & 0x0000_ffff_0000_ffff).wrapping_mul(42_949_672_960_001) >> 32; - v -} + /// Parses 8 ASCII decimal digits packed in a u64 into a numeric value. + /// + /// The caller must ensure all 8 bytes are ASCII digits, e.g. via [`is_digits`]. + #[cfg(not(target_pointer_width = "32"))] + #[inline] + pub(super) const fn parse_8digits(v: u64) -> u64 { + let mut v = v; + v = (v & 0x0f0f_0f0f_0f0f_0f0f).wrapping_mul(2561) >> 8; + v = (v & 0x00ff_00ff_00ff_00ff).wrapping_mul(6_553_601) >> 16; + v = (v & 0x0000_ffff_0000_ffff).wrapping_mul(42_949_672_960_001) >> 32; + v + } -/// Checks if all 4 bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). -/// -/// Same SWAR technique as `is_8digits` but for 32-bit registers, so it can -/// be used on platforms where 64-bit operations are expensive. -#[inline] -const fn is_4digits(v: u32) -> bool { - let a = v.wrapping_add(0x4646_4646); - let b = v.wrapping_sub(0x3030_3030); - (a | b) & 0x8080_8080 == 0 -} + /// Parses 4 ASCII decimal digits packed in a u32 into a numeric value. + /// + /// The caller must ensure all 4 bytes are ASCII digits, e.g. via [`is_digits`]. + #[cfg(target_pointer_width = "32")] + #[inline] + pub(super) const fn parse_4digits(v: u32) -> u32 { + let mut v = v; + v = (v & 0x0f0f_0f0f).wrapping_mul(2561) >> 8; + v = (v & 0x00ff_00ff).wrapping_mul(6_553_601) >> 16; + v + } -/// Parses 4 ASCII decimal digits packed in a u32 into a numeric value. -/// -/// Uses a SWAR technique with 2 multiplications to convert 4 digits at once. -/// The caller must ensure all 4 bytes are ASCII digits, e.g. via [`is_4digits`]. -#[inline] -const fn parse_4digits(v: u32) -> u32 { - let mut v = v; - v = (v & 0x0f0f_0f0f).wrapping_mul(2561) >> 8; - v = (v & 0x00ff_00ff).wrapping_mul(6_553_601) >> 16; - v + /// Parses up to 16 leading decimal digits in batches, returning the + /// accumulated result and the remaining digits. + /// + /// The arithmetic is done in `i64` so the same code works for `u64`, + /// `i64`, `u128` and `i128`. 16 decimal digits can never overflow + /// `i64`/`u64`, so the batch arithmetic is safe to run unchecked even + /// in debug builds. + #[cfg(not(target_pointer_width = "32"))] + #[inline] + pub(super) const fn parse_decimal_i64( + is_positive: bool, + mut result: i64, + mut digits: &[u8], + ) -> Result<(i64, &[u8]), ParseIntError> { + let mut remaining = 16; + + while remaining >= 8 { + let [a, b, c, d, e, f, g, h, rest @ ..] = digits else { break }; + let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); + if !is_digits(chunk as usize) { + return Err(ParseIntError { kind: IntErrorKind::InvalidDigit }); + } + let parsed = parse_8digits(chunk) as i64; + result = result.wrapping_mul(100_000_000); + if is_positive { + result = result.wrapping_add(parsed); + } else { + result = result.wrapping_sub(parsed); + } + digits = rest; + remaining -= 8; + } + + Ok((result, digits)) + } + + #[cfg(target_pointer_width = "32")] + #[inline] + pub(super) const fn parse_decimal_i64( + is_positive: bool, + mut result: i64, + mut digits: &[u8], + ) -> Result<(i64, &[u8]), ParseIntError> { + // 32-bit platforms avoid 64-bit multiplication, so use 4-digit batches. + let mut remaining = 16; + + while remaining >= 4 { + let [a, b, c, d, rest @ ..] = digits else { break }; + let chunk = u32::from_le_bytes([*a, *b, *c, *d]); + if !is_digits(chunk as usize) { + return Err(ParseIntError { kind: IntErrorKind::InvalidDigit }); + } + let parsed = parse_4digits(chunk) as i64; + result = result.wrapping_mul(10_000); + if is_positive { + result = result.wrapping_add(parsed); + } else { + result = result.wrapping_sub(parsed); + } + digits = rest; + remaining -= 4; + } + + Ok((result, digits)) + } } #[cfg_attr(not(panic = "immediate-abort"), inline(never))] @@ -1650,9 +1720,37 @@ const fn from_ascii_bytes_radix_panic(radix: u32) -> ! { ) } -macro_rules! from_str_int_impl { - ($signedness:ident $($int_ty:ty)+) => {$( - #[stable(feature = "rust1", since = "1.0.0")] +macro_rules! define_swar_min_len { + ($int_ty:ty, true) => { + const SWAR_MIN_LEN: usize = 16; + }; + ($int_ty:ty, false) => {}; +} + +macro_rules! maybe_define_swar { + ($int_ty:ty, true) => { + /// Parses up to 16 leading radix-10 digits in batches. + /// + /// This thin wrapper dispatches to `decimal_swar`, which lives outside + /// the per-type macro so its body is not duplicated for types that + /// never reach it. + #[inline] + pub(super) const fn swar_parse_decimal( + is_positive: bool, + result: $int_ty, + digits: &[u8], + ) -> Result<($int_ty, &[u8]), ParseIntError> { + match decimal_swar::parse_decimal_i64(is_positive, result as i64, digits) { + Ok((r, rest)) => Ok((r as $int_ty, rest)), + Err(e) => Err(e), + } + } + }; + ($int_ty:ty, false) => {}; +} + +macro_rules! from_str_int_impl_inner { + ($signedness:ident $int_ty:ty, $swar:tt) => { #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] const impl FromStr for $int_ty { type Err = ParseIntError; @@ -1871,32 +1969,20 @@ macro_rules! from_str_int_impl { // SWAR fast path: process leading decimal digits in // batches using SIMD-within-a-register. Only radix 10 and - // only types of more than 4 bytes (u32 max is 10 digits, so - // for smaller types this whole branch is dead code). + // only for types of at least 8 bytes; smaller types use the + // no-SWAR macro arm and never see this code. // // We never batch more than 16 leading digits: that many - // decimal digits can never overflow `$int_ty`, so the batch - // arithmetic is safe to run unchecked even in debug builds. - // - // In the no-overflow branch, batching can only fire for - // inputs that exactly fill the bound (u64, 16 digits); the - // tail is then empty. In the checked branch, the batch - // shrinks the input and the remaining digits are validated - // by the checked loop, which still catches overflow for - // inputs longer than the type's range. - // - // On 64-bit+ platforms, use 8-digit batches (u64). On - // 32-bit platforms, u64 multiplication is emulated and - // slower than the per-byte loop, so only use 4-digit - // batches (u32). - let swar_min_len = if size_of::<$int_ty>() <= 4 { - usize::MAX - } else { - 16 - }; + // decimal digits can never overflow `u64`/`u128`, so the + // batch arithmetic is safe to run unchecked even in debug + // builds. The tail after the batches is validated by the + // checked loop, which still catches overflow for inputs + // longer than the type's range. + define_swar_min_len!($int_ty, $swar); + macro_rules! run_swar { - () => {{ - if radix == 10 && digits.len() >= swar_min_len { + (true) => {{ + if radix == 10 && digits.len() >= SWAR_MIN_LEN { match Self::swar_parse_decimal(is_positive, result, digits) { Ok((r, rest)) => { result = r; @@ -1906,6 +1992,7 @@ macro_rules! from_str_int_impl { } } }}; + (false) => {{}}; } macro_rules! run_unchecked_loop { @@ -2033,7 +2120,7 @@ macro_rules! from_str_int_impl { run_no_check_loop!(wrapping_sub) }; } else { - run_swar!(); + run_swar!($swar); if is_positive { run_checked_loop!(checked_add, PosOverflow) } else { @@ -2043,74 +2130,21 @@ macro_rules! from_str_int_impl { Ok(result) } - /// SWAR (SIMD-within-a-register) helper for radix-10 parsing. - /// Parses up to 16 leading decimal digits in 8- and 4-digit - /// batches, returning the accumulated result and the remaining - /// digits. The caller then runs the checked loop on the tail. - /// - /// Batches are bounded so the unchecked batch arithmetic can - /// never overflow `$int_ty`, even in debug builds. - /// - /// Must stay inlinable (`#[inline]`, not outlined): an out-of-line - /// call here, even though it is only reachable on the long-input - /// path, makes LLVM set up a stack frame in the hot caller and - /// measurably slows short inputs that never reach the SWAR path. - #[inline] - pub(super) const fn swar_parse_decimal( - is_positive: bool, - mut result: $int_ty, - mut digits: &[u8], - ) -> Result<($int_ty, &[u8]), ParseIntError> { - use self::IntErrorKind::*; - use self::ParseIntError as PIE; - - let mut remaining = match size_of::<$int_ty>() { - 1 => 0, - 2 => 4, - 4 => 8, - _ => 16, - }; - - #[cfg(not(target_pointer_width = "32"))] - while remaining >= 8 { - let [a, b, c, d, e, f, g, h, rest @ ..] = digits else { break }; - let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); - if !is_8digits(chunk) { - return Err(PIE { kind: InvalidDigit }); - } - let parsed = parse_8digits(chunk) as $int_ty; - result = result * (100_000_000u32 as $int_ty); - if is_positive { - result = result + parsed; - } else { - result = result - parsed; - } - digits = rest; - remaining -= 8; - } - - while remaining >= 4 { - let [a, b, c, d, rest @ ..] = digits else { break }; - let chunk = u32::from_le_bytes([*a, *b, *c, *d]); - if !is_4digits(chunk) { - return Err(PIE { kind: InvalidDigit }); - } - let parsed = parse_4digits(chunk) as $int_ty; - result = result * (10_000u32 as $int_ty); - if is_positive { - result = result + parsed; - } else { - result = result - parsed; - } - digits = rest; - remaining -= 4; - } - - Ok((result, digits)) - } + maybe_define_swar!($int_ty, $swar); } - )*} + }; +} + +macro_rules! from_str_int_impl { + ($signedness:ident $($int_ty:ty)+) => { + $( from_str_int_impl_inner! { $signedness $int_ty, true } )+ + }; + ($signedness:ident @no_swar $($int_ty:ty)+) => { + $( from_str_int_impl_inner! { $signedness $int_ty, false } )+ + }; } -from_str_int_impl! { signed isize i8 i16 i32 i64 i128 } -from_str_int_impl! { unsigned usize u8 u16 u32 u64 u128 } +from_str_int_impl! { signed i64 i128 } +from_str_int_impl! { signed @no_swar isize i8 i16 i32 } +from_str_int_impl! { unsigned u64 u128 } +from_str_int_impl! { unsigned @no_swar usize u8 u16 u32 } From b61c82d01edfafe5034d0a3dbfc88a139b6efbaf Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Sun, 6 Sep 2026 12:02:46 +0800 Subject: [PATCH 7/8] Update parse_ints.stderr for the new macro nesting from_str_int_impl now delegates to from_str_int_impl_inner, so the macro-backtrace note in the expected stderr changed. --- tests/ui/consts/const-eval/parse_ints.stderr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/consts/const-eval/parse_ints.stderr b/tests/ui/consts/const-eval/parse_ints.stderr index b8027fd951d5a..4cb7f81f044ed 100644 --- a/tests/ui/consts/const-eval/parse_ints.stderr +++ b/tests/ui/consts/const-eval/parse_ints.stderr @@ -14,7 +14,7 @@ note: inside `core::num::::from_ascii_bytes_radix_impl` ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | = note: in this macro invocation - = note: this error originates in the macro `from_str_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `from_str_int_impl_inner` which comes from the expansion of the macro `from_str_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation panicked: from_ascii_bytes_radix: radix must lie in the range `[2, 36]` --> $DIR/parse_ints.rs:8:25 @@ -32,7 +32,7 @@ note: inside `core::num::::from_ascii_bytes_radix_impl` ::: $SRC_DIR/core/src/num/mod.rs:LL:COL | = note: in this macro invocation - = note: this error originates in the macro `from_str_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `from_str_int_impl_inner` which comes from the expansion of the macro `from_str_int_impl` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors From c7c57a9fff2c25b994edd957cd9ad73138c6661b Mon Sep 17 00:00:00 2001 From: kianalikasana Date: Sun, 6 Sep 2026 12:26:14 +0800 Subject: [PATCH 8/8] Explain the batch digit check and fold constants in the comments The is_digits comment claimed the +0x46 test overflows only past 0xb9, but wrapping past 0x100 actually leaves the high bit clear again; those bytes are caught by the -'0' side. Rewrote it to derive why 0x46 is the right constant ('9' + 0x46 = 0x7f, ':' + 0x46 = 0x80) and which range each half covers. parse_8digits/parse_4digits now document what each multiply constant does instead of leaving 2561/6_553_601/42_949_672_960_001 unexplained. --- library/core/src/num/mod.rs | 141 +++++++++++++----------------------- 1 file changed, 50 insertions(+), 91 deletions(-) diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs index 71a63c6178ffd..7c8b1c121a666 100644 --- a/library/core/src/num/mod.rs +++ b/library/core/src/num/mod.rs @@ -1599,16 +1599,24 @@ mod decimal_swar { /// Checks if all bytes in `v` are ASCII decimal digits (`b'0'..=b'9'`). /// - /// For each byte `c`, two tests must both clear the high bit: - /// - `c + 0x46` overflows only when `c > 0xb9`, which is true for all - /// non-digits above `'9'` (0x39) and false for digits `0x30..=0x39`. - /// Adding `'F'` (0x46) maps `'0'..='9'` to `0x76..=0x7f`, all with - /// the high bit clear. - /// - `c - '0'` (0x30) underflows only when `c < 0x30`, which is true - /// for all non-digits below `'0'`. - /// - /// ORing the two results and testing the high bit catches any byte - /// that fails either test. + /// The per-byte range test is turned into a high-bit test so that all + /// bytes are checked with a single branch: + /// + /// - `c - b'0'` wraps around, setting the high bit, for every byte + /// below `'0'` (and for `0xb0..=0xff`, see below). + /// - For the upper bound we need a constant `k` with `'9' + k < 0x80` + /// and `':' + k >= 0x80`, so that a single bit separates the last + /// digit from the first non-digit above it. `0x7f - 0x39 = 0x46` is + /// the unique such constant: `'9' + 0x46 = 0x7f` and `':' + 0x46 = + /// 0x80`. (Adding `'9'` itself would put `':'` at 0x73 with the + /// high bit still clear, and the test would never fire.) + /// + /// The addition flags `0x3a..=0xb9`; past that the sum wraps past + /// `0x100`, which leaves the high bit clear again, but those bytes + /// are caught by the subtraction (`c - b'0' >= 0x80`). Between the + /// two tests every non-digit byte is flagged and no digit ever is. + /// + /// This is the same check `dec2flt`'s `is_8digits` performs. #[inline] pub(super) const fn is_digits(v: usize) -> bool { let a = v.wrapping_add(usize::repeat_u8(0x46)); @@ -1616,9 +1624,27 @@ mod decimal_swar { (a | b) & usize::repeat_u8(0x80) == 0 } - /// Parses 8 ASCII decimal digits packed in a u64 into a numeric value. - /// - /// The caller must ensure all 8 bytes are ASCII digits, e.g. via [`is_digits`]. + /// Parses 8 ASCII decimal digits packed in a `u64` into their numeric + /// value (little-endian: the first digit is the least significant + /// byte). + /// + /// Three multiply-shift steps fold neighboring groups together. A + /// step that merges groups of `g` digits multiplies by + /// `10^g * 2^(8g) + 1`, which adds each group's value to the group + /// above it times `10^g`; the shift slides the results down, and the + /// masks keep only the cleanly merged groups (the multiplies also + /// leave overlapping garbage in between): + /// + /// - `& 0x0f` strips the `0x3` high nibble of each ASCII digit, + /// leaving groups of one digit each, + /// - `* 2561 >> 8` (`2561 = 10 * 256 + 1`) leaves two-digit values, + /// - `* 6_553_601 >> 16` (`= 100 * 65_536 + 1`) leaves four-digit + /// values, + /// - `* 42_949_672_960_001 >> 32` (`= 10_000 * 2^32 + 1`) leaves the + /// final eight-digit value. + /// + /// The caller must ensure all 8 bytes are ASCII digits, e.g. via + /// [`is_digits`]. #[cfg(not(target_pointer_width = "32"))] #[inline] pub(super) const fn parse_8digits(v: u64) -> u64 { @@ -1629,9 +1655,18 @@ mod decimal_swar { v } - /// Parses 4 ASCII decimal digits packed in a u32 into a numeric value. + /// Parses 4 ASCII decimal digits packed in a `u32` into their numeric + /// value (little-endian: the first digit is the least significant + /// byte). + /// + /// The same folding scheme as [`parse_8digits`], stopped one step + /// early since only four digits are needed: `& 0x0f` strips the `0x3` + /// high nibble of each ASCII digit, `* 2561 >> 8` (`2561 = + /// 10 * 256 + 1`) leaves two-digit values, and `* 6_553_601 >> 16` + /// (`= 100 * 65_536 + 1`) leaves the four-digit value. /// - /// The caller must ensure all 4 bytes are ASCII digits, e.g. via [`is_digits`]. + /// The caller must ensure all 4 bytes are ASCII digits, e.g. via + /// [`is_digits`]. #[cfg(target_pointer_width = "32")] #[inline] pub(super) const fn parse_4digits(v: u32) -> u32 { @@ -1995,16 +2030,6 @@ macro_rules! from_str_int_impl_inner { (false) => {{}}; } - macro_rules! run_unchecked_loop { - ($unchecked_additive_op:tt) => {{ - while let [c, rest @ ..] = digits { - result = result * (radix as $int_ty); - let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit); - result = result $unchecked_additive_op (x as $int_ty); - digits = rest; - } - }}; - } macro_rules! run_checked_loop { ($checked_additive_op:ident, $overflow_err:ident) => {{ while let [c, rest @ ..] = digits { @@ -2036,61 +2061,8 @@ macro_rules! from_str_int_impl_inner { // `u8::MAX` is `ff` - any str of len 2 is guaranteed to not overflow. // `i8::MAX` is `7f` - only a str of len 1 is guaranteed to not overflow. if can_not_overflow::<$int_ty>(radix, is_signed_ty, digits) { - // If the len of the str is short compared to the range of the type - // we are parsing into, then we can be certain that an overflow will not occur. - // This bound is when `radix.pow(digits.len()) - 1 <= T::MAX` but the condition - // above is a faster (conservative) approximation of this. - // - // Consider radix 16 as it has the highest information density per digit and will thus overflow the earliest: - // `u8::MAX` is `ff` - any str of len 2 is guaranteed to not overflow. - // `i8::MAX` is `7f` - only a str of len 1 is guaranteed to not overflow. - // // NOTE: We could use unchecked arithmetic here, but we don't, based on the observation // that it produces the same assembly as wrapping ones. See #163099. - - // SWAR fast path: process decimal digits in batches using - // SIMD-within-a-register. Only applies to radix 10, where - // the digit range is contiguous and the multiply-by-10^N - // packing works. Safe because `can_not_overflow` guarantees - // the full result fits in `$int_ty`. - // - // On 64-bit+ platforms, use 8-digit batches (u64). On - // 32-bit platforms, u64 multiplication is emulated and - // slower than the per-byte loop, so only use 4-digit - // batches (u32). - if radix == 10 { - #[cfg(not(target_pointer_width = "32"))] - while let [a, b, c, d, e, f, g, h, rest @ ..] = digits { - let chunk = u64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h]); - if !is_8digits(chunk) { - return Err(PIE { kind: InvalidDigit }); - } - let parsed = parse_8digits(chunk) as $int_ty; - result = result * (100_000_000u32 as $int_ty); - if is_positive { - result = result + parsed; - } else { - result = result - parsed; - } - digits = rest; - } - - while let [a, b, c, d, rest @ ..] = digits { - let chunk = u32::from_le_bytes([*a, *b, *c, *d]); - if !is_4digits(chunk) { - return Err(PIE { kind: InvalidDigit }); - } - let parsed = parse_4digits(chunk) as $int_ty; - result = result * (10_000u32 as $int_ty); - if is_positive { - result = result + parsed; - } else { - result = result - parsed; - } - digits = rest; - } - } - macro_rules! run_no_check_loop { ($additive_op:ident) => {{ while let [c, rest @ ..] = digits { @@ -2101,19 +2073,6 @@ macro_rules! from_str_int_impl_inner { } }}; } - if is_positive { - run_no_check_loop!(wrapping_add) - } else { - run_no_check_loop!(wrapping_sub) - }; - while let [c, rest @ ..] = digits { - result = <$int_ty>::wrapping_mul(result, radix as _); - let x = unwrap_or_PIE!((*c as char).to_digit(radix), InvalidDigit); - result = result.$additive_op(x as $int_ty); - digits = rest; - } - }}; - } if is_positive { run_no_check_loop!(wrapping_add) } else {