From b821c77424f6082f9b3801e409ae37982641e59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C5=A1a=20Tomi=C4=87?= Date: Fri, 13 Mar 2026 14:10:33 +0100 Subject: [PATCH 1/2] perf: Int::decode small-value fast path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decode SLEB128-encoded Int values into i64 first, only falling back to BigInt for values that exceed 64-bit range. Mirrors the existing Nat::decode fast path. Benchmark: option_list decoding 26.2M → 23.2M instructions (-11.7%), variant_list decoding 25.1M → 22.1M instructions (-11.9%), heap usage halved for both. Wire format is unchanged. Made-with: Cursor --- rust/candid/src/types/number.rs | 65 +++++++++++----------- rust/candid/tests/compatibility_numbers.rs | 3 + 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 9f223c9a..80ac358f 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -322,59 +322,58 @@ impl Int { where R: io::Read, { - let mut small = 0u64; + let mut small = 0i64; let mut shift = 0u32; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + let low_bits = i64::from(byte & 0x7f); + + let fits_i64 = if shift < 57 { + true + } else if shift < 64 { + let remaining_bits = 64 - shift; + if (byte & 0x40) != 0 { + (low_bits | !0x7f) >> (remaining_bits - 1) == -1 + } else { + low_bits >> (remaining_bits - 1) == 0 + } + } else { + false + }; + + if fits_i64 { small |= low_bits << shift; - if byte & 0x80u8 == 0 { - // Last byte: sign-extend and return as i64 if possible. - let total_bits = shift + 7; - if byte & 0x40u8 != 0 { - // Negative: fill bits from total_bits upward with 1s. - let signed = if total_bits >= 64 { - // Bit 63 is already the LEB sign bit inside small. - small as i64 - } else { - let sign_mask = !((1u64 << total_bits) - 1); - (small | sign_mask) as i64 - }; - return Ok(Int(BigInt::from(signed))); - } else if small <= i64::MAX as u64 { - return Ok(Int(BigInt::from(small as i64))); - } else { - // Positive value in [2^63, 2^64-1]: fits u64 but not i64. - return Ok(Int(BigInt::from(BigUint::from(small)))); + shift += 7; + if byte & 0x80 == 0 { + if shift < 64 && (byte & 0x40) != 0 { + small |= !0i64 << shift; } + return Ok(Int(BigInt::from(small))); } - shift += 7; continue; } - // Value overflows u64: fall back to BigInt for remaining bytes. - let mut result = BigInt::from(BigUint::from(small)); - result |= BigInt::from(low_bits) << shift; - if byte & 0x80u8 == 0 { - shift += 7; - if byte & 0x40u8 != 0 { + let mut result = BigInt::from(small); + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; + shift += 7; + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); } - shift += 7; loop { let mut buf = [0]; r.read_exact(&mut buf)?; let byte = buf[0]; - let low_bits = BigInt::from(byte & 0x7fu8); - result |= low_bits << shift; + let big_low_bits = BigInt::from(byte & 0x7fu8); + result |= big_low_bits << shift; shift += 7; - if byte & 0x80u8 == 0 { - if byte & 0x40u8 != 0 { + if byte & 0x80 == 0 { + if (byte & 0x40) != 0 { result |= BigInt::from(-1) << shift; } return Ok(Int(result)); diff --git a/rust/candid/tests/compatibility_numbers.rs b/rust/candid/tests/compatibility_numbers.rs index 025123c5..c5f756a0 100644 --- a/rust/candid/tests/compatibility_numbers.rs +++ b/rust/candid/tests/compatibility_numbers.rs @@ -13,9 +13,12 @@ fn number_fast_paths_preserve_small_and_large_values() { assert_eq!(decoded_nat_values, nat_values); let int_values = vec![ + Int::from(0), Int::from(-1), Int::from(42), Int::from(i64::MIN), + Int::from(i64::MAX), + Int::parse(b"24197857200151251861972493965091130863").unwrap(), Int::parse(b"-600000000000000000000000000000000000").unwrap(), ]; let int_bytes = Encode!(&int_values).unwrap(); From db3bdea5ae1ed269ea04bbf876d4eda5f51331fa Mon Sep 17 00:00:00 2001 From: Linwei Shang Date: Sun, 15 Mar 2026 14:09:23 -0400 Subject: [PATCH 2/2] refactor: inline leb128_fits_u64 into its only call site Co-Authored-By: Claude Sonnet 4.6 --- rust/candid/src/types/number.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/rust/candid/src/types/number.rs b/rust/candid/src/types/number.rs index 80ac358f..14ba0b89 100644 --- a/rust/candid/src/types/number.rs +++ b/rust/candid/src/types/number.rs @@ -208,19 +208,6 @@ impl<'de> Deserialize<'de> for Nat { // LEB128 encoding for bignum. -/// Returns true if OR-ing `low_bits` at position `shift` fits within a u64. -/// The `shift < 64` guard prevents a debug-mode panic from the shift expression. -#[inline] -fn leb128_fits_u64(low_bits: u64, shift: u32) -> bool { - if shift == 0 { - true - } else if shift < 64 { - low_bits < (1u64 << (64 - shift)) - } else { - false - } -} - impl Nat { pub fn encode(&self, w: &mut W) -> crate::Result<()> where @@ -258,7 +245,7 @@ impl Nat { r.read_exact(&mut buf)?; let byte = buf[0]; let low_bits = u64::from(byte & 0x7f); - if leb128_fits_u64(low_bits, shift) { + if shift == 0 || (shift < 64 && low_bits < (1u64 << (64 - shift))) { small |= low_bits << shift; if byte & 0x80u8 == 0 { return Ok(Nat(BigUint::from(small)));