Skip to content

Commit 734368a

Browse files
committed
Auto merge of #87869 - thomcc:skinny-io-error, r=yaahc
Make io::Error use 64 bits on targets with 64 bit pointers. I've wanted this for a long time, but didn't see a good way to do it without having extra allocation. When looking at it yesterday, it was more clear what to do for some reason. This approach avoids any additional allocations, and reduces the size by half (8 bytes, down from 16). AFAICT it doesn't come additional runtime cost, and the compiler seems to do a better job with code using it. Additionally, this `io::Error` has a niche (still), so `io::Result<()>` is *also* 64 bits (8 bytes, down from 16), and `io::Result<usize>` (used for lots of io trait functions) is 2x64 bits (16 bytes, down from 24 — this means on x86_64 it can use the nice rax/rdx 2-reg struct return). More generally, it shaves a whole 64 bit integer register off of the size of basically any `io::Result<()>`. (For clarity: Improving `io::Result` (rather than io::Error) was most of the motivation for this) On 32 bit (or other non-64bit) targets we still use something equivalent the old repr — I don't think think there's improving it, since one of the fields it stores is a `i32`, so we can't get below that, and it's already about as close as we can get to it. --- ### Isn't Pointer Tagging Dodgy? The details of the layout, and why its implemented the way it is, are explained in the header comment of library/std/src/io/error/repr_bitpacked.rs. There's probably more details than there need to be, but I didn't trim it down that much, since there's a lot of stuff I did deliberately, that might have not seemed that way. There's actually only one variant holding a pointer which gets tagged. This one is the (holder for the) user-provided error. I believe the scheme used to tag it is not UB, and that it preserves pointer provenance (even though often pointer tagging does not) because the tagging operation is just `core::ptr::add`, and untagging is `core::ptr::sub`. The result of both operations lands inside the original allocation, so it would follow the safety contract of `core::ptr::{add,sub}`. The other pointer this had to encode is not tagged — or rather, the tagged repr is equivalent to untagged (it's tagged with 0b00, and has >=4b alignment, so we can reuse the bottom bits). And the other variants we encode are just integers, which (which can be untagged using bitwise operations without worry — they're integers). CC `@RalfJung` for the stuff in repr_bitpacked.rs, as my comments are informed by a lot of the UCG work, but it's possible I missed something or got it wrong (even if the implementation is okay, there are parts of the header comment that says things like "We can't do $x" which could be false). --- ### Why So Many Changes? The repr change was mostly internal, but changed one widely used API: I had to switch how `io::Error::new_const` works. This required switching `io::Error::new_const` to take the full message data (including the kind) as a `&'static`, rather than just the string. This would have been really tedious, but I made a macro that made it much simpler, but it was a wide change since `io::Error::new_const` is used everywhere. This included changing files for a lot of targets I don't have easy access to (SGX? Haiku? Windows? Who has heard of these things), so I expect there to be spottiness in CI initially, unless luck is on my side. Anyway this large only tangentially-related change is all in the first commit (although that commit also pulls the previous repr out into its own file), whereas the packing stuff is all in commit 2. --- P.S. I haven't looked at all of this since writing it, and will do a pass over it again later, sorry for any obvious typos or w/e. I also definitely repeat myself in comments and such. (It probably could use more tests too. I did some basic testing, and made it so we `debug_assert!` in cases the decode isn't what we encoded, but I don't know the degree which I can assume libstd's testing of IO would exercise this. That is: it wouldn't be surprising to me if libstds IO testing were minimal, especially around error cases, although I have no idea).
2 parents f52c318 + 9cbe994 commit 734368a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+837
-269
lines changed

Diff for: library/std/src/ffi/c_str.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ impl fmt::Display for NulError {
10771077
impl From<NulError> for io::Error {
10781078
/// Converts a [`NulError`] into a [`io::Error`].
10791079
fn from(_: NulError) -> io::Error {
1080-
io::Error::new_const(io::ErrorKind::InvalidInput, &"data provided contains a nul byte")
1080+
io::const_io_error!(io::ErrorKind::InvalidInput, "data provided contains a nul byte")
10811081
}
10821082
}
10831083

Diff for: library/std/src/fs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2263,9 +2263,9 @@ impl DirBuilder {
22632263
match path.parent() {
22642264
Some(p) => self.create_dir_all(p)?,
22652265
None => {
2266-
return Err(io::Error::new_const(
2266+
return Err(io::const_io_error!(
22672267
io::ErrorKind::Uncategorized,
2268-
&"failed to create whole tree",
2268+
"failed to create whole tree",
22692269
));
22702270
}
22712271
}

Diff for: library/std/src/io/buffered/bufreader.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,9 @@ impl<R: Read> Read for BufReader<R> {
357357
let mut bytes = Vec::new();
358358
self.read_to_end(&mut bytes)?;
359359
let string = crate::str::from_utf8(&bytes).map_err(|_| {
360-
io::Error::new_const(
360+
io::const_io_error!(
361361
io::ErrorKind::InvalidData,
362-
&"stream did not contain valid UTF-8",
362+
"stream did not contain valid UTF-8",
363363
)
364364
})?;
365365
*buf += string;

Diff for: library/std/src/io/buffered/bufwriter.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::error;
22
use crate::fmt;
33
use crate::io::{
4-
self, Error, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, DEFAULT_BUF_SIZE,
4+
self, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, DEFAULT_BUF_SIZE,
55
};
66
use crate::mem;
77
use crate::ptr;
@@ -168,9 +168,9 @@ impl<W: Write> BufWriter<W> {
168168

169169
match r {
170170
Ok(0) => {
171-
return Err(Error::new_const(
171+
return Err(io::const_io_error!(
172172
ErrorKind::WriteZero,
173-
&"failed to write the buffered data",
173+
"failed to write the buffered data",
174174
));
175175
}
176176
Ok(n) => guard.consume(n),

Diff for: library/std/src/io/cursor.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ mod tests;
44
use crate::io::prelude::*;
55

66
use crate::cmp;
7-
use crate::io::{self, Error, ErrorKind, IoSlice, IoSliceMut, ReadBuf, SeekFrom};
7+
use crate::io::{self, ErrorKind, IoSlice, IoSliceMut, ReadBuf, SeekFrom};
88

99
use core::convert::TryInto;
1010

@@ -297,9 +297,9 @@ where
297297
self.pos = n;
298298
Ok(self.pos)
299299
}
300-
None => Err(Error::new_const(
300+
None => Err(io::const_io_error!(
301301
ErrorKind::InvalidInput,
302-
&"invalid seek to a negative or overflowing position",
302+
"invalid seek to a negative or overflowing position",
303303
)),
304304
}
305305
}
@@ -400,9 +400,9 @@ fn slice_write_vectored(
400400
// Resizing write implementation
401401
fn vec_write(pos_mut: &mut u64, vec: &mut Vec<u8>, buf: &[u8]) -> io::Result<usize> {
402402
let pos: usize = (*pos_mut).try_into().map_err(|_| {
403-
Error::new_const(
403+
io::const_io_error!(
404404
ErrorKind::InvalidInput,
405-
&"cursor position exceeds maximum possible vector length",
405+
"cursor position exceeds maximum possible vector length",
406406
)
407407
})?;
408408
// Make sure the internal buffer is as least as big as where we

Diff for: library/std/src/io/error.rs

+122-65
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
#[cfg(test)]
22
mod tests;
33

4+
#[cfg(target_pointer_width = "64")]
5+
mod repr_bitpacked;
6+
#[cfg(target_pointer_width = "64")]
7+
use repr_bitpacked::Repr;
8+
9+
#[cfg(not(target_pointer_width = "64"))]
10+
mod repr_unpacked;
11+
#[cfg(not(target_pointer_width = "64"))]
12+
use repr_unpacked::Repr;
13+
414
use crate::convert::From;
515
use crate::error;
616
use crate::fmt;
@@ -66,15 +76,58 @@ impl fmt::Debug for Error {
6676
}
6777
}
6878

69-
enum Repr {
79+
// Only derive debug in tests, to make sure it
80+
// doesn't accidentally get printed.
81+
#[cfg_attr(test, derive(Debug))]
82+
enum ErrorData<C> {
7083
Os(i32),
7184
Simple(ErrorKind),
72-
// &str is a fat pointer, but &&str is a thin pointer.
73-
SimpleMessage(ErrorKind, &'static &'static str),
74-
Custom(Box<Custom>),
85+
SimpleMessage(&'static SimpleMessage),
86+
Custom(C),
7587
}
7688

89+
// `#[repr(align(4))]` is probably redundant, it should have that value or
90+
// higher already. We include it just because repr_bitpacked.rs's encoding
91+
// requires an alignment >= 4 (note that `#[repr(align)]` will not reduce the
92+
// alignment required by the struct, only increase it).
93+
//
94+
// If we add more variants to ErrorData, this can be increased to 8, but it
95+
// should probably be behind `#[cfg_attr(target_pointer_width = "64", ...)]` or
96+
// whatever cfg we're using to enable the `repr_bitpacked` code, since only the
97+
// that version needs the alignment, and 8 is higher than the alignment we'll
98+
// have on 32 bit platforms.
99+
//
100+
// (For the sake of being explicit: the alignment requirement here only matters
101+
// if `error/repr_bitpacked.rs` is in use — for the unpacked repr it doesn't
102+
// matter at all)
103+
#[repr(align(4))]
77104
#[derive(Debug)]
105+
pub(crate) struct SimpleMessage {
106+
kind: ErrorKind,
107+
message: &'static str,
108+
}
109+
110+
impl SimpleMessage {
111+
pub(crate) const fn new(kind: ErrorKind, message: &'static str) -> Self {
112+
Self { kind, message }
113+
}
114+
}
115+
116+
/// Create and return an `io::Error` for a given `ErrorKind` and constant
117+
/// message. This doesn't allocate.
118+
pub(crate) macro const_io_error($kind:expr, $message:expr $(,)?) {
119+
$crate::io::error::Error::from_static_message({
120+
const MESSAGE_DATA: $crate::io::error::SimpleMessage =
121+
$crate::io::error::SimpleMessage::new($kind, $message);
122+
&MESSAGE_DATA
123+
})
124+
}
125+
126+
// As with `SimpleMessage`: `#[repr(align(4))]` here is just because
127+
// repr_bitpacked's encoding requires it. In practice it almost certainly be
128+
// already be this high or higher.
129+
#[derive(Debug)]
130+
#[repr(align(4))]
78131
struct Custom {
79132
kind: ErrorKind,
80133
error: Box<dyn error::Error + Send + Sync>,
@@ -396,7 +449,7 @@ impl From<ErrorKind> for Error {
396449
/// ```
397450
#[inline]
398451
fn from(kind: ErrorKind) -> Error {
399-
Error { repr: Repr::Simple(kind) }
452+
Error { repr: Repr::new_simple(kind) }
400453
}
401454
}
402455

@@ -461,20 +514,22 @@ impl Error {
461514
}
462515

463516
fn _new(kind: ErrorKind, error: Box<dyn error::Error + Send + Sync>) -> Error {
464-
Error { repr: Repr::Custom(Box::new(Custom { kind, error })) }
517+
Error { repr: Repr::new_custom(Box::new(Custom { kind, error })) }
465518
}
466519

467-
/// Creates a new I/O error from a known kind of error as well as a
468-
/// constant message.
520+
/// Creates a new I/O error from a known kind of error as well as a constant
521+
/// message.
469522
///
470523
/// This function does not allocate.
471524
///
472-
/// This function should maybe change to
473-
/// `new_const<const MSG: &'static str>(kind: ErrorKind)`
474-
/// in the future, when const generics allow that.
525+
/// You should not use this directly, and instead use the `const_io_error!`
526+
/// macro: `io::const_io_error!(ErrorKind::Something, "some_message")`.
527+
///
528+
/// This function should maybe change to `from_static_message<const MSG: &'static
529+
/// str>(kind: ErrorKind)` in the future, when const generics allow that.
475530
#[inline]
476-
pub(crate) const fn new_const(kind: ErrorKind, message: &'static &'static str) -> Error {
477-
Self { repr: Repr::SimpleMessage(kind, message) }
531+
pub(crate) const fn from_static_message(msg: &'static SimpleMessage) -> Error {
532+
Self { repr: Repr::new_simple_message(msg) }
478533
}
479534

480535
/// Returns an error representing the last OS error which occurred.
@@ -532,7 +587,7 @@ impl Error {
532587
#[must_use]
533588
#[inline]
534589
pub fn from_raw_os_error(code: i32) -> Error {
535-
Error { repr: Repr::Os(code) }
590+
Error { repr: Repr::new_os(code) }
536591
}
537592

538593
/// Returns the OS error that this error represents (if any).
@@ -568,11 +623,11 @@ impl Error {
568623
#[must_use]
569624
#[inline]
570625
pub fn raw_os_error(&self) -> Option<i32> {
571-
match self.repr {
572-
Repr::Os(i) => Some(i),
573-
Repr::Custom(..) => None,
574-
Repr::Simple(..) => None,
575-
Repr::SimpleMessage(..) => None,
626+
match self.repr.data() {
627+
ErrorData::Os(i) => Some(i),
628+
ErrorData::Custom(..) => None,
629+
ErrorData::Simple(..) => None,
630+
ErrorData::SimpleMessage(..) => None,
576631
}
577632
}
578633

@@ -607,11 +662,11 @@ impl Error {
607662
#[must_use]
608663
#[inline]
609664
pub fn get_ref(&self) -> Option<&(dyn error::Error + Send + Sync + 'static)> {
610-
match self.repr {
611-
Repr::Os(..) => None,
612-
Repr::Simple(..) => None,
613-
Repr::SimpleMessage(..) => None,
614-
Repr::Custom(ref c) => Some(&*c.error),
665+
match self.repr.data() {
666+
ErrorData::Os(..) => None,
667+
ErrorData::Simple(..) => None,
668+
ErrorData::SimpleMessage(..) => None,
669+
ErrorData::Custom(c) => Some(&*c.error),
615670
}
616671
}
617672

@@ -681,11 +736,11 @@ impl Error {
681736
#[must_use]
682737
#[inline]
683738
pub fn get_mut(&mut self) -> Option<&mut (dyn error::Error + Send + Sync + 'static)> {
684-
match self.repr {
685-
Repr::Os(..) => None,
686-
Repr::Simple(..) => None,
687-
Repr::SimpleMessage(..) => None,
688-
Repr::Custom(ref mut c) => Some(&mut *c.error),
739+
match self.repr.data_mut() {
740+
ErrorData::Os(..) => None,
741+
ErrorData::Simple(..) => None,
742+
ErrorData::SimpleMessage(..) => None,
743+
ErrorData::Custom(c) => Some(&mut *c.error),
689744
}
690745
}
691746

@@ -720,11 +775,11 @@ impl Error {
720775
#[must_use = "`self` will be dropped if the result is not used"]
721776
#[inline]
722777
pub fn into_inner(self) -> Option<Box<dyn error::Error + Send + Sync>> {
723-
match self.repr {
724-
Repr::Os(..) => None,
725-
Repr::Simple(..) => None,
726-
Repr::SimpleMessage(..) => None,
727-
Repr::Custom(c) => Some(c.error),
778+
match self.repr.into_data() {
779+
ErrorData::Os(..) => None,
780+
ErrorData::Simple(..) => None,
781+
ErrorData::SimpleMessage(..) => None,
782+
ErrorData::Custom(c) => Some(c.error),
728783
}
729784
}
730785

@@ -750,44 +805,46 @@ impl Error {
750805
#[must_use]
751806
#[inline]
752807
pub fn kind(&self) -> ErrorKind {
753-
match self.repr {
754-
Repr::Os(code) => sys::decode_error_kind(code),
755-
Repr::Custom(ref c) => c.kind,
756-
Repr::Simple(kind) => kind,
757-
Repr::SimpleMessage(kind, _) => kind,
808+
match self.repr.data() {
809+
ErrorData::Os(code) => sys::decode_error_kind(code),
810+
ErrorData::Custom(c) => c.kind,
811+
ErrorData::Simple(kind) => kind,
812+
ErrorData::SimpleMessage(m) => m.kind,
758813
}
759814
}
760815
}
761816

762817
impl fmt::Debug for Repr {
763818
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
764-
match *self {
765-
Repr::Os(code) => fmt
819+
match self.data() {
820+
ErrorData::Os(code) => fmt
766821
.debug_struct("Os")
767822
.field("code", &code)
768823
.field("kind", &sys::decode_error_kind(code))
769824
.field("message", &sys::os::error_string(code))
770825
.finish(),
771-
Repr::Custom(ref c) => fmt::Debug::fmt(&c, fmt),
772-
Repr::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(),
773-
Repr::SimpleMessage(kind, &message) => {
774-
fmt.debug_struct("Error").field("kind", &kind).field("message", &message).finish()
775-
}
826+
ErrorData::Custom(c) => fmt::Debug::fmt(&c, fmt),
827+
ErrorData::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(),
828+
ErrorData::SimpleMessage(msg) => fmt
829+
.debug_struct("Error")
830+
.field("kind", &msg.kind)
831+
.field("message", &msg.message)
832+
.finish(),
776833
}
777834
}
778835
}
779836

780837
#[stable(feature = "rust1", since = "1.0.0")]
781838
impl fmt::Display for Error {
782839
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
783-
match self.repr {
784-
Repr::Os(code) => {
840+
match self.repr.data() {
841+
ErrorData::Os(code) => {
785842
let detail = sys::os::error_string(code);
786843
write!(fmt, "{} (os error {})", detail, code)
787844
}
788-
Repr::Custom(ref c) => c.error.fmt(fmt),
789-
Repr::Simple(kind) => write!(fmt, "{}", kind.as_str()),
790-
Repr::SimpleMessage(_, &msg) => msg.fmt(fmt),
845+
ErrorData::Custom(ref c) => c.error.fmt(fmt),
846+
ErrorData::Simple(kind) => write!(fmt, "{}", kind.as_str()),
847+
ErrorData::SimpleMessage(msg) => msg.message.fmt(fmt),
791848
}
792849
}
793850
}
@@ -796,29 +853,29 @@ impl fmt::Display for Error {
796853
impl error::Error for Error {
797854
#[allow(deprecated, deprecated_in_future)]
798855
fn description(&self) -> &str {
799-
match self.repr {
800-
Repr::Os(..) | Repr::Simple(..) => self.kind().as_str(),
801-
Repr::SimpleMessage(_, &msg) => msg,
802-
Repr::Custom(ref c) => c.error.description(),
856+
match self.repr.data() {
857+
ErrorData::Os(..) | ErrorData::Simple(..) => self.kind().as_str(),
858+
ErrorData::SimpleMessage(msg) => msg.message,
859+
ErrorData::Custom(c) => c.error.description(),
803860
}
804861
}
805862

806863
#[allow(deprecated)]
807864
fn cause(&self) -> Option<&dyn error::Error> {
808-
match self.repr {
809-
Repr::Os(..) => None,
810-
Repr::Simple(..) => None,
811-
Repr::SimpleMessage(..) => None,
812-
Repr::Custom(ref c) => c.error.cause(),
865+
match self.repr.data() {
866+
ErrorData::Os(..) => None,
867+
ErrorData::Simple(..) => None,
868+
ErrorData::SimpleMessage(..) => None,
869+
ErrorData::Custom(c) => c.error.cause(),
813870
}
814871
}
815872

816873
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
817-
match self.repr {
818-
Repr::Os(..) => None,
819-
Repr::Simple(..) => None,
820-
Repr::SimpleMessage(..) => None,
821-
Repr::Custom(ref c) => c.error.source(),
874+
match self.repr.data() {
875+
ErrorData::Os(..) => None,
876+
ErrorData::Simple(..) => None,
877+
ErrorData::SimpleMessage(..) => None,
878+
ErrorData::Custom(c) => c.error.source(),
822879
}
823880
}
824881
}

0 commit comments

Comments
 (0)