1
Fork 0

Add add/sub methods that only panic with debug assertions to rustc

This mitigates the perf impact of enabling overflow checks on rustc.
The change to use overflow checks will be done in a later PR.
This commit is contained in:
Nilstrieb 2024-03-28 20:27:12 +01:00
parent c3b05c6e5b
commit 5039160c5b
6 changed files with 109 additions and 27 deletions

View file

@ -0,0 +1,65 @@
// This would belong to `rustc_data_structures`, but `rustc_serialize` needs it too.
/// Addition, but only overflow checked when `cfg(debug_assertions)` is set
/// instead of respecting `-Coverflow-checks`.
///
/// This exists for performance reasons, as we ship rustc with overflow checks.
/// While overflow checks are perf neutral in almost all of the compiler, there
/// are a few particularly hot areas where we don't want overflow checks in our
/// dist builds. Overflow is still a bug there, so we want overflow check for
/// builds with debug assertions.
///
/// That's a long way to say that this should be used in areas where overflow
/// is a bug but overflow checking is too slow.
pub trait DebugStrictAdd {
/// See [`DebugStrictAdd`].
fn debug_strict_add(self, other: Self) -> Self;
}
macro_rules! impl_debug_strict_add {
($( $ty:ty )*) => {
$(
impl DebugStrictAdd for $ty {
fn debug_strict_add(self, other: Self) -> Self {
if cfg!(debug_assertions) {
self + other
} else {
self.wrapping_add(other)
}
}
}
)*
};
}
/// See [`DebugStrictAdd`].
pub trait DebugStrictSub {
/// See [`DebugStrictAdd`].
fn debug_strict_sub(self, other: Self) -> Self;
}
macro_rules! impl_debug_strict_sub {
($( $ty:ty )*) => {
$(
impl DebugStrictSub for $ty {
fn debug_strict_sub(self, other: Self) -> Self {
if cfg!(debug_assertions) {
self - other
} else {
self.wrapping_sub(other)
}
}
}
)*
};
}
impl_debug_strict_add! {
u8 u16 u32 u64 u128 usize
i8 i16 i32 i64 i128 isize
}
impl_debug_strict_sub! {
u8 u16 u32 u64 u128 usize
i8 i16 i32 i64 i128 isize
}

View file

@ -1,6 +1,10 @@
use crate::opaque::MemDecoder;
use crate::serialize::Decoder;
// This code is very hot and uses lots of arithmetic, avoid overflow checks for performance.
// See https://github.com/rust-lang/rust/pull/119440#issuecomment-1874255727
use crate::int_overflow::DebugStrictAdd;
/// Returns the length of the longest LEB128 encoding for `T`, assuming `T` is an integer type
pub const fn max_leb128_len<T>() -> usize {
// The longest LEB128 encoding for an integer uses 7 bits per byte.
@ -24,7 +28,7 @@ macro_rules! impl_write_unsigned_leb128 {
*out.get_unchecked_mut(i) = value as u8;
}
i += 1;
i = i.debug_strict_add(1);
break;
} else {
unsafe {
@ -32,7 +36,7 @@ macro_rules! impl_write_unsigned_leb128 {
}
value >>= 7;
i += 1;
i = i.debug_strict_add(1);
}
}
@ -69,7 +73,7 @@ macro_rules! impl_read_unsigned_leb128 {
} else {
result |= ((byte & 0x7F) as $int_ty) << shift;
}
shift += 7;
shift = shift.debug_strict_add(7);
}
}
};
@ -101,7 +105,7 @@ macro_rules! impl_write_signed_leb128 {
*out.get_unchecked_mut(i) = byte;
}
i += 1;
i = i.debug_strict_add(1);
if !more {
break;
@ -130,7 +134,7 @@ macro_rules! impl_read_signed_leb128 {
loop {
byte = decoder.read_u8();
result |= <$int_ty>::from(byte & 0x7F) << shift;
shift += 7;
shift = shift.debug_strict_add(7);
if (byte & 0x80) == 0 {
break;

View file

@ -23,5 +23,6 @@ pub use self::serialize::{Decodable, Decoder, Encodable, Encoder};
mod serialize;
pub mod int_overflow;
pub mod leb128;
pub mod opaque;

View file

@ -7,6 +7,10 @@ use std::ops::Range;
use std::path::Path;
use std::path::PathBuf;
// This code is very hot and uses lots of arithmetic, avoid overflow checks for performance.
// See https://github.com/rust-lang/rust/pull/119440#issuecomment-1874255727
use crate::int_overflow::DebugStrictAdd;
// -----------------------------------------------------------------------------
// Encoder
// -----------------------------------------------------------------------------
@ -65,7 +69,7 @@ impl FileEncoder {
// Tracking position this way instead of having a `self.position` field
// means that we only need to update `self.buffered` on a write call,
// as opposed to updating `self.position` and `self.buffered`.
self.flushed + self.buffered
self.flushed.debug_strict_add(self.buffered)
}
#[cold]
@ -119,7 +123,7 @@ impl FileEncoder {
}
if let Some(dest) = self.buffer_empty().get_mut(..buf.len()) {
dest.copy_from_slice(buf);
self.buffered += buf.len();
self.buffered = self.buffered.debug_strict_add(buf.len());
} else {
self.write_all_cold_path(buf);
}
@ -158,7 +162,7 @@ impl FileEncoder {
if written > N {
Self::panic_invalid_write::<N>(written);
}
self.buffered += written;
self.buffered = self.buffered.debug_strict_add(written);
}
#[cold]