//@ run-pass //! Test that users are able to use stable mir APIs to retrieve information of global allocations //! such as `vtable_allocation`. //@ ignore-stage1 //@ ignore-cross-compile //@ ignore-remote //@ ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837 //@ edition: 2021 #![feature(rustc_private)] #![feature(assert_matches)] #![feature(ascii_char, ascii_char_variants)] extern crate rustc_hir; #[macro_use] extern crate rustc_smir; extern crate rustc_driver; extern crate rustc_interface; extern crate stable_mir; use rustc_smir::rustc_internal; use stable_mir::crate_def::CrateDef; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{Instance, InstanceKind, StaticDef}; use stable_mir::mir::{Body, TerminatorKind}; use stable_mir::ty::{Allocation, ConstantKind, RigidTy, TyKind}; use stable_mir::{CrateItem, CrateItems, ItemKind}; use std::ascii::Char; use std::assert_matches::assert_matches; use std::cmp::{max, min}; use std::collections::HashMap; use std::ffi::CStr; use std::io::Write; use std::ops::ControlFlow; const CRATE_NAME: &str = "input"; /// This function uses the Stable MIR APIs to get information about the test crate. fn test_stable_mir() -> ControlFlow<()> { // Find items in the local crate. let items = stable_mir::all_local_items(); check_foo(*get_item(&items, (ItemKind::Static, "FOO")).unwrap()); check_bar(*get_item(&items, (ItemKind::Static, "BAR")).unwrap()); check_len(*get_item(&items, (ItemKind::Static, "LEN")).unwrap()); check_cstr(*get_item(&items, (ItemKind::Static, "C_STR")).unwrap()); check_other_consts(*get_item(&items, (ItemKind::Fn, "other_consts")).unwrap()); check_type_id(*get_item(&items, (ItemKind::Fn, "check_type_id")).unwrap()); ControlFlow::Continue(()) } /// Check the allocation data for static `FOO`. /// /// ```no_run /// static FOO: [&str; 2] = ["hi", "there"]; /// ``` fn check_foo(item: CrateItem) { let def = StaticDef::try_from(item).unwrap(); let alloc = def.eval_initializer().unwrap(); assert_eq!(alloc.provenance.ptrs.len(), 2); let alloc_id_0 = alloc.provenance.ptrs[0].1.0; assert_matches!(GlobalAlloc::from(alloc_id_0), GlobalAlloc::Memory(..)); let alloc_id_1 = alloc.provenance.ptrs[1].1.0; assert_matches!(GlobalAlloc::from(alloc_id_1), GlobalAlloc::Memory(..)); } /// Check the allocation data for static `BAR`. /// /// ```no_run /// static BAR: &str = "Bar"; /// ``` fn check_bar(item: CrateItem) { let def = StaticDef::try_from(item).unwrap(); let alloc = def.eval_initializer().unwrap(); assert_eq!(alloc.provenance.ptrs.len(), 1); let alloc_id_0 = alloc.provenance.ptrs[0].1.0; let GlobalAlloc::Memory(allocation) = GlobalAlloc::from(alloc_id_0) else { unreachable!() }; assert_eq!(allocation.bytes.len(), 3); assert_eq!(allocation.bytes[0].unwrap(), Char::CapitalB.to_u8()); assert_eq!(allocation.bytes[1].unwrap(), Char::SmallA.to_u8()); assert_eq!(allocation.bytes[2].unwrap(), Char::SmallR.to_u8()); assert_eq!(std::str::from_utf8(&allocation.raw_bytes().unwrap()), Ok("Bar")); } /// Check the allocation data for static `C_STR`. /// /// ```no_run /// static C_STR: &core::ffi::cstr = c"cstr"; /// ``` fn check_cstr(item: CrateItem) { let def = StaticDef::try_from(item).unwrap(); let alloc = def.eval_initializer().unwrap(); assert_eq!(alloc.provenance.ptrs.len(), 1); let deref = item.ty().kind().builtin_deref(true).unwrap(); assert!(deref.ty.kind().is_cstr(), "Expected CStr, but got: {:?}", item.ty()); let alloc_id_0 = alloc.provenance.ptrs[0].1.0; let GlobalAlloc::Memory(allocation) = GlobalAlloc::from(alloc_id_0) else { unreachable!() }; assert_eq!(allocation.bytes.len(), 5); assert_eq!(CStr::from_bytes_until_nul(&allocation.raw_bytes().unwrap()), Ok(c"cstr")); } /// Check the allocation data for constants used in `other_consts` function. fn check_other_consts(item: CrateItem) { // Instance body will force constant evaluation. let body = Instance::try_from(item).unwrap().body().unwrap(); let assigns = collect_consts(&body); assert_eq!(assigns.len(), 8); for (name, alloc) in assigns { match name.as_str() { "_max_u128" => { assert_eq!(alloc.read_uint(), Ok(u128::MAX), "Failed parsing allocation: {alloc:?}") } "_min_i128" => { assert_eq!(alloc.read_int(), Ok(i128::MIN), "Failed parsing allocation: {alloc:?}") } "_max_i8" => { assert_eq!( alloc.read_int().unwrap() as i8, i8::MAX, "Failed parsing allocation: {alloc:?}" ) } "_char" => { assert_eq!( char::from_u32(alloc.read_uint().unwrap() as u32), Some('x'), "Failed parsing allocation: {alloc:?}" ) } "_false" => { assert_eq!(alloc.read_bool(), Ok(false), "Failed parsing allocation: {alloc:?}") } "_true" => { assert_eq!(alloc.read_bool(), Ok(true), "Failed parsing allocation: {alloc:?}") } "_ptr" => { assert_eq!(alloc.is_null(), Ok(false), "Failed parsing allocation: {alloc:?}") } "_null_ptr" => { assert_eq!(alloc.is_null(), Ok(true), "Failed parsing allocation: {alloc:?}") } "_tuple" => { // The order of fields is not guaranteed. let first = alloc.read_partial_uint(0..4).unwrap(); let second = alloc.read_partial_uint(4..8).unwrap(); assert_eq!(max(first, second) as u32, u32::MAX); assert_eq!(min(first, second), 10); } _ => { unreachable!("{name} -- {alloc:?}") } } } } /// Check that we can retrieve the type id of char and bool, and that they have different values. fn check_type_id(item: CrateItem) { let body = Instance::try_from(item).unwrap().body().unwrap(); let mut ids: Vec = vec![]; for term in body.blocks.iter().map(|bb| &bb.terminator) { match &term.kind { TerminatorKind::Call { func, destination, .. } => { let TyKind::RigidTy(ty) = func.ty(body.locals()).unwrap().kind() else { unreachable!() }; let RigidTy::FnDef(def, args) = ty else { unreachable!() }; let instance = Instance::resolve(def, &args).unwrap(); assert_eq!(instance.kind, InstanceKind::Intrinsic); let dest_ty = destination.ty(body.locals()).unwrap(); let alloc = instance.try_const_eval(dest_ty).unwrap(); ids.push(alloc.read_uint().unwrap()); } _ => { /* Do nothing */ } } } assert_eq!(ids.len(), 2); assert_ne!(ids[0], ids[1]); } /// Collects all the constant assignments. pub fn collect_consts(body: &Body) -> HashMap { body.var_debug_info .iter() .filter_map(|info| { info.constant().map(|const_op| { let ConstantKind::Allocated(alloc) = const_op.const_.kind() else { unreachable!() }; (info.name.clone(), alloc) }) }) .collect::>() } /// Check the allocation data for `LEN`. /// /// ```no_run /// static LEN: usize = 2; /// ``` fn check_len(item: CrateItem) { let def = StaticDef::try_from(item).unwrap(); let alloc = def.eval_initializer().unwrap(); assert!(alloc.provenance.ptrs.is_empty()); assert_eq!(alloc.read_uint(), Ok(2)); } fn get_item<'a>( items: &'a CrateItems, item: (ItemKind, &str), ) -> Option<&'a stable_mir::CrateItem> { items.iter().find(|crate_item| (item.0 == crate_item.kind()) && crate_item.name() == item.1) } /// This test will generate and analyze a dummy crate using the stable mir. /// For that, it will first write the dummy crate into a file. /// Then it will create a `StableMir` using custom arguments and then /// it will run the compiler. fn main() { let path = "alloc_input.rs"; generate_input(&path).unwrap(); let args = vec![ "rustc".to_string(), "--edition=2021".to_string(), "--crate-name".to_string(), CRATE_NAME.to_string(), path.to_string(), ]; run!(args, test_stable_mir).unwrap(); } fn generate_input(path: &str) -> std::io::Result<()> { let mut file = std::fs::File::create(path)?; write!( file, r#" #![feature(core_intrinsics)] use std::intrinsics::type_id; static LEN: usize = 2; static FOO: [&str; 2] = ["hi", "there"]; static BAR: &str = "Bar"; static C_STR: &std::ffi::CStr = c"cstr"; const NULL: *const u8 = std::ptr::null(); const TUPLE: (u32, u32) = (10, u32::MAX); fn other_consts() {{ let _max_u128 = u128::MAX; let _min_i128 = i128::MIN; let _max_i8 = i8::MAX; let _char = 'x'; let _false = false; let _true = true; let _ptr = &BAR; let _null_ptr: *const u8 = NULL; let _tuple = TUPLE; }} fn check_type_id() {{ let _char_id = type_id::(); let _bool_id = type_id::(); }} pub fn main() {{ println!("{{FOO:?}}! {{BAR}}"); assert_eq!(FOO.len(), LEN); other_consts(); }}"# )?; Ok(()) }