diff --git a/Cargo.toml b/Cargo.toml index d3697990..8aa6b979 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,6 @@ name = "thrust-rustc" path = "src/main.rs" test = false -[lib] -# TODO: why is this necessary? -test = false - [[test]] name = "ui" harness = false diff --git a/src/analyze.rs b/src/analyze.rs index a4ffa5f4..5d9b9bd7 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -30,10 +30,27 @@ mod basic_block; mod crate_; mod did_cache; mod local_def; +mod reconstruct_slice_indexing; // TODO: organize structure and remove cross dependency between refine pub use did_cache::DefIdCache; +fn fn_operand<'tcx>( + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: mir_ty::GenericArgsRef<'tcx>, + span: rustc_span::Span, +) -> mir::Operand<'tcx> { + mir::Operand::Constant(Box::new(mir::ConstOperand { + span, + user_ty: None, + const_: mir::Const::Val( + mir::ConstValue::ZeroSized, + mir_ty::Ty::new_fn_def(tcx, def_id, args), + ), + })) +} + pub fn mir_borrowck_skip_formula_fn( tcx: rustc_middle::ty::TyCtxt<'_>, local_def_id: rustc_span::def_id::LocalDefId, diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index 6e5aecf2..d497a4a5 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -1012,6 +1012,51 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { // definition assert!(lhs.projection.is_empty()); + if let Rvalue::UnaryOp(mir::UnOp::PtrMetadata, operand) = rvalue { + // Calls to `[T]::len` have already been lowered to this assignment in optimized MIR: + // + // _len = PtrMetadata(copy _slice); + // + let operand_mir_ty = operand.ty(&self.local_decls, self.tcx); + let mir_ty::TyKind::Ref(_, slice_ty, mutability) = operand_mir_ty.kind() else { + unimplemented!("PtrMetadata for {operand_mir_ty:?}") + }; + let mir_ty::TyKind::Slice(elem_ty) = slice_ty.kind() else { + unimplemented!("PtrMetadata for {operand_mir_ty:?}") + }; + let slice_len = self + .tcx + .lang_items() + .slice_len_fn() + .expect("slice len lang item is unavailable"); + let args = self.tcx.mk_args(&[(*elem_ty).into()]); + let func = analyze::fn_operand(self.tcx, slice_len, args, rustc_span::DUMMY_SP); + let operand = if mutability.is_mut() { + let place = operand + .place() + .expect("mutable slice metadata operand must be a place"); + let region = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased); + let ty = mir_ty::Ty::new_ref(self.tcx, region, *slice_ty, mir_ty::Mutability::Not); + let local = self + .local_decls + .push(mir::LocalDecl::new(ty, rustc_span::DUMMY_SP).immutable()); + let rty = self.immut_borrow_place(self.tcx.mk_place_deref(place)); + self.bind_local(local, rty); + Operand::Copy(local.into()) + } else { + operand.clone() + }; + let decl = self.local_decls[lhs.local].clone(); + let rty = self + .type_builder + .for_template(&mut self.ctx) + .with_scope(&self.env) + .build_refined(decl.ty); + self.type_call(func, [operand], &rty); + self.bind_local(lhs.local, rty); + return; + } + if let Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, referent) = rvalue { // mutable borrow let rty = self.mutable_borrow(stmt_idx, *referent); diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 3938e071..2991a770 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -1310,6 +1310,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let _guard = span.enter(); self.unelaborate_derefs(); + analyze::reconstruct_slice_indexing::reconstruct(self.tcx, &mut self.body); self.reassign_local_mutabilities(); self.refine_basic_blocks(); self.analyze_basic_blocks(expected); diff --git a/src/analyze/reconstruct_slice_indexing.rs b/src/analyze/reconstruct_slice_indexing.rs new file mode 100644 index 00000000..b600cd2a --- /dev/null +++ b/src/analyze/reconstruct_slice_indexing.rs @@ -0,0 +1,408 @@ +//! Reconstructs slice indexing calls erased by MIR lowering. + +use rustc_hir::def::Namespace; +use rustc_hir::lang_items::LangItem; +use rustc_middle::mir::{ + self, BasicBlock, Body, Local, Operand, Rvalue, StatementKind, TerminatorKind, +}; +use rustc_middle::ty::{self as mir_ty, Ty, TyCtxt}; +use rustc_span::def_id::DefId; +use rustc_span::source_map::Spanned; +use rustc_span::{sym, Symbol}; + +use crate::analyze; + +/// The bounds-check block emitted for an indexing expression. +struct BoundsCheck<'tcx> { + block: BasicBlock, + condition_local: Option, + len: Operand<'tcx>, + index: Operand<'tcx>, + index_local: Local, + target: BasicBlock, + unwind: mir::UnwindAction, + source_info: mir::SourceInfo, +} + +/// The slice access in the target of a [`BoundsCheck`]. +struct SliceAccess<'tcx> { + indexed_place: mir::Place<'tcx>, + receiver: mir::Place<'tcx>, + receiver_mutability: mir_ty::Mutability, + slice_ty: Ty<'tcx>, + elem_ty: Ty<'tcx>, + mutable: bool, +} + +struct IndexedPlaceFinder<'a, 'tcx> { + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, + index: Local, + found: Option<(mir::Place<'tcx>, bool)>, +} + +impl<'tcx> mir::visit::Visitor<'tcx> for IndexedPlaceFinder<'_, 'tcx> { + fn visit_place( + &mut self, + place: &mir::Place<'tcx>, + context: mir::visit::PlaceContext, + _location: mir::Location, + ) { + if self.found.is_some() { + return; + } + let Some(index_pos) = place + .projection + .iter() + .position(|elem| elem == mir::PlaceElem::Index(self.index)) + else { + return; + }; + let indexed = mir::Place { + local: place.local, + projection: self + .tcx + .mk_place_elems(&place.projection.as_slice()[..=index_pos]), + }; + let base = mir::Place { + local: place.local, + projection: self + .tcx + .mk_place_elems(&place.projection.as_slice()[..index_pos]), + }; + if matches!( + base.ty(&self.body.local_decls, self.tcx).ty.kind(), + mir_ty::TyKind::Slice(_) + ) { + self.found = Some((indexed, context.is_mutating_use())); + } + } +} + +/// Reconstructs the trait call erased by MIR's first-class slice indexing operation. +/// +/// For example, optimized MIR for `slice[index]` contains: +/// +/// ```text +/// fn shared_slice_index(_1: &[i32], _2: usize) -> i32 { +/// let mut _0: i32; +/// let mut _3: usize; +/// let mut _4: bool; +/// +/// bb0: { +/// _3 = PtrMetadata(copy _1); +/// _4 = Lt(copy _2, copy _3); +/// assert(move _4, "index out of bounds: the length is {} but the index is {}", move _3, copy _2) -> [success: bb1, unwind continue]; +/// } +/// +/// bb1: { +/// _0 = copy (*_1)[_2]; +/// return; +/// } +/// } +/// ``` +/// +/// This reconstruction replaces that sequence with an `Index::index` or `IndexMut::index_mut` +/// call terminator and rewrites the indexed place to dereference its result: +/// +/// ```text +/// fn shared_slice_index(_1: &[i32], _2: usize) -> i32 { +/// let mut _0: i32; +/// let _5: &i32; +/// +/// bb0: { +/// _5 = <[i32] as Index>::index(copy _1, copy _2) +/// -> [return: bb1, unwind continue]; +/// } +/// +/// bb1: { +/// _0 = copy (*_5); +/// return; +/// } +/// } +/// ``` +/// +/// The analyzer therefore applies the registered trait-method type and remains independent of +/// the model selected for slices. +pub fn reconstruct<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { + for block in body.basic_blocks.indices().collect::>() { + let Some(bounds_check) = find_bounds_check(body, block) else { + continue; + }; + let Some(access) = find_slice_access(tcx, body, &bounds_check) else { + tracing::trace!(?block, "bounds check is not followed by slice indexing"); + continue; + }; + // The rewritten target uses a result local defined by this block's new call. Entering it + // from another predecessor would leave that local undefined. + assert_eq!( + body.basic_blocks.predecessors()[bounds_check.target].as_slice(), + [block], + "slice indexing target must have exactly one predecessor", + ); + + tracing::debug!( + assert_block = ?block, + target = ?bounds_check.target, + indexed_place = ?access.indexed_place, + receiver = ?access.receiver, + mutable = access.mutable, + "reconstructing a trait method call from slice indexing" + ); + reconstruct_access(tcx, body, bounds_check, access); + } +} + +/// Recognizes the bounds-check terminator and records the simple local used as the index. +fn find_bounds_check<'tcx>(body: &Body<'tcx>, block: BasicBlock) -> Option> { + let terminator = body.basic_blocks[block].terminator.as_ref()?; + let TerminatorKind::Assert { + cond, + msg, + target, + unwind, + .. + } = &terminator.kind + else { + return None; + }; + let mir::AssertMessage::BoundsCheck { len, index } = &**msg else { + return None; + }; + let index_place = index.place()?; + if !index_place.projection.is_empty() { + return None; + } + + Some(BoundsCheck { + block, + condition_local: cond + .place() + .filter(|place| place.projection.is_empty()) + .map(|place| place.local), + len: len.clone(), + index: index.clone(), + index_local: index_place.local, + target: *target, + unwind: *unwind, + source_info: terminator.source_info, + }) +} + +/// Finds the matching `(*slice)[index]` place in the bounds check's target block. +fn find_slice_access<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + bounds_check: &BoundsCheck<'tcx>, +) -> Option> { + use mir::visit::Visitor as _; + + let mut finder = IndexedPlaceFinder { + body, + tcx, + index: bounds_check.index_local, + found: None, + }; + finder.visit_basic_block_data(bounds_check.target, &body.basic_blocks[bounds_check.target]); + let (indexed_place, mutable) = finder.found?; + + // The indexed place has the shape `receiver.*[index]`; strip the dereference and index to + // recover the reference passed to `Index::index` or `IndexMut::index_mut`. + let mut receiver_projection = indexed_place.projection.as_slice().to_vec(); + if receiver_projection.pop() != Some(mir::PlaceElem::Index(bounds_check.index_local)) + || receiver_projection.pop() != Some(mir::PlaceElem::Deref) + { + return None; + } + let receiver = mir::Place { + local: indexed_place.local, + projection: tcx.mk_place_elems(&receiver_projection), + }; + let mir_ty::TyKind::Ref(_, slice_ty, receiver_mutability) = + receiver.ty(&body.local_decls, tcx).ty.kind() + else { + return None; + }; + let mir_ty::TyKind::Slice(elem_ty) = slice_ty.kind() else { + return None; + }; + if mutable && !receiver_mutability.is_mut() { + return None; + } + + Some(SliceAccess { + indexed_place, + receiver, + receiver_mutability: *receiver_mutability, + slice_ty: *slice_ty, + elem_ty: *elem_ty, + mutable, + }) +} + +fn reconstruct_access<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mut Body<'tcx>, + bounds_check: BoundsCheck<'tcx>, + access: SliceAccess<'tcx>, +) { + let region = mir_ty::Region::new_from_kind(tcx, mir_ty::RegionKind::ReErased); + let result_ty = mir_ty::Ty::new_ref( + tcx, + region, + access.elem_ty, + if access.mutable { + mir_ty::Mutability::Mut + } else { + mir_ty::Mutability::Not + }, + ); + let result_local = body + .local_decls + .push(mir::LocalDecl::new(result_ty, bounds_check.source_info.span).immutable()); + + replace_indexed_place( + body, + tcx, + bounds_check.target, + access.indexed_place, + result_local, + ); + let receiver = receiver_operand(tcx, body, &bounds_check, &access, region); + remove_bounds_check_setup(body, &bounds_check); + + let (lang_item, method_name) = if access.mutable { + (LangItem::IndexMut, sym::index_mut) + } else { + (LangItem::Index, sym::index) + }; + let method = lang_item_method(tcx, lang_item, method_name); + let args = tcx.mk_args(&[access.slice_ty.into(), tcx.types.usize.into()]); + let func = super::fn_operand(tcx, method, args, bounds_check.source_info.span); + let call_args = [receiver, bounds_check.index] + .into_iter() + .map(|node| Spanned { + node, + span: bounds_check.source_info.span, + }) + .collect::>() + .into_boxed_slice(); + + body.basic_blocks.as_mut()[bounds_check.block].terminator = Some(mir::Terminator { + source_info: bounds_check.source_info, + kind: TerminatorKind::Call { + func, + args: call_args, + destination: result_local.into(), + target: Some(bounds_check.target), + unwind: bounds_check.unwind, + call_source: mir::CallSource::Normal, + fn_span: bounds_check.source_info.span, + }, + }); + tracing::trace!(?result_local, ?method, "slice indexing call inserted"); +} + +/// Replaces every use of the indexed place in the target block with `*result_local`. +fn replace_indexed_place<'tcx>( + body: &mut Body<'tcx>, + tcx: TyCtxt<'tcx>, + target: BasicBlock, + indexed_place: mir::Place<'tcx>, + result_local: Local, +) { + let replacement = tcx.mk_place_deref(result_local.into()); + let mut replacer = + analyze::ReplacePlacesVisitor::with_replacement(tcx, indexed_place, replacement); + let target_data = &mut body.basic_blocks.as_mut()[target]; + for statement in &mut target_data.statements { + replacer.visit_statement(statement); + } + if let Some(terminator) = &mut target_data.terminator { + replacer.visit_terminator(terminator); + } +} + +/// Builds the receiver operand, inserting a shared reborrow when indexing through `&mut [T]`. +fn receiver_operand<'tcx>( + tcx: TyCtxt<'tcx>, + body: &mut Body<'tcx>, + bounds_check: &BoundsCheck<'tcx>, + access: &SliceAccess<'tcx>, + region: mir_ty::Region<'tcx>, +) -> Operand<'tcx> { + if !access.mutable && access.receiver_mutability.is_mut() { + let ty = mir_ty::Ty::new_ref(tcx, region, access.slice_ty, mir_ty::Mutability::Not); + let local = body + .local_decls + .push(mir::LocalDecl::new(ty, bounds_check.source_info.span).immutable()); + body.basic_blocks.as_mut()[bounds_check.block] + .statements + .push(mir::Statement::new( + bounds_check.source_info, + StatementKind::Assign(Box::new(( + local.into(), + Rvalue::Ref( + region, + mir::BorrowKind::Shared, + tcx.mk_place_deref(access.receiver), + ), + ))), + )); + Operand::Copy(local.into()) + } else if access.mutable { + Operand::Move(access.receiver) + } else { + Operand::Copy(access.receiver) + } +} + +/// Removes the MIR temporaries that only supported the now-replaced bounds check. +fn remove_bounds_check_setup<'tcx>(body: &mut Body<'tcx>, bounds_check: &BoundsCheck<'tcx>) { + let mut lowered_locals: Vec<_> = bounds_check.condition_local.into_iter().collect(); + if let Some(len_place) = bounds_check + .len + .place() + .filter(|place| place.projection.is_empty()) + { + lowered_locals.push(len_place.local); + for statement in &body.basic_blocks[bounds_check.block].statements { + let Some((lhs, Rvalue::UnaryOp(mir::UnOp::PtrMetadata, operand))) = + statement.kind.as_assign() + else { + continue; + }; + if lhs.local == len_place.local { + if let Some(raw_place) = operand.place().filter(|place| place.projection.is_empty()) + { + lowered_locals.push(raw_place.local); + } + } + } + } + + tracing::trace!( + ?lowered_locals, + "removing replaced bounds-check temporaries" + ); + for statement in &mut body.basic_blocks.as_mut()[bounds_check.block].statements { + let Some((lhs, _)) = statement.kind.as_assign() else { + continue; + }; + if lowered_locals.contains(&lhs.local) { + statement.kind = StatementKind::Nop; + } + } +} + +fn lang_item_method(tcx: TyCtxt<'_>, item: LangItem, name: Symbol) -> DefId { + let trait_id = tcx.lang_items().get(item).unwrap(); + tcx.associated_items(trait_id) + .in_definition_order() + .find(|item| item.name() == name && item.namespace() == Namespace::ValueNS) + .unwrap() + .def_id +} + +#[cfg(test)] +mod tests; diff --git a/src/analyze/reconstruct_slice_indexing/tests.rs b/src/analyze/reconstruct_slice_indexing/tests.rs new file mode 100644 index 00000000..30a34769 --- /dev/null +++ b/src/analyze/reconstruct_slice_indexing/tests.rs @@ -0,0 +1,205 @@ +use std::io::Write as _; +use std::sync::Mutex; + +use rustc_driver::{Callbacks, Compilation}; +use rustc_interface::interface::Compiler; +use rustc_middle::mir::{BasicBlock, Body, Operand, Rvalue, TerminatorKind}; +use rustc_middle::ty::TyCtxt; +use rustc_span::source_map::Spanned; + +use super::{lang_item_method, reconstruct}; + +type MirInspector = dyn for<'tcx> FnOnce(TyCtxt<'tcx>, Body<'tcx>) + Send; + +struct TestCallbacks { + function_name: &'static str, + inspect: Option>, +} + +impl Callbacks for TestCallbacks { + fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation { + let local_def_id = tcx + .mir_keys(()) + .iter() + .copied() + .filter(|local_def_id| tcx.def_kind(*local_def_id).is_fn_like()) + .find(|local_def_id| { + tcx.item_name(local_def_id.to_def_id()).as_str() == self.function_name + }) + .unwrap_or_else(|| panic!("function `{}` was not compiled", self.function_name)); + let body = tcx.optimized_mir(local_def_id.to_def_id()).clone(); + let inspect = self + .inspect + .take() + .expect("MIR inspector was already called"); + inspect(tcx, body); + Compilation::Stop + } +} + +fn with_optimized_mir( + source: &str, + function_name: &'static str, + inspect: impl for<'tcx> FnOnce(TyCtxt<'tcx>, Body<'tcx>) + Send + 'static, +) { + // rustc_driver installs process-global compiler state, so do not run embedded compilers in + // parallel when the test harness executes these focused tests on different worker threads. + static RUSTC_DRIVER: Mutex<()> = Mutex::new(()); + let _guard = RUSTC_DRIVER + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + + let mut input = tempfile::Builder::new().suffix(".rs").tempfile().unwrap(); + input.write_all(source.as_bytes()).unwrap(); + let args = [ + "rustc".to_owned(), + input.path().display().to_string(), + "--crate-name=reconstruct_slice_indexing_test".to_owned(), + "--crate-type=lib".to_owned(), + "--edition=2021".to_owned(), + "-Cdebug-assertions=off".to_owned(), + ]; + let mut callbacks = TestCallbacks { + function_name, + inspect: Some(Box::new(inspect)), + }; + rustc_driver::run_compiler(&args, &mut callbacks); +} + +struct ReconstructedCall<'a, 'tcx> { + block: BasicBlock, + args: &'a [Spanned>], +} + +fn find_call<'a, 'tcx>( + body: &'a Body<'tcx>, + def_id: rustc_span::def_id::DefId, +) -> ReconstructedCall<'a, 'tcx> { + let mut calls = body + .basic_blocks + .iter_enumerated() + .filter_map(|(block, data)| { + let TerminatorKind::Call { func, args, .. } = &data.terminator().kind else { + return None; + }; + func.const_fn_def() + .is_some_and(|(called, _)| called == def_id) + .then_some(ReconstructedCall { block, args }) + }); + let call = calls.next().expect("expected reconstructed call"); + assert!( + calls.next().is_none(), + "expected exactly one reconstructed call" + ); + call +} + +#[test] +fn reconstructs_shared_slice_indexing() { + with_optimized_mir( + "pub fn test(slice: &[i32], index: usize) -> i32 { slice[index] }", + "test", + |tcx, mut body| { + let (receiver, index) = { + let mut params = body.args_iter(); + (params.next().unwrap(), params.next().unwrap()) + }; + reconstruct(tcx, &mut body); + let index_method = lang_item_method( + tcx, + rustc_hir::lang_items::LangItem::Index, + rustc_span::sym::index, + ); + let call = find_call(&body, index_method); + assert_eq!(call.args.len(), 2); + assert_eq!(call.args[0].node, Operand::Copy(receiver.into())); + assert_eq!(call.args[1].node, Operand::Copy(index.into())); + }, + ); +} + +#[test] +fn reconstructs_mutable_slice_indexing() { + with_optimized_mir( + "pub fn test(slice: &mut [i32], index: usize) -> i32 { slice[index] += 1; slice[index] }", + "test", + |tcx, mut body| { + let (receiver, index) = { + let mut params = body.args_iter(); + (params.next().unwrap(), params.next().unwrap()) + }; + reconstruct(tcx, &mut body); + let index_mut = lang_item_method( + tcx, + rustc_hir::lang_items::LangItem::IndexMut, + rustc_span::sym::index_mut, + ); + let index_method = lang_item_method( + tcx, + rustc_hir::lang_items::LangItem::Index, + rustc_span::sym::index, + ); + let mutable_call = find_call(&body, index_mut); + assert_eq!(mutable_call.args.len(), 2); + assert_eq!(mutable_call.args[0].node, Operand::Move(receiver.into())); + assert_eq!(mutable_call.args[1].node, Operand::Copy(index.into())); + + let shared_call = find_call(&body, index_method); + assert_eq!(shared_call.args.len(), 2); + let Operand::Copy(shared_receiver) = &shared_call.args[0].node else { + panic!("shared indexing receiver must be copied"); + }; + assert!(shared_receiver.projection.is_empty()); + assert_eq!(shared_call.args[1].node, Operand::Copy(index.into())); + let (_, reborrow) = body.basic_blocks[shared_call.block] + .statements + .iter() + .filter_map(|statement| statement.kind.as_assign()) + .find(|(place, _)| place.local == shared_receiver.local) + .expect("shared receiver reborrow was not inserted"); + assert!(matches!( + reborrow, + Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Shared, referent) + if *referent == tcx.mk_place_deref(receiver.into()) + )); + }, + ); +} + +#[test] +fn reconstructs_shared_indexing_through_mutable_slice() { + with_optimized_mir( + "pub fn test(slice: &mut [i32], index: usize) -> i32 { slice[index] }", + "test", + |tcx, mut body| { + let (receiver, index) = { + let mut params = body.args_iter(); + (params.next().unwrap(), params.next().unwrap()) + }; + reconstruct(tcx, &mut body); + let index_method = lang_item_method( + tcx, + rustc_hir::lang_items::LangItem::Index, + rustc_span::sym::index, + ); + let call = find_call(&body, index_method); + assert_eq!(call.args.len(), 2); + let Operand::Copy(shared_receiver) = &call.args[0].node else { + panic!("shared indexing receiver must be copied"); + }; + assert!(shared_receiver.projection.is_empty()); + assert_eq!(call.args[1].node, Operand::Copy(index.into())); + let (_, reborrow) = body.basic_blocks[call.block] + .statements + .iter() + .filter_map(|statement| statement.kind.as_assign()) + .find(|(place, _)| place.local == shared_receiver.local) + .expect("shared receiver reborrow was not inserted"); + assert!(matches!( + reborrow, + Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Shared, referent) + if *referent == tcx.mk_place_deref(receiver.into()) + )); + }, + ); +} diff --git a/src/lib.rs b/src/lib.rs index cbbe60b7..f554558c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ extern crate rustc_abi; extern crate rustc_ast; extern crate rustc_borrowck; extern crate rustc_data_structures; +#[cfg(test)] +extern crate rustc_driver; extern crate rustc_hir; extern crate rustc_index; extern crate rustc_interface; diff --git a/std.rs b/std.rs index 9d1a883f..32525a2b 100644 --- a/std.rs +++ b/std.rs @@ -364,6 +364,10 @@ mod thrust_models { type Ty = model::Seq<::Ty>; } + impl Model for [T] where T: Model { + type Ty = model::Seq<::Ty>; + } + impl Model for Option where T: Model { type Ty = Option<::Ty>; } @@ -796,6 +800,154 @@ fn _extern_spec_vec_truncate(vec: &mut Vec, len: usize) where T: thrust_mo Vec::truncate(vec, len) } +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == slice.1)] +fn _extern_spec_slice_len(slice: &[T]) -> usize + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::len(slice) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result == (slice.1 == 0))] +fn _extern_spec_slice_is_empty(slice: &[T]) -> bool + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::is_empty(slice) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (index < slice.1 && result == Some(&slice.0[index])) + || (slice.1 <= index && result == None) +)] +fn _extern_spec_slice_get(slice: &[T], index: usize) -> Option<&T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::get(slice, index) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (index < (*slice).1 + && result == Some(thrust_models::model::Mut::new( + (*slice).0[index], + (!slice).0[index], + )) + && !slice == thrust_models::model::Seq( + (*slice).0.store(index, (!slice).0[index]), + (*slice).1, + ) + ) + || ((*slice).1 <= index && result == None && !slice == *slice) +)] +fn _extern_spec_slice_get_mut(slice: &mut [T], index: usize) -> Option<&mut T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::get_mut(slice, index) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (slice.1 > 0 && result == Some(&slice.0[0])) + || (slice.1 == 0 && result == None) +)] +fn _extern_spec_slice_first(slice: &[T]) -> Option<&T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::first(slice) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + ((*slice).1 > 0 + && result == Some(thrust_models::model::Mut::new( + (*slice).0[0], + (!slice).0[0], + )) + && !slice == thrust_models::model::Seq( + (*slice).0.store(0, (!slice).0[0]), + (*slice).1, + ) + ) + || ((*slice).1 == 0 && result == None && !slice == *slice) +)] +fn _extern_spec_slice_first_mut(slice: &mut [T]) -> Option<&mut T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::first_mut(slice) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (slice.1 > 0 && result == Some(&slice.0[slice.1 - 1])) + || (slice.1 == 0 && result == None) +)] +fn _extern_spec_slice_last(slice: &[T]) -> Option<&T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::last(slice) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + ((*slice).1 > 0 + && result == Some(thrust_models::model::Mut::new( + (*slice).0[(*slice).1 - 1], + (!slice).0[(*slice).1 - 1], + )) + && !slice == thrust_models::model::Seq( + (*slice).0.store( + (*slice).1 - 1, + (!slice).0[(*slice).1 - 1], + ), + (*slice).1, + ) + ) + || ((*slice).1 == 0 && result == None && !slice == *slice) +)] +fn _extern_spec_slice_last_mut(slice: &mut [T]) -> Option<&mut T> + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T]>::last_mut(slice) +} + +// TODO: The following specs for Index/IndexMut methods are too specific; we should write specs for +// a generic index (I: SliceIndex) that isn't specific to usize, maybe once #83 is implemented. + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(index < slice.1)] +#[thrust_macros::ensures(*result == slice.0[index])] +fn _extern_spec_slice_index(slice: &[T], index: usize) -> &T + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T] as std::ops::Index>::index(slice, index) +} + +#[thrust::extern_spec_fn] +#[thrust_macros::requires(index < (*slice).1)] +#[thrust_macros::ensures( + *result == (*slice).0[index] && + !result == (!slice).0[index] && + !slice == thrust_models::model::Seq( + (*slice).0.store(index, !result), + (*slice).1, + ) +)] +fn _extern_spec_slice_index_mut(slice: &mut [T], index: usize) -> &mut T + where T: thrust_models::Model, T::Ty: PartialEq +{ + <[T] as std::ops::IndexMut>::index_mut(slice, index) +} + // TODO: The following specs of some trait methods are too restrictive; we should allow for a // per-impl spec once we can describe the spec of blanket impls. diff --git a/tests/ui/fail/slice_first_mut.rs b/tests/ui/fail/slice_first_mut.rs new file mode 100644 index 00000000..df4bb101 --- /dev/null +++ b/tests/ui/fail/slice_first_mut.rs @@ -0,0 +1,17 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 0 && (*result).0[0] == 10 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.first_mut().unwrap() = 11; + assert!(*slice.first().unwrap() == 12); +} diff --git a/tests/ui/fail/slice_index.rs b/tests/ui/fail/slice_index.rs new file mode 100644 index 00000000..1d780e9d --- /dev/null +++ b/tests/ui/fail/slice_index.rs @@ -0,0 +1,15 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures(result.1 == 1 && result.0[0] == 10)] +fn slice() -> &'static [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + assert!(slice.len() == 1); + let _ = slice[1]; +} diff --git a/tests/ui/fail/slice_index_mut.rs b/tests/ui/fail/slice_index_mut.rs new file mode 100644 index 00000000..a9301178 --- /dev/null +++ b/tests/ui/fail/slice_index_mut.rs @@ -0,0 +1,17 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 1 && (*result).0[1] == 20 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + slice[1] += 1; + assert!(slice[1] == 22); +} diff --git a/tests/ui/fail/slice_last_mut.rs b/tests/ui/fail/slice_last_mut.rs new file mode 100644 index 00000000..9b46c333 --- /dev/null +++ b/tests/ui/fail/slice_last_mut.rs @@ -0,0 +1,18 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 0 + && (*result).0[(*result).1 - 1] == 30 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.last_mut().unwrap() = 31; + assert!(*slice.last().unwrap() == 32); +} diff --git a/tests/ui/fail/slice_methods.rs b/tests/ui/fail/slice_methods.rs new file mode 100644 index 00000000..2ede1088 --- /dev/null +++ b/tests/ui/fail/slice_methods.rs @@ -0,0 +1,18 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + result.1 == 2 + && result.0[0] == 10 + && result.0[1] == 20 +)] +fn slice() -> &'static [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + assert!(*slice.last().unwrap() == 10); +} diff --git a/tests/ui/fail/slice_methods_mut.rs b/tests/ui/fail/slice_methods_mut.rs new file mode 100644 index 00000000..6783f536 --- /dev/null +++ b/tests/ui/fail/slice_methods_mut.rs @@ -0,0 +1,17 @@ +//@error-in-other-file: Unsat +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 1 && (*result).0[1] == 20 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.get_mut(1).unwrap() = 21; + assert!(*slice.get(1).unwrap() == 22); +} diff --git a/tests/ui/pass/slice_first_mut.rs b/tests/ui/pass/slice_first_mut.rs new file mode 100644 index 00000000..69c617ae --- /dev/null +++ b/tests/ui/pass/slice_first_mut.rs @@ -0,0 +1,17 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 0 && (*result).0[0] == 10 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.first_mut().unwrap() = 11; + assert!(*slice.first().unwrap() == 11); +} diff --git a/tests/ui/pass/slice_index.rs b/tests/ui/pass/slice_index.rs new file mode 100644 index 00000000..cf425aa3 --- /dev/null +++ b/tests/ui/pass/slice_index.rs @@ -0,0 +1,20 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + result.1 == 2 + && result.0[0] == 10 + && result.0[1] == 20 +)] +fn slice() -> &'static [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + assert!(slice.len() == 2); + assert!(slice[0] == 10); + assert!(slice[1] == 20); +} diff --git a/tests/ui/pass/slice_index_mut.rs b/tests/ui/pass/slice_index_mut.rs new file mode 100644 index 00000000..51ef5189 --- /dev/null +++ b/tests/ui/pass/slice_index_mut.rs @@ -0,0 +1,17 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 1 && (*result).0[1] == 20 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + slice[1] += 1; + assert!(slice[1] == 21); +} diff --git a/tests/ui/pass/slice_last_mut.rs b/tests/ui/pass/slice_last_mut.rs new file mode 100644 index 00000000..df7ffa3a --- /dev/null +++ b/tests/ui/pass/slice_last_mut.rs @@ -0,0 +1,18 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 0 + && (*result).0[(*result).1 - 1] == 30 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.last_mut().unwrap() = 31; + assert!(*slice.last().unwrap() == 31); +} diff --git a/tests/ui/pass/slice_methods.rs b/tests/ui/pass/slice_methods.rs new file mode 100644 index 00000000..5962a63b --- /dev/null +++ b/tests/ui/pass/slice_methods.rs @@ -0,0 +1,25 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + result.1 == 4 + && result.0[0] == 10 + && result.0[1] == 20 + && result.0[2] == 30 + && result.0[3] == 40 +)] +fn slice() -> &'static [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + assert!(!slice.is_empty()); + assert!(*slice.first().unwrap() == 10); + assert!(*slice.get(1).unwrap() == 20); + assert!(*<[i32] as std::ops::Index>::index(slice, 2) == 30); + assert!(*slice.last().unwrap() == 40); + assert!(slice.get(4).is_none()); +} diff --git a/tests/ui/pass/slice_methods_mut.rs b/tests/ui/pass/slice_methods_mut.rs new file mode 100644 index 00000000..b24db2a3 --- /dev/null +++ b/tests/ui/pass/slice_methods_mut.rs @@ -0,0 +1,17 @@ +//@check-pass +//@compile-flags: -C debug-assertions=off + +#[thrust::trusted] +#[thrust_macros::requires(true)] +#[thrust_macros::ensures( + (*result).1 > 1 && (*result).0[1] == 20 +)] +fn slice() -> &'static mut [i32] { + unimplemented!() +} + +fn main() { + let slice = slice(); + *slice.get_mut(1).unwrap() = 21; + assert!(*slice.get(1).unwrap() == 21); +}