Skip to content

Commit 22fd2f3

Browse files
authored
Merge pull request rust-lang#4114 from RalfJung/fd-ref-refactor
FD handling: avoid unnecessary dynamic downcasts
2 parents 3fceb03 + 7e64216 commit 22fd2f3

File tree

7 files changed

+262
-278
lines changed

7 files changed

+262
-278
lines changed

src/tools/miri/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#![feature(strict_overflow_ops)]
1414
#![feature(pointer_is_aligned_to)]
1515
#![feature(unqualified_local_imports)]
16+
#![feature(derive_coerce_pointee)]
17+
#![feature(arbitrary_self_types)]
1618
// Configure clippy and other lints
1719
#![allow(
1820
clippy::collapsible_else_if,

src/tools/miri/src/shims/files.rs

+140-113
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::any::Any;
22
use std::collections::BTreeMap;
33
use std::io::{IsTerminal, Read, SeekFrom, Write};
4+
use std::marker::CoercePointee;
45
use std::ops::Deref;
56
use std::rc::{Rc, Weak};
67
use std::{fs, io};
@@ -10,16 +11,126 @@ use rustc_abi::Size;
1011
use crate::shims::unix::UnixFileDescription;
1112
use crate::*;
1213

14+
/// A unique id for file descriptions. While we could use the address, considering that
15+
/// is definitely unique, the address would expose interpreter internal state when used
16+
/// for sorting things. So instead we generate a unique id per file description is the name
17+
/// for all `dup`licates and is never reused.
18+
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
19+
pub struct FdId(usize);
20+
21+
#[derive(Debug, Clone)]
22+
struct FdIdWith<T: ?Sized> {
23+
id: FdId,
24+
inner: T,
25+
}
26+
27+
/// A refcounted pointer to a file description, also tracking the
28+
/// globally unique ID of this file description.
29+
#[repr(transparent)]
30+
#[derive(CoercePointee, Debug)]
31+
pub struct FileDescriptionRef<T: ?Sized>(Rc<FdIdWith<T>>);
32+
33+
impl<T: ?Sized> Clone for FileDescriptionRef<T> {
34+
fn clone(&self) -> Self {
35+
FileDescriptionRef(self.0.clone())
36+
}
37+
}
38+
39+
impl<T: ?Sized> Deref for FileDescriptionRef<T> {
40+
type Target = T;
41+
fn deref(&self) -> &T {
42+
&self.0.inner
43+
}
44+
}
45+
46+
impl<T: ?Sized> FileDescriptionRef<T> {
47+
pub fn id(&self) -> FdId {
48+
self.0.id
49+
}
50+
}
51+
52+
/// Holds a weak reference to the actual file description.
53+
#[derive(Clone, Debug)]
54+
pub struct WeakFileDescriptionRef<T: ?Sized>(Weak<FdIdWith<T>>);
55+
56+
impl<T: ?Sized> FileDescriptionRef<T> {
57+
pub fn downgrade(this: &Self) -> WeakFileDescriptionRef<T> {
58+
WeakFileDescriptionRef(Rc::downgrade(&this.0))
59+
}
60+
}
61+
62+
impl<T: ?Sized> WeakFileDescriptionRef<T> {
63+
pub fn upgrade(&self) -> Option<FileDescriptionRef<T>> {
64+
self.0.upgrade().map(FileDescriptionRef)
65+
}
66+
}
67+
68+
impl<T> VisitProvenance for WeakFileDescriptionRef<T> {
69+
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
70+
// A weak reference can never be the only reference to some pointer or place.
71+
// Since the actual file description is tracked by strong ref somewhere,
72+
// it is ok to make this a NOP operation.
73+
}
74+
}
75+
76+
/// A helper trait to indirectly allow downcasting on `Rc<FdIdWith<dyn _>>`.
77+
/// Ideally we'd just add a `FdIdWith<Self>: Any` bound to the `FileDescription` trait,
78+
/// but that does not allow upcasting.
79+
pub trait FileDescriptionExt: 'static {
80+
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any>;
81+
82+
/// We wrap the regular `close` function generically, so both handle `Rc::into_inner`
83+
/// and epoll interest management.
84+
fn close_ref<'tcx>(
85+
self: FileDescriptionRef<Self>,
86+
communicate_allowed: bool,
87+
ecx: &mut MiriInterpCx<'tcx>,
88+
) -> InterpResult<'tcx, io::Result<()>>;
89+
}
90+
91+
impl<T: FileDescription + 'static> FileDescriptionExt for T {
92+
fn into_rc_any(self: FileDescriptionRef<Self>) -> Rc<dyn Any> {
93+
self.0
94+
}
95+
96+
fn close_ref<'tcx>(
97+
self: FileDescriptionRef<Self>,
98+
communicate_allowed: bool,
99+
ecx: &mut MiriInterpCx<'tcx>,
100+
) -> InterpResult<'tcx, io::Result<()>> {
101+
match Rc::into_inner(self.0) {
102+
Some(fd) => {
103+
// Remove entry from the global epoll_event_interest table.
104+
ecx.machine.epoll_interests.remove(fd.id);
105+
106+
fd.inner.close(communicate_allowed, ecx)
107+
}
108+
None => {
109+
// Not the last reference.
110+
interp_ok(Ok(()))
111+
}
112+
}
113+
}
114+
}
115+
116+
pub type DynFileDescriptionRef = FileDescriptionRef<dyn FileDescription>;
117+
118+
impl FileDescriptionRef<dyn FileDescription> {
119+
pub fn downcast<T: FileDescription + 'static>(self) -> Option<FileDescriptionRef<T>> {
120+
let inner = self.into_rc_any().downcast::<FdIdWith<T>>().ok()?;
121+
Some(FileDescriptionRef(inner))
122+
}
123+
}
124+
13125
/// Represents an open file description.
14-
pub trait FileDescription: std::fmt::Debug + Any {
126+
pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
15127
fn name(&self) -> &'static str;
16128

17129
/// Reads as much as possible into the given buffer `ptr`.
18130
/// `len` indicates how many bytes we should try to read.
19131
/// `dest` is where the return value should be stored: number of bytes read, or `-1` in case of error.
20132
fn read<'tcx>(
21-
&self,
22-
_self_ref: &FileDescriptionRef,
133+
self: FileDescriptionRef<Self>,
23134
_communicate_allowed: bool,
24135
_ptr: Pointer,
25136
_len: usize,
@@ -33,8 +144,7 @@ pub trait FileDescription: std::fmt::Debug + Any {
33144
/// `len` indicates how many bytes we should try to write.
34145
/// `dest` is where the return value should be stored: number of bytes written, or `-1` in case of error.
35146
fn write<'tcx>(
36-
&self,
37-
_self_ref: &FileDescriptionRef,
147+
self: FileDescriptionRef<Self>,
38148
_communicate_allowed: bool,
39149
_ptr: Pointer,
40150
_len: usize,
@@ -54,11 +164,15 @@ pub trait FileDescription: std::fmt::Debug + Any {
54164
throw_unsup_format!("cannot seek on {}", self.name());
55165
}
56166

167+
/// Close the file descriptor.
57168
fn close<'tcx>(
58-
self: Box<Self>,
169+
self,
59170
_communicate_allowed: bool,
60171
_ecx: &mut MiriInterpCx<'tcx>,
61-
) -> InterpResult<'tcx, io::Result<()>> {
172+
) -> InterpResult<'tcx, io::Result<()>>
173+
where
174+
Self: Sized,
175+
{
62176
throw_unsup_format!("cannot close {}", self.name());
63177
}
64178

@@ -77,21 +191,13 @@ pub trait FileDescription: std::fmt::Debug + Any {
77191
}
78192
}
79193

80-
impl dyn FileDescription {
81-
#[inline(always)]
82-
pub fn downcast<T: Any>(&self) -> Option<&T> {
83-
(self as &dyn Any).downcast_ref()
84-
}
85-
}
86-
87194
impl FileDescription for io::Stdin {
88195
fn name(&self) -> &'static str {
89196
"stdin"
90197
}
91198

92199
fn read<'tcx>(
93-
&self,
94-
_self_ref: &FileDescriptionRef,
200+
self: FileDescriptionRef<Self>,
95201
communicate_allowed: bool,
96202
ptr: Pointer,
97203
len: usize,
@@ -103,7 +209,7 @@ impl FileDescription for io::Stdin {
103209
// We want isolation mode to be deterministic, so we have to disallow all reads, even stdin.
104210
helpers::isolation_abort_error("`read` from stdin")?;
105211
}
106-
let result = Read::read(&mut { self }, &mut bytes);
212+
let result = Read::read(&mut &*self, &mut bytes);
107213
match result {
108214
Ok(read_size) => ecx.return_read_success(ptr, &bytes, read_size, dest),
109215
Err(e) => ecx.set_last_error_and_return(e, dest),
@@ -121,8 +227,7 @@ impl FileDescription for io::Stdout {
121227
}
122228

123229
fn write<'tcx>(
124-
&self,
125-
_self_ref: &FileDescriptionRef,
230+
self: FileDescriptionRef<Self>,
126231
_communicate_allowed: bool,
127232
ptr: Pointer,
128233
len: usize,
@@ -131,7 +236,7 @@ impl FileDescription for io::Stdout {
131236
) -> InterpResult<'tcx> {
132237
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
133238
// We allow writing to stderr even with isolation enabled.
134-
let result = Write::write(&mut { self }, bytes);
239+
let result = Write::write(&mut &*self, bytes);
135240
// Stdout is buffered, flush to make sure it appears on the
136241
// screen. This is the write() syscall of the interpreted
137242
// program, we want it to correspond to a write() syscall on
@@ -155,8 +260,7 @@ impl FileDescription for io::Stderr {
155260
}
156261

157262
fn write<'tcx>(
158-
&self,
159-
_self_ref: &FileDescriptionRef,
263+
self: FileDescriptionRef<Self>,
160264
_communicate_allowed: bool,
161265
ptr: Pointer,
162266
len: usize,
@@ -166,7 +270,7 @@ impl FileDescription for io::Stderr {
166270
let bytes = ecx.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?;
167271
// We allow writing to stderr even with isolation enabled.
168272
// No need to flush, stderr is not buffered.
169-
let result = Write::write(&mut { self }, bytes);
273+
let result = Write::write(&mut &*self, bytes);
170274
match result {
171275
Ok(write_size) => ecx.return_write_success(write_size, dest),
172276
Err(e) => ecx.set_last_error_and_return(e, dest),
@@ -188,8 +292,7 @@ impl FileDescription for NullOutput {
188292
}
189293

190294
fn write<'tcx>(
191-
&self,
192-
_self_ref: &FileDescriptionRef,
295+
self: FileDescriptionRef<Self>,
193296
_communicate_allowed: bool,
194297
_ptr: Pointer,
195298
len: usize,
@@ -201,91 +304,10 @@ impl FileDescription for NullOutput {
201304
}
202305
}
203306

204-
/// Structure contains both the file description and its unique identifier.
205-
#[derive(Clone, Debug)]
206-
pub struct FileDescWithId<T: FileDescription + ?Sized> {
207-
id: FdId,
208-
file_description: Box<T>,
209-
}
210-
211-
#[derive(Clone, Debug)]
212-
pub struct FileDescriptionRef(Rc<FileDescWithId<dyn FileDescription>>);
213-
214-
impl Deref for FileDescriptionRef {
215-
type Target = dyn FileDescription;
216-
217-
fn deref(&self) -> &Self::Target {
218-
&*self.0.file_description
219-
}
220-
}
221-
222-
impl FileDescriptionRef {
223-
fn new(fd: impl FileDescription, id: FdId) -> Self {
224-
FileDescriptionRef(Rc::new(FileDescWithId { id, file_description: Box::new(fd) }))
225-
}
226-
227-
pub fn close<'tcx>(
228-
self,
229-
communicate_allowed: bool,
230-
ecx: &mut MiriInterpCx<'tcx>,
231-
) -> InterpResult<'tcx, io::Result<()>> {
232-
// Destroy this `Rc` using `into_inner` so we can call `close` instead of
233-
// implicitly running the destructor of the file description.
234-
let id = self.get_id();
235-
match Rc::into_inner(self.0) {
236-
Some(fd) => {
237-
// Remove entry from the global epoll_event_interest table.
238-
ecx.machine.epoll_interests.remove(id);
239-
240-
fd.file_description.close(communicate_allowed, ecx)
241-
}
242-
None => interp_ok(Ok(())),
243-
}
244-
}
245-
246-
pub fn downgrade(&self) -> WeakFileDescriptionRef {
247-
WeakFileDescriptionRef { weak_ref: Rc::downgrade(&self.0) }
248-
}
249-
250-
pub fn get_id(&self) -> FdId {
251-
self.0.id
252-
}
253-
}
254-
255-
/// Holds a weak reference to the actual file description.
256-
#[derive(Clone, Debug, Default)]
257-
pub struct WeakFileDescriptionRef {
258-
weak_ref: Weak<FileDescWithId<dyn FileDescription>>,
259-
}
260-
261-
impl WeakFileDescriptionRef {
262-
pub fn upgrade(&self) -> Option<FileDescriptionRef> {
263-
if let Some(file_desc_with_id) = self.weak_ref.upgrade() {
264-
return Some(FileDescriptionRef(file_desc_with_id));
265-
}
266-
None
267-
}
268-
}
269-
270-
impl VisitProvenance for WeakFileDescriptionRef {
271-
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
272-
// A weak reference can never be the only reference to some pointer or place.
273-
// Since the actual file description is tracked by strong ref somewhere,
274-
// it is ok to make this a NOP operation.
275-
}
276-
}
277-
278-
/// A unique id for file descriptions. While we could use the address, considering that
279-
/// is definitely unique, the address would expose interpreter internal state when used
280-
/// for sorting things. So instead we generate a unique id per file description is the name
281-
/// for all `dup`licates and is never reused.
282-
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
283-
pub struct FdId(usize);
284-
285307
/// The file descriptor table
286308
#[derive(Debug)]
287309
pub struct FdTable {
288-
pub fds: BTreeMap<i32, FileDescriptionRef>,
310+
pub fds: BTreeMap<i32, DynFileDescriptionRef>,
289311
/// Unique identifier for file description, used to differentiate between various file description.
290312
next_file_description_id: FdId,
291313
}
@@ -313,8 +335,9 @@ impl FdTable {
313335
fds
314336
}
315337

316-
pub fn new_ref(&mut self, fd: impl FileDescription) -> FileDescriptionRef {
317-
let file_handle = FileDescriptionRef::new(fd, self.next_file_description_id);
338+
pub fn new_ref<T: FileDescription>(&mut self, fd: T) -> FileDescriptionRef<T> {
339+
let file_handle =
340+
FileDescriptionRef(Rc::new(FdIdWith { id: self.next_file_description_id, inner: fd }));
318341
self.next_file_description_id = FdId(self.next_file_description_id.0.strict_add(1));
319342
file_handle
320343
}
@@ -325,12 +348,16 @@ impl FdTable {
325348
self.insert(fd_ref)
326349
}
327350

328-
pub fn insert(&mut self, fd_ref: FileDescriptionRef) -> i32 {
351+
pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
329352
self.insert_with_min_num(fd_ref, 0)
330353
}
331354

332355
/// Insert a file description, giving it a file descriptor that is at least `min_fd_num`.
333-
pub fn insert_with_min_num(&mut self, file_handle: FileDescriptionRef, min_fd_num: i32) -> i32 {
356+
pub fn insert_with_min_num(
357+
&mut self,
358+
file_handle: DynFileDescriptionRef,
359+
min_fd_num: i32,
360+
) -> i32 {
334361
// Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
335362
// between used FDs, the find_map combinator will return it. If the first such unused FD
336363
// is after all other used FDs, the find_map combinator will return None, and we will use
@@ -356,12 +383,12 @@ impl FdTable {
356383
new_fd_num
357384
}
358385

359-
pub fn get(&self, fd_num: i32) -> Option<FileDescriptionRef> {
386+
pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
360387
let fd = self.fds.get(&fd_num)?;
361388
Some(fd.clone())
362389
}
363390

364-
pub fn remove(&mut self, fd_num: i32) -> Option<FileDescriptionRef> {
391+
pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
365392
self.fds.remove(&fd_num)
366393
}
367394

0 commit comments

Comments
 (0)