Support more intrinsics in mir interpreter

This commit is contained in:
hkalbasi 2023-06-23 21:08:05 +03:30
parent 403433a355
commit 5eb4796d3d
6 changed files with 493 additions and 60 deletions

View File

@ -14,6 +14,66 @@ fn size_of() {
);
}
#[test]
fn size_of_val() {
check_number(
r#"
//- minicore: coerce_unsized
extern "rust-intrinsic" {
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
}
struct X(i32, u8);
const GOAL: usize = size_of_val(&X(1, 2));
"#,
8,
);
check_number(
r#"
//- minicore: coerce_unsized
extern "rust-intrinsic" {
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
}
const GOAL: usize = {
let x: &[i32] = &[1, 2, 3];
size_of_val(x)
};
"#,
12,
);
check_number(
r#"
//- minicore: coerce_unsized, fmt, builtin_impls
extern "rust-intrinsic" {
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
}
const GOAL: usize = {
let x: &i16 = &5;
let y: &dyn core::fmt::Debug = x;
let z: &dyn core::fmt::Debug = &y;
size_of_val(x) + size_of_val(y) * 10 + size_of_val(z) * 100
};
"#,
1622,
);
check_number(
r#"
//- minicore: coerce_unsized
extern "rust-intrinsic" {
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
}
const GOAL: usize = {
size_of_val("salam")
};
"#,
5,
);
}
#[test]
fn transmute() {
check_number(
@ -69,7 +129,7 @@ fn wrapping_add() {
}
#[test]
fn saturating_add() {
fn saturating() {
check_number(
r#"
extern "rust-intrinsic" {
@ -80,6 +140,16 @@ fn saturating_add() {
"#,
255,
);
check_number(
r#"
extern "rust-intrinsic" {
pub fn saturating_sub<T>(a: T, b: T) -> T;
}
const GOAL: bool = saturating_sub(5u8, 7) == 0 && saturating_sub(8u8, 4) == 4;
"#,
1,
);
check_number(
r#"
extern "rust-intrinsic" {
@ -160,6 +230,24 @@ fn needs_drop() {
);
}
#[test]
fn discriminant_value() {
check_number(
r#"
//- minicore: discriminant, option
use core::marker::DiscriminantKind;
extern "rust-intrinsic" {
pub fn discriminant_value<T>(v: &T) -> <T as DiscriminantKind>::Discriminant;
}
const GOAL: bool = {
discriminant_value(&Some(2i32)) == discriminant_value(&Some(5i32))
&& discriminant_value(&Some(2i32)) != discriminant_value(&None::<i32>)
};
"#,
1,
);
}
#[test]
fn likely() {
check_number(
@ -376,3 +464,47 @@ fn cttz() {
3,
);
}
#[test]
fn rotate() {
check_number(
r#"
extern "rust-intrinsic" {
pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
}
const GOAL: i64 = rotate_left(0xaa00000000006e1i64, 12);
"#,
0x6e10aa,
);
check_number(
r#"
extern "rust-intrinsic" {
pub fn rotate_right<T: Copy>(x: T, y: T) -> T;
}
const GOAL: i64 = rotate_right(0x6e10aa, 12);
"#,
0xaa00000000006e1,
);
check_number(
r#"
extern "rust-intrinsic" {
pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
}
const GOAL: i8 = rotate_left(129, 2);
"#,
6,
);
check_number(
r#"
extern "rust-intrinsic" {
pub fn rotate_right<T: Copy>(x: T, y: T) -> T;
}
const GOAL: i32 = rotate_right(10006016, 1020315);
"#,
320192512,
);
}

View File

@ -1049,57 +1049,8 @@ fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals<'_>) -> Result<Interva
Rvalue::Discriminant(p) => {
let ty = self.place_ty(p, locals)?;
let bytes = self.eval_place(p, locals)?.get(&self)?;
let layout = self.layout(&ty)?;
let enum_id = 'b: {
match ty.kind(Interner) {
TyKind::Adt(e, _) => match e.0 {
AdtId::EnumId(e) => break 'b e,
_ => (),
},
_ => (),
}
return Ok(Owned(0u128.to_le_bytes().to_vec()));
};
match &layout.variants {
Variants::Single { index } => {
let r = self.const_eval_discriminant(EnumVariantId {
parent: enum_id,
local_id: index.0,
})?;
Owned(r.to_le_bytes().to_vec())
}
Variants::Multiple { tag, tag_encoding, variants, .. } => {
let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else {
not_supported!("missing target data layout");
};
let size = tag.size(&*target_data_layout).bytes_usize();
let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field
match tag_encoding {
TagEncoding::Direct => {
let tag = &bytes[offset..offset + size];
Owned(pad16(tag, false).to_vec())
}
TagEncoding::Niche { untagged_variant, niche_start, .. } => {
let tag = &bytes[offset..offset + size];
let candidate_tag = i128::from_le_bytes(pad16(tag, false))
.wrapping_sub(*niche_start as i128)
as usize;
let variant = variants
.iter_enumerated()
.map(|(x, _)| x)
.filter(|x| x != untagged_variant)
.nth(candidate_tag)
.unwrap_or(*untagged_variant)
.0;
let result = self.const_eval_discriminant(EnumVariantId {
parent: enum_id,
local_id: variant,
})?;
Owned(result.to_le_bytes().to_vec())
}
}
}
}
let result = self.compute_discriminant(ty, bytes)?;
Owned(result.to_le_bytes().to_vec())
}
Rvalue::Repeat(x, len) => {
let len = match try_const_usize(self.db, &len) {
@ -1229,6 +1180,60 @@ fn eval_rvalue(&mut self, r: &Rvalue, locals: &mut Locals<'_>) -> Result<Interva
})
}
fn compute_discriminant(&self, ty: Ty, bytes: &[u8]) -> Result<i128> {
let layout = self.layout(&ty)?;
let enum_id = 'b: {
match ty.kind(Interner) {
TyKind::Adt(e, _) => match e.0 {
AdtId::EnumId(e) => break 'b e,
_ => (),
},
_ => (),
}
return Ok(0);
};
match &layout.variants {
Variants::Single { index } => {
let r = self.const_eval_discriminant(EnumVariantId {
parent: enum_id,
local_id: index.0,
})?;
Ok(r)
}
Variants::Multiple { tag, tag_encoding, variants, .. } => {
let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else {
not_supported!("missing target data layout");
};
let size = tag.size(&*target_data_layout).bytes_usize();
let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field
match tag_encoding {
TagEncoding::Direct => {
let tag = &bytes[offset..offset + size];
Ok(i128::from_le_bytes(pad16(tag, false)))
}
TagEncoding::Niche { untagged_variant, niche_start, .. } => {
let tag = &bytes[offset..offset + size];
let candidate_tag = i128::from_le_bytes(pad16(tag, false))
.wrapping_sub(*niche_start as i128)
as usize;
let variant = variants
.iter_enumerated()
.map(|(x, _)| x)
.filter(|x| x != untagged_variant)
.nth(candidate_tag)
.unwrap_or(*untagged_variant)
.0;
let result = self.const_eval_discriminant(EnumVariantId {
parent: enum_id,
local_id: variant,
})?;
Ok(result)
}
}
}
}
}
fn coerce_unsized_look_through_fields<T>(
&self,
ty: &Ty,

View File

@ -5,6 +5,8 @@
use super::*;
mod simd;
macro_rules! from_bytes {
($ty:tt, $value:expr) => {
($ty::from_le_bytes(match ($value).try_into() {
@ -53,6 +55,28 @@ pub(super) fn detect_and_exec_special_function(
)?;
return Ok(true);
}
let is_platform_intrinsic = match &function_data.abi {
Some(abi) => *abi == Interned::new_str("platform-intrinsic"),
None => match def.lookup(self.db.upcast()).container {
hir_def::ItemContainerId::ExternBlockId(block) => {
let id = block.lookup(self.db.upcast()).id;
id.item_tree(self.db.upcast())[id.value].abi.as_deref()
== Some("platform-intrinsic")
}
_ => false,
},
};
if is_platform_intrinsic {
self.exec_platform_intrinsic(
function_data.name.as_text().unwrap_or_default().as_str(),
args,
generic_args,
destination,
&locals,
span,
)?;
return Ok(true);
}
let is_extern_c = match def.lookup(self.db.upcast()).container {
hir_def::ItemContainerId::ExternBlockId(block) => {
let id = block.lookup(self.db.upcast()).id;
@ -330,6 +354,21 @@ fn exec_extern_c(
}
}
fn exec_platform_intrinsic(
&mut self,
name: &str,
args: &[IntervalAndTy],
generic_args: &Substitution,
destination: Interval,
locals: &Locals<'_>,
span: MirSpan,
) -> Result<()> {
if let Some(name) = name.strip_prefix("simd_") {
return self.exec_simd_intrinsic(name, args, generic_args, destination, locals, span);
}
not_supported!("unknown platform intrinsic {name}");
}
fn exec_intrinsic(
&mut self,
name: &str,
@ -478,6 +517,33 @@ fn exec_intrinsic(
let size = self.size_of_sized(ty, locals, "size_of arg")?;
destination.write_from_bytes(self, &size.to_le_bytes()[0..destination.size])
}
"size_of_val" => {
let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
return Err(MirEvalError::TypeError("size_of_val generic arg is not provided"));
};
let [arg] = args else {
return Err(MirEvalError::TypeError("size_of_val args are not provided"));
};
let metadata = arg.interval.slice(self.ptr_size()..self.ptr_size() * 2);
let size = match ty.kind(Interner) {
TyKind::Str => return destination.write_from_interval(self, metadata),
TyKind::Slice(inner) => {
let len = from_bytes!(usize, metadata.get(self)?);
len * self.size_of_sized(inner, locals, "slice inner type")?
}
TyKind::Dyn(_) => self.size_of_sized(
self.vtable_map.ty_of_bytes(metadata.get(self)?)?,
locals,
"dyn concrete type",
)?,
_ => self.size_of_sized(
ty,
locals,
"unsized type other than str, slice, and dyn",
)?,
};
destination.write_from_bytes(self, &size.to_le_bytes())
}
"min_align_of" | "pref_align_of" => {
let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
return Err(MirEvalError::TypeError("align_of generic arg is not provided"));
@ -501,13 +567,17 @@ fn exec_intrinsic(
let ans = lhs.get(self)? == rhs.get(self)?;
destination.write_from_bytes(self, &[u8::from(ans)])
}
"saturating_add" => {
"saturating_add" | "saturating_sub" => {
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("saturating_add args are not provided"));
};
let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
let ans = lhs.saturating_add(rhs);
let ans = match name {
"saturating_add" => lhs.saturating_add(rhs),
"saturating_sub" => lhs.saturating_sub(rhs),
_ => unreachable!(),
};
let bits = destination.size * 8;
// FIXME: signed
let is_signed = false;
@ -544,6 +614,26 @@ fn exec_intrinsic(
let ans = lhs.wrapping_mul(rhs);
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
"wrapping_shl" | "unchecked_shl" => {
// FIXME: signed
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("unchecked_shl args are not provided"));
};
let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
let ans = lhs.wrapping_shl(rhs as u32);
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
"wrapping_shr" | "unchecked_shr" => {
// FIXME: signed
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("unchecked_shr args are not provided"));
};
let lhs = u128::from_le_bytes(pad16(lhs.get(self)?, false));
let rhs = u128::from_le_bytes(pad16(rhs.get(self)?, false));
let ans = lhs.wrapping_shr(rhs as u32);
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
"unchecked_rem" => {
// FIXME: signed
let [lhs, rhs] = args else {
@ -666,6 +756,79 @@ fn exec_intrinsic(
destination
.write_from_bytes(self, &(result as u128).to_le_bytes()[0..destination.size])
}
"rotate_left" => {
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("rotate_left args are not provided"));
};
let lhs = &lhs.get(self)?[0..destination.size];
let rhs = rhs.get(self)?[0] as u32;
match destination.size {
1 => {
let r = from_bytes!(u8, lhs).rotate_left(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
2 => {
let r = from_bytes!(u16, lhs).rotate_left(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
4 => {
let r = from_bytes!(u32, lhs).rotate_left(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
8 => {
let r = from_bytes!(u64, lhs).rotate_left(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
16 => {
let r = from_bytes!(u128, lhs).rotate_left(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
s => not_supported!("destination with size {s} for rotate_left"),
}
}
"rotate_right" => {
let [lhs, rhs] = args else {
return Err(MirEvalError::TypeError("rotate_right args are not provided"));
};
let lhs = &lhs.get(self)?[0..destination.size];
let rhs = rhs.get(self)?[0] as u32;
match destination.size {
1 => {
let r = from_bytes!(u8, lhs).rotate_right(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
2 => {
let r = from_bytes!(u16, lhs).rotate_right(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
4 => {
let r = from_bytes!(u32, lhs).rotate_right(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
8 => {
let r = from_bytes!(u64, lhs).rotate_right(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
16 => {
let r = from_bytes!(u128, lhs).rotate_right(rhs);
destination.write_from_bytes(self, &r.to_le_bytes())
}
s => not_supported!("destination with size {s} for rotate_right"),
}
}
"discriminant_value" => {
let [arg] = args else {
return Err(MirEvalError::TypeError("discriminant_value arg is not provided"));
};
let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else {
return Err(MirEvalError::TypeError("discriminant_value generic arg is not provided"));
};
let addr = Address::from_bytes(arg.get(self)?)?;
let size = self.size_of_sized(ty, locals, "discriminant_value ptr type")?;
let interval = Interval { addr, size };
let r = self.compute_discriminant(ty.clone(), interval.get(self)?)?;
destination.write_from_bytes(self, &r.to_le_bytes()[0..destination.size])
}
"const_eval_select" => {
let [tuple, const_fn, _] = args else {
return Err(MirEvalError::TypeError("const_eval_select args are not provided"));

View File

@ -0,0 +1,124 @@
//! Shim implementation for simd intrinsics
use crate::TyKind;
use super::*;
macro_rules! from_bytes {
($ty:tt, $value:expr) => {
($ty::from_le_bytes(match ($value).try_into() {
Ok(x) => x,
Err(_) => return Err(MirEvalError::TypeError("mismatched size")),
}))
};
}
macro_rules! not_supported {
($x: expr) => {
return Err(MirEvalError::NotSupported(format!($x)))
};
}
impl Evaluator<'_> {
fn detect_simd_ty(&self, ty: &Ty) -> Result<usize> {
match ty.kind(Interner) {
TyKind::Adt(_, subst) => {
let Some(len) = subst.as_slice(Interner).get(1).and_then(|x| x.constant(Interner)) else {
return Err(MirEvalError::TypeError("simd type without len param"));
};
match try_const_usize(self.db, len) {
Some(x) => Ok(x as usize),
None => Err(MirEvalError::TypeError("simd type with unevaluatable len param")),
}
}
_ => Err(MirEvalError::TypeError("simd type which is not a struct")),
}
}
pub(super) fn exec_simd_intrinsic(
&mut self,
name: &str,
args: &[IntervalAndTy],
_generic_args: &Substitution,
destination: Interval,
_locals: &Locals<'_>,
_span: MirSpan,
) -> Result<()> {
match name {
"and" | "or" | "xor" => {
let [left, right] = args else {
return Err(MirEvalError::TypeError("simd bit op args are not provided"));
};
let result = left
.get(self)?
.iter()
.zip(right.get(self)?)
.map(|(&x, &y)| match name {
"and" => x & y,
"or" => x | y,
"xor" => x ^ y,
_ => unreachable!(),
})
.collect::<Vec<_>>();
destination.write_from_bytes(self, &result)
}
"eq" | "ne" => {
let [left, right] = args else {
return Err(MirEvalError::TypeError("simd_eq args are not provided"));
};
let result = left.get(self)? == right.get(self)?;
let result = result ^ (name == "ne");
destination.write_from_bytes(self, &[u8::from(result)])
}
"bitmask" => {
let [op] = args else {
return Err(MirEvalError::TypeError("simd_shuffle args are not provided"));
};
let op_len = self.detect_simd_ty(&op.ty)?;
let op_count = op.interval.size / op_len;
let mut result: u64 = 0;
for (i, val) in op.get(self)?.chunks(op_count).enumerate() {
if !val.iter().all(|&x| x == 0) {
result |= 1 << i;
}
}
destination.write_from_bytes(self, &result.to_le_bytes()[0..destination.size])
}
"shuffle" => {
let [left, right, index] = args else {
return Err(MirEvalError::TypeError("simd_shuffle args are not provided"));
};
let TyKind::Array(_, index_len) = index.ty.kind(Interner) else {
return Err(MirEvalError::TypeError("simd_shuffle index argument has non-array type"));
};
let index_len = match try_const_usize(self.db, index_len) {
Some(x) => x as usize,
None => {
return Err(MirEvalError::TypeError(
"simd type with unevaluatable len param",
))
}
};
let left_len = self.detect_simd_ty(&left.ty)?;
let left_count = left.interval.size / left_len;
let vector =
left.get(self)?.chunks(left_count).chain(right.get(self)?.chunks(left_count));
let mut result = vec![];
for index in index.get(self)?.chunks(index.interval.size / index_len) {
let index = from_bytes!(u32, index) as usize;
let val = match vector.clone().nth(index) {
Some(x) => x,
None => {
return Err(MirEvalError::TypeError(
"out of bound access in simd shuffle",
))
}
};
result.extend(val);
}
destination.write_from_bytes(self, &result)
}
_ => not_supported!("unknown simd intrinsic {name}"),
}
}
}

View File

@ -474,7 +474,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9287..9295,
range: 9288..9296,
},
),
tooltip: "",
@ -487,7 +487,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9319..9323,
range: 9320..9324,
},
),
tooltip: "",
@ -511,7 +511,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9287..9295,
range: 9288..9296,
},
),
tooltip: "",
@ -524,7 +524,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9319..9323,
range: 9320..9324,
},
),
tooltip: "",
@ -548,7 +548,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9287..9295,
range: 9288..9296,
},
),
tooltip: "",
@ -561,7 +561,7 @@ fn main() {
file_id: FileId(
1,
),
range: 9319..9323,
range: 9320..9324,
},
),
tooltip: "",

View File

@ -20,6 +20,7 @@
//! deref_mut: deref
//! deref: sized
//! derive:
//! discriminant:
//! drop:
//! eq: sized
//! error: fmt
@ -129,6 +130,14 @@ pub trait Tuple {}
#[lang = "phantom_data"]
pub struct PhantomData<T: ?Sized>;
// endregion:phantom_data
// region:discriminant
#[lang = "discriminant_kind"]
pub trait DiscriminantKind {
#[lang = "discriminant_type"]
type Discriminant;
}
// endregion:discriminant
}
// region:default