-use std::{convert::{TryInto, TryFrom}, iter};
+use std::{collections::hash_map::Entry, iter};
use log::trace;
-use rustc_hir::def_id::DefId;
+use rustc_apfloat::Float;
+use rustc_ast::expand::allocator::AllocatorKind;
+use rustc_hir::{
+ def::DefKind,
+ def_id::{CrateNum, DefId, LOCAL_CRATE},
+};
+use rustc_middle::middle::{
+ codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
+ exported_symbols::ExportedSymbol,
+};
use rustc_middle::mir;
-use rustc_target::{abi::{Align, Size}, spec::PanicStrategy};
use rustc_middle::ty;
-use rustc_apfloat::Float;
-use rustc_span::symbol::sym;
+use rustc_session::config::CrateType;
+use rustc_span::Symbol;
+use rustc_target::{
+ abi::{Align, Size},
+ spec::abi::Abi,
+};
-use crate::*;
use super::backtrace::EvalContextExt as _;
-use helpers::check_arg_count;
+use crate::helpers::convert::Truncate;
+use crate::*;
+
+/// Returned by `emulate_foreign_item_by_name`.
+pub enum EmulateByNameResult<'mir, 'tcx> {
+ /// The caller is expected to jump to the return block.
+ NeedsJumping,
+ /// Jumping has already been taken care of.
+ AlreadyJumped,
+ /// A MIR body has been found for the function
+ MirBody(&'mir mir::Body<'tcx>, ty::Instance<'tcx>),
+ /// The item is not supported.
+ NotSupported,
+}
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
/// Returns the minimum alignment for the target architecture for allocations of the given size.
fn min_align(&self, size: u64, kind: MiriMemoryKind) -> Align {
let this = self.eval_context_ref();
- // List taken from `libstd/sys_common/alloc.rs`.
- let min_align = match this.tcx.sess.target.arch.as_str() {
+ // List taken from `library/std/src/sys/common/alloc.rs`.
+ // This list should be kept in sync with the one from libstd.
+ let min_align = match this.tcx.sess.target.arch.as_ref() {
"x86" | "arm" | "mips" | "powerpc" | "powerpc64" | "asmjs" | "wasm32" => 8,
"x86_64" | "aarch64" | "mips64" | "s390x" | "sparc64" => 16,
arch => bug!("Unsupported target architecture: {}", arch),
Align::from_bytes(prev_power_of_two(size)).unwrap()
}
- fn malloc(&mut self, size: u64, zero_init: bool, kind: MiriMemoryKind) -> Scalar<Tag> {
+ fn malloc(
+ &mut self,
+ size: u64,
+ zero_init: bool,
+ kind: MiriMemoryKind,
+ ) -> InterpResult<'tcx, Pointer<Option<Tag>>> {
let this = self.eval_context_mut();
if size == 0 {
- Scalar::null_ptr(this)
+ Ok(Pointer::null())
} else {
let align = this.min_align(size, kind);
- let ptr = this.memory.allocate(Size::from_bytes(size), align, kind.into());
+ let ptr = this.allocate_ptr(Size::from_bytes(size), align, kind.into())?;
if zero_init {
// We just allocated this, the access is definitely in-bounds.
- this.memory.write_bytes(ptr.into(), iter::repeat(0u8).take(size as usize)).unwrap();
+ this.write_bytes_ptr(ptr.into(), iter::repeat(0u8).take(size as usize)).unwrap();
}
- Scalar::Ptr(ptr)
+ Ok(ptr.into())
}
}
- fn free(&mut self, ptr: Scalar<Tag>, kind: MiriMemoryKind) -> InterpResult<'tcx> {
+ fn free(&mut self, ptr: Pointer<Option<Tag>>, kind: MiriMemoryKind) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
- if !this.is_null(ptr)? {
- let ptr = this.force_ptr(ptr)?;
- this.memory.deallocate(ptr, None, kind.into())?;
+ if !this.ptr_is_null(ptr)? {
+ this.deallocate_ptr(ptr, None, kind.into())?;
}
Ok(())
}
fn realloc(
&mut self,
- old_ptr: Scalar<Tag>,
+ old_ptr: Pointer<Option<Tag>>,
new_size: u64,
kind: MiriMemoryKind,
- ) -> InterpResult<'tcx, Scalar<Tag>> {
+ ) -> InterpResult<'tcx, Pointer<Option<Tag>>> {
let this = self.eval_context_mut();
let new_align = this.min_align(new_size, kind);
- if this.is_null(old_ptr)? {
+ if this.ptr_is_null(old_ptr)? {
if new_size == 0 {
- Ok(Scalar::null_ptr(this))
+ Ok(Pointer::null())
} else {
let new_ptr =
- this.memory.allocate(Size::from_bytes(new_size), new_align, kind.into());
- Ok(Scalar::Ptr(new_ptr))
+ this.allocate_ptr(Size::from_bytes(new_size), new_align, kind.into())?;
+ Ok(new_ptr.into())
}
} else {
- let old_ptr = this.force_ptr(old_ptr)?;
if new_size == 0 {
- this.memory.deallocate(old_ptr, None, kind.into())?;
- Ok(Scalar::null_ptr(this))
+ this.deallocate_ptr(old_ptr, None, kind.into())?;
+ Ok(Pointer::null())
} else {
- let new_ptr = this.memory.reallocate(
+ let new_ptr = this.reallocate_ptr(
old_ptr,
None,
Size::from_bytes(new_size),
new_align,
kind.into(),
)?;
- Ok(Scalar::Ptr(new_ptr))
+ Ok(new_ptr.into())
+ }
+ }
+ }
+
+ /// Lookup the body of a function that has `link_name` as the symbol name.
+ fn lookup_exported_symbol(
+ &mut self,
+ link_name: Symbol,
+ ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
+ let this = self.eval_context_mut();
+ let tcx = this.tcx.tcx;
+
+ // If the result was cached, just return it.
+ // (Cannot use `or_insert` since the code below might have to throw an error.)
+ let entry = this.machine.exported_symbols_cache.entry(link_name);
+ let instance = *match entry {
+ Entry::Occupied(e) => e.into_mut(),
+ Entry::Vacant(e) => {
+ // Find it if it was not cached.
+ let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None;
+ // `dependency_formats` includes all the transitive informations needed to link a crate,
+ // which is what we need here since we need to dig out `exported_symbols` from all transitive
+ // dependencies.
+ let dependency_formats = tcx.dependency_formats(());
+ let dependency_format = dependency_formats
+ .iter()
+ .find(|(crate_type, _)| *crate_type == CrateType::Executable)
+ .expect("interpreting a non-executable crate");
+ for cnum in iter::once(LOCAL_CRATE).chain(
+ dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
+ (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
+ }),
+ ) {
+ // We can ignore `_export_info` here: we are a Rust crate, and everything is exported
+ // from a Rust crate.
+ for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
+ if let ExportedSymbol::NonGeneric(def_id) = symbol {
+ let attrs = tcx.codegen_fn_attrs(def_id);
+ let symbol_name = if let Some(export_name) = attrs.export_name {
+ export_name
+ } else if attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) {
+ tcx.item_name(def_id)
+ } else {
+ // Skip over items without an explicitly defined symbol name.
+ continue;
+ };
+ if symbol_name == link_name {
+ if let Some((original_instance, original_cnum)) = instance_and_crate
+ {
+ // Make sure we are consistent wrt what is 'first' and 'second'.
+ let original_span =
+ tcx.def_span(original_instance.def_id()).data();
+ let span = tcx.def_span(def_id).data();
+ if original_span < span {
+ throw_machine_stop!(
+ TerminationInfo::MultipleSymbolDefinitions {
+ link_name,
+ first: original_span,
+ first_crate: tcx.crate_name(original_cnum),
+ second: span,
+ second_crate: tcx.crate_name(cnum),
+ }
+ );
+ } else {
+ throw_machine_stop!(
+ TerminationInfo::MultipleSymbolDefinitions {
+ link_name,
+ first: span,
+ first_crate: tcx.crate_name(cnum),
+ second: original_span,
+ second_crate: tcx.crate_name(original_cnum),
+ }
+ );
+ }
+ }
+ if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
+ throw_ub_format!(
+ "attempt to call an exported symbol that is not defined as a function"
+ );
+ }
+ instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
+ }
+ }
+ }
+ }
+
+ e.insert(instance_and_crate.map(|ic| ic.0))
}
+ };
+ match instance {
+ None => Ok(None), // no symbol with this name
+ Some(instance) => Ok(Some((this.load_mir(instance.def, None)?, instance))),
}
}
/// by this function.
/// Returns Ok(Some(body)) if processing the foreign item
/// is delegated to another function.
- #[rustfmt::skip]
fn emulate_foreign_item(
&mut self,
def_id: DefId,
+ abi: Abi,
args: &[OpTy<'tcx, Tag>],
- ret: Option<(PlaceTy<'tcx, Tag>, mir::BasicBlock)>,
- unwind: Option<mir::BasicBlock>,
- ) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
+ dest: &PlaceTy<'tcx, Tag>,
+ ret: Option<mir::BasicBlock>,
+ unwind: StackPopUnwind,
+ ) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
let this = self.eval_context_mut();
- let attrs = this.tcx.get_attrs(def_id);
- let link_name = match this.tcx.sess.first_attr_value_str_by_name(&attrs, sym::link_name) {
- Some(name) => name.as_str(),
- None => this.tcx.item_name(def_id).as_str(),
- };
- // Strip linker suffixes (seen on 32-bit macOS).
- let link_name = link_name.trim_end_matches("$UNIX2003");
+ let link_name = this.item_link_name(def_id);
let tcx = this.tcx.tcx;
// First: functions that diverge.
- let (dest, ret) = match ret {
- None => match link_name {
- "miri_start_panic" => {
- this.handle_miri_start_panic(args, unwind)?;
- return Ok(None);
- }
- // This matches calls to the foreign item `panic_impl`.
- // The implementation is provided by the function with the `#[panic_handler]` attribute.
- "panic_impl" => {
- let panic_impl_id = tcx.lang_items().panic_impl().unwrap();
- let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id);
- return Ok(Some(&*this.load_mir(panic_impl_instance.def, None)?));
- }
- | "exit"
- | "ExitProcess"
- => {
- let &[code] = check_arg_count(args)?;
- // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway
- let code = this.read_scalar(code)?.to_i32()?;
- throw_machine_stop!(TerminationInfo::Exit(code.into()));
- }
- "abort" => {
- throw_machine_stop!(TerminationInfo::Abort("the program aborted execution".to_owned()))
- }
- _ => throw_unsup_format!("can't call (diverging) foreign function: {}", link_name),
- },
+ let ret = match ret {
+ None =>
+ match &*link_name.as_str() {
+ "miri_start_panic" => {
+ // `check_shim` happens inside `handle_miri_start_panic`.
+ this.handle_miri_start_panic(abi, link_name, args, unwind)?;
+ return Ok(None);
+ }
+ // This matches calls to the foreign item `panic_impl`.
+ // The implementation is provided by the function with the `#[panic_handler]` attribute.
+ "panic_impl" => {
+ // We don't use `check_shim` here because we are just forwarding to the lang
+ // item. Argument count checking will be performed when the returned `Body` is
+ // called.
+ this.check_abi_and_shim_symbol_clash(abi, Abi::Rust, link_name)?;
+ let panic_impl_id = tcx.lang_items().panic_impl().unwrap();
+ let panic_impl_instance = ty::Instance::mono(tcx, panic_impl_id);
+ return Ok(Some((
+ &*this.load_mir(panic_impl_instance.def, None)?,
+ panic_impl_instance,
+ )));
+ }
+ #[rustfmt::skip]
+ | "exit"
+ | "ExitProcess"
+ => {
+ let exp_abi = if link_name.as_str() == "exit" {
+ Abi::C { unwind: false }
+ } else {
+ Abi::System { unwind: false }
+ };
+ let [code] = this.check_shim(abi, exp_abi, link_name, args)?;
+ // it's really u32 for ExitProcess, but we have to put it into the `Exit` variant anyway
+ let code = this.read_scalar(code)?.to_i32()?;
+ throw_machine_stop!(TerminationInfo::Exit(code.into()));
+ }
+ "abort" => {
+ let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ throw_machine_stop!(TerminationInfo::Abort(
+ "the program aborted execution".to_owned()
+ ))
+ }
+ _ => {
+ if let Some(body) = this.lookup_exported_symbol(link_name)? {
+ return Ok(Some(body));
+ }
+ this.handle_unsupported(format!(
+ "can't call (diverging) foreign function: {}",
+ link_name
+ ))?;
+ return Ok(None);
+ }
+ },
Some(p) => p,
};
- // Second: some functions that we forward to MIR implementations.
- match link_name {
- // This matches calls to the foreign item `__rust_start_panic`, that is,
- // calls to `extern "Rust" { fn __rust_start_panic(...) }`
- // (and `__rust_panic_cleanup`, respectively).
- // We forward this to the underlying *implementation* in the panic runtime crate.
- // Normally, this will be either `libpanic_unwind` or `libpanic_abort`, but it could
- // also be a custom user-provided implementation via `#![feature(panic_runtime)]`
- "__rust_start_panic" | "__rust_panic_cleanup" => {
- // This replicates some of the logic in `inject_panic_runtime`.
- // FIXME: is there a way to reuse that logic?
- let panic_runtime = match this.tcx.sess.panic_strategy() {
- PanicStrategy::Unwind => sym::panic_unwind,
- PanicStrategy::Abort => sym::panic_abort,
- };
- let start_panic_instance =
- this.resolve_path(&[&*panic_runtime.as_str(), link_name]);
- return Ok(Some(&*this.load_mir(start_panic_instance.def, None)?));
+ // Second: functions that return.
+ match this.emulate_foreign_item_by_name(link_name, abi, args, dest, ret)? {
+ EmulateByNameResult::NeedsJumping => {
+ trace!("{:?}", this.dump_place(**dest));
+ this.go_to_block(ret);
}
- _ => {}
- }
+ EmulateByNameResult::AlreadyJumped => (),
+ EmulateByNameResult::MirBody(mir, instance) => return Ok(Some((mir, instance))),
+ EmulateByNameResult::NotSupported => {
+ if let Some(body) = this.lookup_exported_symbol(link_name)? {
+ return Ok(Some(body));
+ }
- // Third: functions that return.
- if this.emulate_foreign_item_by_name(link_name, args, dest, ret)? {
- trace!("{:?}", this.dump_place(*dest));
- this.go_to_block(ret);
+ this.handle_unsupported(format!("can't call foreign function: {}", link_name))?;
+ return Ok(None);
+ }
}
Ok(None)
}
- /// Emulates calling a foreign item using its name, failing if the item is not supported.
- /// Returns `true` if the caller is expected to jump to the return block, and `false` if
- /// jumping has already been taken care of.
+ /// Emulates calling the internal __rust_* allocator functions
+ fn emulate_allocator(
+ &mut self,
+ symbol: Symbol,
+ default: impl FnOnce(&mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx>,
+ ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
+ let this = self.eval_context_mut();
+
+ let allocator_kind = if let Some(allocator_kind) = this.tcx.allocator_kind(()) {
+ allocator_kind
+ } else {
+ // in real code, this symbol does not exist without an allocator
+ return Ok(EmulateByNameResult::NotSupported);
+ };
+
+ match allocator_kind {
+ AllocatorKind::Global => {
+ let (body, instance) = this
+ .lookup_exported_symbol(symbol)?
+ .expect("symbol should be present if there is a global allocator");
+
+ Ok(EmulateByNameResult::MirBody(body, instance))
+ }
+ AllocatorKind::Default => {
+ default(this)?;
+ Ok(EmulateByNameResult::NeedsJumping)
+ }
+ }
+ }
+
+ /// Emulates calling a foreign item using its name.
fn emulate_foreign_item_by_name(
&mut self,
- link_name: &str,
+ link_name: Symbol,
+ abi: Abi,
args: &[OpTy<'tcx, Tag>],
- dest: PlaceTy<'tcx, Tag>,
+ dest: &PlaceTy<'tcx, Tag>,
ret: mir::BasicBlock,
- ) -> InterpResult<'tcx, bool> {
+ ) -> InterpResult<'tcx, EmulateByNameResult<'mir, 'tcx>> {
let this = self.eval_context_mut();
// Here we dispatch all the shims for foreign functions. If you have a platform specific
// shim, add it to the corresponding submodule.
- match link_name {
+ match &*link_name.as_str() {
// Miri-specific extern functions
"miri_static_root" => {
- let &[ptr] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
- let ptr = this.force_ptr(ptr)?;
- if ptr.offset != Size::ZERO {
+ let [ptr] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
+ let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr)?;
+ if offset != Size::ZERO {
throw_unsup_format!("pointer passed to miri_static_root must point to beginning of an allocated block");
}
- this.machine.static_roots.push(ptr.alloc_id);
+ this.machine.static_roots.push(alloc_id);
+ }
+
+ // Obtains the size of a Miri backtrace. See the README for details.
+ "miri_backtrace_size" => {
+ this.handle_miri_backtrace_size(abi, link_name, args, dest)?;
}
// Obtains a Miri backtrace. See the README for details.
"miri_get_backtrace" => {
- this.handle_miri_get_backtrace(args, dest)?;
+ // `check_shim` happens inside `handle_miri_get_backtrace`.
+ this.handle_miri_get_backtrace(abi, link_name, args, dest)?;
}
// Resolves a Miri backtrace frame. See the README for details.
"miri_resolve_frame" => {
- this.handle_miri_resolve_frame(args, dest)?;
+ // `check_shim` happens inside `handle_miri_resolve_frame`.
+ this.handle_miri_resolve_frame(abi, link_name, args, dest)?;
}
+ // Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details.
+ "miri_resolve_frame_names" => {
+ this.handle_miri_resolve_frame_names(abi, link_name, args)?;
+ }
// Standard C allocation
"malloc" => {
- let &[size] = check_arg_count(args)?;
+ let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
- let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C);
- this.write_scalar(res, dest)?;
+ let res = this.malloc(size, /*zero_init:*/ false, MiriMemoryKind::C)?;
+ this.write_pointer(res, dest)?;
}
"calloc" => {
- let &[items, len] = check_arg_count(args)?;
+ let [items, len] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
let items = this.read_scalar(items)?.to_machine_usize(this)?;
let len = this.read_scalar(len)?.to_machine_usize(this)?;
let size =
items.checked_mul(len).ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?;
- let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C);
- this.write_scalar(res, dest)?;
+ let res = this.malloc(size, /*zero_init:*/ true, MiriMemoryKind::C)?;
+ this.write_pointer(res, dest)?;
}
"free" => {
- let &[ptr] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
+ let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
this.free(ptr, MiriMemoryKind::C)?;
}
"realloc" => {
- let &[old_ptr, new_size] = check_arg_count(args)?;
- let old_ptr = this.read_scalar(old_ptr)?.check_init()?;
+ let [old_ptr, new_size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let old_ptr = this.read_pointer(old_ptr)?;
let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
let res = this.realloc(old_ptr, new_size, MiriMemoryKind::C)?;
- this.write_scalar(res, dest)?;
+ this.write_pointer(res, dest)?;
}
// Rust allocation
- // (Usually these would be forwarded to to `#[global_allocator]`; we instead implement a generic
- // allocation that also checks that all conditions are met, such as not permitting zero-sized allocations.)
"__rust_alloc" => {
- let &[size, align] = check_arg_count(args)?;
+ let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
- Self::check_alloc_request(size, align)?;
- let ptr = this.memory.allocate(
- Size::from_bytes(size),
- Align::from_bytes(align).unwrap(),
- MiriMemoryKind::Rust.into(),
- );
- this.write_scalar(ptr, dest)?;
+
+ return this.emulate_allocator(Symbol::intern("__rg_alloc"), |this| {
+ Self::check_alloc_request(size, align)?;
+
+ let ptr = this.allocate_ptr(
+ Size::from_bytes(size),
+ Align::from_bytes(align).unwrap(),
+ MiriMemoryKind::Rust.into(),
+ )?;
+
+ this.write_pointer(ptr, dest)
+ });
}
"__rust_alloc_zeroed" => {
- let &[size, align] = check_arg_count(args)?;
+ let [size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
let size = this.read_scalar(size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
- Self::check_alloc_request(size, align)?;
- let ptr = this.memory.allocate(
- Size::from_bytes(size),
- Align::from_bytes(align).unwrap(),
- MiriMemoryKind::Rust.into(),
- );
- // We just allocated this, the access is definitely in-bounds.
- this.memory.write_bytes(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
- this.write_scalar(ptr, dest)?;
+
+ return this.emulate_allocator(Symbol::intern("__rg_alloc_zeroed"), |this| {
+ Self::check_alloc_request(size, align)?;
+
+ let ptr = this.allocate_ptr(
+ Size::from_bytes(size),
+ Align::from_bytes(align).unwrap(),
+ MiriMemoryKind::Rust.into(),
+ )?;
+
+ // We just allocated this, the access is definitely in-bounds.
+ this.write_bytes_ptr(ptr.into(), iter::repeat(0u8).take(usize::try_from(size).unwrap())).unwrap();
+ this.write_pointer(ptr, dest)
+ });
}
"__rust_dealloc" => {
- let &[ptr, old_size, align] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
+ let [ptr, old_size, align] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
- // No need to check old_size/align; we anyway check that they match the allocation.
- let ptr = this.force_ptr(ptr)?;
- this.memory.deallocate(
- ptr,
- Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
- MiriMemoryKind::Rust.into(),
- )?;
+
+ return this.emulate_allocator(Symbol::intern("__rg_dealloc"), |this| {
+ // No need to check old_size/align; we anyway check that they match the allocation.
+ this.deallocate_ptr(
+ ptr,
+ Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
+ MiriMemoryKind::Rust.into(),
+ )
+ });
}
"__rust_realloc" => {
- let &[ptr, old_size, align, new_size] = check_arg_count(args)?;
- let ptr = this.force_ptr(this.read_scalar(ptr)?.check_init()?)?;
+ let [ptr, old_size, align, new_size] = this.check_shim(abi, Abi::Rust, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
let old_size = this.read_scalar(old_size)?.to_machine_usize(this)?;
let align = this.read_scalar(align)?.to_machine_usize(this)?;
let new_size = this.read_scalar(new_size)?.to_machine_usize(this)?;
- Self::check_alloc_request(new_size, align)?;
// No need to check old_size; we anyway check that they match the allocation.
- let align = Align::from_bytes(align).unwrap();
- let new_ptr = this.memory.reallocate(
- ptr,
- Some((Size::from_bytes(old_size), align)),
- Size::from_bytes(new_size),
- align,
- MiriMemoryKind::Rust.into(),
- )?;
- this.write_scalar(new_ptr, dest)?;
+
+ return this.emulate_allocator(Symbol::intern("__rg_realloc"), |this| {
+ Self::check_alloc_request(new_size, align)?;
+
+ let align = Align::from_bytes(align).unwrap();
+ let new_ptr = this.reallocate_ptr(
+ ptr,
+ Some((Size::from_bytes(old_size), align)),
+ Size::from_bytes(new_size),
+ align,
+ MiriMemoryKind::Rust.into(),
+ )?;
+ this.write_pointer(new_ptr, dest)
+ });
}
// C memory handling functions
"memcmp" => {
- let &[left, right, n] = check_arg_count(args)?;
- let left = this.read_scalar(left)?.check_init()?;
- let right = this.read_scalar(right)?.check_init()?;
+ let [left, right, n] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let left = this.read_pointer(left)?;
+ let right = this.read_pointer(right)?;
let n = Size::from_bytes(this.read_scalar(n)?.to_machine_usize(this)?);
let result = {
- let left_bytes = this.memory.read_bytes(left, n)?;
- let right_bytes = this.memory.read_bytes(right, n)?;
+ let left_bytes = this.read_bytes_ptr(left, n)?;
+ let right_bytes = this.read_bytes_ptr(right, n)?;
use std::cmp::Ordering::*;
match left_bytes.cmp(right_bytes) {
this.write_scalar(Scalar::from_i32(result), dest)?;
}
"memrchr" => {
- let &[ptr, val, num] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
+ let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
let val = this.read_scalar(val)?.to_i32()? as u8;
let num = this.read_scalar(num)?.to_machine_usize(this)?;
if let Some(idx) = this
- .memory
- .read_bytes(ptr, Size::from_bytes(num))?
+ .read_bytes_ptr(ptr, Size::from_bytes(num))?
.iter()
.rev()
.position(|&c| c == val)
{
- let new_ptr = ptr.ptr_offset(Size::from_bytes(num - idx as u64 - 1), this)?;
- this.write_scalar(new_ptr, dest)?;
+ let new_ptr = ptr.offset(Size::from_bytes(num - idx as u64 - 1), this)?;
+ this.write_pointer(new_ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
"memchr" => {
- let &[ptr, val, num] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
+ let [ptr, val, num] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
let val = this.read_scalar(val)?.to_i32()? as u8;
let num = this.read_scalar(num)?.to_machine_usize(this)?;
let idx = this
- .memory
- .read_bytes(ptr, Size::from_bytes(num))?
+ .read_bytes_ptr(ptr, Size::from_bytes(num))?
.iter()
.position(|&c| c == val);
if let Some(idx) = idx {
- let new_ptr = ptr.ptr_offset(Size::from_bytes(idx as u64), this)?;
- this.write_scalar(new_ptr, dest)?;
+ let new_ptr = ptr.offset(Size::from_bytes(idx as u64), this)?;
+ this.write_pointer(new_ptr, dest)?;
} else {
this.write_null(dest)?;
}
}
"strlen" => {
- let &[ptr] = check_arg_count(args)?;
- let ptr = this.read_scalar(ptr)?.check_init()?;
- let n = this.memory.read_c_str(ptr)?.len();
+ let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+ let ptr = this.read_pointer(ptr)?;
+ let n = this.read_c_str(ptr)?.len();
this.write_scalar(Scalar::from_machine_usize(u64::try_from(n).unwrap(), this), dest)?;
}
// math functions
+ #[rustfmt::skip]
| "cbrtf"
| "coshf"
| "sinhf"
| "asinf"
| "atanf"
=> {
- let &[f] = check_arg_count(args)?;
+ let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f = f32::from_bits(this.read_scalar(f)?.to_u32()?);
- let f = match link_name {
+ let f = match &*link_name.as_str() {
"cbrtf" => f.cbrt(),
"coshf" => f.cosh(),
"sinhf" => f.sinh(),
};
this.write_scalar(Scalar::from_u32(f.to_bits()), dest)?;
}
+ #[rustfmt::skip]
| "_hypotf"
| "hypotf"
| "atan2f"
=> {
- let &[f1, f2] = check_arg_count(args)?;
+ let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// underscore case for windows, here and below
// (see https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/floating-point-primitives?view=vs-2019)
// FIXME: Using host floats.
let f1 = f32::from_bits(this.read_scalar(f1)?.to_u32()?);
let f2 = f32::from_bits(this.read_scalar(f2)?.to_u32()?);
- let n = match link_name {
+ let n = match &*link_name.as_str() {
"_hypotf" | "hypotf" => f1.hypot(f2),
"atan2f" => f1.atan2(f2),
_ => bug!(),
};
this.write_scalar(Scalar::from_u32(n.to_bits()), dest)?;
}
+ #[rustfmt::skip]
| "cbrt"
| "cosh"
| "sinh"
| "asin"
| "atan"
=> {
- let &[f] = check_arg_count(args)?;
+ let [f] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f = f64::from_bits(this.read_scalar(f)?.to_u64()?);
- let f = match link_name {
+ let f = match &*link_name.as_str() {
"cbrt" => f.cbrt(),
"cosh" => f.cosh(),
"sinh" => f.sinh(),
};
this.write_scalar(Scalar::from_u64(f.to_bits()), dest)?;
}
+ #[rustfmt::skip]
| "_hypot"
| "hypot"
| "atan2"
=> {
- let &[f1, f2] = check_arg_count(args)?;
+ let [f1, f2] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// FIXME: Using host floats.
let f1 = f64::from_bits(this.read_scalar(f1)?.to_u64()?);
let f2 = f64::from_bits(this.read_scalar(f2)?.to_u64()?);
- let n = match link_name {
+ let n = match &*link_name.as_str() {
"_hypot" | "hypot" => f1.hypot(f2),
"atan2" => f1.atan2(f2),
_ => bug!(),
};
this.write_scalar(Scalar::from_u64(n.to_bits()), dest)?;
}
+ #[rustfmt::skip]
| "_ldexp"
| "ldexp"
| "scalbn"
=> {
- let &[x, exp] = check_arg_count(args)?;
+ let [x, exp] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
// For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
let x = this.read_scalar(x)?.to_f64()?;
let exp = this.read_scalar(exp)?.to_i32()?;
}
// Architecture-specific shims
+ "llvm.x86.addcarry.64" if this.tcx.sess.target.arch == "x86_64" => {
+ // Computes u8+u64+u64, returning tuple (u8,u64) comprising the output carry and truncated sum.
+ let [c_in, a, b] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
+ let c_in = this.read_scalar(c_in)?.to_u8()?;
+ let a = this.read_scalar(a)?.to_u64()?;
+ let b = this.read_scalar(b)?.to_u64()?;
+
+ let wide_sum = u128::from(c_in) + u128::from(a) + u128::from(b);
+ let (c_out, sum) = ((wide_sum >> 64).truncate::<u8>(), wide_sum.truncate::<u64>());
+
+ let c_out_field = this.place_field(dest, 0)?;
+ this.write_scalar(Scalar::from_u8(c_out), &c_out_field)?;
+ let sum_field = this.place_field(dest, 1)?;
+ this.write_scalar(Scalar::from_u64(sum), &sum_field)?;
+ }
"llvm.x86.sse2.pause" if this.tcx.sess.target.arch == "x86" || this.tcx.sess.target.arch == "x86_64" => {
- let &[] = check_arg_count(args)?;
+ let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
this.yield_active_thread();
}
- "llvm.aarch64.hint" if this.tcx.sess.target.arch == "aarch64" => {
- let &[hint] = check_arg_count(args)?;
- let hint = this.read_scalar(hint)?.to_i32()?;
- match hint {
- 1 => { // HINT_YIELD
+ "llvm.aarch64.isb" if this.tcx.sess.target.arch == "aarch64" => {
+ let [arg] = this.check_shim(abi, Abi::Unadjusted, link_name, args)?;
+ let arg = this.read_scalar(arg)?.to_i32()?;
+ match arg {
+ 15 => { // SY ("full system scope")
this.yield_active_thread();
}
_ => {
- throw_unsup_format!("unsupported llvm.aarch64.hint argument {}", hint);
+ throw_unsup_format!("unsupported llvm.aarch64.isb argument {}", arg);
}
}
}
// Platform-specific shims
- _ => match this.tcx.sess.target.os.as_str() {
- "linux" | "macos" => return shims::posix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, args, dest, ret),
- "windows" => return shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, args, dest, ret),
+ _ => match this.tcx.sess.target.os.as_ref() {
+ "linux" | "macos" => return shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest, ret),
+ "windows" => return shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_by_name(this, link_name, abi, args, dest, ret),
target => throw_unsup_format!("the target `{}` is not supported", target),
}
};
- Ok(true)
+ // We only fall through to here if we did *not* hit the `_` arm above,
+ // i.e., if we actually emulated the function.
+ Ok(EmulateByNameResult::NeedsJumping)
}
/// Check some basic requirements for this allocation request: