Skip to content

Commit 1cb641c

Browse files
committed
rework + review
1 parent a4e189b commit 1cb641c

File tree

5 files changed

+141
-177
lines changed

5 files changed

+141
-177
lines changed

src/shims/native_lib/ffi.rs

Lines changed: 21 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use libffi::middle::{Arg as ArgPtr, Cif, Type as FfiType};
55
///
66
/// SAFETY: The safety invariants of the foreign function being called must be
77
/// upheld (if any).
8-
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &[OwnedArg]) -> R {
8+
pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &mut [OwnedArg]) -> R {
99
let mut arg_tys = vec![];
1010
let mut arg_ptrs = vec![];
1111
for arg in args {
12-
arg_tys.push(arg.ty());
12+
arg_tys.push(arg.take_ty());
1313
arg_ptrs.push(arg.ptr())
1414
}
1515
let cif = Cif::new(arg_tys, R::reify().into_middle());
@@ -21,103 +21,34 @@ pub unsafe fn call<R: libffi::high::CType>(fun: CodePtr, args: &[OwnedArg]) -> R
2121

2222
/// An argument for an FFI call.
2323
#[derive(Debug, Clone)]
24-
pub enum OwnedArg {
25-
/// Primitive type.
26-
Primitive(ScalarArg),
27-
/// ADT with its computed type layout and bytes.
28-
Adt(FfiType, Box<[u8]>),
24+
pub struct OwnedArg {
25+
/// The type descriptor for this argument.
26+
ty: Option<FfiType>,
27+
/// Corresponding bytes for the value.
28+
bytes: Box<[u8]>,
2929
}
3030

3131
impl OwnedArg {
32-
/// Gets the libffi type descriptor for this argument.
33-
fn ty(&self) -> FfiType {
34-
match self {
35-
OwnedArg::Primitive(scalar_arg) => scalar_arg.ty(),
36-
OwnedArg::Adt(ty, _) => ty.clone(),
37-
}
32+
/// Instantiates an argument from a type descriptor and bytes.
33+
pub fn new(ty: FfiType, bytes: Box<[u8]>) -> Self {
34+
Self { ty: Some(ty), bytes }
35+
}
36+
37+
/// Gets the libffi type descriptor for this argument. Should only be
38+
/// called once on a given `OwnedArg`.
39+
fn take_ty(&mut self) -> FfiType {
40+
self.ty.take().unwrap()
3841
}
3942

4043
/// Instantiates a libffi argument pointer pointing to this argument's bytes.
4144
/// NB: Since `libffi::middle::Arg` ignores the lifetime of the reference
4245
/// it's derived from, it is up to the caller to ensure the `OwnedArg` is
4346
/// not dropped before unsafely calling `libffi::middle::Cif::call()`!
4447
fn ptr(&self) -> ArgPtr {
45-
match self {
46-
OwnedArg::Primitive(scalar_arg) => scalar_arg.ptr(),
47-
// FIXME: Using `&items[0]` to reference the whole array is definitely
48-
// unsound under SB, but we're waiting on
49-
// https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73
50-
// to land in a release so that we don't need to do this.
51-
OwnedArg::Adt(_, items) => ArgPtr::new(&items[0]),
52-
}
53-
}
54-
}
55-
56-
impl From<ScalarArg> for OwnedArg {
57-
fn from(prim: ScalarArg) -> Self {
58-
Self::Primitive(prim)
59-
}
60-
}
61-
62-
#[derive(Debug, Clone)]
63-
/// Enum of supported scalar arguments to external C functions.
64-
pub enum ScalarArg {
65-
/// 8-bit signed integer.
66-
Int8(i8),
67-
/// 16-bit signed integer.
68-
Int16(i16),
69-
/// 32-bit signed integer.
70-
Int32(i32),
71-
/// 64-bit signed integer.
72-
Int64(i64),
73-
/// isize.
74-
ISize(isize),
75-
/// 8-bit unsigned integer.
76-
UInt8(u8),
77-
/// 16-bit unsigned integer.
78-
UInt16(u16),
79-
/// 32-bit unsigned integer.
80-
UInt32(u32),
81-
/// 64-bit unsigned integer.
82-
UInt64(u64),
83-
/// usize.
84-
USize(usize),
85-
/// Raw pointer, stored as C's `void*`.
86-
RawPtr(*mut std::ffi::c_void),
87-
}
88-
89-
impl ScalarArg {
90-
/// See `OwnedArg::ty()`.
91-
fn ty(&self) -> FfiType {
92-
match self {
93-
ScalarArg::Int8(_) => FfiType::i8(),
94-
ScalarArg::Int16(_) => FfiType::i16(),
95-
ScalarArg::Int32(_) => FfiType::i32(),
96-
ScalarArg::Int64(_) => FfiType::i64(),
97-
ScalarArg::ISize(_) => FfiType::isize(),
98-
ScalarArg::UInt8(_) => FfiType::u8(),
99-
ScalarArg::UInt16(_) => FfiType::u16(),
100-
ScalarArg::UInt32(_) => FfiType::u32(),
101-
ScalarArg::UInt64(_) => FfiType::u64(),
102-
ScalarArg::USize(_) => FfiType::usize(),
103-
ScalarArg::RawPtr(_) => FfiType::pointer(),
104-
}
105-
}
106-
107-
/// See `OwnedArg::ptr()`.
108-
fn ptr(&self) -> ArgPtr {
109-
match self {
110-
ScalarArg::Int8(i) => ArgPtr::new(i),
111-
ScalarArg::Int16(i) => ArgPtr::new(i),
112-
ScalarArg::Int32(i) => ArgPtr::new(i),
113-
ScalarArg::Int64(i) => ArgPtr::new(i),
114-
ScalarArg::ISize(i) => ArgPtr::new(i),
115-
ScalarArg::UInt8(i) => ArgPtr::new(i),
116-
ScalarArg::UInt16(i) => ArgPtr::new(i),
117-
ScalarArg::UInt32(i) => ArgPtr::new(i),
118-
ScalarArg::UInt64(i) => ArgPtr::new(i),
119-
ScalarArg::USize(i) => ArgPtr::new(i),
120-
ScalarArg::RawPtr(i) => ArgPtr::new(i),
121-
}
48+
// FIXME: Using `&self.bytes[0]` to reference the whole array is
49+
// definitely unsound under SB, but we're waiting on
50+
// https://github.com/libffi-rs/libffi-rs/commit/112a37b3b6ffb35bd75241fbcc580de40ba74a73
51+
// to land in a release so that we don't need to do this.
52+
ArgPtr::new(&self.bytes[0])
12253
}
12354
}

src/shims/native_lib/mod.rs

Lines changed: 114 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Implements calling functions from a native library.
22
3+
use std::io::Write;
34
use std::ops::Deref;
45

56
use libffi::low::CodePtr;
67
use libffi::middle::Type as FfiType;
7-
use rustc_abi::Size;
8+
use rustc_abi::{HasDataLayout, Size};
89
use rustc_middle::ty::{self as ty, IntTy, Ty, UintTy};
910
use rustc_span::Symbol;
1011
use serde::{Deserialize, Serialize};
@@ -21,7 +22,7 @@ mod ffi;
2122
)]
2223
pub mod trace;
2324

24-
use self::ffi::{OwnedArg, ScalarArg};
25+
use self::ffi::OwnedArg;
2526
use crate::*;
2627

2728
/// The final results of an FFI trace, containing every relevant event detected
@@ -77,7 +78,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
7778
link_name: Symbol,
7879
dest: &MPlaceTy<'tcx>,
7980
ptr: CodePtr,
80-
libffi_args: &[OwnedArg],
81+
libffi_args: &mut [OwnedArg],
8182
) -> InterpResult<'tcx, (crate::ImmTy<'tcx>, Option<MemEvents>)> {
8283
let this = self.eval_context_mut();
8384
#[cfg(target_os = "linux")]
@@ -274,90 +275,122 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
274275
/// and convert it to a `OwnedArg`.
275276
fn op_to_ffi_arg(&self, v: &OpTy<'tcx>, tracing: bool) -> InterpResult<'tcx, OwnedArg> {
276277
let this = self.eval_context_ref();
277-
let scalar = |v| interp_ok(this.read_immediate(v)?.to_scalar());
278-
interp_ok(match v.layout.ty.kind() {
279-
// If the primitive provided can be converted to a type matching the type pattern
280-
// then create a `OwnedArg` of this primitive value with the corresponding `OwnedArg` constructor.
281-
// the ints
282-
ty::Int(IntTy::I8) => ScalarArg::Int8(scalar(v)?.to_i8()?).into(),
283-
ty::Int(IntTy::I16) => ScalarArg::Int16(scalar(v)?.to_i16()?).into(),
284-
ty::Int(IntTy::I32) => ScalarArg::Int32(scalar(v)?.to_i32()?).into(),
285-
ty::Int(IntTy::I64) => ScalarArg::Int64(scalar(v)?.to_i64()?).into(),
286-
ty::Int(IntTy::Isize) =>
287-
ScalarArg::ISize(scalar(v)?.to_target_isize(this)?.try_into().unwrap()).into(),
288-
// the uints
289-
ty::Uint(UintTy::U8) => ScalarArg::UInt8(scalar(v)?.to_u8()?).into(),
290-
ty::Uint(UintTy::U16) => ScalarArg::UInt16(scalar(v)?.to_u16()?).into(),
291-
ty::Uint(UintTy::U32) => ScalarArg::UInt32(scalar(v)?.to_u32()?).into(),
292-
ty::Uint(UintTy::U64) => ScalarArg::UInt64(scalar(v)?.to_u64()?).into(),
293-
ty::Uint(UintTy::Usize) =>
294-
ScalarArg::USize(scalar(v)?.to_target_usize(this)?.try_into().unwrap()).into(),
295-
ty::RawPtr(..) => {
296-
let ptr = scalar(v)?.to_pointer(this)?;
297-
this.expose_and_warn(ptr.provenance, tracing)?;
298-
299-
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
300-
// below to expose the actual interpreter-level allocation.
301-
ScalarArg::RawPtr(std::ptr::with_exposed_provenance_mut(ptr.addr().bytes_usize()))
302-
.into()
278+
279+
// This should go first so that we emit unsupported before doing a bunch
280+
// of extra work for types that aren't supported yet.
281+
let ty = this.ty_to_ffitype(v.layout.ty)?;
282+
283+
// Now grab the bytes of the argument.
284+
let bytes = match v.as_mplace_or_imm() {
285+
either::Either::Left(mplace) => {
286+
// Get the alloc id corresponding to this mplace, alongside
287+
// a pointer that's offset to point to this particular
288+
// mplace (not one at the base addr of the allocation).
289+
let mplace_ptr = mplace.ptr();
290+
let sz = mplace.layout.size.bytes_usize();
291+
if sz == 0 {
292+
throw_unsup_format!("Attempting to pass a ZST over FFI");
293+
}
294+
let (id, ofs, _) = this.ptr_get_alloc_id(mplace_ptr, sz.try_into().unwrap())?;
295+
let ofs = ofs.bytes_usize();
296+
// Expose all provenances in the allocation within the byte
297+
// range of the struct, if any.
298+
let alloc = this.get_alloc_raw(id)?;
299+
let alloc_ptr = this.get_alloc_bytes_unchecked_raw(id)?;
300+
for prov in alloc.provenance().get_range(this, (ofs..ofs.strict_add(sz)).into()) {
301+
this.expose_provenance(prov)?;
302+
}
303+
// SAFETY: We know for sure that at alloc_ptr + ofs the next layout.size
304+
// bytes are part of this allocation and initialised. They might be marked
305+
// as uninit in Miri, but all bytes returned by `MiriAllocBytes` are
306+
// initialised.
307+
unsafe {
308+
Box::from(std::slice::from_raw_parts(
309+
alloc_ptr.add(ofs),
310+
mplace.layout.size.bytes_usize(),
311+
))
312+
}
303313
}
304-
// For ADTs, create an FfiType from their fields.
305-
ty::Adt(adt_def, args) => {
306-
let adt = this.adt_to_ffitype(v.layout.ty, *adt_def, args)?;
307-
308-
// Copy the raw bytes backing this arg.
309-
let bytes = match v.as_mplace_or_imm() {
310-
either::Either::Left(mplace) => {
311-
// Get the alloc id corresponding to this mplace, alongside
312-
// a pointer that's offset to point to this particular
313-
// mplace (not one at the base addr of the allocation).
314-
let mplace_ptr = mplace.ptr();
315-
let sz = mplace.layout.size.bytes_usize();
316-
let id = this
317-
.alloc_id_from_addr(
318-
mplace_ptr.addr().bytes(),
319-
sz.try_into().unwrap(),
320-
/* only_exposed_allocations */ false,
321-
)
322-
.unwrap();
323-
// Expose all provenances in the allocation within the byte
324-
// range of the struct, if any.
325-
let alloc = this.get_alloc_raw(id)?;
326-
let alloc_ptr = this.get_alloc_bytes_unchecked_raw(id)?;
327-
let start_addr =
328-
mplace_ptr.addr().bytes_usize().strict_sub(alloc_ptr.addr());
329-
for prov in alloc
330-
.provenance()
331-
.get_range(this, (start_addr..start_addr.strict_add(sz)).into())
332-
{
333-
this.expose_provenance(prov)?;
314+
either::Either::Right(imm) => {
315+
let (first, maybe_second) = imm.to_scalar_and_meta();
316+
// If a scalar is a pointer, then expose its provenance.
317+
if let interpret::Scalar::Ptr(p, _) = first {
318+
// This relies on the `expose_provenance` in the `visit_reachable_allocs` callback
319+
// below to expose the actual interpreter-level allocation.
320+
this.expose_and_warn(Some(p.provenance), tracing)?;
321+
}
322+
323+
// Turn the scalar(s) into u128s so we can write their bytes
324+
// into the buffer.
325+
let (sc_int_first, sz_first) = {
326+
let sc = first.to_scalar_int()?;
327+
(sc.to_bits_unchecked(), sc.size().bytes_usize())
328+
};
329+
let (sc_int_second, sz_second) = match maybe_second {
330+
MemPlaceMeta::Meta(sc) => {
331+
// Might also be a pointer.
332+
if let interpret::Scalar::Ptr(p, _) = first {
333+
this.expose_and_warn(Some(p.provenance), tracing)?;
334334
}
335-
// SAFETY: We know for sure that at mplace_ptr.addr() the next layout.size
336-
// bytes are part of this allocation and initialised. They might be marked
337-
// as uninit in Miri, but all bytes returned by `MiriAllocBytes` are
338-
// initialised.
339-
unsafe {
340-
std::slice::from_raw_parts(
341-
alloc_ptr.with_addr(mplace_ptr.addr().bytes_usize()),
342-
mplace.layout.size.bytes_usize(),
343-
)
344-
.to_vec()
345-
.into_boxed_slice()
335+
let sc = sc.to_scalar_int()?;
336+
(sc.to_bits_unchecked(), sc.size().bytes_usize())
337+
}
338+
MemPlaceMeta::None => (0, 0),
339+
};
340+
let sz = imm.layout.size.bytes_usize();
341+
// TODO: Is this actually ok? Seems like the only way to figure
342+
// out how the scalars are laid out relative to each other.
343+
let align_second = match imm.layout.backend_repr {
344+
rustc_abi::BackendRepr::Scalar(_) => 1,
345+
rustc_abi::BackendRepr::ScalarPair(_, sc2) => sc2.align(this).bytes_usize(),
346+
_ => unreachable!(),
347+
};
348+
// How many bytes to skip between scalars if necessary for alignment.
349+
let skip = sz_first.next_multiple_of(align_second).strict_sub(sz_first);
350+
351+
let mut bytes: Box<[u8]> = (0..sz).map(|_| 0u8).collect();
352+
353+
// Copy over the bytes in an endianness-agnostic way. Since each
354+
// scalar may be up to 128 bits and write_target_uint doesn't
355+
// give us an easy way to do multiple writes in a row, we
356+
// adapt its logic for two consecutive writes.
357+
let mut bytes_wr = bytes.as_mut();
358+
match this.data_layout().endian {
359+
rustc_abi::Endian::Little => {
360+
// Only write as many bytes as specified, not all of the u128.
361+
let wr = bytes_wr.write(&sc_int_first.to_le_bytes()[..sz_first]).unwrap();
362+
assert_eq!(wr, sz_first);
363+
// If the second scalar is zeroed, it's more efficient to skip it.
364+
if sc_int_second != 0 {
365+
bytes_wr = bytes_wr.split_at_mut(skip).1;
366+
let wr =
367+
bytes_wr.write(&sc_int_second.to_le_bytes()[..sz_second]).unwrap();
368+
assert_eq!(wr, sz_second);
346369
}
347370
}
348-
either::Either::Right(_) => {
349-
// TODO: support this!
350-
throw_unsup_format!(
351-
"Immediate structs can't be passed over FFI: {}",
352-
v.layout.ty
353-
)
371+
rustc_abi::Endian::Big => {
372+
// TODO: My gut says this is wrong, let's see if CI complains.
373+
let wr = bytes_wr
374+
.write(&sc_int_first.to_be_bytes()[16usize.strict_sub(sz_first)..])
375+
.unwrap();
376+
assert_eq!(wr, sz_first);
377+
if sc_int_second != 0 {
378+
bytes_wr = bytes_wr.split_at_mut(skip).1;
379+
let wr = bytes_wr
380+
.write(
381+
&sc_int_second.to_be_bytes()[16usize.strict_sub(sz_second)..],
382+
)
383+
.unwrap();
384+
assert_eq!(wr, sz_second);
385+
}
354386
}
355-
};
387+
}
388+
// Any remaining bytes are padding, so ignore.
356389

357-
ffi::OwnedArg::Adt(adt, bytes)
390+
bytes
358391
}
359-
_ => throw_unsup_format!("unsupported argument type for native call: {}", v.layout.ty),
360-
})
392+
};
393+
interp_ok(OwnedArg::new(ty, bytes))
361394
}
362395

363396
/// Parses an ADT to construct the matching libffi type.
@@ -495,7 +528,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
495528

496529
// Call the function and store output, depending on return type in the function signature.
497530
let (ret, maybe_memevents) =
498-
this.call_native_with_args(link_name, dest, code_ptr, &libffi_args)?;
531+
this.call_native_with_args(link_name, dest, code_ptr, &mut libffi_args)?;
499532

500533
if tracing {
501534
this.tracing_apply_accesses(maybe_memevents.unwrap())?;

tests/native-lib/aggregate_arguments.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77

88
typedef struct PassMe {
99
int32_t value;
10-
int16_t other_value;
10+
int64_t other_value;
1111
} PassMe;
1212

13-
EXPORT int32_t pass_struct(const PassMe pass_me) {
13+
EXPORT int64_t pass_struct(const PassMe pass_me) {
1414
return pass_me.value + pass_me.other_value;
1515
}
1616

0 commit comments

Comments
 (0)