Skip to content

Commit 734368a

Browse files
committedFeb 7, 2022
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).
·
1.87.01.60.0
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
 

‎library/std/src/ffi/c_str.rs

Lines changed: 1 addition & 1 deletion
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

‎library/std/src/fs.rs

Lines changed: 2 additions & 2 deletions
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
}

0 commit comments

Comments
 (0)
Please sign in to comment.