-
Notifications
You must be signed in to change notification settings - Fork 352
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for vectorized read with libc::readv #4084
base: master
Are you sure you want to change the base?
Conversation
195ddfe
to
144a986
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll assume this should not be reviewed yet since it is marked as a draft. I just noticed this one small thing. ;)
I'd recommend to make a first PR with just readv
. Smaller PRs are always easier to handle. Please write @rustbot ready
and remove the "draft" status once you want a review.
src/shims/unix/foreign_items.rs
Outdated
let [fd, iov, iovcnt] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?; | ||
let fd = this.read_scalar(fd)?.to_i32()?; | ||
let iovcnt = this.read_scalar(iovcnt)?.to_i32()?; | ||
this.readv(fd, iov, iovcnt as _, None, dest)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't use as
casts. Why are you casting here anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @RalfJung,
Thank you for pointing this out—you're absolutely right that the casting is unnecessary. I appreciate your feedback.
As you noted, the PR is still in draft status, and there are some cleanups I plan to address. I shared it at this stage primarily to seek input, particularly regarding the issue we previously discussed here: Debugging Scalar Size Mismatch Error in Rust Miri.
Thank you again for reviewing and sharing your thoughts! :)
Ah, that was not clear at all. Please note things like that in the PR description. Anyway judging from Zulip it seems you made progress there. I'm going to wait until you ask for further input on this PR. :) |
144a986
to
279090c
Compare
Hi @RalfJung, I've completed the initial implementation of readv() support and would appreciate your review. The changes focus on three main areas:
Looking forward to your feedback, particularly on the new array bounds checking approach and test coverage strategy. :) |
@rustbot ready |
src/helpers.rs
Outdated
/// where we need to access multiple independent memory regions, such as when processing an array | ||
/// of iovec structures. Unlike simple pointer arithmetic bounds checking, this implementation | ||
/// understands and validates array-based access patterns. | ||
fn deref_pointer_and_offset_vectored( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very specific operation for a particular Linux/POSIX API. I don't think we need a global helper for this, so please move it into the only file where it is actually used.
It's also completely unclear to me why you need a complicated new check here. readv
just iterates the array and does a read
call for each element, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Thank you for your review comment regarding the placement and necessity of the
deref_pointer_and_offset_vectored
function. I'd like to explain the reasoning behind this implementation and agree with your suggestion about its placement. -
The implementation of
readv()
requires handling a base pointer to an array of iovec structures (iov_ptr: &OpTy<'tcx>
) along with a count (iovcnt: i32
). When reconstructing array elements from this base pointer, I encountered limitations with the existingderef_pointer_and_offset()
API, specifically triggering assertions that weren't suitable for our array-based access pattern. -
You can find the complete MIRI backtrace demonstrating these limitations here: https://gist.github.com/shamb0/01491a8367c78404769b3e210ec860e0
- While the boundary checking logic may appear complex, it's necessary for safely handling array reconstruction in this context. However, I agree that this functionality is specific to the readv implementation and doesn't need to be a global helper. I've moved the function to
src/shims/unix/fd.rs
where it's being used, making its purpose and context clearer.
src/shims/unix/fd.rs
Outdated
fd_num: i32, | ||
iov_ptr: &OpTy<'tcx>, | ||
iovcnt: i32, | ||
offset: Option<i128>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be always None
, so please remove the argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/shims/unix/fd.rs
Outdated
// Early returns for empty or invalid cases | ||
if iovcnt == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this justified by the docs? Also, what does the "or invalid" mean here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
I've updated the documentation to clarify the behavior of readv when iovcnt is 0. The updated documentation now explicitly covers:
- The function's return value semantics based on the POSIX specification
- The meaning of various error conditions and their corresponding return codes
- Special cases like empty buffer lists (iovcnt = 0)
src/shims/unix/fd.rs
Outdated
trace!("readv: FD not found"); | ||
return this.set_last_error_and_return(LibcError("EBADF"), dest); | ||
}; | ||
trace!("readv: FD mapped to {fd:?}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the debug tracing from the PR for review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/shims/unix/fd.rs
Outdated
// We need temporary storage for each individual read operation's result | ||
// Using an intermediate buffer helps handle error conditions cleanly | ||
// We use i128 to safely handle both success (positive) and error (-1) cases | ||
let read_dest = this.allocate(this.machine.layouts.i128, MiriMemoryKind::Machine.into())?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you are allocating new storage here. What I would expect is that you call read
once for each element of the vector, and directly put the results into the user-provided buffer. Yes that means we are using more than one syscall, but we don't really care about such details here -- the only thing that matters is the end-to-end behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah it seems the docs say that the entire read must be atomic. Please fix the comment to explain why you are allocating a buffer here; currently you say something about error conditions but it's not clear what this means and it misses the main point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've cleaned up the code and removed the problematic logic that attempted to handle reads individually.
The new implementation will focus on ensuring atomicity by performing a single read operation into a contiguous buffer, as required by POSIX readv() semantics.
let mut current_offset = offset; | ||
|
||
// Process each iovec structure | ||
for i in 0..iovcnt { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't use a loop of reads, the read must be atomic. I thought that's why you are allocating a new buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've cleaned up the code and removed the problematic logic that attempted to handle reads individually.
The new implementation will focus on ensuring atomicity by performing a single read operation into a contiguous buffer, as required by POSIX readv() semantics.
src/shims/unix/fd.rs
Outdated
this.read_scalar(&read_dest)?.to_i128()? | ||
} else { | ||
// Handle regular read case | ||
fd.read(&fd, this.machine.communicate(), iov_base_ptr, iov_len, &read_dest, this)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This unfortunately does not work if read
blocks. The code after read
will run too early, before the read actually completed.
Implementing this properly for blocking read
is very complicated and will require some non-trivial refactoring of the file description trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for highlighting the critical issue regarding blocking reads and concurrent access. This was an oversight in my initial implementation that could have led to race conditions.
I've refined the implementation, with a focus on atomic operations. The key improvement is the introduction of a new read_buffer
interface in the FileDescription
trait:
impl FileDescription for FileHandle {
fn read_buffer<'tcx>(
&self,
self_ref: &FileDescriptionRef,
communicate_allowed: bool,
buf: &mut [u8],
dest: &MPlaceTy<'tcx>,
ecx: &mut MiriInterpCx<'tcx>,
) -> InterpResult<'tcx> {
// Implementation details
}
}
The revised strategy for readv()
now follows these steps:
- Allocates an intermediate byte buffer for the entire read operation
- Performs a single atomic read operation into this buffer
- Distributes the data from the intermediate buffer to the user's scattered iovec buffers
This approach ensures thread safety while maintaining POSIX compliance for atomic vectored I/O operations. I would appreciate your thoughts on this implementation and welcome any suggestions for further improvements.
You have CI failures. Please make sure CI is green before asking for review; that avoids reviewers having to spend time on things that CI can check automatically. If you don't understand why CI fails, ask for help. |
b99627c
to
d1e7023
Compare
You're absolutely correct about the importance of ensuring all CI checks pass before requesting a review. Moving forward, I will:
|
d1e7023
to
102f366
Compare
- This enables vectorized reads. Signed-off-by: shamb0 <[email protected]>
102f366
to
222bac2
Compare
Fixes #4048
Add POSIX readv() System Call Support to MIRI
This PR implements emulation support for libc::readv(), a POSIX vectored I/O operation that allows reading data into multiple buffers with a single system call. This enhancement addresses issue #4048.