diff --git a/Cargo.lock b/Cargo.lock index c8c1b881c3f..f8806794979 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -600,6 +600,7 @@ dependencies = [ "limit", "nohash-hasher", "once_cell", + "oorandom", "profile", "project-model", "rustc-hash", diff --git a/crates/hir-ty/Cargo.toml b/crates/hir-ty/Cargo.toml index f462ca3dff5..abc19d63abf 100644 --- a/crates/hir-ty/Cargo.toml +++ b/crates/hir-ty/Cargo.toml @@ -19,6 +19,7 @@ bitflags = "2.1.0" smallvec.workspace = true ena = "0.14.0" either = "1.7.0" +oorandom = "11.1.3" tracing = "0.1.35" rustc-hash = "1.1.0" scoped-tls = "1.0.0" diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 35ecb898245..8e21272e14a 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -2494,6 +2494,28 @@ fn exec_limits() { ); } +#[test] +fn memory_limit() { + check_fail( + r#" + extern "Rust" { + #[rustc_allocator] + fn __rust_alloc(size: usize, align: usize) -> *mut u8; + } + + const GOAL: u8 = unsafe { + __rust_alloc(30_000_000_000, 1); // 30GB + 2 + }; + "#, + |e| { + e == ConstEvalError::MirEvalError(MirEvalError::Panic( + "Memory allocation of 30000000000 bytes failed".to_string(), + )) + }, + ); +} + #[test] fn type_error() { check_fail( diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs index 89f3a6d4905..cb8f1b55575 100644 --- a/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -602,3 +602,41 @@ fn rotate() { 320192512, ); } + +#[test] +fn simd() { + check_number( + r#" + pub struct i8x16( + i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8, + ); + extern "platform-intrinsic" { + pub fn simd_bitmask(x: T) -> U; + } + const GOAL: u16 = simd_bitmask(i8x16( + 0, 1, 0, 0, 2, 255, 100, 0, 50, 0, 1, 1, 0, 0, 0, 0 + )); + "#, + 0b0000110101110010, + ); + check_number( + r#" + pub struct i8x16( + i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8, + ); + extern "platform-intrinsic" { + pub fn simd_lt(x: T, y: T) -> U; + pub fn simd_bitmask(x: T) -> U; + } + const GOAL: u16 = simd_bitmask(simd_lt::( + i8x16( + -105, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + ), + i8x16( + -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + ), + )); + "#, + 0xFFFF, + ); +} diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 010ebcbd068..a1bab097951 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -137,6 +137,7 @@ pub struct Evaluator<'a> { /// time of use. vtable_map: VTableMap, thread_local_storage: TlsData, + random_state: oorandom::Rand64, stdout: Vec, stderr: Vec, layout_cache: RefCell>>, @@ -147,6 +148,8 @@ pub struct Evaluator<'a> { execution_limit: usize, /// An additional limit on stack depth, to prevent stack overflow stack_depth_limit: usize, + /// Maximum count of bytes that heap and stack can grow + memory_limit: usize, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -520,6 +523,7 @@ impl Evaluator<'_> { thread_local_storage: TlsData::default(), static_locations: HashMap::default(), db, + random_state: oorandom::Rand64::new(0), trait_env, crate_id, stdout: vec![], @@ -527,6 +531,7 @@ impl Evaluator<'_> { assert_placeholder_ty_is_unused, stack_depth_limit: 100, execution_limit: 1000_000, + memory_limit: 1000_000_000, // 2GB, 1GB for stack and 1GB for heap layout_cache: RefCell::new(HashMap::default()), } } @@ -938,6 +943,11 @@ impl Evaluator<'_> { }; locals.ptr = locals_ptr; let prev_stack_pointer = self.stack.len(); + if stack_size > self.memory_limit { + return Err(MirEvalError::Panic(format!( + "Stack overflow. Tried to grow stack to {stack_size} bytes" + ))); + } self.stack.extend(iter::repeat(0).take(stack_size)); Ok((locals, prev_stack_pointer)) } @@ -1180,7 +1190,7 @@ impl Evaluator<'_> { let Some((size, align)) = self.size_align_of(ty, locals)? else { not_supported!("unsized box initialization"); }; - let addr = self.heap_allocate(size, align); + let addr = self.heap_allocate(size, align)?; Owned(addr.to_bytes()) } Rvalue::CopyForDeref(_) => not_supported!("copy for deref"), @@ -1565,7 +1575,7 @@ impl Evaluator<'_> { ConstScalar::Bytes(v, memory_map) => { let mut v: Cow<'_, [u8]> = Cow::Borrowed(v); let patch_map = memory_map.transform_addresses(|b, align| { - let addr = self.heap_allocate(b.len(), align); + let addr = self.heap_allocate(b.len(), align)?; self.write_memory(addr, b)?; Ok(addr.to_usize()) })?; @@ -1580,7 +1590,7 @@ impl Evaluator<'_> { return Err(MirEvalError::InvalidConst(konst.clone())); } } - let addr = self.heap_allocate(size, align); + let addr = self.heap_allocate(size, align)?; self.write_memory(addr, &v)?; self.patch_addresses(&patch_map, &memory_map.vtable, addr, ty, locals)?; Interval::new(addr, size) @@ -1683,13 +1693,19 @@ impl Evaluator<'_> { } } - fn heap_allocate(&mut self, size: usize, align: usize) -> Address { + fn heap_allocate(&mut self, size: usize, align: usize) -> Result
{ + if !align.is_power_of_two() || align > 10000 { + return Err(MirEvalError::UndefinedBehavior(format!("Alignment {align} is invalid"))); + } while self.heap.len() % align != 0 { self.heap.push(0); } + if size.checked_add(self.heap.len()).map_or(true, |x| x > self.memory_limit) { + return Err(MirEvalError::Panic(format!("Memory allocation of {size} bytes failed"))); + } let pos = self.heap.len(); self.heap.extend(iter::repeat(0).take(size)); - Address::Heap(pos) + Ok(Address::Heap(pos)) } fn detect_fn_trait(&self, def: FunctionId) -> Option { @@ -2200,7 +2216,7 @@ impl Evaluator<'_> { )?; // FIXME: there is some leak here let size = layout.size.bytes_usize(); - let addr = self.heap_allocate(size, layout.align.abi.bytes() as usize); + let addr = self.heap_allocate(size, layout.align.abi.bytes() as usize)?; self.write_memory(addr, &result)?; IntervalAndTy { interval: Interval { addr, size }, ty } }; @@ -2235,10 +2251,10 @@ impl Evaluator<'_> { let Some((size, align)) = self.size_align_of(&ty, locals)? else { not_supported!("unsized extern static"); }; - let addr = self.heap_allocate(size, align); + let addr = self.heap_allocate(size, align)?; Interval::new(addr, size) }; - let addr = self.heap_allocate(self.ptr_size(), self.ptr_size()); + let addr = self.heap_allocate(self.ptr_size(), self.ptr_size())?; self.write_memory(addr, &result.addr.to_bytes())?; self.static_locations.insert(st, addr); Ok(addr) @@ -2398,11 +2414,11 @@ pub fn render_const_using_debug_impl( not_supported!("core::fmt::Debug::fmt not found"); }; // a1 = &[""] - let a1 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size()); + let a1 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?; // a2 = &[::core::fmt::ArgumentV1::new(&(THE_CONST), ::core::fmt::Debug::fmt)] // FIXME: we should call the said function, but since its name is going to break in the next rustc version // and its ABI doesn't break yet, we put it in memory manually. - let a2 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size()); + let a2 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?; evaluator.write_memory(a2, &data.addr.to_bytes())?; let debug_fmt_fn_ptr = evaluator.vtable_map.id(TyKind::FnDef( db.intern_callable_def(debug_fmt_fn.into()).into(), @@ -2412,7 +2428,7 @@ pub fn render_const_using_debug_impl( evaluator.write_memory(a2.offset(evaluator.ptr_size()), &debug_fmt_fn_ptr.to_le_bytes())?; // a3 = ::core::fmt::Arguments::new_v1(a1, a2) // FIXME: similarly, we should call function here, not directly working with memory. - let a3 = evaluator.heap_allocate(evaluator.ptr_size() * 6, evaluator.ptr_size()); + let a3 = evaluator.heap_allocate(evaluator.ptr_size() * 6, evaluator.ptr_size())?; evaluator.write_memory(a3.offset(2 * evaluator.ptr_size()), &a1.to_bytes())?; evaluator.write_memory(a3.offset(3 * evaluator.ptr_size()), &[1])?; evaluator.write_memory(a3.offset(4 * evaluator.ptr_size()), &a2.to_bytes())?; diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs index 8f22bb36568..3b8b7201223 100644 --- a/crates/hir-ty/src/mir/eval/shim.rs +++ b/crates/hir-ty/src/mir/eval/shim.rs @@ -140,7 +140,7 @@ impl Evaluator<'_> { }; let size = from_bytes!(usize, size.get(self)?); let align = from_bytes!(usize, align.get(self)?); - let result = self.heap_allocate(size, align); + let result = self.heap_allocate(size, align)?; destination.write_from_bytes(self, &result.to_bytes())?; } "rustc_deallocator" => { /* no-op for now */ } @@ -155,7 +155,7 @@ impl Evaluator<'_> { } else { let ptr = Address::from_bytes(ptr.get(self)?)?; let align = from_bytes!(usize, align.get(self)?); - let result = self.heap_allocate(new_size, align); + let result = self.heap_allocate(new_size, align)?; Interval { addr: result, size: old_size } .write_from_interval(self, Interval { addr: ptr, size: old_size })?; destination.write_from_bytes(self, &result.to_bytes())?; @@ -239,6 +239,34 @@ impl Evaluator<'_> { } } + fn exec_syscall( + &mut self, + id: i64, + args: &[IntervalAndTy], + destination: Interval, + _locals: &Locals, + _span: MirSpan, + ) -> Result<()> { + match id { + 318 => { + // SYS_getrandom + let [buf, len, _flags] = args else { + return Err(MirEvalError::TypeError("SYS_getrandom args are not provided")); + }; + let addr = Address::from_bytes(buf.get(self)?)?; + let size = from_bytes!(usize, len.get(self)?); + for i in 0..size { + let rand_byte = self.random_state.rand_u64() as u8; + self.write_memory(addr.offset(i), &[rand_byte])?; + } + destination.write_from_interval(self, len.interval) + } + _ => { + not_supported!("Unknown syscall id {id:?}") + } + } + } + fn exec_extern_c( &mut self, as_str: &str, @@ -246,7 +274,7 @@ impl Evaluator<'_> { _generic_args: &Substitution, destination: Interval, locals: &Locals, - _span: MirSpan, + span: MirSpan, ) -> Result<()> { match as_str { "memcmp" => { @@ -343,6 +371,15 @@ impl Evaluator<'_> { destination.write_from_bytes(self, &0u64.to_le_bytes()[0..destination.size])?; Ok(()) } + "syscall" => { + let Some((id, rest)) = args.split_first() else { + return Err(MirEvalError::TypeError( + "syscall arg1 is not provided", + )); + }; + let id = from_bytes!(i64, id.get(self)?); + self.exec_syscall(id, rest, destination, locals, span) + } _ => not_supported!("unknown external function {as_str}"), } } diff --git a/crates/hir-ty/src/mir/eval/shim/simd.rs b/crates/hir-ty/src/mir/eval/shim/simd.rs index 64fd1301829..bc9529d38f4 100644 --- a/crates/hir-ty/src/mir/eval/shim/simd.rs +++ b/crates/hir-ty/src/mir/eval/shim/simd.rs @@ -22,7 +22,7 @@ macro_rules! not_supported { } impl Evaluator<'_> { - fn detect_simd_ty(&self, ty: &Ty) -> Result { + fn detect_simd_ty(&self, ty: &Ty) -> Result<(usize, Ty)> { match ty.kind(Interner) { TyKind::Adt(id, subst) => { let len = match subst.as_slice(Interner).get(1).and_then(|it| it.constant(Interner)) @@ -30,13 +30,21 @@ impl Evaluator<'_> { Some(len) => len, _ => { if let AdtId::StructId(id) = id.0 { - return Ok(self.db.struct_data(id).variant_data.fields().len()); + let struct_data = self.db.struct_data(id); + let fields = struct_data.variant_data.fields(); + let Some((first_field, _)) = fields.iter().next() else { + not_supported!("simd type with no field"); + }; + let field_ty = self.db.field_types(id.into())[first_field] + .clone() + .substitute(Interner, subst); + return Ok((fields.len(), field_ty)); } return Err(MirEvalError::TypeError("simd type with no len param")); } }; match try_const_usize(self.db, len) { - Some(it) => Ok(it as usize), + Some(_) => not_supported!("array like simd type"), None => Err(MirEvalError::TypeError("simd type with unevaluatable len param")), } } @@ -75,7 +83,8 @@ impl Evaluator<'_> { let [left, right] = args else { return Err(MirEvalError::TypeError("simd args are not provided")); }; - let len = self.detect_simd_ty(&left.ty)?; + let (len, ty) = self.detect_simd_ty(&left.ty)?; + let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_))); let size = left.interval.size / len; let dest_size = destination.size / len; let mut destination_bytes = vec![]; @@ -89,6 +98,13 @@ impl Evaluator<'_> { break; } } + if is_signed { + if let Some((&l, &r)) = l.iter().zip(r).rev().next() { + if l != r { + result = (l as i8).cmp(&(r as i8)); + } + } + } let result = match result { Ordering::Less => ["lt", "le", "ne"].contains(&name), Ordering::Equal => ["ge", "le", "eq"].contains(&name), @@ -102,9 +118,9 @@ impl Evaluator<'_> { } "bitmask" => { let [op] = args else { - return Err(MirEvalError::TypeError("simd_shuffle args are not provided")); + return Err(MirEvalError::TypeError("simd_bitmask args are not provided")); }; - let op_len = self.detect_simd_ty(&op.ty)?; + 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() { @@ -131,7 +147,7 @@ impl Evaluator<'_> { )) } }; - let left_len = self.detect_simd_ty(&left.ty)?; + let (left_len, _) = self.detect_simd_ty(&left.ty)?; let left_size = left.interval.size / left_len; let vector = left.get(self)?.chunks(left_size).chain(right.get(self)?.chunks(left_size)); diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs index 1129eaa5499..03c083bac42 100644 --- a/crates/hir-ty/src/mir/eval/tests.rs +++ b/crates/hir-ty/src/mir/eval/tests.rs @@ -613,6 +613,34 @@ fn main() { ); } +#[test] +fn syscalls() { + check_pass( + r#" +//- minicore: option + +extern "C" { + pub unsafe extern "C" fn syscall(num: i64, ...) -> i64; +} + +const SYS_getrandom: i64 = 318; + +fn should_not_reach() { + _ // FIXME: replace this function with panic when that works +} + +fn main() { + let mut x: i32 = 0; + let r = syscall(SYS_getrandom, &mut x, 4usize, 0); + if r != 4 { + should_not_reach(); + } +} + +"#, + ) +} + #[test] fn posix_tls() { check_pass(