blob: c781f2f49fba4b5e707f3b5e22ae0a00f262850d [file] [edit]
use rustc_middle::mir::BinOp;
use rustc_middle::{mir, span_bug, ty};
use rustc_span::{Symbol, sym};
use tracing::trace;
use super::{
AtomicRmwOp, Immediate, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Scalar, interp_ok,
};
impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
/// Returns `true` if emulation happened.
/// Here we implement the intrinsics that are common to all CTFE instances; individual machines can add their own
/// intrinsic handling.
pub fn eval_atomic_intrinsic(
&mut self,
intrinsic_name: Symbol,
generic_args: ty::GenericArgsRef<'tcx>,
args: &[OpTy<'tcx, M::Provenance>],
dest: &PlaceTy<'tcx, M::Provenance>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx, bool> {
let get_ord_at = |i: usize| {
let ordering = generic_args.const_at(i).to_value();
ordering.to_branch()[0].to_value().to_leaf().to_atomic_ordering()
};
match intrinsic_name {
sym::atomic_load => {
let ord = get_ord_at(1);
let _volatile = generic_args.const_at(2).to_value(); // makes no difference for us
let [ptr] = args else { span_bug!(self.cur_span(), "invalid `atomic_load` call") };
let place = self.deref_pointer(ptr)?;
let val = M::atomic_load(self, &place, ord)?;
self.write_scalar(val, dest)?;
}
sym::atomic_store => {
let ord = get_ord_at(1);
let _volatile = generic_args.const_at(2).to_value(); // makes no difference for us
let [ptr, val] = args else {
span_bug!(self.cur_span(), "invalid `atomic_store` call")
};
let place = self.deref_pointer(ptr)?;
let val = self.read_immediate(val)?;
M::atomic_store(self, &place, &val, ord)?;
}
sym::atomic_or
| sym::atomic_xor
| sym::atomic_and
| sym::atomic_nand
| sym::atomic_xadd
| sym::atomic_xsub
| sym::atomic_min
| sym::atomic_umin
| sym::atomic_max
| sym::atomic_umax
| sym::atomic_xchg => {
let num_ty_generics = match intrinsic_name {
sym::atomic_min
| sym::atomic_umin
| sym::atomic_max
| sym::atomic_umax
| sym::atomic_xchg => 1,
_ => 2,
};
let ord = get_ord_at(num_ty_generics);
let [ptr, operand] = args else {
span_bug!(self.cur_span(), "invalid `{intrinsic_name}` call")
};
let place = self.deref_pointer(ptr)?;
let operand = self.read_immediate(operand)?;
let op = match intrinsic_name {
sym::atomic_or => AtomicRmwOp::MirOp { op: BinOp::BitOr, neg: false },
sym::atomic_xor => AtomicRmwOp::MirOp { op: BinOp::BitXor, neg: false },
sym::atomic_and => AtomicRmwOp::MirOp { op: BinOp::BitAnd, neg: false },
sym::atomic_nand => AtomicRmwOp::MirOp { op: BinOp::BitAnd, neg: true },
sym::atomic_xadd => AtomicRmwOp::MirOp { op: BinOp::Add, neg: false },
sym::atomic_xsub => AtomicRmwOp::MirOp { op: BinOp::Sub, neg: false },
sym::atomic_min => AtomicRmwOp::Min,
sym::atomic_umin => AtomicRmwOp::Min,
sym::atomic_max => AtomicRmwOp::Max,
sym::atomic_umax => AtomicRmwOp::Max,
sym::atomic_xchg => AtomicRmwOp::Swap,
_ => unreachable!(),
};
let res = M::atomic_rmw(self, &place, op, &operand, ord)?;
self.write_scalar(res, dest)?;
}
sym::atomic_cxchg | sym::atomic_cxchgweak => {
let success_ord = get_ord_at(1);
let failure_ord = get_ord_at(2);
let [ptr, expected_old, new] = args else {
span_bug!(self.cur_span(), "invalid `{intrinsic_name}` call")
};
let place = self.deref_pointer(ptr)?;
let expected_old = self.read_immediate(expected_old)?;
let new = self.read_immediate(new)?;
let (actual_old, success) = M::atomic_compare_exchange(
self,
&place,
&expected_old,
&new,
/* can_fail_spuriously */ intrinsic_name == sym::atomic_cxchgweak,
success_ord,
failure_ord,
)?;
let res = Immediate::ScalarPair(actual_old, Scalar::from_bool(success));
self.write_immediate(res, dest)?;
}
sym::atomic_fence | sym::atomic_singlethreadfence => {
let ord = get_ord_at(0);
let [] = args else {
span_bug!(self.cur_span(), "invalid `{intrinsic_name}` call")
};
M::atomic_fence(self, ord, intrinsic_name == sym::atomic_singlethreadfence)?;
}
// Unsupported intrinsic: skip the return_to_block below.
_ => return interp_ok(false),
}
trace!("{:?}", self.dump_place(&dest.clone().into()));
self.return_to_block(ret)?;
interp_ok(true)
}
}