From 9a8e1f0aaa8e157cb68e8950a19cec63fb14184e Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 23 Feb 2023 20:16:00 +0100 Subject: [PATCH 01/32] Start work on MicroMir --- Cargo.toml | 1 + micromir/Cargo.toml | 13 ++ micromir/src/defs/body.rs | 38 +++++ micromir/src/defs/mod.rs | 11 ++ micromir/src/defs/operand.rs | 47 ++++++ micromir/src/defs/rvalue.rs | 33 ++++ micromir/src/defs/statement.rs | 44 ++++++ micromir/src/defs/terminator.rs | 79 ++++++++++ micromir/src/lib.rs | 12 ++ micromir/src/translation/mod.rs | 144 ++++++++++++++++++ prusti-interface/src/environment/body.rs | 5 + prusti-interface/src/environment/procedure.rs | 2 + 12 files changed, 429 insertions(+) create mode 100644 micromir/Cargo.toml create mode 100644 micromir/src/defs/body.rs create mode 100644 micromir/src/defs/mod.rs create mode 100644 micromir/src/defs/operand.rs create mode 100644 micromir/src/defs/rvalue.rs create mode 100644 micromir/src/defs/statement.rs create mode 100644 micromir/src/defs/terminator.rs create mode 100644 micromir/src/lib.rs create mode 100644 micromir/src/translation/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6175322a7f3..74d88886b7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "prusti-common", "prusti-utils", "tracing", + "micromir", "prusti-interface", "prusti-viper", "prusti-server", diff --git a/micromir/Cargo.toml b/micromir/Cargo.toml new file mode 100644 index 00000000000..ff1e9da1288 --- /dev/null +++ b/micromir/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "micromir" +version = "0.1.0" +authors = ["Prusti Devs "] +edition = "2021" + +[dependencies] +tracing = { path = "../tracing" } +prusti-rustc-interface = { path = "../prusti-rustc-interface" } + +[package.metadata.rust-analyzer] +# This crate uses #[feature(rustc_private)] +rustc_private = true diff --git a/micromir/src/defs/body.rs b/micromir/src/defs/body.rs new file mode 100644 index 00000000000..e6604f1c4b0 --- /dev/null +++ b/micromir/src/defs/body.rs @@ -0,0 +1,38 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::rc::Rc; + +use prusti_rustc_interface::{ + index::vec::IndexVec, + middle::mir::{BasicBlock, Body}, +}; + +use crate::{translation, MicroStatement, MicroTerminator}; + +#[derive(Clone, Debug)] +pub struct MicroBody<'tcx> { + pub basic_blocks: MicroBasicBlocks<'tcx>, + pub body: Rc>, +} +impl<'tcx> MicroBody<'tcx> { + pub fn new(body: Rc>) -> Self { + let basic_blocks = translation::translate_bbs(&body); + Self { basic_blocks, body } + } +} + +#[derive(Clone, Debug)] +pub struct MicroBasicBlocks<'tcx> { + pub basic_blocks: IndexVec>, +} + +#[derive(Clone, Debug)] +pub struct MicroBasicBlockData<'tcx> { + pub statements: Vec>, + pub terminator: Option>, + pub is_cleanup: bool, +} diff --git a/micromir/src/defs/mod.rs b/micromir/src/defs/mod.rs new file mode 100644 index 00000000000..16cfa6a7c27 --- /dev/null +++ b/micromir/src/defs/mod.rs @@ -0,0 +1,11 @@ +mod operand; +mod rvalue; +mod terminator; +mod statement; +mod body; + +pub use body::*; +pub use operand::*; +pub use rvalue::*; +pub use statement::*; +pub use terminator::*; diff --git a/micromir/src/defs/operand.rs b/micromir/src/defs/operand.rs new file mode 100644 index 00000000000..1e5011d1827 --- /dev/null +++ b/micromir/src/defs/operand.rs @@ -0,0 +1,47 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::index::vec::Idx; +use std::fmt::{Debug, Formatter}; + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct MicroOperand(Temporary); + +#[derive(Clone, Copy, Hash, Eq, PartialEq)] +pub struct Temporary { + private: u32, +} +impl Temporary { + pub const fn from_usize(value: usize) -> Self { + Self { + private: value as u32, + } + } + pub const fn from_u32(value: u32) -> Self { + Self { private: value } + } + pub const fn as_u32(self) -> u32 { + self.private + } + pub const fn as_usize(self) -> usize { + self.private as usize + } +} +impl Idx for Temporary { + fn new(value: usize) -> Self { + Self { + private: value as u32, + } + } + fn index(self) -> usize { + self.private as usize + } +} +impl Debug for Temporary { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "tmp{}", self.private) + } +} diff --git a/micromir/src/defs/rvalue.rs b/micromir/src/defs/rvalue.rs new file mode 100644 index 00000000000..4355e0b65da --- /dev/null +++ b/micromir/src/defs/rvalue.rs @@ -0,0 +1,33 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::MicroOperand; +use prusti_rustc_interface::{ + middle::{ + mir::{AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Place, UnOp}, + ty::{self, Region, Ty}, + }, + span::def_id::DefId, +}; + +#[derive(Clone, Debug, PartialEq, Hash)] +pub enum MicroRvalue<'tcx> { + Use(MicroOperand), + Repeat(MicroOperand, ty::Const<'tcx>), + Ref(Region<'tcx>, BorrowKind, Place<'tcx>), + ThreadLocalRef(DefId), + AddressOf(Mutability, Place<'tcx>), + Len(Place<'tcx>), + Cast(CastKind, MicroOperand, Ty<'tcx>), + BinaryOp(BinOp, Box<(MicroOperand, MicroOperand)>), + CheckedBinaryOp(BinOp, Box<(MicroOperand, MicroOperand)>), + NullaryOp(NullOp, Ty<'tcx>), + UnaryOp(UnOp, MicroOperand), + Discriminant(Place<'tcx>), + Aggregate(Box>, Vec), + ShallowInitBox(MicroOperand, Ty<'tcx>), + CopyForDeref(Place<'tcx>), +} diff --git a/micromir/src/defs/statement.rs b/micromir/src/defs/statement.rs new file mode 100644 index 00000000000..4b0aa67c2d6 --- /dev/null +++ b/micromir/src/defs/statement.rs @@ -0,0 +1,44 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + index::vec::IndexVec, + middle::{ + mir::{ + Coverage, FakeReadCause, Local, NonDivergingIntrinsic, Place, RetagKind, + UserTypeProjection, + }, + ty::{self}, + }, + target::abi::VariantIdx, +}; + +use crate::{MicroRvalue, Temporary}; + +#[derive(Clone, Debug)] +pub struct MicroStatement<'tcx> { + pub operands: IndexVec, + pub kind: MicroStatementKind<'tcx>, +} + +#[derive(Clone, Debug, PartialEq, Hash)] +pub enum MicroStatementKind<'tcx> { + Assign(Box<(Place<'tcx>, MicroRvalue<'tcx>)>), + FakeRead(Box<(FakeReadCause, Place<'tcx>)>), + SetDiscriminant { + place: Box>, + variant_index: VariantIdx, + }, + Deinit(Box>), + StorageLive(Local), + StorageDead(Local), + Retag(RetagKind, Box>), + AscribeUserType(Box<(Place<'tcx>, UserTypeProjection)>, ty::Variance), + Coverage(Box), + Intrinsic(Box>), + ConstEvalCounter, + Nop, +} diff --git a/micromir/src/defs/terminator.rs b/micromir/src/defs/terminator.rs new file mode 100644 index 00000000000..10e0f46e74a --- /dev/null +++ b/micromir/src/defs/terminator.rs @@ -0,0 +1,79 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::middle::mir::{AssertMessage, BasicBlock, Place, SwitchTargets}; + +use crate::MicroOperand; + +#[derive(Clone, Debug)] +pub struct MicroTerminator<'tcx> { + pub kind: MicroTerminatorKind<'tcx>, +} + +#[derive(Clone, Debug, PartialEq, Hash)] +pub enum MicroTerminatorKind<'tcx> { + Goto { + target: BasicBlock, + }, + SwitchInt { + discr: MicroOperand, + targets: SwitchTargets, + }, + Resume, + Abort, + Return, + Unreachable, + Drop { + place: Place<'tcx>, + target: BasicBlock, + unwind: Option, + }, + DropAndReplace { + place: Place<'tcx>, + value: MicroOperand, + target: BasicBlock, + unwind: Option, + }, + Call { + func: MicroOperand, + args: Vec, + destination: Place<'tcx>, + target: Option, + cleanup: Option, + from_hir_call: bool, + // fn_span: Span, + }, + Assert { + cond: MicroOperand, + expected: bool, + msg: AssertMessage<'tcx>, + target: BasicBlock, + cleanup: Option, + }, + Yield { + value: MicroOperand, + resume: BasicBlock, + resume_arg: Place<'tcx>, + drop: Option, + }, + GeneratorDrop, + FalseEdge { + real_target: BasicBlock, + imaginary_target: BasicBlock, + }, + FalseUnwind { + real_target: BasicBlock, + unwind: Option, + }, + // InlineAsm { + // template: &'tcx [InlineAsmTemplatePiece], + // operands: Vec>, + // options: InlineAsmOptions, + // line_spans: &'tcx [Span], + // destination: Option, + // cleanup: Option, + // }, +} diff --git a/micromir/src/lib.rs b/micromir/src/lib.rs new file mode 100644 index 00000000000..5bbbc4388f4 --- /dev/null +++ b/micromir/src/lib.rs @@ -0,0 +1,12 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![feature(rustc_private)] +#![feature(box_syntax, box_patterns)] + +mod translation; +mod defs; + +pub use defs::*; diff --git a/micromir/src/translation/mod.rs b/micromir/src/translation/mod.rs new file mode 100644 index 00000000000..8fae226d144 --- /dev/null +++ b/micromir/src/translation/mod.rs @@ -0,0 +1,144 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::middle::mir::{BasicBlockData, Body, Statement, Terminator}; + +use crate::{MicroBasicBlockData, MicroBasicBlocks, MicroStatement, MicroTerminator}; + +pub(crate) fn translate_bbs<'tcx>(body: &Body<'tcx>) -> MicroBasicBlocks<'tcx> { + println!("body: {body:#?}"); + MicroBasicBlocks { + basic_blocks: body.basic_blocks.iter().map(translate_bb).collect(), + } +} + +pub(crate) fn translate_bb<'tcx>(data: &BasicBlockData<'tcx>) -> MicroBasicBlockData<'tcx> { + MicroBasicBlockData { + statements: data.statements.iter().map(translate_stmt).collect(), + terminator: data.terminator.as_ref().map(translate_term), + is_cleanup: data.is_cleanup, + } +} + +pub(crate) fn translate_stmt<'tcx>(_stmt: &Statement<'tcx>) -> MicroStatement<'tcx> { + todo!() + // let kind = match &stmt.kind { + // StatementKind::Assign(box (p, r)) => + // MicroStatementKind::Assign(box (*p, r.clone())), + // StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (*c, *p)), + // StatementKind::SetDiscriminant { + // place, + // variant_index, + // } => MicroStatementKind::SetDiscriminant { + // place: box **place, + // variant_index: *variant_index, + // }, + // StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box *p), + // StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(*l), + // StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(*l), + // StatementKind::Retag(k, box p) => MicroStatementKind::Retag(*k, box *p), + // StatementKind::AscribeUserType(box (p, ty), v) => { + // MicroStatementKind::AscribeUserType(box (*p, ty.clone()), *v) + // } + // StatementKind::Coverage(box c) => MicroStatementKind::Coverage(box c.clone()), + // StatementKind::Intrinsic(box i) => MicroStatementKind::Intrinsic(box i.clone()), + // StatementKind::Nop => MicroStatementKind::Nop, + // }; + // MicroStatement { operands: IndexVec::new(), kind } +} + +pub(crate) fn translate_term<'tcx>(_term: &Terminator<'tcx>) -> MicroTerminator<'tcx> { + todo!() + // let kind = match &term.kind { + // &TerminatorKind::Goto { target } => MicroTerminatorKind::Goto { target }, + // TerminatorKind::SwitchInt { discr, targets } => MicroTerminatorKind::SwitchInt { + // discr: discr.clone(), + // targets: targets.clone(), + // }, + // TerminatorKind::Resume => MicroTerminatorKind::Resume, + // TerminatorKind::Abort => MicroTerminatorKind::Abort, + // TerminatorKind::Return => MicroTerminatorKind::Return, + // TerminatorKind::Unreachable => MicroTerminatorKind::Unreachable, + // &TerminatorKind::Drop { + // place, + // target, + // unwind, + // } => MicroTerminatorKind::Drop { + // place, + // target, + // unwind, + // }, + // TerminatorKind::DropAndReplace { + // place, + // value, + // target, + // unwind, + // } => MicroTerminatorKind::DropAndReplace { + // place: *place, + // value: value.clone(), + // target: *target, + // unwind: *unwind, + // }, + // TerminatorKind::Call { + // func, + // args, + // destination, + // target, + // cleanup, + // from_hir_call, + // fn_span: _, + // } => MicroTerminatorKind::Call { + // func: func.clone(), + // args: args.clone(), + // destination: *destination, + // target: *target, + // cleanup: *cleanup, + // from_hir_call: *from_hir_call, + // // fn_span: *fn_span, + // }, + // TerminatorKind::Assert { + // cond, + // expected, + // msg, + // target, + // cleanup, + // } => MicroTerminatorKind::Assert { + // cond: cond.clone(), + // expected: *expected, + // msg: msg.clone(), + // target: *target, + // cleanup: *cleanup, + // }, + // TerminatorKind::Yield { + // value, + // resume, + // resume_arg, + // drop, + // } => MicroTerminatorKind::Yield { + // value: value.clone(), + // resume: *resume, + // resume_arg: *resume_arg, + // drop: *drop, + // }, + // TerminatorKind::GeneratorDrop => MicroTerminatorKind::GeneratorDrop, + // &TerminatorKind::FalseEdge { + // real_target, + // imaginary_target, + // } => MicroTerminatorKind::FalseEdge { + // real_target, + // imaginary_target, + // }, + // &TerminatorKind::FalseUnwind { + // real_target, + // unwind, + // } => MicroTerminatorKind::FalseUnwind { + // real_target, + // unwind, + // }, + // TerminatorKind::InlineAsm { .. } => todo!(), + // }; + // MicroTerminator { kind } +} diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 3327ca82e6c..599d9b7ba1d 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -16,6 +16,11 @@ use crate::environment::{borrowck::facts::BorrowckFacts, mir_storage}; /// Prusti might want to work with. Cheap to clone #[derive(Clone, TyEncodable, TyDecodable)] pub struct MirBody<'tcx>(Rc>); +impl<'tcx> MirBody<'tcx> { + pub fn body(&self) -> Rc> { + self.0.clone() + } +} impl<'tcx> std::ops::Deref for MirBody<'tcx> { type Target = mir::Body<'tcx>; fn deref(&self) -> &Self::Target { diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index fbb3537a7fc..17c49bbd01c 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -10,6 +10,7 @@ use crate::{ environment::{debug_utils::to_text::ToText, mir_utils::RealEdges, Environment}, }; use log::{debug, trace}; +use micromir::MicroBody; use prusti_rustc_interface::{ data_structures::fx::{FxHashMap, FxHashSet}, hir::def_id, @@ -43,6 +44,7 @@ impl<'tcx> Procedure<'tcx> { let mir = env .body .get_impure_fn_body_identity(proc_def_id.expect_local()); + let _micro_mir = MicroBody::new(mir.body()); let real_edges = RealEdges::new(&mir); let reachable_basic_blocks = build_reachable_basic_blocks(&mir, &real_edges); let nonspec_basic_blocks = build_nonspec_basic_blocks(env.query, &mir, &real_edges); From 9c967edad9b2e3fdcce2184bbe29eccf728a6630 Mon Sep 17 00:00:00 2001 From: Jonas Date: Wed, 1 Mar 2023 18:17:05 +0100 Subject: [PATCH 02/32] More work on MicroMir --- micromir/Cargo.toml | 2 + micromir/src/defs/body.rs | 67 +++- micromir/src/defs/mod.rs | 6 + micromir/src/defs/operand.rs | 29 +- micromir/src/defs/rvalue.rs | 51 ++- micromir/src/defs/statement.rs | 67 +++- micromir/src/defs/terminator.rs | 114 +++++- micromir/src/free_pcs/mod.rs | 9 + micromir/src/free_pcs/permission.rs | 332 ++++++++++++++++++ micromir/src/lib.rs | 6 +- micromir/src/repack/calculate.rs | 235 +++++++++++++ micromir/src/repack/mod.rs | 14 + micromir/src/repack/place.rs | 200 +++++++++++ micromir/src/repack/repack.rs | 183 ++++++++++ micromir/src/repack/triple.rs | 116 ++++++ micromir/src/translation/mod.rs | 144 -------- prusti-interface/Cargo.toml | 1 + prusti-interface/src/environment/procedure.rs | 3 +- 18 files changed, 1410 insertions(+), 169 deletions(-) create mode 100644 micromir/src/free_pcs/mod.rs create mode 100644 micromir/src/free_pcs/permission.rs create mode 100644 micromir/src/repack/calculate.rs create mode 100644 micromir/src/repack/mod.rs create mode 100644 micromir/src/repack/place.rs create mode 100644 micromir/src/repack/repack.rs create mode 100644 micromir/src/repack/triple.rs delete mode 100644 micromir/src/translation/mod.rs diff --git a/micromir/Cargo.toml b/micromir/Cargo.toml index ff1e9da1288..72528ee8269 100644 --- a/micromir/Cargo.toml +++ b/micromir/Cargo.toml @@ -5,7 +5,9 @@ authors = ["Prusti Devs "] edition = "2021" [dependencies] +derive_more = "0.99" tracing = { path = "../tracing" } +analysis = { path = "../analysis" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } [package.metadata.rust-analyzer] diff --git a/micromir/src/defs/body.rs b/micromir/src/defs/body.rs index e6604f1c4b0..631cb062cda 100644 --- a/micromir/src/defs/body.rs +++ b/micromir/src/defs/body.rs @@ -4,24 +4,44 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::rc::Rc; - +use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ index::vec::IndexVec, - middle::mir::{BasicBlock, Body}, + middle::{ + mir::{BasicBlock, BasicBlockData, Body}, + ty::TyCtxt, + }, }; +use std::rc::Rc; -use crate::{translation, MicroStatement, MicroTerminator}; +use crate::{MicroStatement, MicroTerminator, PlaceCapabilitySummary}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deref, DerefMut)] pub struct MicroBody<'tcx> { + pub(crate) done_repacking: bool, pub basic_blocks: MicroBasicBlocks<'tcx>, + #[deref] + #[deref_mut] pub body: Rc>, } impl<'tcx> MicroBody<'tcx> { - pub fn new(body: Rc>) -> Self { - let basic_blocks = translation::translate_bbs(&body); - Self { basic_blocks, body } + pub fn new(body: Rc>, tcx: TyCtxt<'tcx>) -> Self { + let mut body = Self::from(body); + body.calculate_repacking(tcx); + body + } +} + +impl<'tcx> From>> for MicroBody<'tcx> { + /// Clones a `mir::Body` into an identical `MicroBody`. + /// Doesn't calculate any repacking information. + fn from(body: Rc>) -> Self { + let basic_blocks = MicroBasicBlocks::from(&*body); + Self { + done_repacking: false, + basic_blocks, + body, + } } } @@ -30,9 +50,38 @@ pub struct MicroBasicBlocks<'tcx> { pub basic_blocks: IndexVec>, } +impl<'tcx> From<&Body<'tcx>> for MicroBasicBlocks<'tcx> { + #[tracing::instrument(level = "debug", skip(body), fields(body = format!("{body:#?}")))] + fn from(body: &Body<'tcx>) -> Self { + Self { + basic_blocks: body + .basic_blocks + .iter() + .map(MicroBasicBlockData::from) + .collect(), + } + } +} + #[derive(Clone, Debug)] pub struct MicroBasicBlockData<'tcx> { pub statements: Vec>, - pub terminator: Option>, + pub terminator: MicroTerminator<'tcx>, pub is_cleanup: bool, } + +impl<'tcx> From<&BasicBlockData<'tcx>> for MicroBasicBlockData<'tcx> { + fn from(data: &BasicBlockData<'tcx>) -> Self { + Self { + statements: data.statements.iter().map(MicroStatement::from).collect(), + terminator: data.terminator().into(), + is_cleanup: data.is_cleanup, + } + } +} + +impl<'tcx> MicroBasicBlockData<'tcx> { + pub(crate) fn get_pcs_mut(&mut self) -> Option<&mut PlaceCapabilitySummary<'tcx>> { + self.terminator.repack_join.as_mut() + } +} diff --git a/micromir/src/defs/mod.rs b/micromir/src/defs/mod.rs index 16cfa6a7c27..2cbc0dd6806 100644 --- a/micromir/src/defs/mod.rs +++ b/micromir/src/defs/mod.rs @@ -1,3 +1,9 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + mod operand; mod rvalue; mod terminator; diff --git a/micromir/src/defs/operand.rs b/micromir/src/defs/operand.rs index 1e5011d1827..617e3f2db4b 100644 --- a/micromir/src/defs/operand.rs +++ b/micromir/src/defs/operand.rs @@ -4,11 +4,36 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use prusti_rustc_interface::index::vec::Idx; +use derive_more::{Deref, DerefMut}; +use prusti_rustc_interface::{ + index::vec::{Idx, IndexVec}, + middle::mir::Operand, +}; use std::fmt::{Debug, Formatter}; -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +#[derive(Clone, Debug, Deref, DerefMut)] +pub struct Operands<'tcx> { + operands: IndexVec>, +} +impl<'tcx> Operands<'tcx> { + pub(crate) fn new() -> Self { + Self { + operands: IndexVec::new(), + } + } + pub(crate) fn translate_operand(&mut self, operand: &Operand<'tcx>) -> MicroOperand { + let index = self.operands.push(operand.clone()); + MicroOperand::new(index) + } +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deref, DerefMut)] pub struct MicroOperand(Temporary); +impl MicroOperand { + pub const fn new(value: Temporary) -> Self { + Self(value) + } +} #[derive(Clone, Copy, Hash, Eq, PartialEq)] pub struct Temporary { diff --git a/micromir/src/defs/rvalue.rs b/micromir/src/defs/rvalue.rs index 4355e0b65da..4c365cc5a60 100644 --- a/micromir/src/defs/rvalue.rs +++ b/micromir/src/defs/rvalue.rs @@ -4,15 +4,29 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::MicroOperand; +use crate::{MicroOperand, Operands}; use prusti_rustc_interface::{ middle::{ - mir::{AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Place, UnOp}, + mir::{ + AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Place, Rvalue, UnOp, + }, ty::{self, Region, Ty}, }, span::def_id::DefId, }; +#[derive(Clone, Debug, PartialEq, Hash)] +pub enum MicroNonDivergingIntrinsic { + Assume(MicroOperand), + CopyNonOverlapping(MicroCopyNonOverlapping), +} +#[derive(Clone, Debug, PartialEq, Hash)] +pub struct MicroCopyNonOverlapping { + pub src: MicroOperand, + pub dst: MicroOperand, + pub count: MicroOperand, +} + #[derive(Clone, Debug, PartialEq, Hash)] pub enum MicroRvalue<'tcx> { Use(MicroOperand), @@ -31,3 +45,36 @@ pub enum MicroRvalue<'tcx> { ShallowInitBox(MicroOperand, Ty<'tcx>), CopyForDeref(Place<'tcx>), } + +impl<'tcx> Operands<'tcx> { + pub(crate) fn translate_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> MicroRvalue<'tcx> { + match rvalue { + Rvalue::Use(o) => MicroRvalue::Use(self.translate_operand(o)), + Rvalue::Repeat(o, c) => MicroRvalue::Repeat(self.translate_operand(o), *c), + Rvalue::Ref(r, bk, p) => MicroRvalue::Ref(*r, *bk, *p), + Rvalue::ThreadLocalRef(d) => MicroRvalue::ThreadLocalRef(*d), + Rvalue::AddressOf(m, p) => MicroRvalue::AddressOf(*m, *p), + Rvalue::Len(p) => MicroRvalue::Len(*p), + Rvalue::Cast(ck, o, ty) => MicroRvalue::Cast(*ck, self.translate_operand(o), *ty), + Rvalue::BinaryOp(op, box (opa, opb)) => MicroRvalue::BinaryOp( + *op, + box (self.translate_operand(opa), self.translate_operand(opb)), + ), + Rvalue::CheckedBinaryOp(op, box (opa, opb)) => MicroRvalue::CheckedBinaryOp( + *op, + box (self.translate_operand(opa), self.translate_operand(opb)), + ), + Rvalue::NullaryOp(op, ty) => MicroRvalue::NullaryOp(*op, *ty), + Rvalue::UnaryOp(op, o) => MicroRvalue::UnaryOp(*op, self.translate_operand(o)), + Rvalue::Discriminant(p) => MicroRvalue::Discriminant(*p), + Rvalue::Aggregate(ak, ops) => MicroRvalue::Aggregate( + ak.clone(), + ops.iter().map(|o| self.translate_operand(o)).collect(), + ), + Rvalue::ShallowInitBox(o, ty) => { + MicroRvalue::ShallowInitBox(self.translate_operand(o), *ty) + } + Rvalue::CopyForDeref(p) => MicroRvalue::CopyForDeref(*p), + } + } +} diff --git a/micromir/src/defs/statement.rs b/micromir/src/defs/statement.rs index 4b0aa67c2d6..bf760d35801 100644 --- a/micromir/src/defs/statement.rs +++ b/micromir/src/defs/statement.rs @@ -5,22 +5,26 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ - index::vec::IndexVec, middle::{ mir::{ - Coverage, FakeReadCause, Local, NonDivergingIntrinsic, Place, RetagKind, - UserTypeProjection, + Coverage, FakeReadCause, Local, NonDivergingIntrinsic, Place, RetagKind, Statement, + StatementKind, UserTypeProjection, }, - ty::{self}, + ty, }, target::abi::VariantIdx, }; -use crate::{MicroRvalue, Temporary}; +use crate::{ + MicroCopyNonOverlapping, MicroNonDivergingIntrinsic, MicroRvalue, Operands, + PlaceCapabilitySummary, +}; #[derive(Clone, Debug)] pub struct MicroStatement<'tcx> { - pub operands: IndexVec, + pub repack_operands: Option>, + pub operands: Operands<'tcx>, + // pub repack_stmt: Option>, pub kind: MicroStatementKind<'tcx>, } @@ -38,7 +42,56 @@ pub enum MicroStatementKind<'tcx> { Retag(RetagKind, Box>), AscribeUserType(Box<(Place<'tcx>, UserTypeProjection)>, ty::Variance), Coverage(Box), - Intrinsic(Box>), + Intrinsic(Box), ConstEvalCounter, Nop, } + +impl<'tcx> From<&Statement<'tcx>> for MicroStatement<'tcx> { + fn from(stmt: &Statement<'tcx>) -> Self { + let mut operands = Operands::new(); + let kind = match &stmt.kind { + StatementKind::Assign(box (p, r)) => { + MicroStatementKind::Assign(box (*p, operands.translate_rvalue(r))) + } + StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (*c, *p)), + StatementKind::SetDiscriminant { + place, + variant_index, + } => MicroStatementKind::SetDiscriminant { + place: box **place, + variant_index: *variant_index, + }, + StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box *p), + StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(*l), + StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(*l), + StatementKind::Retag(k, box p) => MicroStatementKind::Retag(*k, box *p), + StatementKind::AscribeUserType(box (p, ty), v) => { + MicroStatementKind::AscribeUserType(box (*p, ty.clone()), *v) + } + StatementKind::Coverage(box c) => MicroStatementKind::Coverage(box c.clone()), + StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(o)) => { + MicroStatementKind::Intrinsic(box MicroNonDivergingIntrinsic::Assume( + operands.translate_operand(o), + )) + } + StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(c)) => { + MicroStatementKind::Intrinsic(box MicroNonDivergingIntrinsic::CopyNonOverlapping( + MicroCopyNonOverlapping { + src: operands.translate_operand(&c.src), + dst: operands.translate_operand(&c.dst), + count: operands.translate_operand(&c.count), + }, + )) + } + StatementKind::ConstEvalCounter => MicroStatementKind::ConstEvalCounter, + StatementKind::Nop => MicroStatementKind::Nop, + }; + MicroStatement { + repack_operands: None, + operands, + // repack_stmt: None, + kind, + } + } +} diff --git a/micromir/src/defs/terminator.rs b/micromir/src/defs/terminator.rs index 10e0f46e74a..228021d1403 100644 --- a/micromir/src/defs/terminator.rs +++ b/micromir/src/defs/terminator.rs @@ -4,13 +4,20 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use prusti_rustc_interface::middle::mir::{AssertMessage, BasicBlock, Place, SwitchTargets}; +use prusti_rustc_interface::{ + middle::mir::{AssertMessage, BasicBlock, Place, SwitchTargets, Terminator, TerminatorKind}, + span::Span, +}; -use crate::MicroOperand; +use crate::{MicroOperand, Operands, PlaceCapabilitySummary}; #[derive(Clone, Debug)] pub struct MicroTerminator<'tcx> { + pub repack_operands: Option>, + pub operands: Operands<'tcx>, pub kind: MicroTerminatorKind<'tcx>, + pub repack_join: Option>, + pub original_kind: TerminatorKind<'tcx>, } #[derive(Clone, Debug, PartialEq, Hash)] @@ -44,7 +51,7 @@ pub enum MicroTerminatorKind<'tcx> { target: Option, cleanup: Option, from_hir_call: bool, - // fn_span: Span, + fn_span: Span, }, Assert { cond: MicroOperand, @@ -77,3 +84,104 @@ pub enum MicroTerminatorKind<'tcx> { // cleanup: Option, // }, } + +impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { + fn from(term: &Terminator<'tcx>) -> Self { + let mut operands = Operands::new(); + let kind = match &term.kind { + &TerminatorKind::Goto { target } => MicroTerminatorKind::Goto { target }, + TerminatorKind::SwitchInt { discr, targets } => MicroTerminatorKind::SwitchInt { + discr: operands.translate_operand(discr), + targets: targets.clone(), + }, + TerminatorKind::Resume => MicroTerminatorKind::Resume, + TerminatorKind::Abort => MicroTerminatorKind::Abort, + TerminatorKind::Return => MicroTerminatorKind::Return, + TerminatorKind::Unreachable => MicroTerminatorKind::Unreachable, + &TerminatorKind::Drop { + place, + target, + unwind, + } => MicroTerminatorKind::Drop { + place, + target, + unwind, + }, + TerminatorKind::DropAndReplace { + place, + value, + target, + unwind, + } => MicroTerminatorKind::DropAndReplace { + place: *place, + value: operands.translate_operand(value), + target: *target, + unwind: *unwind, + }, + TerminatorKind::Call { + func, + args, + destination, + target, + cleanup, + from_hir_call, + fn_span, + } => MicroTerminatorKind::Call { + func: operands.translate_operand(func), + args: args.iter().map(|a| operands.translate_operand(a)).collect(), + destination: *destination, + target: *target, + cleanup: *cleanup, + from_hir_call: *from_hir_call, + fn_span: *fn_span, + }, + TerminatorKind::Assert { + cond, + expected, + msg, + target, + cleanup, + } => MicroTerminatorKind::Assert { + cond: operands.translate_operand(cond), + expected: *expected, + msg: msg.clone(), + target: *target, + cleanup: *cleanup, + }, + TerminatorKind::Yield { + value, + resume, + resume_arg, + drop, + } => MicroTerminatorKind::Yield { + value: operands.translate_operand(value), + resume: *resume, + resume_arg: *resume_arg, + drop: *drop, + }, + TerminatorKind::GeneratorDrop => MicroTerminatorKind::GeneratorDrop, + &TerminatorKind::FalseEdge { + real_target, + imaginary_target, + } => MicroTerminatorKind::FalseEdge { + real_target, + imaginary_target, + }, + &TerminatorKind::FalseUnwind { + real_target, + unwind, + } => MicroTerminatorKind::FalseUnwind { + real_target, + unwind, + }, + TerminatorKind::InlineAsm { .. } => todo!(), + }; + MicroTerminator { + repack_operands: None, + operands, + kind, + repack_join: None, + original_kind: term.kind.clone(), + } + } +} diff --git a/micromir/src/free_pcs/mod.rs b/micromir/src/free_pcs/mod.rs new file mode 100644 index 00000000000..4d469804e68 --- /dev/null +++ b/micromir/src/free_pcs/mod.rs @@ -0,0 +1,9 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mod permission; + +pub use permission::*; diff --git a/micromir/src/free_pcs/permission.rs b/micromir/src/free_pcs/permission.rs new file mode 100644 index 00000000000..a014a2e52c4 --- /dev/null +++ b/micromir/src/free_pcs/permission.rs @@ -0,0 +1,332 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::cmp::Ordering; + +use derive_more::{Deref, DerefMut}; + +use prusti_rustc_interface::{ + data_structures::fx::FxHashMap, + index::vec::IndexVec, + middle::mir::{Local, Place}, +}; + +use crate::PlaceRepacker; + +pub type FreeStateUpdate<'tcx> = LocalsState>; +#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut, Default)] +pub struct LocalUpdate<'tcx>((Option>, Option>)); + +impl<'tcx> LocalUpdate<'tcx> { + pub(crate) fn requires_unalloc(&mut self) { + Self::unalloc(&mut self.0 .0); + } + pub(crate) fn ensures_unalloc(&mut self) { + Self::unalloc(&mut self.0 .1); + } + fn unalloc(local: &mut Option>) { + if let Some(pre) = local { + assert_eq!(*pre, PermissionLocal::Unallocated); + } else { + *local = Some(PermissionLocal::Unallocated); + } + } + pub(crate) fn requires_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { + Self::alloc(&mut self.0 .0, place, perm); + } + pub(crate) fn ensures_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { + Self::alloc(&mut self.0 .1, place, perm); + } + fn alloc(local: &mut Option>, place: Place<'tcx>, perm: PermissionKind) { + if let Some(pre) = local { + let old = pre.get_allocated_mut().insert(place, perm); + assert!(old.is_none()); + } else { + *local = Some(PermissionLocal::Allocated( + PermissionProjections::new_update(place, perm), + )); + } + } + pub(crate) fn get_pre(&self) -> &Option> { + &self.0 .0 + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut)] +/// Generic state of a set of locals +pub struct LocalsState(IndexVec); + +/// The free pcs of all locals +pub type FreeState<'tcx> = LocalsState>; + +impl FromIterator for LocalsState { + fn from_iter>(iter: I) -> Self { + Self(IndexVec::from_iter(iter)) + } +} + +impl<'tcx> LocalsState> { + pub fn initial(local_count: usize, initial: impl Fn(Local) -> Option) -> Self { + Self(IndexVec::from_fn_n( + |local: Local| { + if let Some(perm) = initial(local) { + let places = PermissionProjections::new(local, perm); + PermissionLocal::Allocated(places) + } else { + PermissionLocal::Unallocated + } + }, + local_count, + )) + } + pub(crate) fn consistency_check(&self) { + for p in self.iter() { + p.consistency_check(); + } + } +} +impl LocalsState { + pub fn default(local_count: usize) -> Self + where + T: Default + Clone, + { + Self(IndexVec::from_elem_n(T::default(), local_count)) + } + pub fn empty(local_count: usize, initial: T) -> Self + where + T: Clone, + { + Self(IndexVec::from_elem_n(initial, local_count)) + } +} +impl<'tcx> LocalsState> { + pub fn update_free(self, state: &mut FreeState<'tcx>) { + for (local, LocalUpdate((pre, post))) in self.0.clone().into_iter_enumerated() { + if cfg!(debug_assertions) { + if let Some(pre) = pre { + match (&state[local], pre) { + (PermissionLocal::Unallocated, PermissionLocal::Unallocated) => {} + (PermissionLocal::Allocated(local_state), PermissionLocal::Allocated(pre)) => { + for (place, required_perm) in pre.iter() { + let perm = local_state.get(place).unwrap(); + let is_read = required_perm.is_shared() && perm.is_exclusive(); + assert!(perm == required_perm || is_read, "Req\n{self:#?}\n, have\n{state:#?}\n{place:#?}\n{perm:#?}\n{required_perm:#?}\n"); + } + } + _ => unreachable!(), + } + } + } + if let Some(post) = post { + match (post, &mut state[local]) { + (post @ PermissionLocal::Unallocated, _) + | (post, PermissionLocal::Unallocated) => state[local] = post, + (PermissionLocal::Allocated(post), PermissionLocal::Allocated(state)) => { + state.extend(post.0) + } + } + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// The permissions of a local +pub enum PermissionLocal<'tcx> { + Unallocated, + Allocated(PermissionProjections<'tcx>), +} + +impl<'tcx> PermissionLocal<'tcx> { + pub fn get_allocated_mut(&mut self) -> &mut PermissionProjections<'tcx> { + match self { + PermissionLocal::Allocated(places) => places, + _ => panic!(), + } + } + + fn consistency_check(&self) { + match self { + PermissionLocal::Unallocated => {} + PermissionLocal::Allocated(places) => { + places.consistency_check(); + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut)] +/// The permissions for all the projections of a place +// We only need the projection part of the place +pub struct PermissionProjections<'tcx>(FxHashMap, PermissionKind>); + +impl<'tcx> PermissionProjections<'tcx> { + pub fn new(local: Local, perm: PermissionKind) -> Self { + Self([(local.into(), perm)].into_iter().collect()) + } + pub fn new_uninit(local: Local) -> Self { + Self::new(local, PermissionKind::Uninit) + } + /// Should only be called when creating an update within `ModifiesFreeState` + pub(crate) fn new_update(place: Place<'tcx>, perm: PermissionKind) -> Self { + Self([(place, perm)].into_iter().collect()) + } + + /// Returns all related projections of the given place that are contained in this map. + /// A `Ordering::Less` means that the given `place` is a prefix of the iterator place. + /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] + pub fn find_all_related( + &self, + place: Place<'tcx>, + ) -> impl Iterator, PermissionKind)> + '_ { + self.iter().filter_map(move |(other, perm)| { + PlaceRepacker::partial_cmp(*other, place).map(|ord| (ord, *other, *perm)) + }) + } + pub(crate) fn unpack( + &mut self, + to: Place<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> Vec> { + // Inefficient to do the work here when not needed + debug_assert!(!self.contains_key(&to)); + let (ord, other, perm) = { + let mut related = self.find_all_related(to); + let r = related.next().unwrap(); + debug_assert!( + related.next().is_none(), + "{:?} ({to:?})", + self.find_all_related(to).collect::>() + ); + r + }; + assert!(ord == Ordering::Less); + let (expanded, others) = rp.expand(other, to); + self.remove(&other); + self.extend(others.into_iter().map(|p| (p, perm))); + self.insert(to, perm); + expanded + } + pub(crate) fn pack( + &mut self, + to: Place<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> Vec> { + // Inefficient to do the work here when not needed + debug_assert!(!self.contains_key(&to)); + let related: Vec<_> = self.find_all_related(to).collect(); + debug_assert!(related.len() > 0); + debug_assert!(related.iter().all(|(ord, _, _)| *ord == Ordering::Greater)); + debug_assert!(related.iter().all(|(_, _, perm)| *perm == related[0].2)); + let mut related_set = related.iter().map(|(_, p, _)| *p).collect(); + let collapsed = rp.collapse(to, &mut related_set); + assert!(related_set.is_empty()); + for (_, p, _) in &related { + self.remove(p); + } + self.insert(to, related[0].2); + collapsed + } + + pub(crate) fn join(&self, other: &mut Self, rp: PlaceRepacker<'_, 'tcx>) { + for (place, kind) in &**self { + let mut place = *place; + let mut expand: Vec<_>; + while { + expand = other + .iter() + .filter_map(|(&p, &k)| { + PlaceRepacker::expandable_no_enum(place, p).map(|o| (o, p, k)) + }) + .collect(); + expand.is_empty() + } { + place = rp.pop_till_enum(place); + } + debug_assert!(expand.iter().all(|o| o.0 == expand[0].0)); + for (_, other_place, perm) in &expand { + let cmp = kind.partial_cmp(&perm).unwrap(); + if cmp.is_lt() { + other.insert(*other_place, *kind); + } + } + match expand[0].0 { + // Current place has already been expanded in `other` + Ok(Ordering::Less) => (), + Ok(Ordering::Equal) => assert_eq!(expand.len(), 1), + Ok(Ordering::Greater) => { + assert_eq!(expand.len(), 1); + // Do expand + // TODO: remove duplicate code with above + let to_expand = expand[0].1; + let (_, others) = rp.expand(to_expand, place); + let perm = other.remove(&to_expand).unwrap(); + other.extend(others.into_iter().map(|p| (p, perm))); + other.insert(place, perm); + } + Err(Ordering::Less) => { + // Do collapse + // TODO: remove duplicate code with above + for (_, p, _) in &expand { + other.remove(p); + } + other.insert(place, *kind); + } + Err(Ordering::Equal) => unreachable!(), + // Current place has already been collapsed in `other` + Err(Ordering::Greater) => (), + } + } + } + + fn consistency_check(&self) { + let keys = self.keys().copied().collect::>(); + for (i, p1) in keys.iter().enumerate() { + for p2 in keys[i + 1..].iter() { + assert!( + PlaceRepacker::partial_cmp(*p1, *p2).is_none(), + "{p1:?} {p2:?}", + ); + } + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum PermissionKind { + Shared, + Exclusive, + Uninit, +} + +impl PartialOrd for PermissionKind { + fn partial_cmp(&self, other: &Self) -> Option { + if *self == *other { + return Some(Ordering::Equal); + } + match (self, other) { + (PermissionKind::Shared, PermissionKind::Exclusive) + | (PermissionKind::Uninit, PermissionKind::Exclusive) => Some(Ordering::Less), + (PermissionKind::Exclusive, PermissionKind::Shared) + | (PermissionKind::Exclusive, PermissionKind::Uninit) => Some(Ordering::Greater), + (PermissionKind::Shared, PermissionKind::Uninit) + | (PermissionKind::Uninit, PermissionKind::Shared) => None, + _ => unreachable!(), + } + } +} + +impl PermissionKind { + pub fn is_shared(self) -> bool { + self == PermissionKind::Shared + } + pub fn is_exclusive(self) -> bool { + self == PermissionKind::Exclusive + } + pub fn is_uninit(self) -> bool { + self == PermissionKind::Uninit + } +} diff --git a/micromir/src/lib.rs b/micromir/src/lib.rs index 5bbbc4388f4..02b8cca2b52 100644 --- a/micromir/src/lib.rs +++ b/micromir/src/lib.rs @@ -5,8 +5,12 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![feature(rustc_private)] #![feature(box_syntax, box_patterns)] +#![feature(drain_filter)] -mod translation; mod defs; +mod repack; +mod free_pcs; pub use defs::*; +pub use free_pcs::*; +pub use repack::*; diff --git a/micromir/src/repack/calculate.rs b/micromir/src/repack/calculate.rs new file mode 100644 index 00000000000..c0d3fe41c09 --- /dev/null +++ b/micromir/src/repack/calculate.rs @@ -0,0 +1,235 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + data_structures::{fx::FxHashSet, graph::WithStartNode}, + dataflow::storage, + index::vec::{Idx, IndexVec}, + middle::{ + mir::{BasicBlock, HasLocalDecls, Local, RETURN_PLACE}, + ty::TyCtxt, + }, +}; + +use crate::{ + FreeState, MicroBasicBlockData, MicroBasicBlocks, MicroBody, MicroStatement, MicroTerminator, + PermissionKind, PlaceCapabilitySummary, +}; + +use super::{place::PlaceRepacker, triple::ModifiesFreeState}; + +impl<'tcx> MicroBody<'tcx> { + fn initial_free_state(&self) -> FreeState<'tcx> { + let always_live = storage::always_storage_live_locals(&self.body); + let return_local = RETURN_PLACE; + let last_arg = Local::new(self.body.arg_count); + FreeState::initial(self.body.local_decls().len(), |local: Local| { + if local == return_local { + Some(PermissionKind::Uninit) + } else if always_live.contains(local) { + Some(PermissionKind::Exclusive) + } else if local <= last_arg { + Some(PermissionKind::Exclusive) + } else { + None + } + }) + } + pub fn calculate_repacking(&mut self, tcx: TyCtxt<'tcx>) { + // Safety check + assert!(!self.done_repacking); + self.done_repacking = true; + + // Calculate initial state + let state = self.initial_free_state(); + let preds = self.body.basic_blocks.predecessors(); + let rp = PlaceRepacker::new(&*self.body, tcx); + let start_node = self.body.basic_blocks.start_node(); + + // Do the actual repacking calculation + self.basic_blocks + .calculate_repacking(start_node, state, |bb| &preds[bb], rp); + } +} + +#[derive(Debug)] +struct Queue { + queue: Vec, + dirty_queue: FxHashSet, + done: IndexVec, + can_redo: IndexVec, +} +impl Queue { + fn new(start_node: BasicBlock, len: usize) -> Self { + let mut done = IndexVec::from_elem_n(false, len); + done[start_node] = true; + Self { + queue: Vec::new(), + dirty_queue: FxHashSet::default(), + done, + can_redo: IndexVec::from_elem_n(true, len), + } + } + fn add_succs<'a>( + &mut self, + term: &MicroTerminator, + preds: impl Fn(BasicBlock) -> &'a [BasicBlock], + ) { + for succ in term.original_kind.successors() { + if preds(succ).iter().all(|pred| self.done[*pred]) { + debug_assert!(!self.done[succ]); + self.queue.push(succ); + } else { + self.can_redo[succ] = true; + self.dirty_queue.insert(succ); + } + } + } + #[tracing::instrument(name = "Queue::pop", level = "debug", ret)] + fn pop(&mut self) -> Option { + if let Some(bb) = self.queue.pop() { + self.done[bb] = true; + Some(bb) + } else { + if self.dirty_queue.len() == 0 { + debug_assert!((0..self.done.len()) + .into_iter() + .map(BasicBlock::from_usize) + .all(|bb| self.done[bb] || !self.can_redo[bb])); + return None; + } + let bb = *self + .dirty_queue + .iter() + .filter(|bb| self.can_redo[**bb]) + .next() + .unwrap(); // Can this happen? If so probably a bug + self.can_redo[bb] = false; + self.dirty_queue.remove(&bb); + Some(bb) + } + } +} + +impl<'tcx> MicroBasicBlocks<'tcx> { + pub(crate) fn calculate_repacking<'a>( + &mut self, + start_node: BasicBlock, + initial: FreeState<'tcx>, + preds: impl Fn(BasicBlock) -> &'a [BasicBlock], + rp: PlaceRepacker<'_, 'tcx>, + ) { + debug_assert!(self + .basic_blocks + .indices() + .all(|bb| bb == start_node || !preds(bb).is_empty())); + + self.basic_blocks[start_node].calculate_repacking(initial, rp); + let mut queue = Queue::new(start_node, self.basic_blocks.len()); + queue.add_succs(&self.basic_blocks[start_node].terminator, &preds); + while let Some(can_do) = queue.pop() { + let is_cleanup = self.basic_blocks[can_do].is_cleanup; + let predecessors = self.get_pred_pcs(preds(can_do)); + let initial = Self::calculate_join(predecessors, is_cleanup, rp); + let changed = self.basic_blocks[can_do].calculate_repacking(initial, rp); + if changed { + queue.add_succs(&self.basic_blocks[can_do].terminator, &preds); + } + } + // debug_assert!(done.iter().all(|b| *b), "{done:?}"); + } + + fn get_pred_pcs( + &mut self, + predecessors: &[BasicBlock], + ) -> Vec<&mut PlaceCapabilitySummary<'tcx>> { + let predecessors = self + .basic_blocks + .iter_enumerated_mut() + .filter(|(bb, _)| predecessors.contains(bb)); + predecessors + .filter_map(|(_, bb)| bb.get_pcs_mut()) + .collect::>() + } + + fn calculate_join( + predecessors: Vec<&mut PlaceCapabilitySummary<'tcx>>, + is_cleanup: bool, + rp: PlaceRepacker<'_, 'tcx>, + ) -> FreeState<'tcx> { + let mut join = predecessors[0].state().clone(); + for pred in predecessors.iter().skip(1) { + pred.state().join(&mut join, is_cleanup, rp); + } + // TODO: calculate the repacking statements needed + // println!("join {join:#?} of\n{predecessors:#?}"); + join + } +} + +impl<'tcx> MicroBasicBlockData<'tcx> { + pub(crate) fn calculate_repacking( + &mut self, + mut incoming: FreeState<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> bool { + // Check that we haven't already calculated this + let pre_pcs = self + .statements + .first() + .map(|stmt| &stmt.repack_operands) + .unwrap_or_else(|| &self.terminator.repack_operands); + if pre_pcs + .as_ref() + .map(|pcs| pcs.state() == &incoming) + .unwrap_or_default() + { + return false; + } + // Do calculation for statements + for stmt in &mut self.statements { + incoming = stmt.calculate_repacking(incoming, rp); + } + // Do calculation for terminator + self.terminator.calculate_repacking(incoming, rp) + } +} + +impl<'tcx> MicroStatement<'tcx> { + #[tracing::instrument(level = "debug", skip(rp))] + pub(crate) fn calculate_repacking( + &mut self, + incoming: FreeState<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> FreeState<'tcx> { + let update = self.get_update(incoming.len()); + let (pcs, mut pre) = incoming.bridge(&update, rp); + self.repack_operands = Some(pcs); + update.update_free(&mut pre); + pre + } +} + +impl<'tcx> MicroTerminator<'tcx> { + #[tracing::instrument(level = "debug", skip(rp))] + pub(crate) fn calculate_repacking( + &mut self, + incoming: FreeState<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> bool { + let update = self.get_update(incoming.len()); + let (pcs, mut pre) = incoming.bridge(&update, rp); + self.repack_operands = Some(pcs); + update.update_free(&mut pre); + let changed = self + .repack_join + .as_ref() + .map(|pcs| pcs.state() != &pre) + .unwrap_or(true); + self.repack_join = Some(PlaceCapabilitySummary::empty(pre)); + changed + } +} diff --git a/micromir/src/repack/mod.rs b/micromir/src/repack/mod.rs new file mode 100644 index 00000000000..dbd0635a80d --- /dev/null +++ b/micromir/src/repack/mod.rs @@ -0,0 +1,14 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +mod repack; +mod calculate; +mod triple; +mod place; + +pub use calculate::*; +pub(crate) use place::*; +pub use repack::*; diff --git a/micromir/src/repack/place.rs b/micromir/src/repack/place.rs new file mode 100644 index 00000000000..e11e621b679 --- /dev/null +++ b/micromir/src/repack/place.rs @@ -0,0 +1,200 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::cmp::Ordering; + +use prusti_rustc_interface::{ + data_structures::fx::FxHashSet, + middle::{ + mir::{Body, Place, PlaceElem, ProjectionElem}, + ty::TyCtxt, + }, +}; + +#[derive(Copy, Clone)] +// TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate +pub(crate) struct PlaceRepacker<'a, 'tcx: 'a> { + mir: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, +} + +impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { + pub fn new(mir: &'a Body<'tcx>, tcx: TyCtxt<'tcx>) -> Self { + Self { mir, tcx } + } + + /// Check if the place `left` is a prefix of `right` or vice versa. For example: + /// + /// + `partial_cmp(x.f, y.f) == None` + /// + `partial_cmp(x.f, x.g) == None` + /// + `partial_cmp(x.f, x.f) == Some(Ordering::Equal)` + /// + `partial_cmp(x.f.g, x.f) == Some(Ordering::Greater)` + /// + `partial_cmp(x.f, x.f.g) == Some(Ordering::Less)` + pub fn partial_cmp(left: Place<'tcx>, right: Place<'tcx>) -> Option { + if left.local != right.local { + return None; + } + if left + .projection + .iter() + .zip(right.projection.iter()) + .any(|(e1, e2)| e1 != e2) + { + return None; + } + Some(left.projection.len().cmp(&right.projection.len())) + } + + /// Check if the place `potential_prefix` is a prefix of `place`. For example: + /// + /// + `is_prefix(x.f, x.f) == true` + /// + `is_prefix(x.f, x.f.g) == true` + /// + `is_prefix(x.f.g, x.f) == false` + fn is_prefix(potential_prefix: Place<'tcx>, place: Place<'tcx>) -> bool { + Self::partial_cmp(potential_prefix, place) + .map(|o| o != Ordering::Greater) + .unwrap_or(false) + } + + /// Expand `current_place` one level down by following the `guide_place`. + /// Returns the new `current_place` and a vector containing other places that + /// could have resulted from the expansion. + fn expand_one_level( + self, + current_place: Place<'tcx>, + guide_place: Place<'tcx>, + ) -> (Place<'tcx>, Vec>) { + use analysis::mir_utils::{expand_one_level, PlaceImpl}; + let res = expand_one_level(self.mir, self.tcx, current_place.into(), guide_place.into()); + ( + res.0.to_mir_place(), + res.1.into_iter().map(PlaceImpl::to_mir_place).collect(), + ) + } + + /// Subtract the `subtrahend` place from the `minuend` place. The + /// subtraction is defined as set minus between `minuend` place replaced + /// with a set of places that are unrolled up to the same level as + /// `subtrahend` and the singleton `subtrahend` set. For example, + /// `subtract(x.f, x.f.g.h)` is performed by unrolling `x.f` into + /// `{x.g, x.h, x.f.f, x.f.h, x.f.g.f, x.f.g.g, x.f.g.h}` and + /// subtracting `{x.f.g.h}` from it, which results into (`{x.f, x.f.g}`, `{x.g, x.h, + /// x.f.f, x.f.h, x.f.g.f, x.f.g.g}`). + #[tracing::instrument(level = "debug", skip(self), ret)] + pub fn expand( + self, + mut minuend: Place<'tcx>, + subtrahend: Place<'tcx>, + ) -> (Vec>, Vec>) { + assert!( + Self::is_prefix(minuend, subtrahend), + "The minuend ({minuend:?}) must be the prefix of the subtrahend ({subtrahend:?})." + ); + let mut place_set = Vec::new(); + let mut expanded = Vec::new(); + while minuend.projection.len() < subtrahend.projection.len() { + expanded.push(minuend); + let (new_minuend, places) = self.expand_one_level(minuend, subtrahend); + minuend = new_minuend; + place_set.extend(places); + } + (expanded, place_set) + } + + /// Try to collapse all places in `places` by following the + /// `guide_place`. This function is basically the reverse of + /// `expand`. + pub fn collapse( + self, + guide_place: Place<'tcx>, + places: &mut FxHashSet>, + ) -> Vec> { + let mut collapsed = Vec::new(); + let mut guide_places = vec![guide_place]; + while let Some(guide_place) = guide_places.pop() { + if !places.remove(&guide_place) { + let expand_guide = *places + .iter() + .find(|p| Self::is_prefix(guide_place, **p)) + .unwrap_or_else(|| { + panic!( + "The `places` set didn't contain all \ + the places required to construct the \ + `guide_place`. Currently tried to find \ + `{guide_place:?}` in `{places:?}`." + ) + }); + let (mut expanded, new_places) = self.expand(guide_place, expand_guide); + // Doing `collapsed.extend(expanded)` would result in a reversed order. + // Could also change this to `collapsed.push(expanded)` and return Vec>. + expanded.extend(collapsed); + collapsed = expanded; + guide_places.extend(new_places); + places.remove(&expand_guide); + } + } + collapsed + } + + /// Pop the last projection from the place and return the new place with the popped element. + pub fn try_pop_one_level(self, place: Place<'tcx>) -> Option<(PlaceElem<'tcx>, Place<'tcx>)> { + if place.projection.len() > 0 { + let last_index = place.projection.len() - 1; + let new_place = Place { + local: place.local, + projection: self.tcx.intern_place_elems(&place.projection[..last_index]), + }; + Some((place.projection[last_index], new_place)) + } else { + None + } + } + + // /// Pop the last element from the place if it is a dereference. + // pub fn try_pop_deref(self, place: Place<'tcx>) -> Option> { + // self.try_pop_one_level(place).and_then(|(elem, base)| { + // if let ProjectionElem::Deref = elem { + // Some(base) + // } else { + // None + // } + // }) + // } + + pub fn pop_till_enum(self, place: Place<'tcx>) -> Place<'tcx> { + let (mut elem, mut base) = self.try_pop_one_level(place).unwrap(); + while !matches!(elem, ProjectionElem::Downcast(..)) { + let (new_elem, new_base) = self.try_pop_one_level(base).unwrap(); + elem = new_elem; + base = new_base; + } + base + } + + /// Checks if we can expand either place to the other, without going through an enum. + /// If we can reach from one to the other, but need to go through an enum, we return `Err`. + pub fn expandable_no_enum( + left: Place<'tcx>, + right: Place<'tcx>, + ) -> Option> { + let ord = Self::partial_cmp(left, right)?; + let (minuend, subtrahend) = match ord { + Ordering::Greater => (right, left), + Ordering::Less => (left, right), + Ordering::Equal => return Some(Ok(Ordering::Equal)), + }; + if subtrahend + .projection + .iter() + .skip(minuend.projection.len()) + .any(|elem| matches!(elem, ProjectionElem::Downcast(..))) + { + Some(Err(ord)) + } else { + Some(Ok(ord)) + } + } +} diff --git a/micromir/src/repack/repack.rs b/micromir/src/repack/repack.rs new file mode 100644 index 00000000000..3777397d8b4 --- /dev/null +++ b/micromir/src/repack/repack.rs @@ -0,0 +1,183 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::cmp::Ordering; + +use prusti_rustc_interface::middle::mir::Place; + +use crate::{ + repack::place::PlaceRepacker, FreeState, FreeStateUpdate, LocalUpdate, PermissionKind, + PermissionLocal, PermissionProjections, +}; + +#[derive(Clone, Debug)] +pub struct PlaceCapabilitySummary<'tcx> { + state_before: FreeState<'tcx>, + repacks: Vec>, +} + +impl<'tcx> PlaceCapabilitySummary<'tcx> { + pub(crate) fn empty(state_before: FreeState<'tcx>) -> Self { + Self { + state_before, + repacks: Vec::new(), + } + } + pub fn state(&self) -> &FreeState<'tcx> { + &self.state_before + } + pub fn state_mut(&mut self) -> &mut FreeState<'tcx> { + &mut self.state_before + } + pub fn repacks(&self) -> &Vec> { + &self.repacks + } +} + +impl<'tcx> FreeState<'tcx> { + /// The `from` state should never contain any `DontCare` permissions + #[tracing::instrument(level = "debug", skip(rp), ret)] + pub(crate) fn bridge( + self, + update: &FreeStateUpdate<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> (PlaceCapabilitySummary<'tcx>, FreeState<'tcx>) { + if cfg!(debug_assertions) { + self.consistency_check(); + } + let mut repacks = Vec::new(); + let pre = update + .iter_enumerated() + .map(|(l, update)| PermissionLocal::bridge(&self[l], update, &mut repacks, rp)) + .collect(); + ( + PlaceCapabilitySummary { + state_before: self, + repacks, + }, + pre, + ) + } + + #[tracing::instrument(level = "debug", skip(rp))] + pub(crate) fn join(&self, to: &mut Self, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { + if cfg!(debug_assertions) { + self.consistency_check(); + } + for (l, to) in to.iter_enumerated_mut() { + PermissionLocal::join(&self[l], to, is_cleanup, rp); + } + } +} + +impl<'tcx> PermissionLocal<'tcx> { + #[tracing::instrument(level = "debug", skip(rp), ret)] + fn bridge( + &self, + update: &LocalUpdate<'tcx>, + repacks: &mut Vec>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> PermissionLocal<'tcx> { + match (self, update.get_pre()) { + (_, None) | (PermissionLocal::Unallocated, Some(PermissionLocal::Unallocated)) => { + self.clone() + } + (PermissionLocal::Allocated(from_places), Some(PermissionLocal::Allocated(places))) => { + let mut from_places = from_places.clone(); + for (&to_place, &to_kind) in &**places { + repacks.extend(from_places.repack(to_place, rp)); + let from_kind = *from_places.get(&to_place).unwrap(); + assert!( + from_kind >= to_kind, + "!({from_kind:?} >= {to_kind:?})" + ); + if from_kind == PermissionKind::Exclusive && to_kind == PermissionKind::Uninit + { + from_places.insert(to_place, to_kind); + repacks.push(RepackOp::Drop(to_place, from_kind)); + } + } + PermissionLocal::Allocated(from_places) + } + a => unreachable!("{:?}", a), + } + } + fn join(&self, to: &mut Self, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { + match (self, &mut *to) { + (PermissionLocal::Unallocated, PermissionLocal::Unallocated) => (), + (PermissionLocal::Allocated(from_places), PermissionLocal::Allocated(places)) => { + from_places.join(places, rp); + } + // Can jump to a `is_cleanup` block with some paths being alloc and other not + (PermissionLocal::Allocated(..), PermissionLocal::Unallocated) if is_cleanup => (), + (PermissionLocal::Unallocated, PermissionLocal::Allocated(..)) if is_cleanup => { + *to = PermissionLocal::Unallocated + } + a => unreachable!("{:?}", a), + }; + } +} + +impl<'tcx> PermissionProjections<'tcx> { + pub(crate) fn repack( + &mut self, + to: Place<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> Box> + '_> { + let mut related = self.find_all_related(to); + let (cmp, p, k) = related.next().unwrap(); + match cmp { + Ordering::Less => { + std::mem::drop(related); + box self + .unpack(to, rp) + .into_iter() + .map(move |p| RepackOp::Unpack(p, k)) + } + Ordering::Equal => box std::iter::empty(), + Ordering::Greater => { + let related = related.collect::>(); + let mut minimum = k; + for (_, _, other) in &related { + match minimum.partial_cmp(other) { + None => { + unreachable!("Cannot find minimum of ({p:?}, {k:?}) and {related:?}") + } + Some(Ordering::Greater) => { + minimum = *other; + } + _ => (), + } + } + let all_related = related + .into_iter() + .chain(std::iter::once((cmp, p, k))) + .filter(|(_, _, k)| *k != minimum); + // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` + // the exclusive permission will never be able to be recovered anymore! + let mut repacks: Vec<_> = all_related + .map(|(_, p, _k)| RepackOp::Drop(p, self.insert(p, minimum).unwrap())) + .collect(); + if minimum != PermissionKind::Uninit { + repacks = Vec::new(); + } + + box repacks.into_iter().chain( + self.pack(to, rp) + .into_iter() + .map(move |p| RepackOp::Unpack(p, k)), + ) + } + } + } +} + +#[derive(Clone, Debug)] +pub enum RepackOp<'tcx> { + Drop(Place<'tcx>, PermissionKind), + Pack(Place<'tcx>, PermissionKind), + Unpack(Place<'tcx>, PermissionKind), +} diff --git a/micromir/src/repack/triple.rs b/micromir/src/repack/triple.rs new file mode 100644 index 00000000000..1da5ee5fb0f --- /dev/null +++ b/micromir/src/repack/triple.rs @@ -0,0 +1,116 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::middle::mir::{Operand, RETURN_PLACE}; + +use crate::{ + FreeStateUpdate, MicroStatement, MicroStatementKind, MicroTerminator, MicroTerminatorKind, + Operands, PermissionKind, +}; + +pub(crate) trait ModifiesFreeState<'tcx> { + fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx>; +} + +impl<'tcx> ModifiesFreeState<'tcx> for Operands<'tcx> { + #[tracing::instrument(level = "debug")] + fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { + let mut update = FreeStateUpdate::default(locals); + for operand in &**self { + match *operand { + Operand::Copy(place) => { + update[place.local].requires_alloc(place, PermissionKind::Shared) + } + Operand::Move(place) => { + update[place.local].requires_alloc(place, PermissionKind::Exclusive); + update[place.local].ensures_alloc(place, PermissionKind::Uninit); + } + Operand::Constant(..) => (), + } + } + update + } +} + +impl<'tcx> ModifiesFreeState<'tcx> for MicroStatement<'tcx> { + #[tracing::instrument(level = "debug")] + fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { + let mut update = self.operands.get_update(locals); + match &self.kind { + MicroStatementKind::Assign(box (place, _)) => { + update[place.local].requires_alloc(*place, PermissionKind::Uninit); + update[place.local].ensures_alloc(*place, PermissionKind::Exclusive); + } + MicroStatementKind::FakeRead(box (_, place)) => { + update[place.local].requires_alloc(*place, PermissionKind::Shared) + } + MicroStatementKind::SetDiscriminant { box place, .. } => { + update[place.local].requires_alloc(*place, PermissionKind::Exclusive) + } + MicroStatementKind::Deinit(box place) => { + update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + update[place.local].ensures_alloc(*place, PermissionKind::Uninit); + } + MicroStatementKind::StorageLive(local) => { + update[*local].requires_unalloc(); + update[*local].ensures_alloc((*local).into(), PermissionKind::Uninit); + } + // TODO: The MIR is allowed to have multiple StorageDead statements for the same local. + // But right now we go `PermissionLocal::Allocated` -SD-> `PermissionLocal::Unallocated`, + // which would error when encountering a second StorageDead statement. + MicroStatementKind::StorageDead(local) => { + update[*local].requires_alloc((*local).into(), PermissionKind::Uninit); + update[*local].ensures_unalloc(); + } + MicroStatementKind::Retag(_, box place) => { + update[place.local].requires_alloc(*place, PermissionKind::Exclusive) + } + MicroStatementKind::AscribeUserType(..) + | MicroStatementKind::Coverage(..) + | MicroStatementKind::Intrinsic(..) + | MicroStatementKind::ConstEvalCounter + | MicroStatementKind::Nop => (), + }; + update + } +} + +impl<'tcx> ModifiesFreeState<'tcx> for MicroTerminator<'tcx> { + #[tracing::instrument(level = "debug")] + fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { + let mut update = self.operands.get_update(locals); + match &self.kind { + MicroTerminatorKind::Goto { .. } + | MicroTerminatorKind::SwitchInt { .. } + | MicroTerminatorKind::Resume + | MicroTerminatorKind::Abort + | MicroTerminatorKind::Unreachable + | MicroTerminatorKind::Assert { .. } + | MicroTerminatorKind::GeneratorDrop + | MicroTerminatorKind::FalseEdge { .. } + | MicroTerminatorKind::FalseUnwind { .. } => (), + MicroTerminatorKind::Return => { + update[RETURN_PLACE].requires_alloc(RETURN_PLACE.into(), PermissionKind::Exclusive) + } + MicroTerminatorKind::Drop { place, .. } => { + update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + update[place.local].ensures_alloc(*place, PermissionKind::Uninit); + } + MicroTerminatorKind::DropAndReplace { place, .. } => { + update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + } + MicroTerminatorKind::Call { destination, .. } => { + update[destination.local].requires_alloc(*destination, PermissionKind::Uninit); + update[destination.local].ensures_alloc(*destination, PermissionKind::Exclusive); + } + MicroTerminatorKind::Yield { resume_arg, .. } => { + update[resume_arg.local].requires_alloc(*resume_arg, PermissionKind::Uninit); + update[resume_arg.local].ensures_alloc(*resume_arg, PermissionKind::Exclusive); + } + }; + update + } +} diff --git a/micromir/src/translation/mod.rs b/micromir/src/translation/mod.rs deleted file mode 100644 index 8fae226d144..00000000000 --- a/micromir/src/translation/mod.rs +++ /dev/null @@ -1,144 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use prusti_rustc_interface::middle::mir::{BasicBlockData, Body, Statement, Terminator}; - -use crate::{MicroBasicBlockData, MicroBasicBlocks, MicroStatement, MicroTerminator}; - -pub(crate) fn translate_bbs<'tcx>(body: &Body<'tcx>) -> MicroBasicBlocks<'tcx> { - println!("body: {body:#?}"); - MicroBasicBlocks { - basic_blocks: body.basic_blocks.iter().map(translate_bb).collect(), - } -} - -pub(crate) fn translate_bb<'tcx>(data: &BasicBlockData<'tcx>) -> MicroBasicBlockData<'tcx> { - MicroBasicBlockData { - statements: data.statements.iter().map(translate_stmt).collect(), - terminator: data.terminator.as_ref().map(translate_term), - is_cleanup: data.is_cleanup, - } -} - -pub(crate) fn translate_stmt<'tcx>(_stmt: &Statement<'tcx>) -> MicroStatement<'tcx> { - todo!() - // let kind = match &stmt.kind { - // StatementKind::Assign(box (p, r)) => - // MicroStatementKind::Assign(box (*p, r.clone())), - // StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (*c, *p)), - // StatementKind::SetDiscriminant { - // place, - // variant_index, - // } => MicroStatementKind::SetDiscriminant { - // place: box **place, - // variant_index: *variant_index, - // }, - // StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box *p), - // StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(*l), - // StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(*l), - // StatementKind::Retag(k, box p) => MicroStatementKind::Retag(*k, box *p), - // StatementKind::AscribeUserType(box (p, ty), v) => { - // MicroStatementKind::AscribeUserType(box (*p, ty.clone()), *v) - // } - // StatementKind::Coverage(box c) => MicroStatementKind::Coverage(box c.clone()), - // StatementKind::Intrinsic(box i) => MicroStatementKind::Intrinsic(box i.clone()), - // StatementKind::Nop => MicroStatementKind::Nop, - // }; - // MicroStatement { operands: IndexVec::new(), kind } -} - -pub(crate) fn translate_term<'tcx>(_term: &Terminator<'tcx>) -> MicroTerminator<'tcx> { - todo!() - // let kind = match &term.kind { - // &TerminatorKind::Goto { target } => MicroTerminatorKind::Goto { target }, - // TerminatorKind::SwitchInt { discr, targets } => MicroTerminatorKind::SwitchInt { - // discr: discr.clone(), - // targets: targets.clone(), - // }, - // TerminatorKind::Resume => MicroTerminatorKind::Resume, - // TerminatorKind::Abort => MicroTerminatorKind::Abort, - // TerminatorKind::Return => MicroTerminatorKind::Return, - // TerminatorKind::Unreachable => MicroTerminatorKind::Unreachable, - // &TerminatorKind::Drop { - // place, - // target, - // unwind, - // } => MicroTerminatorKind::Drop { - // place, - // target, - // unwind, - // }, - // TerminatorKind::DropAndReplace { - // place, - // value, - // target, - // unwind, - // } => MicroTerminatorKind::DropAndReplace { - // place: *place, - // value: value.clone(), - // target: *target, - // unwind: *unwind, - // }, - // TerminatorKind::Call { - // func, - // args, - // destination, - // target, - // cleanup, - // from_hir_call, - // fn_span: _, - // } => MicroTerminatorKind::Call { - // func: func.clone(), - // args: args.clone(), - // destination: *destination, - // target: *target, - // cleanup: *cleanup, - // from_hir_call: *from_hir_call, - // // fn_span: *fn_span, - // }, - // TerminatorKind::Assert { - // cond, - // expected, - // msg, - // target, - // cleanup, - // } => MicroTerminatorKind::Assert { - // cond: cond.clone(), - // expected: *expected, - // msg: msg.clone(), - // target: *target, - // cleanup: *cleanup, - // }, - // TerminatorKind::Yield { - // value, - // resume, - // resume_arg, - // drop, - // } => MicroTerminatorKind::Yield { - // value: value.clone(), - // resume: *resume, - // resume_arg: *resume_arg, - // drop: *drop, - // }, - // TerminatorKind::GeneratorDrop => MicroTerminatorKind::GeneratorDrop, - // &TerminatorKind::FalseEdge { - // real_target, - // imaginary_target, - // } => MicroTerminatorKind::FalseEdge { - // real_target, - // imaginary_target, - // }, - // &TerminatorKind::FalseUnwind { - // real_target, - // unwind, - // } => MicroTerminatorKind::FalseUnwind { - // real_target, - // unwind, - // }, - // TerminatorKind::InlineAsm { .. } => todo!(), - // }; - // MicroTerminator { kind } -} diff --git a/prusti-interface/Cargo.toml b/prusti-interface/Cargo.toml index f7e19dd0f3e..1812b1f52f1 100644 --- a/prusti-interface/Cargo.toml +++ b/prusti-interface/Cargo.toml @@ -28,6 +28,7 @@ rustc-hash = "1.1.0" datafrog = "2.0.1" vir = { path = "../vir" } version-compare = "0.1" +micromir = { path = "../micromir" } [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index 17c49bbd01c..fabb99e076d 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -44,7 +44,8 @@ impl<'tcx> Procedure<'tcx> { let mir = env .body .get_impure_fn_body_identity(proc_def_id.expect_local()); - let _micro_mir = MicroBody::new(mir.body()); + let micro_mir = MicroBody::new(mir.body(), env.tcx()); + println!("--------\n{:?}\n--------", micro_mir.basic_blocks); let real_edges = RealEdges::new(&mir); let reachable_basic_blocks = build_reachable_basic_blocks(&mir, &real_edges); let nonspec_basic_blocks = build_nonspec_basic_blocks(env.query, &mir, &real_edges); From d86eabb7b00cefb0280a7df5ed30c7946a5e6877 Mon Sep 17 00:00:00 2001 From: Jonas Date: Mon, 6 Mar 2023 19:11:17 +0000 Subject: [PATCH 03/32] More work on MicroMir --- micromir/Cargo.toml | 10 +- micromir/src/check/checker.rs | 136 ++++++ micromir/src/check/mod.rs | 7 + micromir/src/defs/body.rs | 64 ++- micromir/src/defs/operand.rs | 50 +- micromir/src/defs/rvalue.rs | 33 +- micromir/src/defs/statement.rs | 98 +++- micromir/src/defs/terminator.rs | 232 +++++++++- micromir/src/free_pcs/permission.rs | 433 +++++++++++++----- micromir/src/lib.rs | 22 +- micromir/src/repack/calculate.rs | 106 ++++- micromir/src/repack/mod.rs | 2 +- micromir/src/repack/place.rs | 270 ++++++----- micromir/src/repack/repack.rs | 290 ++++++++---- micromir/src/repack/triple.rs | 80 ++-- micromir/src/utils/mod.rs | 7 + micromir/src/utils/place.rs | 235 ++++++++++ micromir/tests/top_crates.rs | 122 +++++ prusti-interface/Cargo.toml | 1 - prusti-interface/src/environment/procedure.rs | 7 +- prusti-utils/src/config.rs | 6 + prusti/Cargo.toml | 1 + prusti/src/callbacks.rs | 7 +- x.py | 1 + 24 files changed, 1788 insertions(+), 432 deletions(-) create mode 100644 micromir/src/check/checker.rs create mode 100644 micromir/src/check/mod.rs create mode 100644 micromir/src/utils/mod.rs create mode 100644 micromir/src/utils/place.rs create mode 100644 micromir/tests/top_crates.rs diff --git a/micromir/Cargo.toml b/micromir/Cargo.toml index 72528ee8269..847715dface 100644 --- a/micromir/Cargo.toml +++ b/micromir/Cargo.toml @@ -7,9 +7,17 @@ edition = "2021" [dependencies] derive_more = "0.99" tracing = { path = "../tracing" } -analysis = { path = "../analysis" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } +# TODO: remove this dep +prusti-interface = { path = "../prusti-interface" } + +[dev-dependencies] +reqwest = { version = "^0.11", features = ["blocking"] } +serde = "^1.0" +serde_derive = "^1.0" +serde_json = "^1.0" + [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] rustc_private = true diff --git a/micromir/src/check/checker.rs b/micromir/src/check/checker.rs new file mode 100644 index 00000000000..6fcf1a64575 --- /dev/null +++ b/micromir/src/check/checker.rs @@ -0,0 +1,136 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::data_structures::fx::FxHashMap; + +use crate::{ + repack::triple::ModifiesFreeState, FreeState, MicroBasicBlocks, PermissionKind, + PermissionLocal, PlaceOrdering, PlaceRepacker, RepackOp, Repacks, +}; + +pub(crate) fn check<'tcx>(bbs: &MicroBasicBlocks<'tcx>, rp: PlaceRepacker<'_, 'tcx>) { + for bb in bbs.basic_blocks.iter() { + let mut curr_state = bb.get_start_state().clone(); + // Consistency + curr_state.consistency_check(rp); + for stmt in bb.statements.iter() { + // Pre-state + let pcs = stmt.repack_operands.as_ref().unwrap(); + assert_eq!(pcs.state(), &curr_state); + // Repacks + pcs.repacks().update_free(&mut curr_state, false, rp); + // Consistency + curr_state.consistency_check(rp); + // Statement + stmt.get_update(curr_state.len()) + .update_free(&mut curr_state); + // Consistency + curr_state.consistency_check(rp); + } + // Pre-state + let pcs = bb.terminator.repack_operands.as_ref().unwrap(); + assert_eq!(pcs.state(), &curr_state); + // Repacks + pcs.repacks().update_free(&mut curr_state, false, rp); + // Consistency + curr_state.consistency_check(rp); + // Terminator + bb.terminator + .get_update(curr_state.len()) + .update_free(&mut curr_state); + // Consistency + curr_state.consistency_check(rp); + // Join repacks + let pcs = bb.terminator.repack_join.as_ref().unwrap(); + assert_eq!(pcs.state(), &curr_state); + for succ in bb.terminator.original_kind.successors() { + let mut curr_state = curr_state.clone(); + // No repack means that `succ` only has one predecessor + if let Some(repack) = pcs.repacks().get(&succ) { + repack.update_free(&mut curr_state, bbs.basic_blocks[succ].is_cleanup, rp); + // Consistency + curr_state.consistency_check(rp); + } + assert_eq!( + bbs.basic_blocks[succ].get_start_state(), + &curr_state, + "{succ:?}" + ); + } + } +} + +impl<'tcx> Repacks<'tcx> { + #[tracing::instrument(level = "debug", skip(rp))] + fn update_free( + &self, + state: &mut FreeState<'tcx>, + can_dealloc: bool, + rp: PlaceRepacker<'_, 'tcx>, + ) { + for rpck in &**self { + match rpck { + RepackOp::Weaken(place, from, to) => { + let curr_state = state[place.local].get_allocated_mut(); + let old = curr_state.insert(*place, *to); + assert_eq!(old, Some(*from), "{rpck:?}, {curr_state:?}"); + } + &RepackOp::DeallocForCleanup(local) => { + assert!(can_dealloc); + let curr_state = state[local].get_allocated_mut(); + assert_eq!(curr_state.len(), 1); + assert_eq!(curr_state[&local.into()], PermissionKind::Uninit); + state[local] = PermissionLocal::Unallocated; + } + &RepackOp::DeallocUnknown(local) => { + assert!(!can_dealloc); + let curr_state = state[local].get_allocated_mut(); + assert_eq!(curr_state.len(), 1); + assert_eq!(curr_state[&local.into()], PermissionKind::Uninit); + state[local] = PermissionLocal::Unallocated; + } + RepackOp::Pack(place, guide, kind) => { + assert_eq!( + place.partial_cmp(*guide), + Some(PlaceOrdering::Prefix), + "{rpck:?}" + ); + let curr_state = state[place.local].get_allocated_mut(); + let mut removed = curr_state + .drain_filter(|p, _| place.related_to(*p)) + .collect::>(); + let (p, others) = rp.expand_one_level(*place, *guide); + assert!(others + .into_iter() + .chain(std::iter::once(p)) + .all(|p| removed.remove(&p).unwrap() == *kind)); + assert!(removed.is_empty(), "{rpck:?}, {removed:?}"); + + let old = curr_state.insert(*place, *kind); + assert_eq!(old, None); + } + RepackOp::Unpack(place, guide, kind) => { + assert_eq!( + place.partial_cmp(*guide), + Some(PlaceOrdering::Prefix), + "{rpck:?}" + ); + let curr_state = state[place.local].get_allocated_mut(); + assert_eq!( + curr_state.remove(place), + Some(*kind), + "{rpck:?} ({:?})", + &**curr_state + ); + + let (p, others) = rp.expand_one_level(*place, *guide); + curr_state.insert(p, *kind); + curr_state.extend(others.into_iter().map(|p| (p, *kind))); + } + } + } + } +} diff --git a/micromir/src/check/mod.rs b/micromir/src/check/mod.rs new file mode 100644 index 00000000000..a5f8a9cc024 --- /dev/null +++ b/micromir/src/check/mod.rs @@ -0,0 +1,7 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +pub(crate) mod checker; diff --git a/micromir/src/defs/body.rs b/micromir/src/defs/body.rs index 631cb062cda..44d6f81e694 100644 --- a/micromir/src/defs/body.rs +++ b/micromir/src/defs/body.rs @@ -12,9 +12,14 @@ use prusti_rustc_interface::{ ty::TyCtxt, }, }; -use std::rc::Rc; +use std::{ + fmt::{Display, Formatter, Result}, + rc::Rc, +}; -use crate::{MicroStatement, MicroTerminator, PlaceCapabilitySummary}; +use crate::{ + FreeState, MicroStatement, MicroTerminator, TermDebug, TerminatorPlaceCapabilitySummary, +}; #[derive(Clone, Debug, Deref, DerefMut)] pub struct MicroBody<'tcx> { @@ -51,7 +56,7 @@ pub struct MicroBasicBlocks<'tcx> { } impl<'tcx> From<&Body<'tcx>> for MicroBasicBlocks<'tcx> { - #[tracing::instrument(level = "debug", skip(body), fields(body = format!("{body:#?}")))] + #[tracing::instrument(level = "info", skip(body), fields(body = format!("{body:#?}")))] fn from(body: &Body<'tcx>) -> Self { Self { basic_blocks: body @@ -63,6 +68,48 @@ impl<'tcx> From<&Body<'tcx>> for MicroBasicBlocks<'tcx> { } } +impl Display for MicroBasicBlocks<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + for (bb, data) in self.basic_blocks.iter_enumerated() { + writeln!(f, "{bb:?}: {{")?; + for stmt in &data.statements { + let repack = stmt.repack_operands.as_ref().unwrap(); + writeln!(f, " // {}", repack.state())?; + for rpck in &**repack.repacks() { + writeln!(f, " {rpck:?};")?; + } + for (tmp, operand) in stmt.operands.iter_enumerated() { + writeln!(f, " {tmp:?} <- {operand:?};")?; + } + writeln!(f, " {:?};", stmt.kind)?; + } + let repack = data.terminator.repack_operands.as_ref().unwrap(); + writeln!(f, " // {}", repack.state())?; + for rpck in &**repack.repacks() { + writeln!(f, " {rpck:?};")?; + } + for (tmp, operand) in data.terminator.operands.iter_enumerated() { + writeln!(f, " {tmp:?} <- {operand:?};")?; + } + let display = TermDebug(&data.terminator.kind, &data.terminator.original_kind); + writeln!(f, " {display:?};")?; + let repack = data.terminator.repack_join.as_ref().unwrap(); + // writeln!(f, " // {}", repack.state())?; + for (bb, repacks) in repack.repacks().iter() { + if repacks.is_empty() { + continue; + } + writeln!(f, " {bb:?}:")?; + for rpck in &**repacks { + writeln!(f, " {rpck:?};")?; + } + } + writeln!(f, "}}")?; + } + Ok(()) + } +} + #[derive(Clone, Debug)] pub struct MicroBasicBlockData<'tcx> { pub statements: Vec>, @@ -81,7 +128,16 @@ impl<'tcx> From<&BasicBlockData<'tcx>> for MicroBasicBlockData<'tcx> { } impl<'tcx> MicroBasicBlockData<'tcx> { - pub(crate) fn get_pcs_mut(&mut self) -> Option<&mut PlaceCapabilitySummary<'tcx>> { + pub(crate) fn get_start_state(&self) -> &FreeState<'tcx> { + if self.statements.is_empty() { + self.terminator.repack_operands.as_ref().unwrap().state() + } else { + self.statements[0].repack_operands.as_ref().unwrap().state() + } + } + pub(crate) fn get_end_pcs_mut( + &mut self, + ) -> Option<&mut TerminatorPlaceCapabilitySummary<'tcx>> { self.terminator.repack_join.as_mut() } } diff --git a/micromir/src/defs/operand.rs b/micromir/src/defs/operand.rs index 617e3f2db4b..9eb60715523 100644 --- a/micromir/src/defs/operand.rs +++ b/micromir/src/defs/operand.rs @@ -7,33 +7,61 @@ use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ index::vec::{Idx, IndexVec}, - middle::mir::Operand, + middle::mir::{Constant, Operand}, }; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Formatter, Result}; -#[derive(Clone, Debug, Deref, DerefMut)] -pub struct Operands<'tcx> { - operands: IndexVec>, +use crate::Place; + +#[derive(Clone, Debug, PartialEq, Hash)] +pub enum MicroFullOperand<'tcx> { + Copy(Place<'tcx>), + Move(Place<'tcx>), + Constant(Box>), +} + +impl<'tcx> From<&Operand<'tcx>> for MicroFullOperand<'tcx> { + fn from(value: &Operand<'tcx>) -> Self { + match value { + &Operand::Copy(p) => MicroFullOperand::Copy(p.into()), + &Operand::Move(p) => MicroFullOperand::Move(p.into()), + Operand::Constant(c) => MicroFullOperand::Constant(c.clone()), + } + } } + +/// Note that one can have the same `Local` multiple times in the `Operands` vector +/// for a single statement. For example in the following code: +/// ``` +/// struct S { a: bool, b: bool, c: bool } +/// fn true_a(s: &S) -> S { +/// S { a: true, .. *s } +/// } +/// ``` +#[derive(Clone, Debug, Deref, DerefMut)] +pub struct Operands<'tcx>(IndexVec>); impl<'tcx> Operands<'tcx> { pub(crate) fn new() -> Self { - Self { - operands: IndexVec::new(), - } + Self(IndexVec::new()) } pub(crate) fn translate_operand(&mut self, operand: &Operand<'tcx>) -> MicroOperand { - let index = self.operands.push(operand.clone()); + let index = self.push(operand.into()); MicroOperand::new(index) } } -#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Deref, DerefMut)] +#[derive(Clone, Copy, Hash, Eq, PartialEq, Deref, DerefMut)] pub struct MicroOperand(Temporary); impl MicroOperand { pub const fn new(value: Temporary) -> Self { Self(value) } } +impl Debug for MicroOperand { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{:?}", self.0) + } +} #[derive(Clone, Copy, Hash, Eq, PartialEq)] pub struct Temporary { @@ -66,7 +94,7 @@ impl Idx for Temporary { } } impl Debug for Temporary { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "tmp{}", self.private) } } diff --git a/micromir/src/defs/rvalue.rs b/micromir/src/defs/rvalue.rs index 4c365cc5a60..018fadbc6b6 100644 --- a/micromir/src/defs/rvalue.rs +++ b/micromir/src/defs/rvalue.rs @@ -4,12 +4,12 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{MicroOperand, Operands}; +use std::fmt::{Display, Formatter, Result}; + +use crate::{MicroOperand, Operands, Place}; use prusti_rustc_interface::{ middle::{ - mir::{ - AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Place, Rvalue, UnOp, - }, + mir::{AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Rvalue, UnOp}, ty::{self, Region, Ty}, }, span::def_id::DefId, @@ -20,6 +20,21 @@ pub enum MicroNonDivergingIntrinsic { Assume(MicroOperand), CopyNonOverlapping(MicroCopyNonOverlapping), } + +impl Display for MicroNonDivergingIntrinsic { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Self::Assume(op) => write!(f, "assume({op:?})"), + Self::CopyNonOverlapping(MicroCopyNonOverlapping { src, dst, count }) => { + write!( + f, + "copy_nonoverlapping(dst = {dst:?}, src = {src:?}, count = {count:?})" + ) + } + } + } +} + #[derive(Clone, Debug, PartialEq, Hash)] pub struct MicroCopyNonOverlapping { pub src: MicroOperand, @@ -51,10 +66,10 @@ impl<'tcx> Operands<'tcx> { match rvalue { Rvalue::Use(o) => MicroRvalue::Use(self.translate_operand(o)), Rvalue::Repeat(o, c) => MicroRvalue::Repeat(self.translate_operand(o), *c), - Rvalue::Ref(r, bk, p) => MicroRvalue::Ref(*r, *bk, *p), + &Rvalue::Ref(r, bk, p) => MicroRvalue::Ref(r, bk, p.into()), Rvalue::ThreadLocalRef(d) => MicroRvalue::ThreadLocalRef(*d), - Rvalue::AddressOf(m, p) => MicroRvalue::AddressOf(*m, *p), - Rvalue::Len(p) => MicroRvalue::Len(*p), + &Rvalue::AddressOf(m, p) => MicroRvalue::AddressOf(m, p.into()), + &Rvalue::Len(p) => MicroRvalue::Len(p.into()), Rvalue::Cast(ck, o, ty) => MicroRvalue::Cast(*ck, self.translate_operand(o), *ty), Rvalue::BinaryOp(op, box (opa, opb)) => MicroRvalue::BinaryOp( *op, @@ -66,7 +81,7 @@ impl<'tcx> Operands<'tcx> { ), Rvalue::NullaryOp(op, ty) => MicroRvalue::NullaryOp(*op, *ty), Rvalue::UnaryOp(op, o) => MicroRvalue::UnaryOp(*op, self.translate_operand(o)), - Rvalue::Discriminant(p) => MicroRvalue::Discriminant(*p), + &Rvalue::Discriminant(p) => MicroRvalue::Discriminant(p.into()), Rvalue::Aggregate(ak, ops) => MicroRvalue::Aggregate( ak.clone(), ops.iter().map(|o| self.translate_operand(o)).collect(), @@ -74,7 +89,7 @@ impl<'tcx> Operands<'tcx> { Rvalue::ShallowInitBox(o, ty) => { MicroRvalue::ShallowInitBox(self.translate_operand(o), *ty) } - Rvalue::CopyForDeref(p) => MicroRvalue::CopyForDeref(*p), + &Rvalue::CopyForDeref(p) => MicroRvalue::CopyForDeref(p.into()), } } } diff --git a/micromir/src/defs/statement.rs b/micromir/src/defs/statement.rs index bf760d35801..e8fa70c9ced 100644 --- a/micromir/src/defs/statement.rs +++ b/micromir/src/defs/statement.rs @@ -4,10 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use core::fmt::{Debug, Formatter, Result}; use prusti_rustc_interface::{ middle::{ mir::{ - Coverage, FakeReadCause, Local, NonDivergingIntrinsic, Place, RetagKind, Statement, + Coverage, FakeReadCause, Local, NonDivergingIntrinsic, RetagKind, Statement, StatementKind, UserTypeProjection, }, ty, @@ -16,11 +17,15 @@ use prusti_rustc_interface::{ }; use crate::{ - MicroCopyNonOverlapping, MicroNonDivergingIntrinsic, MicroRvalue, Operands, + MicroCopyNonOverlapping, MicroNonDivergingIntrinsic, MicroRvalue, Operands, Place, PlaceCapabilitySummary, }; -#[derive(Clone, Debug)] +#[derive(Clone)] +/// Note that in rare cases an operand and the target of kind can be the same place! +/// For example, the following function: +/// https://github.com/dtolnay/syn/blob/636509368ed9dbfad8bf3d15f84b0046804a1c14/src/bigint.rs#L13-L29 +/// generates a `MicroStatement { operands: [Copy(_5), Move(_22)], stmt: _5 = BinaryOp(BitOr, (tmp0, tmp1)) }` pub struct MicroStatement<'tcx> { pub repack_operands: Option>, pub operands: Operands<'tcx>, @@ -28,7 +33,7 @@ pub struct MicroStatement<'tcx> { pub kind: MicroStatementKind<'tcx>, } -#[derive(Clone, Debug, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum MicroStatementKind<'tcx> { Assign(Box<(Place<'tcx>, MicroRvalue<'tcx>)>), FakeRead(Box<(FakeReadCause, Place<'tcx>)>), @@ -52,22 +57,22 @@ impl<'tcx> From<&Statement<'tcx>> for MicroStatement<'tcx> { let mut operands = Operands::new(); let kind = match &stmt.kind { StatementKind::Assign(box (p, r)) => { - MicroStatementKind::Assign(box (*p, operands.translate_rvalue(r))) + MicroStatementKind::Assign(box ((*p).into(), operands.translate_rvalue(r))) } - StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (*c, *p)), - StatementKind::SetDiscriminant { - place, + &StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (c, p.into())), + &StatementKind::SetDiscriminant { + box place, variant_index, } => MicroStatementKind::SetDiscriminant { - place: box **place, - variant_index: *variant_index, + place: box place.into(), + variant_index: variant_index, }, - StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box *p), - StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(*l), - StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(*l), - StatementKind::Retag(k, box p) => MicroStatementKind::Retag(*k, box *p), + &StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box p.into()), + &StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(l), + &StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(l), + &StatementKind::Retag(k, box p) => MicroStatementKind::Retag(k, box p.into()), StatementKind::AscribeUserType(box (p, ty), v) => { - MicroStatementKind::AscribeUserType(box (*p, ty.clone()), *v) + MicroStatementKind::AscribeUserType(box ((*p).into(), ty.clone()), *v) } StatementKind::Coverage(box c) => MicroStatementKind::Coverage(box c.clone()), StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(o)) => { @@ -95,3 +100,66 @@ impl<'tcx> From<&Statement<'tcx>> for MicroStatement<'tcx> { } } } + +impl Debug for MicroStatement<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + let mut dbg = fmt.debug_struct("MicroStatement"); + if let Some(repack) = &self.repack_operands { + dbg.field("pcs", repack); + } + if self.operands.len() > 0 { + dbg.field("operands", &*self.operands); + } + dbg.field("stmt", &self.kind); + dbg.finish() + } +} + +impl Debug for MicroStatementKind<'_> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + use MicroStatementKind::*; + match self { + Assign(box (ref place, ref rv)) => write!(fmt, "{:?} = {:?}", place, rv), + FakeRead(box (ref cause, ref place)) => { + write!(fmt, "FakeRead({:?}, {:?})", cause, place) + } + Retag(ref kind, ref place) => write!( + fmt, + "Retag({}{:?})", + match kind { + RetagKind::FnEntry => "[fn entry] ", + RetagKind::TwoPhase => "[2phase] ", + RetagKind::Raw => "[raw] ", + RetagKind::Default => "", + }, + place, + ), + StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place), + StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place), + SetDiscriminant { + ref place, + variant_index, + } => { + write!(fmt, "discriminant({:?}) = {:?}", place, variant_index) + } + Deinit(ref place) => write!(fmt, "Deinit({:?})", place), + AscribeUserType(box (ref place, ref c_ty), ref variance) => { + write!( + fmt, + "AscribeUserType({:?}, {:?}, {:?})", + place, variance, c_ty + ) + } + Coverage(box self::Coverage { + ref kind, + code_region: Some(ref rgn), + }) => { + write!(fmt, "Coverage::{:?} for {:?}", kind, rgn) + } + Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), + Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), + ConstEvalCounter => write!(fmt, "ConstEvalCounter"), + Nop => write!(fmt, "nop"), + } + } +} diff --git a/micromir/src/defs/terminator.rs b/micromir/src/defs/terminator.rs index 228021d1403..fff450b7d82 100644 --- a/micromir/src/defs/terminator.rs +++ b/micromir/src/defs/terminator.rs @@ -4,23 +4,30 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use core::fmt::{Debug, Formatter, Result, Write}; use prusti_rustc_interface::{ - middle::mir::{AssertMessage, BasicBlock, Place, SwitchTargets, Terminator, TerminatorKind}, + middle::mir::{AssertMessage, BasicBlock, SwitchTargets, Terminator, TerminatorKind}, span::Span, }; +use std::{borrow::Cow, iter}; -use crate::{MicroOperand, Operands, PlaceCapabilitySummary}; +use crate::{ + FreeState, MicroOperand, Operands, Place, PlaceCapabilitySummary, + TerminatorPlaceCapabilitySummary, +}; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct MicroTerminator<'tcx> { pub repack_operands: Option>, pub operands: Operands<'tcx>, pub kind: MicroTerminatorKind<'tcx>, - pub repack_join: Option>, + pub repack_join: Option>, + // TODO: debug only + pub previous_rjs: Vec>, pub original_kind: TerminatorKind<'tcx>, } -#[derive(Clone, Debug, PartialEq, Hash)] +#[derive(Clone, PartialEq, Hash)] pub enum MicroTerminatorKind<'tcx> { Goto { target: BasicBlock, @@ -103,7 +110,7 @@ impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { target, unwind, } => MicroTerminatorKind::Drop { - place, + place: place.into(), target, unwind, }, @@ -113,7 +120,7 @@ impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { target, unwind, } => MicroTerminatorKind::DropAndReplace { - place: *place, + place: (*place).into(), value: operands.translate_operand(value), target: *target, unwind: *unwind, @@ -129,7 +136,7 @@ impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { } => MicroTerminatorKind::Call { func: operands.translate_operand(func), args: args.iter().map(|a| operands.translate_operand(a)).collect(), - destination: *destination, + destination: (*destination).into(), target: *target, cleanup: *cleanup, from_hir_call: *from_hir_call, @@ -156,7 +163,7 @@ impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { } => MicroTerminatorKind::Yield { value: operands.translate_operand(value), resume: *resume, - resume_arg: *resume_arg, + resume_arg: (*resume_arg).into(), drop: *drop, }, TerminatorKind::GeneratorDrop => MicroTerminatorKind::GeneratorDrop, @@ -181,7 +188,214 @@ impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { operands, kind, repack_join: None, + previous_rjs: Vec::new(), original_kind: term.kind.clone(), } } } + +impl<'tcx> Debug for MicroTerminator<'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let mut dbg = f.debug_struct("MicroTerminator"); + if let Some(repack) = &self.repack_operands { + dbg.field("pcs", repack); + } + if self.operands.len() > 0 { + dbg.field("operands", &*self.operands); + } + dbg.field("term", &TermDebug(&self.kind, &self.original_kind)); + if let Some(repack) = &self.repack_join { + dbg.field("pcs_join", repack); + } + dbg.finish() + } +} + +pub(crate) struct TermDebug<'a, 'tcx>( + pub(crate) &'a MicroTerminatorKind<'tcx>, + pub(crate) &'a TerminatorKind<'tcx>, +); +impl<'a, 'tcx> Debug for TermDebug<'a, 'tcx> { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { + self.0.fmt_head(fmt)?; + let successor_count = self.1.successors().count(); + let labels = self.0.fmt_successor_labels(); + assert_eq!(successor_count, labels.len()); + + match successor_count { + 0 => Ok(()), + 1 => write!(fmt, " -> {:?}", self.1.successors().next().unwrap()), + _ => { + write!(fmt, " -> [")?; + for (i, target) in self.1.successors().enumerate() { + if i > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{}: {:?}", labels[i], target)?; + } + write!(fmt, "]") + } + } + } +} + +impl<'tcx> MicroTerminatorKind<'tcx> { + pub fn fmt_head(&self, fmt: &mut W) -> Result { + use MicroTerminatorKind::*; + match self { + Goto { .. } => write!(fmt, "goto"), + SwitchInt { discr, .. } => write!(fmt, "switchInt({:?})", discr), + Return => write!(fmt, "return"), + GeneratorDrop => write!(fmt, "generator_drop"), + Resume => write!(fmt, "resume"), + Abort => write!(fmt, "abort"), + Yield { + value, resume_arg, .. + } => write!(fmt, "{:?} = yield({:?})", resume_arg, value), + Unreachable => write!(fmt, "unreachable"), + Drop { place, .. } => write!(fmt, "drop({:?})", place), + DropAndReplace { place, value, .. } => { + write!(fmt, "replace({:?} <- {:?})", place, value) + } + Call { + func, + args, + destination, + .. + } => { + write!(fmt, "{:?} = ", destination)?; + write!(fmt, "{:?}(", func)?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{:?}", arg)?; + } + write!(fmt, ")") + } + Assert { + cond, + expected, + msg, + .. + } => { + write!(fmt, "assert(")?; + if !expected { + write!(fmt, "!")?; + } + write!(fmt, "{:?}, ", cond)?; + msg.fmt_assert_args(fmt)?; + write!(fmt, ")") + } + FalseEdge { .. } => write!(fmt, "falseEdge"), + FalseUnwind { .. } => write!(fmt, "falseUnwind"), + // InlineAsm { template, ref operands, options, .. } => { + // write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; + // for op in operands { + // write!(fmt, ", ")?; + // let print_late = |&late| if late { "late" } else { "" }; + // match op { + // InlineAsmOperand::In { reg, value } => { + // write!(fmt, "in({}) {:?}", reg, value)?; + // } + // InlineAsmOperand::Out { reg, late, place: Some(place) } => { + // write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; + // } + // InlineAsmOperand::Out { reg, late, place: None } => { + // write!(fmt, "{}out({}) _", print_late(late), reg)?; + // } + // InlineAsmOperand::InOut { + // reg, + // late, + // in_value, + // out_place: Some(out_place), + // } => { + // write!( + // fmt, + // "in{}out({}) {:?} => {:?}", + // print_late(late), + // reg, + // in_value, + // out_place + // )?; + // } + // InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { + // write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; + // } + // InlineAsmOperand::Const { value } => { + // write!(fmt, "const {:?}", value)?; + // } + // InlineAsmOperand::SymFn { value } => { + // write!(fmt, "sym_fn {:?}", value)?; + // } + // InlineAsmOperand::SymStatic { def_id } => { + // write!(fmt, "sym_static {:?}", def_id)?; + // } + // } + // } + // write!(fmt, ", options({:?}))", options) + // } + } + } + + pub fn fmt_successor_labels(&self) -> Vec> { + use MicroTerminatorKind::*; + match *self { + Return | Resume | Abort | Unreachable | GeneratorDrop => vec![], + Goto { .. } => vec!["".into()], + SwitchInt { ref targets, .. } => targets + .iter() + .map(|(u, _)| Cow::Owned(u.to_string())) + .chain(iter::once("otherwise".into())) + .collect(), + Call { + target: Some(_), + cleanup: Some(_), + .. + } => { + vec!["return".into(), "unwind".into()] + } + Call { + target: Some(_), + cleanup: None, + .. + } => vec!["return".into()], + Call { + target: None, + cleanup: Some(_), + .. + } => vec!["unwind".into()], + Call { + target: None, + cleanup: None, + .. + } => vec![], + Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], + Yield { drop: None, .. } => vec!["resume".into()], + DropAndReplace { unwind: None, .. } | Drop { unwind: None, .. } => { + vec!["return".into()] + } + DropAndReplace { + unwind: Some(_), .. + } + | Drop { + unwind: Some(_), .. + } => { + vec!["return".into(), "unwind".into()] + } + Assert { cleanup: None, .. } => vec!["".into()], + Assert { .. } => vec!["success".into(), "unwind".into()], + FalseEdge { .. } => vec!["real".into(), "imaginary".into()], + FalseUnwind { + unwind: Some(_), .. + } => vec!["real".into(), "cleanup".into()], + FalseUnwind { unwind: None, .. } => vec!["real".into()], + // InlineAsm { destination: Some(_), cleanup: Some(_), .. } => { + // vec!["return".into(), "unwind".into()] + // } + // InlineAsm { destination: Some(_), cleanup: None, .. } => vec!["return".into()], + // InlineAsm { destination: None, cleanup: Some(_), .. } => vec!["unwind".into()], + // InlineAsm { destination: None, cleanup: None, .. } => vec![], + } + } +} diff --git a/micromir/src/free_pcs/permission.rs b/micromir/src/free_pcs/permission.rs index a014a2e52c4..56b7fbed150 100644 --- a/micromir/src/free_pcs/permission.rs +++ b/micromir/src/free_pcs/permission.rs @@ -4,54 +4,122 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::cmp::Ordering; +use std::{ + cmp::Ordering, + fmt::{Debug, Display, Formatter, Result}, +}; use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ - data_structures::fx::FxHashMap, + data_structures::fx::{FxHashMap, FxHashSet}, index::vec::IndexVec, - middle::mir::{Local, Place}, + middle::mir::Local, }; -use crate::PlaceRepacker; +use crate::{Place, PlaceOrdering, PlaceRepacker}; pub type FreeStateUpdate<'tcx> = LocalsState>; #[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut, Default)] -pub struct LocalUpdate<'tcx>((Option>, Option>)); +pub struct LocalUpdate<'tcx>( + ( + Option>, + Option>, + ), +); + +#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut, Default)] +pub struct LocalRequirement<'tcx> { + unalloc_allowed: bool, + #[deref] + #[deref_mut] + place_reqs: FxHashMap, FxHashSet>, +} impl<'tcx> LocalUpdate<'tcx> { - pub(crate) fn requires_unalloc(&mut self) { - Self::unalloc(&mut self.0 .0); + fn init_pre(&mut self) -> &mut LocalRequirement<'tcx> { + assert!(self.0 .0.is_none()); + self.0 .0 = Some(LocalRequirement::default()); + self.0 .0.as_mut().unwrap() } - pub(crate) fn ensures_unalloc(&mut self) { - Self::unalloc(&mut self.0 .1); + pub(crate) fn requires_unalloc_or_uninit(&mut self, local: Local) { + let req = self.init_pre(); + req.unalloc_allowed = true; + self.requires_alloc(local.into(), &[PermissionKind::Uninit]); } - fn unalloc(local: &mut Option>) { - if let Some(pre) = local { - assert_eq!(*pre, PermissionLocal::Unallocated); + pub(crate) fn requires_alloc(&mut self, place: Place<'tcx>, perms: &[PermissionKind]) { + let req = if self.0 .0.is_none() { + self.init_pre() } else { - *local = Some(PermissionLocal::Unallocated); - } + self.0 .0.as_mut().unwrap() + }; + assert!( + req.keys().all(|other| !place.related_to(*other)), + "{req:?} {place:?} {perms:?}" + ); + req.insert(place, perms.iter().copied().collect()); } - pub(crate) fn requires_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { - Self::alloc(&mut self.0 .0, place, perm); + pub(crate) fn requires_unalloc(&mut self) { + let req = self.init_pre(); + req.unalloc_allowed = true; } - pub(crate) fn ensures_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { - Self::alloc(&mut self.0 .1, place, perm); + pub(crate) fn requires_alloc_one(&mut self, place: Place<'tcx>, perm: PermissionKind) { + self.requires_alloc(place, &[perm]); } - fn alloc(local: &mut Option>, place: Place<'tcx>, perm: PermissionKind) { - if let Some(pre) = local { - let old = pre.get_allocated_mut().insert(place, perm); - assert!(old.is_none()); + + pub(crate) fn ensures_unalloc(&mut self) { + assert!(self.0 .1.is_none()); + self.0 .1 = Some(PermissionLocal::Unallocated); + } + pub(crate) fn ensures_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { + if let Some(pre) = &mut self.0 .1 { + let pre = pre.get_allocated_mut(); + assert!(pre.keys().all(|other| !place.related_to(*other))); + pre.insert(place, perm); } else { - *local = Some(PermissionLocal::Allocated( + self.0 .1 = Some(PermissionLocal::Allocated( PermissionProjections::new_update(place, perm), )); } } - pub(crate) fn get_pre(&self) -> &Option> { - &self.0 .0 + + /// Used for the edge case of assigning to the same place you copy from, do not use otherwise! + pub(crate) fn get_pre_for(&self, place: Place<'tcx>) -> Option<&FxHashSet> { + let pre = self.0 .0.as_ref()?; + pre.get(&place) + } + + pub(crate) fn get_pre(&self, state: &PermissionLocal<'tcx>) -> Option> { + let pre = self.0 .0.as_ref()?; + match state { + PermissionLocal::Unallocated => { + assert!(pre.unalloc_allowed); + return Some(PermissionLocal::Unallocated); + } + PermissionLocal::Allocated(state) => { + let mut achievable = PermissionProjections(FxHashMap::default()); + for (place, allowed_perms) in pre.iter() { + let related_set = state.find_all_related(*place, None); + let mut perm = None; + for &ap in allowed_perms { + if related_set.minimum >= ap { + perm = Some(ap); + } + if related_set.minimum == ap { + break; + } + } + assert!( + perm.is_some(), + "{place:?}, {allowed_perms:?}, {state:?}, {:?}, {:?}", + related_set.minimum, + related_set.from + ); + achievable.insert(*place, perm.unwrap()); + } + Some(PermissionLocal::Allocated(achievable)) + } + } } } @@ -67,6 +135,27 @@ impl FromIterator for LocalsState { Self(IndexVec::from_iter(iter)) } } +impl Display for FreeState<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{{")?; + let mut first = true; + for state in self.iter() { + if let PermissionLocal::Allocated(state) = state { + if !first { + write!(f, ", ")?; + } + first = false; + for (i, (place, perm)) in state.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{perm:?} {place:?}")?; + } + } + } + write!(f, "}}") + } +} impl<'tcx> LocalsState> { pub fn initial(local_count: usize, initial: impl Fn(Local) -> Option) -> Self { @@ -82,9 +171,10 @@ impl<'tcx> LocalsState> { local_count, )) } - pub(crate) fn consistency_check(&self) { + #[tracing::instrument(level = "trace", skip(rp))] + pub(crate) fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { for p in self.iter() { - p.consistency_check(); + p.consistency_check(rp); } } } @@ -104,23 +194,26 @@ impl LocalsState { } impl<'tcx> LocalsState> { pub fn update_free(self, state: &mut FreeState<'tcx>) { - for (local, LocalUpdate((pre, post))) in self.0.clone().into_iter_enumerated() { + for (local, update) in self.0.into_iter_enumerated() { if cfg!(debug_assertions) { - if let Some(pre) = pre { - match (&state[local], pre) { - (PermissionLocal::Unallocated, PermissionLocal::Unallocated) => {} - (PermissionLocal::Allocated(local_state), PermissionLocal::Allocated(pre)) => { - for (place, required_perm) in pre.iter() { - let perm = local_state.get(place).unwrap(); - let is_read = required_perm.is_shared() && perm.is_exclusive(); - assert!(perm == required_perm || is_read, "Req\n{self:#?}\n, have\n{state:#?}\n{place:#?}\n{perm:#?}\n{required_perm:#?}\n"); - } + use PermissionLocal::*; + match (&state[local], update.get_pre(&state[local])) { + (_, None) => {} + (Unallocated, Some(Unallocated)) => {} + (Allocated(local_state), Some(Allocated(pre))) => { + for (place, required_perm) in pre.0 { + let perm = *local_state.get(&place).unwrap(); + let is_read = required_perm.is_shared() && perm.is_exclusive(); + assert!( + perm == required_perm || is_read, + "Have\n{state:#?}\n{place:#?}\n{perm:#?}\n{required_perm:#?}\n" + ); } - _ => unreachable!(), } + _ => unreachable!(), } } - if let Some(post) = post { + if let Some(post) = update.0 .1 { match (post, &mut state[local]) { (post @ PermissionLocal::Unallocated, _) | (post, PermissionLocal::Unallocated) => state[local] = post, @@ -133,14 +226,28 @@ impl<'tcx> LocalsState> { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] /// The permissions of a local pub enum PermissionLocal<'tcx> { Unallocated, Allocated(PermissionProjections<'tcx>), } +impl Debug for PermissionLocal<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + PermissionLocal::Unallocated => write!(f, "U"), + PermissionLocal::Allocated(a) => write!(f, "{a:?}"), + } + } +} impl<'tcx> PermissionLocal<'tcx> { + pub fn get_allocated(&self) -> &PermissionProjections<'tcx> { + match self { + PermissionLocal::Allocated(places) => places, + _ => panic!(), + } + } pub fn get_allocated_mut(&mut self) -> &mut PermissionProjections<'tcx> { match self { PermissionLocal::Allocated(places) => places, @@ -148,21 +255,44 @@ impl<'tcx> PermissionLocal<'tcx> { } } - fn consistency_check(&self) { + fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { match self { PermissionLocal::Unallocated => {} PermissionLocal::Allocated(places) => { - places.consistency_check(); + places.consistency_check(rp); } } } } -#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut)] +#[derive(Clone, PartialEq, Eq, Deref, DerefMut)] /// The permissions for all the projections of a place // We only need the projection part of the place pub struct PermissionProjections<'tcx>(FxHashMap, PermissionKind>); +impl<'tcx> Debug for PermissionProjections<'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.fmt(f) + } +} + +#[derive(Debug)] +pub(crate) struct RelatedSet<'tcx> { + pub(crate) from: Vec<(Place<'tcx>, PermissionKind)>, + pub(crate) to: Place<'tcx>, + pub(crate) minimum: PermissionKind, + pub(crate) relation: PlaceOrdering, +} +impl<'tcx> RelatedSet<'tcx> { + pub fn get_from(&self) -> FxHashSet> { + assert!(matches!( + self.relation, + PlaceOrdering::Suffix | PlaceOrdering::Both + )); + self.from.iter().map(|(p, _)| *p).collect() + } +} + impl<'tcx> PermissionProjections<'tcx> { pub fn new(local: Local, perm: PermissionKind) -> Self { Self([(local.into(), perm)].into_iter().collect()) @@ -178,130 +308,179 @@ impl<'tcx> PermissionProjections<'tcx> { /// Returns all related projections of the given place that are contained in this map. /// A `Ordering::Less` means that the given `place` is a prefix of the iterator place. /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] - pub fn find_all_related( + /// It also checks that the ordering conforms to the expected ordering (the above would + /// fail in any situation since all orderings need to be the same) + #[tracing::instrument(level = "trace", ret)] + pub(crate) fn find_all_related( &self, - place: Place<'tcx>, - ) -> impl Iterator, PermissionKind)> + '_ { - self.iter().filter_map(move |(other, perm)| { - PlaceRepacker::partial_cmp(*other, place).map(|ord| (ord, *other, *perm)) - }) + to: Place<'tcx>, + mut expected: Option, + ) -> RelatedSet<'tcx> { + let mut minimum = None::; + let mut related = Vec::new(); + for (&from, &perm) in &**self { + if let Some(ord) = from.partial_cmp(to) { + minimum = if let Some(min) = minimum { + Some(min.minimum(perm).unwrap()) + } else { + Some(perm) + }; + if let Some(expected) = expected { + assert_eq!(ord, expected); + } else { + expected = Some(ord); + } + related.push((from, perm)); + } + } + assert!( + !related.is_empty(), + "Cannot find related of {to:?} in {self:?}" + ); + let relation = expected.unwrap(); + if matches!(relation, PlaceOrdering::Prefix | PlaceOrdering::Equal) { + assert_eq!(related.len(), 1); + } + RelatedSet { + from: related, + to, + minimum: minimum.unwrap(), + relation, + } } + // pub fn all_related_with_minimum( + // &self, + // place: Place<'tcx>, + // ) -> (PermissionKind, PlaceOrdering, Vec<(Place<'tcx>, PermissionKind)>) { + // let mut ord = None; + // let related: Vec<_> = self + // .find_all_related(place, &mut ord) + // .map(|(_, p, k)| (p, k)) + // .collect(); + // let mut minimum = related.iter().map(|(_, k)| *k).reduce(|acc, k| { + // acc.minimum(k).unwrap() + // }); + // (minimum.unwrap(), ord.unwrap(), related) + // } + + #[tracing::instrument(name = "PermissionProjections::unpack", level = "trace", skip(rp), ret)] pub(crate) fn unpack( &mut self, + from: Place<'tcx>, to: Place<'tcx>, rp: PlaceRepacker<'_, 'tcx>, - ) -> Vec> { - // Inefficient to do the work here when not needed + ) -> Vec<(Place<'tcx>, Place<'tcx>)> { debug_assert!(!self.contains_key(&to)); - let (ord, other, perm) = { - let mut related = self.find_all_related(to); - let r = related.next().unwrap(); - debug_assert!( - related.next().is_none(), - "{:?} ({to:?})", - self.find_all_related(to).collect::>() - ); - r - }; - assert!(ord == Ordering::Less); - let (expanded, others) = rp.expand(other, to); - self.remove(&other); + let (expanded, others) = rp.expand(from, to); + let perm = self.remove(&from).unwrap(); self.extend(others.into_iter().map(|p| (p, perm))); self.insert(to, perm); expanded } + + // TODO: this could be implemented more efficiently, by assuming that a valid + // state can always be packed up to the root + #[tracing::instrument(name = "PermissionProjections::pack", level = "trace", skip(rp), ret)] pub(crate) fn pack( &mut self, + mut from: FxHashSet>, to: Place<'tcx>, + perm: PermissionKind, rp: PlaceRepacker<'_, 'tcx>, - ) -> Vec> { - // Inefficient to do the work here when not needed + ) -> Vec<(Place<'tcx>, Place<'tcx>)> { debug_assert!(!self.contains_key(&to)); - let related: Vec<_> = self.find_all_related(to).collect(); - debug_assert!(related.len() > 0); - debug_assert!(related.iter().all(|(ord, _, _)| *ord == Ordering::Greater)); - debug_assert!(related.iter().all(|(_, _, perm)| *perm == related[0].2)); - let mut related_set = related.iter().map(|(_, p, _)| *p).collect(); - let collapsed = rp.collapse(to, &mut related_set); - assert!(related_set.is_empty()); - for (_, p, _) in &related { - self.remove(p); + for place in &from { + let p = self.remove(place).unwrap(); + assert_eq!(p, perm, "Cannot pack {place:?} with {p:?} into {to:?}"); } - self.insert(to, related[0].2); + let collapsed = rp.collapse(to, &mut from); + assert!(from.is_empty()); + self.insert(to, perm); collapsed } + #[tracing::instrument(name = "PermissionProjections::join", level = "info", skip(rp))] pub(crate) fn join(&self, other: &mut Self, rp: PlaceRepacker<'_, 'tcx>) { - for (place, kind) in &**self { - let mut place = *place; - let mut expand: Vec<_>; - while { - expand = other - .iter() - .filter_map(|(&p, &k)| { - PlaceRepacker::expandable_no_enum(place, p).map(|o| (o, p, k)) - }) - .collect(); - expand.is_empty() - } { - place = rp.pop_till_enum(place); - } - debug_assert!(expand.iter().all(|o| o.0 == expand[0].0)); - for (_, other_place, perm) in &expand { - let cmp = kind.partial_cmp(&perm).unwrap(); - if cmp.is_lt() { - other.insert(*other_place, *kind); + for (&place, &kind) in &**self { + let related = other.find_all_related(place, None); + match related.relation { + PlaceOrdering::Prefix => { + let from = related.from[0].0; + let joinable_place = rp.joinable_to(from, place); + if joinable_place != from { + other.unpack(from, joinable_place, rp); + } + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + other.insert(joinable_place, new_min); + } } - } - match expand[0].0 { - // Current place has already been expanded in `other` - Ok(Ordering::Less) => (), - Ok(Ordering::Equal) => assert_eq!(expand.len(), 1), - Ok(Ordering::Greater) => { - assert_eq!(expand.len(), 1); - // Do expand - // TODO: remove duplicate code with above - let to_expand = expand[0].1; - let (_, others) = rp.expand(to_expand, place); - let perm = other.remove(&to_expand).unwrap(); - other.extend(others.into_iter().map(|p| (p, perm))); - other.insert(place, perm); + PlaceOrdering::Equal => { + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + other.insert(place, new_min); + } } - Err(Ordering::Less) => { - // Do collapse - // TODO: remove duplicate code with above - for (_, p, _) in &expand { - other.remove(p); + PlaceOrdering::Suffix => { + // Downgrade the permission if needed + for &(p, k) in &related.from { + let new_min = kind.minimum(k).unwrap(); + if new_min != k { + other.insert(p, new_min); + } } - other.insert(place, *kind); } - Err(Ordering::Equal) => unreachable!(), - // Current place has already been collapsed in `other` - Err(Ordering::Greater) => (), + PlaceOrdering::Both => { + // Downgrade the permission if needed + let min = kind.minimum(related.minimum).unwrap(); + for &(p, k) in &related.from { + let new_min = min.minimum(k).unwrap(); + if new_min != k { + other.insert(p, new_min); + } + } + let cp = rp.common_prefix(related.from[0].0, place); + other.pack(related.get_from(), cp, min, rp); + } } } } - fn consistency_check(&self) { + fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { + // All keys unrelated to each other let keys = self.keys().copied().collect::>(); for (i, p1) in keys.iter().enumerate() { for p2 in keys[i + 1..].iter() { - assert!( - PlaceRepacker::partial_cmp(*p1, *p2).is_none(), - "{p1:?} {p2:?}", - ); + assert!(!p1.related_to(*p2), "{p1:?} {p2:?}",); } } + // Can always pack up to the root + let root: Place = self.iter().next().unwrap().0.local.into(); + let mut keys = self.keys().copied().collect(); + rp.collapse(root, &mut keys); + assert!(keys.is_empty()); } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum PermissionKind { Shared, Exclusive, Uninit, } +impl Debug for PermissionKind { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + PermissionKind::Shared => write!(f, "s"), + PermissionKind::Exclusive => write!(f, "e"), + PermissionKind::Uninit => write!(f, "u"), + } + } +} + impl PartialOrd for PermissionKind { fn partial_cmp(&self, other: &Self) -> Option { if *self == *other { @@ -329,4 +508,10 @@ impl PermissionKind { pub fn is_uninit(self) -> bool { self == PermissionKind::Uninit } + pub fn minimum(self, other: Self) -> Option { + match self.partial_cmp(&other)? { + Ordering::Greater => Some(other), + _ => Some(self), + } + } } diff --git a/micromir/src/lib.rs b/micromir/src/lib.rs index 02b8cca2b52..a40c4bccfcd 100644 --- a/micromir/src/lib.rs +++ b/micromir/src/lib.rs @@ -3,14 +3,34 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. + #![feature(rustc_private)] #![feature(box_syntax, box_patterns)] -#![feature(drain_filter)] +#![feature(drain_filter, hash_drain_filter)] +#![feature(type_alias_impl_trait)] +mod check; mod defs; mod repack; mod free_pcs; +mod utils; pub use defs::*; pub use free_pcs::*; pub use repack::*; +pub use utils::place::*; + +use prusti_interface::environment::Environment; + +pub fn test_free_pcs(env: &Environment) { + for proc_id in env.get_annotated_procedures_and_types().0.iter() { + let name = env.name.get_unique_item_name(*proc_id); + // if name != "syn::ty::parsing::ambig_ty" { + // continue; + // } + println!("id: {name}"); + let current_procedure = env.get_procedure(*proc_id); + let mir = current_procedure.get_mir_rc(); + let _ = MicroBody::new(mir, env.tcx()); + } +} diff --git a/micromir/src/repack/calculate.rs b/micromir/src/repack/calculate.rs index c0d3fe41c09..8cc1b208225 100644 --- a/micromir/src/repack/calculate.rs +++ b/micromir/src/repack/calculate.rs @@ -15,8 +15,8 @@ use prusti_rustc_interface::{ }; use crate::{ - FreeState, MicroBasicBlockData, MicroBasicBlocks, MicroBody, MicroStatement, MicroTerminator, - PermissionKind, PlaceCapabilitySummary, + check::checker, FreeState, MicroBasicBlockData, MicroBasicBlocks, MicroBody, MicroStatement, + MicroTerminator, PermissionKind, TerminatorPlaceCapabilitySummary, }; use super::{place::PlaceRepacker, triple::ModifiesFreeState}; @@ -52,6 +52,11 @@ impl<'tcx> MicroBody<'tcx> { // Do the actual repacking calculation self.basic_blocks .calculate_repacking(start_node, state, |bb| &preds[bb], rp); + + if cfg!(debug_assertions) { + // println!("--------\n{}\n--------", &self.basic_blocks); + checker::check(&self.basic_blocks, rp); + } } } @@ -61,6 +66,7 @@ struct Queue { dirty_queue: FxHashSet, done: IndexVec, can_redo: IndexVec, + recompute_count: IndexVec, } impl Queue { fn new(start_node: BasicBlock, len: usize) -> Self { @@ -71,6 +77,7 @@ impl Queue { dirty_queue: FxHashSet::default(), done, can_redo: IndexVec::from_elem_n(true, len), + recompute_count: IndexVec::from_elem_n(0, len), } } fn add_succs<'a>( @@ -88,8 +95,8 @@ impl Queue { } } } - #[tracing::instrument(name = "Queue::pop", level = "debug", ret)] - fn pop(&mut self) -> Option { + #[tracing::instrument(name = "Queue::pop", level = "warn", skip(min_by), ret)] + fn pop(&mut self, min_by: impl Fn(&BasicBlock) -> usize) -> Option { if let Some(bb) = self.queue.pop() { self.done[bb] = true; Some(bb) @@ -101,14 +108,18 @@ impl Queue { .all(|bb| self.done[bb] || !self.can_redo[bb])); return None; } - let bb = *self + let bb = self .dirty_queue .iter() - .filter(|bb| self.can_redo[**bb]) - .next() + .copied() + .filter(|bb| self.can_redo[*bb]) + .min_by_key(min_by) .unwrap(); // Can this happen? If so probably a bug self.can_redo[bb] = false; self.dirty_queue.remove(&bb); + self.recompute_count[bb] += 1; + // TODO: assert that recompute count is low + assert!(self.recompute_count[bb] < 200); Some(bb) } } @@ -130,47 +141,79 @@ impl<'tcx> MicroBasicBlocks<'tcx> { self.basic_blocks[start_node].calculate_repacking(initial, rp); let mut queue = Queue::new(start_node, self.basic_blocks.len()); queue.add_succs(&self.basic_blocks[start_node].terminator, &preds); - while let Some(can_do) = queue.pop() { + while let Some(can_do) = queue.pop(|bb: &BasicBlock| { + let preds = preds(*bb); + preds.len() - self.get_valid_pred_count(preds) + }) { + // if can_do.as_u32() == 27 { + // tracing::warn!("IJOFD"); + // } let is_cleanup = self.basic_blocks[can_do].is_cleanup; let predecessors = self.get_pred_pcs(preds(can_do)); - let initial = Self::calculate_join(predecessors, is_cleanup, rp); + let initial = if predecessors.len() == 1 { + predecessors[0].state().clone() + } else { + Self::calculate_join(can_do, predecessors, is_cleanup, rp) + }; + // TODO: A better way to do this might be to calculate a pre/post for entire basic blocks; + // start with pre/post of all `None` and walk over the statements collecting all the + // pre/posts, ignoring some (e.g. if we already have `x.f` in our pre then if we ran into + // `x.f.g` we'd ignore it, and if we ran into `x` we'd add `rp.expand(`x`, `x.f`).1`). + // And then calculate the fixpoint from that (rather than having to go through all the + // statements again each time). Then, once we have the state for the start and end of each + // bb, we simply calculate intermediate states along with repacking for all straight-line + // code within each bb. let changed = self.basic_blocks[can_do].calculate_repacking(initial, rp); if changed { queue.add_succs(&self.basic_blocks[can_do].terminator, &preds); } } - // debug_assert!(done.iter().all(|b| *b), "{done:?}"); } fn get_pred_pcs( &mut self, predecessors: &[BasicBlock], - ) -> Vec<&mut PlaceCapabilitySummary<'tcx>> { + ) -> Vec<&mut TerminatorPlaceCapabilitySummary<'tcx>> { let predecessors = self .basic_blocks .iter_enumerated_mut() .filter(|(bb, _)| predecessors.contains(bb)); predecessors - .filter_map(|(_, bb)| bb.get_pcs_mut()) + .filter_map(|(_, bb)| bb.get_end_pcs_mut()) .collect::>() } + fn get_valid_pred_count(&self, predecessors: &[BasicBlock]) -> usize { + predecessors + .iter() + .map(|bb| &self.basic_blocks[*bb]) + .filter(|bb| bb.terminator.repack_join.is_some()) + .count() + } + + #[tracing::instrument(level = "info", skip(rp))] fn calculate_join( - predecessors: Vec<&mut PlaceCapabilitySummary<'tcx>>, + bb: BasicBlock, + predecessors: Vec<&mut TerminatorPlaceCapabilitySummary<'tcx>>, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>, ) -> FreeState<'tcx> { let mut join = predecessors[0].state().clone(); for pred in predecessors.iter().skip(1) { pred.state().join(&mut join, is_cleanup, rp); + if cfg!(debug_assertions) { + join.consistency_check(rp); + } + } + for pred in predecessors { + pred.join(&join, bb, is_cleanup, rp); } - // TODO: calculate the repacking statements needed - // println!("join {join:#?} of\n{predecessors:#?}"); join } } impl<'tcx> MicroBasicBlockData<'tcx> { + #[tracing::instrument(level = "info", skip(rp))] pub(crate) fn calculate_repacking( &mut self, mut incoming: FreeState<'tcx>, @@ -207,8 +250,14 @@ impl<'tcx> MicroStatement<'tcx> { ) -> FreeState<'tcx> { let update = self.get_update(incoming.len()); let (pcs, mut pre) = incoming.bridge(&update, rp); + if cfg!(debug_assertions) { + pre.consistency_check(rp); + } self.repack_operands = Some(pcs); update.update_free(&mut pre); + if cfg!(debug_assertions) { + pre.consistency_check(rp); + } pre } } @@ -222,14 +271,27 @@ impl<'tcx> MicroTerminator<'tcx> { ) -> bool { let update = self.get_update(incoming.len()); let (pcs, mut pre) = incoming.bridge(&update, rp); + if cfg!(debug_assertions) { + pre.consistency_check(rp); + } self.repack_operands = Some(pcs); update.update_free(&mut pre); - let changed = self - .repack_join - .as_ref() - .map(|pcs| pcs.state() != &pre) - .unwrap_or(true); - self.repack_join = Some(PlaceCapabilitySummary::empty(pre)); - changed + if cfg!(debug_assertions) { + pre.consistency_check(rp); + } + if let Some(pcs) = self.repack_join.as_mut() { + let changed = pcs.state() != ⪯ + debug_assert!(!(changed && self.previous_rjs.contains(&pre))); + if cfg!(debug_assertions) { + let old = std::mem::replace(pcs.state_mut(), pre); + self.previous_rjs.push(old); + } else { + *pcs.state_mut() = pre; + } + changed + } else { + self.repack_join = Some(TerminatorPlaceCapabilitySummary::empty(pre)); + true + } } } diff --git a/micromir/src/repack/mod.rs b/micromir/src/repack/mod.rs index dbd0635a80d..07dd57f08a6 100644 --- a/micromir/src/repack/mod.rs +++ b/micromir/src/repack/mod.rs @@ -6,7 +6,7 @@ mod repack; mod calculate; -mod triple; +pub(crate) mod triple; mod place; pub use calculate::*; diff --git a/micromir/src/repack/place.rs b/micromir/src/repack/place.rs index e11e621b679..2a4990151d1 100644 --- a/micromir/src/repack/place.rs +++ b/micromir/src/repack/place.rs @@ -4,16 +4,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::cmp::Ordering; - use prusti_rustc_interface::{ data_structures::fx::FxHashSet, middle::{ - mir::{Body, Place, PlaceElem, ProjectionElem}, - ty::TyCtxt, + mir::{Body, Field, ProjectionElem}, + ty::{TyCtxt, TyKind}, }, }; +use crate::Place; + #[derive(Copy, Clone)] // TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate pub(crate) struct PlaceRepacker<'a, 'tcx: 'a> { @@ -26,77 +26,150 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { Self { mir, tcx } } - /// Check if the place `left` is a prefix of `right` or vice versa. For example: - /// - /// + `partial_cmp(x.f, y.f) == None` - /// + `partial_cmp(x.f, x.g) == None` - /// + `partial_cmp(x.f, x.f) == Some(Ordering::Equal)` - /// + `partial_cmp(x.f.g, x.f) == Some(Ordering::Greater)` - /// + `partial_cmp(x.f, x.f.g) == Some(Ordering::Less)` - pub fn partial_cmp(left: Place<'tcx>, right: Place<'tcx>) -> Option { - if left.local != right.local { - return None; - } - if left - .projection - .iter() - .zip(right.projection.iter()) - .any(|(e1, e2)| e1 != e2) - { - return None; - } - Some(left.projection.len().cmp(&right.projection.len())) - } - - /// Check if the place `potential_prefix` is a prefix of `place`. For example: - /// - /// + `is_prefix(x.f, x.f) == true` - /// + `is_prefix(x.f, x.f.g) == true` - /// + `is_prefix(x.f.g, x.f) == false` - fn is_prefix(potential_prefix: Place<'tcx>, place: Place<'tcx>) -> bool { - Self::partial_cmp(potential_prefix, place) - .map(|o| o != Ordering::Greater) - .unwrap_or(false) - } - /// Expand `current_place` one level down by following the `guide_place`. /// Returns the new `current_place` and a vector containing other places that /// could have resulted from the expansion. - fn expand_one_level( + #[tracing::instrument(level = "trace", skip(self), ret)] + pub(crate) fn expand_one_level( self, current_place: Place<'tcx>, guide_place: Place<'tcx>, ) -> (Place<'tcx>, Vec>) { - use analysis::mir_utils::{expand_one_level, PlaceImpl}; - let res = expand_one_level(self.mir, self.tcx, current_place.into(), guide_place.into()); - ( - res.0.to_mir_place(), - res.1.into_iter().map(PlaceImpl::to_mir_place).collect(), - ) + let index = current_place.projection.len(); + let new_projection = self.tcx.mk_place_elems( + current_place + .projection + .iter() + .chain([guide_place.projection[index]]), + ); + let new_current_place = Place::new(current_place.local, new_projection); + let other_places = match guide_place.projection[index] { + ProjectionElem::Field(projected_field, _field_ty) => { + self.expand_place(current_place, Some(projected_field.index())) + } + ProjectionElem::ConstantIndex { + offset, + min_length, + from_end, + } => (0..min_length) + .into_iter() + .filter(|&i| { + if from_end { + i != min_length - offset + } else { + i != offset + } + }) + .map(|i| { + self.tcx + .mk_place_elem( + *current_place, + ProjectionElem::ConstantIndex { + offset: i, + min_length, + from_end, + }, + ) + .into() + }) + .collect(), + ProjectionElem::Deref + | ProjectionElem::Index(..) + | ProjectionElem::Subslice { .. } + | ProjectionElem::Downcast(..) + | ProjectionElem::OpaqueCast(..) => vec![], + }; + (new_current_place, other_places) + } + + /// Expands a place `x.f.g` of type struct into a vector of places for + /// each of the struct's fields `{x.f.g.f, x.f.g.g, x.f.g.h}`. If + /// `without_field` is not `None`, then omits that field from the final + /// vector. + pub fn expand_place( + self, + place: Place<'tcx>, + without_field: Option, + ) -> Vec> { + let mut places = Vec::new(); + let typ = place.ty(self.mir, self.tcx); + if !matches!(typ.ty.kind(), TyKind::Adt(..)) { + assert!( + typ.variant_index.is_none(), + "We have assumed that only enums can have variant_index set. Got {typ:?}." + ); + } + match typ.ty.kind() { + TyKind::Adt(def, substs) => { + let variant = typ + .variant_index + .map(|i| def.variant(i)) + .unwrap_or_else(|| def.non_enum_variant()); + for (index, field_def) in variant.fields.iter().enumerate() { + if Some(index) != without_field { + let field = Field::from_usize(index); + let field_place = + self.tcx + .mk_place_field(*place, field, field_def.ty(self.tcx, substs)); + places.push(field_place.into()); + } + } + } + TyKind::Tuple(slice) => { + for (index, arg) in slice.iter().enumerate() { + if Some(index) != without_field { + let field = Field::from_usize(index); + let field_place = self.tcx.mk_place_field(*place, field, arg); + places.push(field_place.into()); + } + } + } + TyKind::Closure(_, substs) => { + for (index, subst_ty) in substs.as_closure().upvar_tys().enumerate() { + if Some(index) != without_field { + let field = Field::from_usize(index); + let field_place = self.tcx.mk_place_field(*place, field, subst_ty); + places.push(field_place.into()); + } + } + } + TyKind::Generator(_, substs, _) => { + for (index, subst_ty) in substs.as_generator().upvar_tys().enumerate() { + if Some(index) != without_field { + let field = Field::from_usize(index); + let field_place = self.tcx.mk_place_field(*place, field, subst_ty); + places.push(field_place.into()); + } + } + } + ty => unreachable!("ty={:?}", ty), + } + places } /// Subtract the `subtrahend` place from the `minuend` place. The /// subtraction is defined as set minus between `minuend` place replaced /// with a set of places that are unrolled up to the same level as /// `subtrahend` and the singleton `subtrahend` set. For example, - /// `subtract(x.f, x.f.g.h)` is performed by unrolling `x.f` into + /// `expand(x.f, x.f.g.h)` is performed by unrolling `x.f` into /// `{x.g, x.h, x.f.f, x.f.h, x.f.g.f, x.f.g.g, x.f.g.h}` and /// subtracting `{x.f.g.h}` from it, which results into (`{x.f, x.f.g}`, `{x.g, x.h, - /// x.f.f, x.f.h, x.f.g.f, x.f.g.g}`). - #[tracing::instrument(level = "debug", skip(self), ret)] + /// x.f.f, x.f.h, x.f.g.f, x.f.g.g}`). The first vector contains the chain of + /// places that were expanded along with the target subtrahend of each expansion. + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn expand( self, mut minuend: Place<'tcx>, subtrahend: Place<'tcx>, - ) -> (Vec>, Vec>) { + ) -> (Vec<(Place<'tcx>, Place<'tcx>)>, Vec>) { assert!( - Self::is_prefix(minuend, subtrahend), + minuend.is_prefix(subtrahend), "The minuend ({minuend:?}) must be the prefix of the subtrahend ({subtrahend:?})." ); let mut place_set = Vec::new(); let mut expanded = Vec::new(); while minuend.projection.len() < subtrahend.projection.len() { - expanded.push(minuend); + expanded.push((minuend, subtrahend)); let (new_minuend, places) = self.expand_one_level(minuend, subtrahend); minuend = new_minuend; place_set.extend(places); @@ -107,18 +180,19 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { /// Try to collapse all places in `places` by following the /// `guide_place`. This function is basically the reverse of /// `expand`. + #[tracing::instrument(level = "trace", skip(self), ret)] pub fn collapse( self, guide_place: Place<'tcx>, places: &mut FxHashSet>, - ) -> Vec> { + ) -> Vec<(Place<'tcx>, Place<'tcx>)> { let mut collapsed = Vec::new(); let mut guide_places = vec![guide_place]; while let Some(guide_place) = guide_places.pop() { if !places.remove(&guide_place) { let expand_guide = *places .iter() - .find(|p| Self::is_prefix(guide_place, **p)) + .find(|p| guide_place.is_prefix(**p)) .unwrap_or_else(|| { panic!( "The `places` set didn't contain all \ @@ -127,74 +201,56 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { `{guide_place:?}` in `{places:?}`." ) }); - let (mut expanded, new_places) = self.expand(guide_place, expand_guide); + let (expanded, new_places) = self.expand(guide_place, expand_guide); // Doing `collapsed.extend(expanded)` would result in a reversed order. // Could also change this to `collapsed.push(expanded)` and return Vec>. - expanded.extend(collapsed); - collapsed = expanded; + collapsed.extend(expanded); guide_places.extend(new_places); places.remove(&expand_guide); } } + collapsed.reverse(); collapsed } - /// Pop the last projection from the place and return the new place with the popped element. - pub fn try_pop_one_level(self, place: Place<'tcx>) -> Option<(PlaceElem<'tcx>, Place<'tcx>)> { - if place.projection.len() > 0 { - let last_index = place.projection.len() - 1; - let new_place = Place { - local: place.local, - projection: self.tcx.intern_place_elems(&place.projection[..last_index]), - }; - Some((place.projection[last_index], new_place)) - } else { - None - } - } - - // /// Pop the last element from the place if it is a dereference. - // pub fn try_pop_deref(self, place: Place<'tcx>) -> Option> { - // self.try_pop_one_level(place).and_then(|(elem, base)| { - // if let ProjectionElem::Deref = elem { - // Some(base) - // } else { - // None - // } - // }) + // /// Pop the last projection from the place and return the new place with the popped element. + // pub fn pop_one_level(self, place: Place<'tcx>) -> (PlaceElem<'tcx>, Place<'tcx>) { + // assert!(place.projection.len() > 0); + // let last_index = place.projection.len() - 1; + // let projection = self.tcx.intern_place_elems(&place.projection[..last_index]); + // ( + // place.projection[last_index], + // Place::new(place.local, projection), + // ) // } - pub fn pop_till_enum(self, place: Place<'tcx>) -> Place<'tcx> { - let (mut elem, mut base) = self.try_pop_one_level(place).unwrap(); - while !matches!(elem, ProjectionElem::Downcast(..)) { - let (new_elem, new_base) = self.try_pop_one_level(base).unwrap(); - elem = new_elem; - base = new_base; - } - base + #[tracing::instrument(level = "debug", skip(self), ret, fields(lp = ?left.projection, rp = ?right.projection))] + pub fn common_prefix(self, left: Place<'tcx>, right: Place<'tcx>) -> Place<'tcx> { + assert_eq!(left.local, right.local); + + let common_prefix = left + .compare_projections(right) + .take_while(|(eq, _, _)| *eq) + .map(|(_, e1, _)| e1); + Place::new(left.local, self.tcx.mk_place_elems(common_prefix)) } - /// Checks if we can expand either place to the other, without going through an enum. - /// If we can reach from one to the other, but need to go through an enum, we return `Err`. - pub fn expandable_no_enum( - left: Place<'tcx>, - right: Place<'tcx>, - ) -> Option> { - let ord = Self::partial_cmp(left, right)?; - let (minuend, subtrahend) = match ord { - Ordering::Greater => (right, left), - Ordering::Less => (left, right), - Ordering::Equal => return Some(Ok(Ordering::Equal)), - }; - if subtrahend - .projection + #[tracing::instrument(level = "info", skip(self), ret)] + pub fn joinable_to(self, from: Place<'tcx>, to: Place<'tcx>) -> Place<'tcx> { + assert!(from.is_prefix(to)); + let proj = from.projection.iter(); + let to_proj = to.projection[from.projection.len()..] .iter() - .skip(minuend.projection.len()) - .any(|elem| matches!(elem, ProjectionElem::Downcast(..))) - { - Some(Err(ord)) - } else { - Some(Ok(ord)) - } + .copied() + .take_while(|p| { + matches!( + p, + ProjectionElem::Deref + | ProjectionElem::Field(..) + | ProjectionElem::ConstantIndex { .. } + ) + }); + let projection = self.tcx.mk_place_elems(proj.chain(to_proj)); + Place::new(from.local, projection) } } diff --git a/micromir/src/repack/repack.rs b/micromir/src/repack/repack.rs index 3777397d8b4..6914f9d2673 100644 --- a/micromir/src/repack/repack.rs +++ b/micromir/src/repack/repack.rs @@ -4,26 +4,87 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use std::cmp::Ordering; +use std::fmt::{Debug, Formatter, Result}; -use prusti_rustc_interface::middle::mir::Place; +use derive_more::{Deref, DerefMut}; +use prusti_rustc_interface::{ + data_structures::fx::FxHashMap, + middle::mir::{BasicBlock, Local}, +}; use crate::{ - repack::place::PlaceRepacker, FreeState, FreeStateUpdate, LocalUpdate, PermissionKind, - PermissionLocal, PermissionProjections, + repack::place::PlaceRepacker, FreeState, FreeStateUpdate, PermissionKind, PermissionLocal, + PermissionProjections, Place, PlaceOrdering, RelatedSet, }; -#[derive(Clone, Debug)] +#[derive(Clone, Deref, DerefMut)] +pub struct Repacks<'tcx>(Vec>); +impl Debug for Repacks<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.fmt(f) + } +} +impl<'tcx> Repacks<'tcx> { + pub fn new() -> Self { + Self(Vec::new()) + } +} + +#[derive(Clone)] pub struct PlaceCapabilitySummary<'tcx> { state_before: FreeState<'tcx>, - repacks: Vec>, + repacks: Repacks<'tcx>, +} + +impl Debug for PlaceCapabilitySummary<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if self.repacks.len() == 0 { + write!(f, "{:?}", &self.state_before) + } else { + f.debug_struct("PCS") + .field("state", &self.state_before) + .field("repacks", &self.repacks) + .finish() + } + } } impl<'tcx> PlaceCapabilitySummary<'tcx> { + pub fn state(&self) -> &FreeState<'tcx> { + &self.state_before + } + pub fn state_mut(&mut self) -> &mut FreeState<'tcx> { + &mut self.state_before + } + pub fn repacks(&self) -> &Repacks<'tcx> { + &self.repacks + } +} + +#[derive(Clone)] +pub struct TerminatorPlaceCapabilitySummary<'tcx> { + state_before: FreeState<'tcx>, + repacks: FxHashMap>, +} + +impl Debug for TerminatorPlaceCapabilitySummary<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + if self.repacks.len() == 0 { + write!(f, "{:?}", &self.state_before) + } else { + f.debug_struct("PCS") + .field("state", &self.state_before) + .field("repacks", &self.repacks) + .finish() + } + } +} + +impl<'tcx> TerminatorPlaceCapabilitySummary<'tcx> { pub(crate) fn empty(state_before: FreeState<'tcx>) -> Self { Self { state_before, - repacks: Vec::new(), + repacks: FxHashMap::default(), } } pub fn state(&self) -> &FreeState<'tcx> { @@ -32,9 +93,26 @@ impl<'tcx> PlaceCapabilitySummary<'tcx> { pub fn state_mut(&mut self) -> &mut FreeState<'tcx> { &mut self.state_before } - pub fn repacks(&self) -> &Vec> { + pub fn repacks(&self) -> &FxHashMap> { &self.repacks } + + #[tracing::instrument(name = "PCS::join", level = "debug", skip(rp))] + pub(crate) fn join( + &mut self, + to: &FreeState<'tcx>, + bb: BasicBlock, + is_cleanup: bool, + rp: PlaceRepacker<'_, 'tcx>, + ) { + let repacks = self.repacks.entry(bb).or_insert_with(Repacks::new); + repacks.clear(); + for (l, to) in to.iter_enumerated() { + let new = + PermissionLocal::bridge(&self.state_before[l], Some(to), repacks, is_cleanup, rp); + debug_assert_eq!(&new, to); + } + } } impl<'tcx> FreeState<'tcx> { @@ -45,13 +123,18 @@ impl<'tcx> FreeState<'tcx> { update: &FreeStateUpdate<'tcx>, rp: PlaceRepacker<'_, 'tcx>, ) -> (PlaceCapabilitySummary<'tcx>, FreeState<'tcx>) { - if cfg!(debug_assertions) { - self.consistency_check(); - } - let mut repacks = Vec::new(); + let mut repacks = Repacks::new(); let pre = update .iter_enumerated() - .map(|(l, update)| PermissionLocal::bridge(&self[l], update, &mut repacks, rp)) + .map(|(l, update)| { + PermissionLocal::bridge( + &self[l], + update.get_pre(&self[l]).as_ref(), + &mut repacks, + false, + rp, + ) + }) .collect(); ( PlaceCapabilitySummary { @@ -62,11 +145,8 @@ impl<'tcx> FreeState<'tcx> { ) } - #[tracing::instrument(level = "debug", skip(rp))] + #[tracing::instrument(name = "FreeState::join", level = "info", skip(rp))] pub(crate) fn join(&self, to: &mut Self, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { - if cfg!(debug_assertions) { - self.consistency_check(); - } for (l, to) in to.iter_enumerated_mut() { PermissionLocal::join(&self[l], to, is_cleanup, rp); } @@ -74,110 +154,146 @@ impl<'tcx> FreeState<'tcx> { } impl<'tcx> PermissionLocal<'tcx> { - #[tracing::instrument(level = "debug", skip(rp), ret)] + #[tracing::instrument(level = "trace", skip(rp), ret)] fn bridge( &self, - update: &LocalUpdate<'tcx>, - repacks: &mut Vec>, + to: Option<&PermissionLocal<'tcx>>, + repacks: &mut Repacks<'tcx>, + is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>, ) -> PermissionLocal<'tcx> { - match (self, update.get_pre()) { - (_, None) | (PermissionLocal::Unallocated, Some(PermissionLocal::Unallocated)) => { - self.clone() - } - (PermissionLocal::Allocated(from_places), Some(PermissionLocal::Allocated(places))) => { + use PermissionLocal::*; + match (self, to) { + (_, None) | (Unallocated, Some(Unallocated)) => self.clone(), + (Allocated(from_places), Some(Allocated(places))) => { let mut from_places = from_places.clone(); for (&to_place, &to_kind) in &**places { - repacks.extend(from_places.repack(to_place, rp)); + from_places.repack_op(to_place, repacks, rp); let from_kind = *from_places.get(&to_place).unwrap(); - assert!( - from_kind >= to_kind, - "!({from_kind:?} >= {to_kind:?})" - ); - if from_kind == PermissionKind::Exclusive && to_kind == PermissionKind::Uninit - { + assert!(from_kind >= to_kind, "!({from_kind:?} >= {to_kind:?})"); + if from_kind > to_kind { from_places.insert(to_place, to_kind); - repacks.push(RepackOp::Drop(to_place, from_kind)); + repacks.push(RepackOp::Weaken(to_place, from_kind, to_kind)); } } - PermissionLocal::Allocated(from_places) + Allocated(from_places) + } + (Allocated(a), Some(Unallocated)) => { + let local = a.iter().next().unwrap().0.local; + let root_place = local.into(); + let mut a = a.clone(); + a.repack_op(root_place, repacks, rp); + if a[&root_place] != PermissionKind::Uninit { + assert_eq!(a[&root_place], PermissionKind::Exclusive); + repacks.push(RepackOp::Weaken( + root_place, + a[&root_place], + PermissionKind::Uninit, + )); + } + if is_cleanup { + repacks.push(RepackOp::DeallocForCleanup(local)); + } else { + println!("TODO: figure out why this happens and if it's ok"); + repacks.push(RepackOp::DeallocUnknown(local)); + } + Unallocated } - a => unreachable!("{:?}", a), + a @ (Unallocated, Some(Allocated(..))) => unreachable!("{:?}", a), } } - fn join(&self, to: &mut Self, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { + #[tracing::instrument(level = "info", skip(rp))] + fn join(&self, to: &mut Self, _is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { match (self, &mut *to) { (PermissionLocal::Unallocated, PermissionLocal::Unallocated) => (), (PermissionLocal::Allocated(from_places), PermissionLocal::Allocated(places)) => { from_places.join(places, rp); } // Can jump to a `is_cleanup` block with some paths being alloc and other not - (PermissionLocal::Allocated(..), PermissionLocal::Unallocated) if is_cleanup => (), - (PermissionLocal::Unallocated, PermissionLocal::Allocated(..)) if is_cleanup => { + (PermissionLocal::Allocated(..), PermissionLocal::Unallocated) => (), + (PermissionLocal::Unallocated, PermissionLocal::Allocated(..)) => { *to = PermissionLocal::Unallocated } - a => unreachable!("{:?}", a), }; } } impl<'tcx> PermissionProjections<'tcx> { - pub(crate) fn repack( + #[tracing::instrument(level = "debug", skip(rp))] + pub(crate) fn repack_op( &mut self, to: Place<'tcx>, + repacks: &mut Vec>, rp: PlaceRepacker<'_, 'tcx>, - ) -> Box> + '_> { - let mut related = self.find_all_related(to); - let (cmp, p, k) = related.next().unwrap(); - match cmp { - Ordering::Less => { - std::mem::drop(related); - box self - .unpack(to, rp) - .into_iter() - .map(move |p| RepackOp::Unpack(p, k)) - } - Ordering::Equal => box std::iter::empty(), - Ordering::Greater => { - let related = related.collect::>(); - let mut minimum = k; - for (_, _, other) in &related { - match minimum.partial_cmp(other) { - None => { - unreachable!("Cannot find minimum of ({p:?}, {k:?}) and {related:?}") - } - Some(Ordering::Greater) => { - minimum = *other; - } - _ => (), - } - } - let all_related = related - .into_iter() - .chain(std::iter::once((cmp, p, k))) - .filter(|(_, _, k)| *k != minimum); - // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` - // the exclusive permission will never be able to be recovered anymore! - let mut repacks: Vec<_> = all_related - .map(|(_, p, _k)| RepackOp::Drop(p, self.insert(p, minimum).unwrap())) - .collect(); - if minimum != PermissionKind::Uninit { - repacks = Vec::new(); - } - - box repacks.into_iter().chain( - self.pack(to, rp) - .into_iter() - .map(move |p| RepackOp::Unpack(p, k)), - ) + ) { + let mut related = self.find_all_related(to, None); + match related.relation { + PlaceOrdering::Prefix => self.unpack_op(related, repacks, rp), + PlaceOrdering::Equal => {} + PlaceOrdering::Suffix => self.pack_op(related, repacks, rp), + PlaceOrdering::Both => { + let cp = rp.common_prefix(related.from[0].0, to); + let minimum = related.minimum; + // Pack + related.to = cp; + related.relation = PlaceOrdering::Both; + self.pack_op(related, repacks, rp); + // Unpack + let related = RelatedSet { + from: vec![(cp, minimum)], + to, + minimum, + relation: PlaceOrdering::Prefix, + }; + self.unpack_op(related, repacks, rp); } } } + pub(crate) fn unpack_op( + &mut self, + related: RelatedSet<'tcx>, + repacks: &mut Vec>, + rp: PlaceRepacker<'_, 'tcx>, + ) { + let unpacks = self.unpack(related.from[0].0, related.to, rp); + repacks.extend( + unpacks + .into_iter() + .map(move |(place, to)| RepackOp::Unpack(place, to, related.minimum)), + ); + } + pub(crate) fn pack_op( + &mut self, + related: RelatedSet<'tcx>, + repacks: &mut Vec>, + rp: PlaceRepacker<'_, 'tcx>, + ) { + let more_than_min = related.from.iter().filter(|(_, k)| *k != related.minimum); + // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` + // the exclusive permission will never be able to be recovered anymore! + for &(p, k) in more_than_min { + let old = self.insert(p, related.minimum); + assert_eq!(old, Some(k)); + repacks.push(RepackOp::Weaken(p, k, related.minimum)); + } + + let packs = self.pack(related.get_from(), related.to, related.minimum, rp); + repacks.extend( + packs + .into_iter() + .map(move |(place, to)| RepackOp::Pack(place, to, related.minimum)), + ); + } } #[derive(Clone, Debug)] pub enum RepackOp<'tcx> { - Drop(Place<'tcx>, PermissionKind), - Pack(Place<'tcx>, PermissionKind), - Unpack(Place<'tcx>, PermissionKind), + Weaken(Place<'tcx>, PermissionKind, PermissionKind), + // TODO: figure out when and why this happens + DeallocUnknown(Local), + DeallocForCleanup(Local), + // First place is packed up, second is guide place to pack up from + Pack(Place<'tcx>, Place<'tcx>, PermissionKind), + // First place is packed up, second is guide place to unpack to + Unpack(Place<'tcx>, Place<'tcx>, PermissionKind), } diff --git a/micromir/src/repack/triple.rs b/micromir/src/repack/triple.rs index 1da5ee5fb0f..048f7079083 100644 --- a/micromir/src/repack/triple.rs +++ b/micromir/src/repack/triple.rs @@ -4,11 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use prusti_rustc_interface::middle::mir::{Operand, RETURN_PLACE}; +use prusti_rustc_interface::middle::mir::RETURN_PLACE; use crate::{ - FreeStateUpdate, MicroStatement, MicroStatementKind, MicroTerminator, MicroTerminatorKind, - Operands, PermissionKind, + FreeStateUpdate, MicroFullOperand, MicroStatement, MicroStatementKind, MicroTerminator, + MicroTerminatorKind, Operands, PermissionKind, }; pub(crate) trait ModifiesFreeState<'tcx> { @@ -16,19 +16,22 @@ pub(crate) trait ModifiesFreeState<'tcx> { } impl<'tcx> ModifiesFreeState<'tcx> for Operands<'tcx> { - #[tracing::instrument(level = "debug")] + #[tracing::instrument(level = "trace")] fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { let mut update = FreeStateUpdate::default(locals); for operand in &**self { match *operand { - Operand::Copy(place) => { - update[place.local].requires_alloc(place, PermissionKind::Shared) + MicroFullOperand::Copy(place) => { + update[place.local].requires_alloc( + place, + &[PermissionKind::Exclusive, PermissionKind::Shared], + ); } - Operand::Move(place) => { - update[place.local].requires_alloc(place, PermissionKind::Exclusive); + MicroFullOperand::Move(place) => { + update[place.local].requires_alloc_one(place, PermissionKind::Exclusive); update[place.local].ensures_alloc(place, PermissionKind::Uninit); } - Operand::Constant(..) => (), + MicroFullOperand::Constant(..) => (), } } update @@ -36,37 +39,40 @@ impl<'tcx> ModifiesFreeState<'tcx> for Operands<'tcx> { } impl<'tcx> ModifiesFreeState<'tcx> for MicroStatement<'tcx> { - #[tracing::instrument(level = "debug")] + #[tracing::instrument(level = "trace")] fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { let mut update = self.operands.get_update(locals); match &self.kind { - MicroStatementKind::Assign(box (place, _)) => { - update[place.local].requires_alloc(*place, PermissionKind::Uninit); - update[place.local].ensures_alloc(*place, PermissionKind::Exclusive); - } - MicroStatementKind::FakeRead(box (_, place)) => { - update[place.local].requires_alloc(*place, PermissionKind::Shared) + &MicroStatementKind::Assign(box (place, _)) => { + if let Some(pre) = update[place.local].get_pre_for(place) { + assert_eq!(pre.len(), 2); + assert!(pre.contains(&PermissionKind::Exclusive)); + assert!(pre.contains(&PermissionKind::Shared)); + } else { + update[place.local].requires_alloc_one(place, PermissionKind::Uninit); + } + update[place.local].ensures_alloc(place, PermissionKind::Exclusive); } + MicroStatementKind::FakeRead(box (_, place)) => update[place.local] + .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Shared]), MicroStatementKind::SetDiscriminant { box place, .. } => { - update[place.local].requires_alloc(*place, PermissionKind::Exclusive) + update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive) } MicroStatementKind::Deinit(box place) => { - update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + // TODO: Maybe OK to also allow `Uninit` here? + update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive); update[place.local].ensures_alloc(*place, PermissionKind::Uninit); } - MicroStatementKind::StorageLive(local) => { - update[*local].requires_unalloc(); - update[*local].ensures_alloc((*local).into(), PermissionKind::Uninit); + &MicroStatementKind::StorageLive(local) => { + update[local].requires_unalloc(); + update[local].ensures_alloc(local.into(), PermissionKind::Uninit); } - // TODO: The MIR is allowed to have multiple StorageDead statements for the same local. - // But right now we go `PermissionLocal::Allocated` -SD-> `PermissionLocal::Unallocated`, - // which would error when encountering a second StorageDead statement. - MicroStatementKind::StorageDead(local) => { - update[*local].requires_alloc((*local).into(), PermissionKind::Uninit); - update[*local].ensures_unalloc(); + &MicroStatementKind::StorageDead(local) => { + update[local].requires_unalloc_or_uninit(local); + update[local].ensures_unalloc(); } MicroStatementKind::Retag(_, box place) => { - update[place.local].requires_alloc(*place, PermissionKind::Exclusive) + update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive) } MicroStatementKind::AscribeUserType(..) | MicroStatementKind::Coverage(..) @@ -79,7 +85,7 @@ impl<'tcx> ModifiesFreeState<'tcx> for MicroStatement<'tcx> { } impl<'tcx> ModifiesFreeState<'tcx> for MicroTerminator<'tcx> { - #[tracing::instrument(level = "debug")] + #[tracing::instrument(level = "trace")] fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { let mut update = self.operands.get_update(locals); match &self.kind { @@ -92,22 +98,24 @@ impl<'tcx> ModifiesFreeState<'tcx> for MicroTerminator<'tcx> { | MicroTerminatorKind::GeneratorDrop | MicroTerminatorKind::FalseEdge { .. } | MicroTerminatorKind::FalseUnwind { .. } => (), - MicroTerminatorKind::Return => { - update[RETURN_PLACE].requires_alloc(RETURN_PLACE.into(), PermissionKind::Exclusive) - } + MicroTerminatorKind::Return => update[RETURN_PLACE] + .requires_alloc_one(RETURN_PLACE.into(), PermissionKind::Exclusive), MicroTerminatorKind::Drop { place, .. } => { - update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + update[place.local] + .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Uninit]); update[place.local].ensures_alloc(*place, PermissionKind::Uninit); } MicroTerminatorKind::DropAndReplace { place, .. } => { - update[place.local].requires_alloc(*place, PermissionKind::Exclusive); + update[place.local] + .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Uninit]); + update[place.local].ensures_alloc(*place, PermissionKind::Exclusive); } MicroTerminatorKind::Call { destination, .. } => { - update[destination.local].requires_alloc(*destination, PermissionKind::Uninit); + update[destination.local].requires_alloc_one(*destination, PermissionKind::Uninit); update[destination.local].ensures_alloc(*destination, PermissionKind::Exclusive); } MicroTerminatorKind::Yield { resume_arg, .. } => { - update[resume_arg.local].requires_alloc(*resume_arg, PermissionKind::Uninit); + update[resume_arg.local].requires_alloc_one(*resume_arg, PermissionKind::Uninit); update[resume_arg.local].ensures_alloc(*resume_arg, PermissionKind::Exclusive); } }; diff --git a/micromir/src/utils/mod.rs b/micromir/src/utils/mod.rs new file mode 100644 index 00000000000..6f5a94a7085 --- /dev/null +++ b/micromir/src/utils/mod.rs @@ -0,0 +1,7 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +pub mod place; diff --git a/micromir/src/utils/place.rs b/micromir/src/utils/place.rs new file mode 100644 index 00000000000..672c7e5d5cd --- /dev/null +++ b/micromir/src/utils/place.rs @@ -0,0 +1,235 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{ + cmp::Ordering, + fmt::{Debug, Formatter, Result}, + hash::{Hash, Hasher}, + mem::discriminant, +}; + +use derive_more::{Deref, DerefMut}; + +use prusti_rustc_interface::middle::{ + mir::{Local, Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem}, + ty::List, +}; + +fn elem_eq<'tcx>(to_cmp: (PlaceElem<'tcx>, PlaceElem<'tcx>)) -> bool { + use ProjectionElem::*; + match to_cmp { + (Field(left, _), Field(right, _)) => left == right, + ( + ConstantIndex { + offset: lo, + min_length: lml, + from_end: lfe, + }, + ConstantIndex { + offset: ro, + min_length: rml, + from_end: rfe, + }, + ) => { + lml == rml + && (if lfe == rfe { + lo == ro + } else { + (lml - lo) == ro + }) + } + (Downcast(_, left), Downcast(_, right)) => left == right, + (left, right) => left == right, + } +} + +#[derive(Clone, Copy, Deref, DerefMut)] +pub struct Place<'tcx>(MirPlace<'tcx>); + +impl<'tcx> Place<'tcx> { + pub(crate) fn new(local: Local, projection: &'tcx List>) -> Self { + Self(MirPlace { local, projection }) + } + + pub(crate) fn compare_projections( + self, + other: Self, + ) -> impl Iterator, PlaceElem<'tcx>)> { + Self::compare_projections_ref(self.as_ref(), other.as_ref()) + } + pub(crate) fn compare_projections_ref( + left: PlaceRef<'tcx>, + right: PlaceRef<'tcx>, + ) -> impl Iterator, PlaceElem<'tcx>)> { + let left = left.projection.iter().copied(); + let right = right.projection.iter().copied(); + left.zip(right).map(|(e1, e2)| (elem_eq((e1, e2)), e1, e2)) + } + + /// Check if the place `left` is a prefix of `right` or vice versa. For example: + /// + /// + `partial_cmp(x.f, y.f) == None` + /// + `partial_cmp(x.f, x.g) == None` + /// + `partial_cmp(x.f, x.f) == Some(Equal)` + /// + `partial_cmp(x.f.g, x.f) == Some(Suffix)` + /// + `partial_cmp(x.f, x.f.g) == Some(Prefix)` + /// + `partial_cmp(x as None, x as Some.0) == Some(Both)` + #[tracing::instrument(level = "trace", ret)] + pub fn partial_cmp(self, right: Self) -> Option { + Self::partial_cmp_ref(self.as_ref(), right.as_ref()) + } + /// The ultimate question this answers is: are the two places mutually + /// exclusive (i.e. can we have both or not)? + /// For example, all of the following are mutually exclusive: + /// - `x` and `x.f` + /// - `(x as Ok).0` and `(x as Err).0` + /// - `x[_1]` and `x[_2]` + /// - `x[2 of 11]` and `x[5 of 14]` + /// But the following are not: + /// - `x` and `y` + /// - `x.f` and `x.g.h` + /// - `x[3 of 6]` and `x[4 of 6]` + pub(crate) fn partial_cmp_ref( + left: PlaceRef<'tcx>, + right: PlaceRef<'tcx>, + ) -> Option { + if left.local != right.local { + return None; + } + let diff = Self::compare_projections_ref(left, right).find(|(eq, _, _)| !eq); + if let Some((_, left, right)) = diff { + use ProjectionElem::*; + fn is_index(elem: PlaceElem<'_>) -> bool { + matches!(elem, Index(_) | ConstantIndex { .. } | Subslice { .. }) + } + match (left, right) { + (Field(..), Field(..)) => None, + (ConstantIndex { min_length: l, .. }, ConstantIndex { min_length: r, .. }) + if r == l => + { + None + } + (Downcast(_, _), Downcast(_, _)) | (OpaqueCast(_), OpaqueCast(_)) => { + Some(PlaceOrdering::Both) + } + (left, right) if is_index(left) && is_index(right) => Some(PlaceOrdering::Both), + diff => unreachable!("Unexpected diff: {diff:?}"), + } + } else { + Some(left.projection.len().cmp(&right.projection.len()).into()) + } + } + + /// Check if the place `potential_prefix` is a prefix of `place`. For example: + /// + /// + `is_prefix(x.f, x.f) == true` + /// + `is_prefix(x.f, x.f.g) == true` + /// + `is_prefix(x.f.g, x.f) == false` + pub(crate) fn is_prefix(self, place: Self) -> bool { + Self::partial_cmp(self, place) + .map(|o| o == PlaceOrdering::Equal || o == PlaceOrdering::Prefix) + .unwrap_or(false) + } + + /// Returns `true` if either of the places can reach the other + /// with a series of expand/collapse operations. Note that + /// both operations are allowed and so e.g. + /// related_to(`_1[_4]`, `_1[_3]`) == true + pub fn related_to(self, right: Self) -> bool { + self.partial_cmp(right).is_some() + } +} + +impl Debug for Place<'_> { + fn fmt(&self, f: &mut Formatter) -> Result { + self.0.fmt(f) + } +} + +impl PartialEq for Place<'_> { + fn eq(&self, other: &Self) -> bool { + self.local == other.local + && self.projection.len() == other.projection.len() + && self.compare_projections(*other).all(|(eq, _, _)| eq) + } +} +impl Eq for Place<'_> {} + +impl Hash for Place<'_> { + fn hash(&self, state: &mut H) { + self.0.local.hash(state); + let projection = self.0.projection; + for pe in projection { + match pe { + ProjectionElem::Field(field, _) => { + discriminant(&pe).hash(state); + field.hash(state); + } + ProjectionElem::ConstantIndex { + offset, + min_length, + from_end, + } => { + discriminant(&pe).hash(state); + let offset = if from_end { + min_length - offset + } else { + offset + }; + offset.hash(state); + min_length.hash(state); + } + pe => { + pe.hash(state); + } + } + if let ProjectionElem::Field(field, _) = pe { + discriminant(&pe).hash(state); + field.hash(state); + } else { + pe.hash(state); + } + } + } +} + +impl<'tcx, T: Into>> From for Place<'tcx> { + fn from(value: T) -> Self { + Self(value.into()) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PlaceOrdering { + // For example `x.f` to `x.f.g`. + Prefix, + // For example `x.f` and `x.f`. + Equal, + // For example `x.f.g` to `x.f`. + Suffix, + // For example `x[a]` and `x[b]` or `x as None` and `x as Some`. + Both, +} + +impl From for PlaceOrdering { + fn from(ordering: Ordering) -> Self { + match ordering { + Ordering::Less => PlaceOrdering::Prefix, + Ordering::Equal => PlaceOrdering::Equal, + Ordering::Greater => PlaceOrdering::Suffix, + } + } +} +impl From for Option { + fn from(ordering: PlaceOrdering) -> Self { + match ordering { + PlaceOrdering::Prefix => Some(Ordering::Less), + PlaceOrdering::Equal => Some(Ordering::Equal), + PlaceOrdering::Suffix => Some(Ordering::Greater), + PlaceOrdering::Both => None, + } + } +} diff --git a/micromir/tests/top_crates.rs b/micromir/tests/top_crates.rs new file mode 100644 index 00000000000..c4d681dcee8 --- /dev/null +++ b/micromir/tests/top_crates.rs @@ -0,0 +1,122 @@ +use serde_derive::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[test] +pub fn top_crates() { + top_crates_range(0..500) +} + +fn get(url: &str) -> reqwest::Result { + println!("Getting: {url}"); + reqwest::blocking::ClientBuilder::new() + .user_agent("Rust Corpus - Top Crates Scrapper") + .build()? + .get(url) + .send() +} + +pub fn top_crates_range(range: std::ops::Range) { + std::fs::create_dir_all("tmp").unwrap(); + let top_crates = top_crates_by_download_count(range.end - 1); + for (i, krate) in top_crates.into_iter().enumerate().skip(range.start) { + let version = krate.version.unwrap_or(krate.newest_version); + println!("Starting: {i} ({})", krate.name); + run_on_crate(&krate.name, &version); + } +} + +fn run_on_crate(name: &str, version: &str) { + let dirname = format!("./tmp/{}-{}", name, version); + let filename = format!("{dirname}.crate"); + if !std::path::PathBuf::from(&filename).exists() { + let dl = format!( + "https://crates.io/api/v1/crates/{}/{}/download", + name, version + ); + let mut resp = get(&dl).expect("Could not fetch top crates"); + let mut file = std::fs::File::create(&filename).unwrap(); + resp.copy_to(&mut file).unwrap(); + } + println!("Unwrapping: {filename}"); + let status = std::process::Command::new("tar") + .args(["-xf", &filename, "-C", "./tmp/"]) + .status() + .unwrap(); + assert!(status.success()); + let mut file = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(format!("{dirname}/Cargo.toml")) + .unwrap(); + use std::io::Write; + writeln!(file, "\n[workspace]").unwrap(); + let cwd = std::env::current_dir().unwrap(); + assert!( + cfg!(debug_assertions), + "Must be run in debug mode, to enable full checking" + ); + let target = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + let prusti = cwd.join( + ["..", "target", target, "cargo-prusti"] + .iter() + .collect::(), + ); + println!("Running: {prusti:?}"); + let exit = std::process::Command::new(prusti) + .env("PRUSTI_TEST_FREE_PCS", "true") + .env("PRUSTI_SKIP_UNSUPPORTED_FEATURES", "true") + // .env("PRUSTI_LOG", "debug") + .env("PRUSTI_NO_VERIFY_DEPS", "true") + .current_dir(&dirname) + .status() + .unwrap(); + assert!(exit.success()); + // std::fs::remove_dir_all(dirname).unwrap(); +} + +/// A create on crates.io. +#[derive(Debug, Deserialize, Serialize)] +struct Crate { + #[serde(rename = "id")] + name: String, + #[serde(rename = "max_stable_version")] + version: Option, + #[serde(rename = "newest_version")] + newest_version: String, +} + +/// The list of crates from crates.io +#[derive(Debug, Deserialize)] +struct CratesList { + crates: Vec, +} + +/// Create a list of top ``count`` crates. +fn top_crates_by_download_count(mut count: usize) -> Vec { + const PAGE_SIZE: usize = 100; + let page_count = count / PAGE_SIZE + 2; + let mut sources = Vec::new(); + for page in 1..page_count { + let url = format!( + "https://crates.io/api/v1/crates?page={}&per_page={}&sort=downloads", + page, PAGE_SIZE + ); + let resp = get(&url).expect("Could not fetch top crates"); + assert!( + resp.status().is_success(), + "Response status: {}", + resp.status() + ); + let page_crates: CratesList = match serde_json::from_reader(resp) { + Ok(page_crates) => page_crates, + Err(e) => panic!("Invalid JSON {e}"), + }; + sources.extend(page_crates.crates.into_iter().take(count)); + count -= std::cmp::min(PAGE_SIZE, count); + } + sources +} diff --git a/prusti-interface/Cargo.toml b/prusti-interface/Cargo.toml index 1812b1f52f1..f7e19dd0f3e 100644 --- a/prusti-interface/Cargo.toml +++ b/prusti-interface/Cargo.toml @@ -28,7 +28,6 @@ rustc-hash = "1.1.0" datafrog = "2.0.1" vir = { path = "../vir" } version-compare = "0.1" -micromir = { path = "../micromir" } [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index fabb99e076d..e182e70d0d1 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -10,7 +10,6 @@ use crate::{ environment::{debug_utils::to_text::ToText, mir_utils::RealEdges, Environment}, }; use log::{debug, trace}; -use micromir::MicroBody; use prusti_rustc_interface::{ data_structures::fx::{FxHashMap, FxHashSet}, hir::def_id, @@ -44,8 +43,6 @@ impl<'tcx> Procedure<'tcx> { let mir = env .body .get_impure_fn_body_identity(proc_def_id.expect_local()); - let micro_mir = MicroBody::new(mir.body(), env.tcx()); - println!("--------\n{:?}\n--------", micro_mir.basic_blocks); let real_edges = RealEdges::new(&mir); let reachable_basic_blocks = build_reachable_basic_blocks(&mir, &real_edges); let nonspec_basic_blocks = build_nonspec_basic_blocks(env.query, &mir, &real_edges); @@ -136,6 +133,10 @@ impl<'tcx> Procedure<'tcx> { &self.mir } + pub fn get_mir_rc(&self) -> std::rc::Rc> { + self.mir.body() + } + /// Get the typing context. pub fn get_tcx(&self) -> TyCtxt<'tcx> { self.tcx diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 65a6bc9e624..e11da7863fc 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -129,6 +129,8 @@ lazy_static::lazy_static! { settings.set_default("use_new_encoder", true).unwrap(); settings.set_default::>("number_of_parallel_verifiers", None).unwrap(); settings.set_default::>("min_prusti_version", None).unwrap(); + // TODO: remove this option + settings.set_default("test_free_pcs", false).unwrap(); settings.set_default("print_desugared_specs", false).unwrap(); settings.set_default("print_typeckd_specs", false).unwrap(); @@ -1023,3 +1025,7 @@ pub fn cargo_command() -> String { pub fn enable_type_invariants() -> bool { read_setting("enable_type_invariants") } + +pub fn test_free_pcs() -> bool { + read_setting("test_free_pcs") +} diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index 678eab4a2e2..62555ec8668 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -21,6 +21,7 @@ lazy_static = "1.4.0" tracing = { path = "../tracing" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-chrome = "0.7" +micromir = { path = "../micromir" } [build-dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock"] } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index f8018ade5ee..587f3b79be0 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,4 +1,5 @@ use crate::verifier::verify; +use micromir::test_free_pcs; use prusti_common::config; use prusti_interface::{ environment::{mir_storage, Environment}, @@ -138,7 +139,11 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { } CrossCrateSpecs::import_export_cross_crate(&mut env, &mut def_spec); if !config::no_verify() { - verify(env, def_spec); + if config::test_free_pcs() { + test_free_pcs(&env); + } else { + verify(env, def_spec); + } } }); diff --git a/x.py b/x.py index 8dc7ed8c063..8cba90906e7 100755 --- a/x.py +++ b/x.py @@ -37,6 +37,7 @@ RUSTFMT_CRATES = [ 'analysis', 'jni-gen', + 'micromir', 'prusti', 'prusti-common', 'prusti-contracts/prusti-contracts', From aa67674cf448fa221d8e399d7fccd677da0e0793 Mon Sep 17 00:00:00 2001 From: Jonas Fiala Date: Wed, 8 Mar 2023 18:23:19 +0000 Subject: [PATCH 04/32] Clippy/fmt fix --- Cargo.lock | 41 +++++++++++++++++++ micromir/src/defs/statement.rs | 22 ++++------ micromir/src/defs/terminator.rs | 16 ++++---- micromir/src/free_pcs/permission.rs | 2 +- micromir/src/repack/calculate.rs | 10 ++--- micromir/src/repack/mod.rs | 4 +- micromir/src/repack/place.rs | 1 - .../src/repack/{repack.rs => repacker.rs} | 13 ++---- micromir/tests/top_crates.rs | 10 ++--- 9 files changed, 72 insertions(+), 47 deletions(-) rename micromir/src/repack/{repack.rs => repacker.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index 0ddfb2684a5..d51fce1f31e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1373,6 +1373,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1682,6 +1695,20 @@ dependencies = [ "autocfg", ] +[[package]] +name = "micromir" +version = "0.1.0" +dependencies = [ + "derive_more", + "prusti-interface", + "prusti-rustc-interface", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "tracing 0.1.0", +] + [[package]] name = "mime" version = "0.3.16" @@ -2107,6 +2134,7 @@ dependencies = [ "env_logger", "lazy_static", "log", + "micromir", "prusti-common", "prusti-interface", "prusti-rustc-interface", @@ -2428,10 +2456,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -2441,6 +2471,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tower-service", "url", @@ -3064,6 +3095,16 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" diff --git a/micromir/src/defs/statement.rs b/micromir/src/defs/statement.rs index e8fa70c9ced..bec83324385 100644 --- a/micromir/src/defs/statement.rs +++ b/micromir/src/defs/statement.rs @@ -65,7 +65,7 @@ impl<'tcx> From<&Statement<'tcx>> for MicroStatement<'tcx> { variant_index, } => MicroStatementKind::SetDiscriminant { place: box place.into(), - variant_index: variant_index, + variant_index, }, &StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box p.into()), &StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(l), @@ -119,9 +119,9 @@ impl Debug for MicroStatementKind<'_> { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { use MicroStatementKind::*; match self { - Assign(box (ref place, ref rv)) => write!(fmt, "{:?} = {:?}", place, rv), + Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), FakeRead(box (ref cause, ref place)) => { - write!(fmt, "FakeRead({:?}, {:?})", cause, place) + write!(fmt, "FakeRead({cause:?}, {place:?})") } Retag(ref kind, ref place) => write!( fmt, @@ -134,27 +134,23 @@ impl Debug for MicroStatementKind<'_> { }, place, ), - StorageLive(ref place) => write!(fmt, "StorageLive({:?})", place), - StorageDead(ref place) => write!(fmt, "StorageDead({:?})", place), + StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), + StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), SetDiscriminant { ref place, variant_index, } => { - write!(fmt, "discriminant({:?}) = {:?}", place, variant_index) + write!(fmt, "discriminant({place:?}) = {variant_index:?}") } - Deinit(ref place) => write!(fmt, "Deinit({:?})", place), + Deinit(ref place) => write!(fmt, "Deinit({place:?})"), AscribeUserType(box (ref place, ref c_ty), ref variance) => { - write!( - fmt, - "AscribeUserType({:?}, {:?}, {:?})", - place, variance, c_ty - ) + write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") } Coverage(box self::Coverage { ref kind, code_region: Some(ref rgn), }) => { - write!(fmt, "Coverage::{:?} for {:?}", kind, rgn) + write!(fmt, "Coverage::{kind:?} for {rgn:?}") } Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), diff --git a/micromir/src/defs/terminator.rs b/micromir/src/defs/terminator.rs index fff450b7d82..36fa2f25b8c 100644 --- a/micromir/src/defs/terminator.rs +++ b/micromir/src/defs/terminator.rs @@ -244,18 +244,18 @@ impl<'tcx> MicroTerminatorKind<'tcx> { use MicroTerminatorKind::*; match self { Goto { .. } => write!(fmt, "goto"), - SwitchInt { discr, .. } => write!(fmt, "switchInt({:?})", discr), + SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), Return => write!(fmt, "return"), GeneratorDrop => write!(fmt, "generator_drop"), Resume => write!(fmt, "resume"), Abort => write!(fmt, "abort"), Yield { value, resume_arg, .. - } => write!(fmt, "{:?} = yield({:?})", resume_arg, value), + } => write!(fmt, "{resume_arg:?} = yield({value:?})"), Unreachable => write!(fmt, "unreachable"), - Drop { place, .. } => write!(fmt, "drop({:?})", place), + Drop { place, .. } => write!(fmt, "drop({place:?})"), DropAndReplace { place, value, .. } => { - write!(fmt, "replace({:?} <- {:?})", place, value) + write!(fmt, "replace({place:?} <- {value:?})") } Call { func, @@ -263,13 +263,13 @@ impl<'tcx> MicroTerminatorKind<'tcx> { destination, .. } => { - write!(fmt, "{:?} = ", destination)?; - write!(fmt, "{:?}(", func)?; + write!(fmt, "{destination:?} = ")?; + write!(fmt, "{func:?}(")?; for (index, arg) in args.iter().enumerate() { if index > 0 { write!(fmt, ", ")?; } - write!(fmt, "{:?}", arg)?; + write!(fmt, "{arg:?}")?; } write!(fmt, ")") } @@ -283,7 +283,7 @@ impl<'tcx> MicroTerminatorKind<'tcx> { if !expected { write!(fmt, "!")?; } - write!(fmt, "{:?}, ", cond)?; + write!(fmt, "{cond:?}, ")?; msg.fmt_assert_args(fmt)?; write!(fmt, ")") } diff --git a/micromir/src/free_pcs/permission.rs b/micromir/src/free_pcs/permission.rs index 56b7fbed150..2b0b5422fd3 100644 --- a/micromir/src/free_pcs/permission.rs +++ b/micromir/src/free_pcs/permission.rs @@ -94,7 +94,7 @@ impl<'tcx> LocalUpdate<'tcx> { match state { PermissionLocal::Unallocated => { assert!(pre.unalloc_allowed); - return Some(PermissionLocal::Unallocated); + Some(PermissionLocal::Unallocated) } PermissionLocal::Allocated(state) => { let mut achievable = PermissionProjections(FxHashMap::default()); diff --git a/micromir/src/repack/calculate.rs b/micromir/src/repack/calculate.rs index 8cc1b208225..df626086e35 100644 --- a/micromir/src/repack/calculate.rs +++ b/micromir/src/repack/calculate.rs @@ -29,9 +29,8 @@ impl<'tcx> MicroBody<'tcx> { FreeState::initial(self.body.local_decls().len(), |local: Local| { if local == return_local { Some(PermissionKind::Uninit) - } else if always_live.contains(local) { - Some(PermissionKind::Exclusive) - } else if local <= last_arg { + // TODO: figure out if `always_live` should start as `Uninit` or `Exclusive` + } else if local <= last_arg || always_live.contains(local) { Some(PermissionKind::Exclusive) } else { None @@ -46,7 +45,7 @@ impl<'tcx> MicroBody<'tcx> { // Calculate initial state let state = self.initial_free_state(); let preds = self.body.basic_blocks.predecessors(); - let rp = PlaceRepacker::new(&*self.body, tcx); + let rp = PlaceRepacker::new(&self.body, tcx); let start_node = self.body.basic_blocks.start_node(); // Do the actual repacking calculation @@ -101,9 +100,8 @@ impl Queue { self.done[bb] = true; Some(bb) } else { - if self.dirty_queue.len() == 0 { + if self.dirty_queue.is_empty() { debug_assert!((0..self.done.len()) - .into_iter() .map(BasicBlock::from_usize) .all(|bb| self.done[bb] || !self.can_redo[bb])); return None; diff --git a/micromir/src/repack/mod.rs b/micromir/src/repack/mod.rs index 07dd57f08a6..dd474de9c75 100644 --- a/micromir/src/repack/mod.rs +++ b/micromir/src/repack/mod.rs @@ -4,11 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -mod repack; +mod repacker; mod calculate; pub(crate) mod triple; mod place; pub use calculate::*; pub(crate) use place::*; -pub use repack::*; +pub use repacker::*; diff --git a/micromir/src/repack/place.rs b/micromir/src/repack/place.rs index 2a4990151d1..e4362cf7d8d 100644 --- a/micromir/src/repack/place.rs +++ b/micromir/src/repack/place.rs @@ -52,7 +52,6 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { min_length, from_end, } => (0..min_length) - .into_iter() .filter(|&i| { if from_end { i != min_length - offset diff --git a/micromir/src/repack/repack.rs b/micromir/src/repack/repacker.rs similarity index 97% rename from micromir/src/repack/repack.rs rename to micromir/src/repack/repacker.rs index 6914f9d2673..eea39625c9a 100644 --- a/micromir/src/repack/repack.rs +++ b/micromir/src/repack/repacker.rs @@ -17,18 +17,13 @@ use crate::{ PermissionProjections, Place, PlaceOrdering, RelatedSet, }; -#[derive(Clone, Deref, DerefMut)] +#[derive(Clone, Default, Deref, DerefMut)] pub struct Repacks<'tcx>(Vec>); impl Debug for Repacks<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.0.fmt(f) } } -impl<'tcx> Repacks<'tcx> { - pub fn new() -> Self { - Self(Vec::new()) - } -} #[derive(Clone)] pub struct PlaceCapabilitySummary<'tcx> { @@ -69,7 +64,7 @@ pub struct TerminatorPlaceCapabilitySummary<'tcx> { impl Debug for TerminatorPlaceCapabilitySummary<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - if self.repacks.len() == 0 { + if self.repacks.is_empty() { write!(f, "{:?}", &self.state_before) } else { f.debug_struct("PCS") @@ -105,7 +100,7 @@ impl<'tcx> TerminatorPlaceCapabilitySummary<'tcx> { is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>, ) { - let repacks = self.repacks.entry(bb).or_insert_with(Repacks::new); + let repacks = self.repacks.entry(bb).or_default(); repacks.clear(); for (l, to) in to.iter_enumerated() { let new = @@ -123,7 +118,7 @@ impl<'tcx> FreeState<'tcx> { update: &FreeStateUpdate<'tcx>, rp: PlaceRepacker<'_, 'tcx>, ) -> (PlaceCapabilitySummary<'tcx>, FreeState<'tcx>) { - let mut repacks = Repacks::new(); + let mut repacks = Repacks::default(); let pre = update .iter_enumerated() .map(|(l, update)| { diff --git a/micromir/tests/top_crates.rs b/micromir/tests/top_crates.rs index c4d681dcee8..84dee437ac0 100644 --- a/micromir/tests/top_crates.rs +++ b/micromir/tests/top_crates.rs @@ -26,13 +26,10 @@ pub fn top_crates_range(range: std::ops::Range) { } fn run_on_crate(name: &str, version: &str) { - let dirname = format!("./tmp/{}-{}", name, version); + let dirname = format!("./tmp/{name}-{version}"); let filename = format!("{dirname}.crate"); if !std::path::PathBuf::from(&filename).exists() { - let dl = format!( - "https://crates.io/api/v1/crates/{}/{}/download", - name, version - ); + let dl = format!("https://crates.io/api/v1/crates/{name}/{version}/download"); let mut resp = get(&dl).expect("Could not fetch top crates"); let mut file = std::fs::File::create(&filename).unwrap(); resp.copy_to(&mut file).unwrap(); @@ -102,8 +99,7 @@ fn top_crates_by_download_count(mut count: usize) -> Vec { let mut sources = Vec::new(); for page in 1..page_count { let url = format!( - "https://crates.io/api/v1/crates?page={}&per_page={}&sort=downloads", - page, PAGE_SIZE + "https://crates.io/api/v1/crates?page={page}&per_page={PAGE_SIZE}&sort=downloads" ); let resp = get(&url).expect("Could not fetch top crates"); assert!( From 0a533aea1540d4d82d437bbae829ec2024396988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 18 Apr 2023 12:54:36 +0200 Subject: [PATCH 05/32] Switch to using rustc dataflow engine --- Cargo.lock | 33 +- Cargo.toml | 2 +- micromir/src/check/checker.rs | 136 ----- micromir/src/defs/body.rs | 143 ----- micromir/src/defs/operand.rs | 100 ---- micromir/src/defs/rvalue.rs | 95 ---- micromir/src/defs/statement.rs | 161 ------ micromir/src/defs/terminator.rs | 401 -------------- micromir/src/free_pcs/permission.rs | 517 ------------------ micromir/src/lib.rs | 36 -- micromir/src/repack/calculate.rs | 295 ---------- micromir/src/repack/repacker.rs | 294 ---------- micromir/src/repack/triple.rs | 124 ----- {micromir => mir-state-analysis}/Cargo.toml | 5 +- .../src/free_pcs/check/checker.rs | 159 ++++++ .../src/free_pcs/check/consistency.rs | 45 ++ .../src/free_pcs/check}/mod.rs | 5 +- .../src/free_pcs/impl/engine.rs | 96 ++++ mir-state-analysis/src/free_pcs/impl/fpcs.rs | 148 +++++ .../src/free_pcs/impl/join_semi_lattice.rs | 197 +++++++ mir-state-analysis/src/free_pcs/impl/local.rs | 163 ++++++ .../src/free_pcs/impl}/mod.rs | 20 +- mir-state-analysis/src/free_pcs/impl/place.rs | 92 ++++ .../src/free_pcs/impl/triple.rs | 148 +++++ .../src/free_pcs/impl/update.rs | 130 +++++ .../src/free_pcs}/mod.rs | 13 +- .../src/free_pcs/results}/mod.rs | 4 +- .../src/free_pcs/results/repacking.rs | 0 .../src/free_pcs/results/repacks.rs | 21 + mir-state-analysis/src/lib.rs | 28 + .../src/utils/mod.rs | 3 + .../src/utils/place.rs | 133 ++++- .../src/utils/repacker.rs | 263 +++++---- .../tests/top_crates.rs | 0 prusti/Cargo.toml | 2 +- prusti/src/callbacks.rs | 11 +- x.py | 2 +- 37 files changed, 1570 insertions(+), 2455 deletions(-) delete mode 100644 micromir/src/check/checker.rs delete mode 100644 micromir/src/defs/body.rs delete mode 100644 micromir/src/defs/operand.rs delete mode 100644 micromir/src/defs/rvalue.rs delete mode 100644 micromir/src/defs/statement.rs delete mode 100644 micromir/src/defs/terminator.rs delete mode 100644 micromir/src/free_pcs/permission.rs delete mode 100644 micromir/src/lib.rs delete mode 100644 micromir/src/repack/calculate.rs delete mode 100644 micromir/src/repack/repacker.rs delete mode 100644 micromir/src/repack/triple.rs rename {micromir => mir-state-analysis}/Cargo.toml (83%) create mode 100644 mir-state-analysis/src/free_pcs/check/checker.rs create mode 100644 mir-state-analysis/src/free_pcs/check/consistency.rs rename {micromir/src/free_pcs => mir-state-analysis/src/free_pcs/check}/mod.rs (79%) create mode 100644 mir-state-analysis/src/free_pcs/impl/engine.rs create mode 100644 mir-state-analysis/src/free_pcs/impl/fpcs.rs create mode 100644 mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs create mode 100644 mir-state-analysis/src/free_pcs/impl/local.rs rename {micromir/src/defs => mir-state-analysis/src/free_pcs/impl}/mod.rs (56%) create mode 100644 mir-state-analysis/src/free_pcs/impl/place.rs create mode 100644 mir-state-analysis/src/free_pcs/impl/triple.rs create mode 100644 mir-state-analysis/src/free_pcs/impl/update.rs rename {micromir/src/repack => mir-state-analysis/src/free_pcs}/mod.rs (63%) rename {micromir/src/check => mir-state-analysis/src/free_pcs/results}/mod.rs (87%) create mode 100644 mir-state-analysis/src/free_pcs/results/repacking.rs create mode 100644 mir-state-analysis/src/free_pcs/results/repacks.rs create mode 100644 mir-state-analysis/src/lib.rs rename {micromir => mir-state-analysis}/src/utils/mod.rs (81%) rename {micromir => mir-state-analysis}/src/utils/place.rs (63%) rename micromir/src/repack/place.rs => mir-state-analysis/src/utils/repacker.rs (55%) rename {micromir => mir-state-analysis}/tests/top_crates.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 8857aded314..3b35deb8326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,9 +633,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1695,20 +1695,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "micromir" -version = "0.1.0" -dependencies = [ - "derive_more", - "prusti-interface", - "prusti-rustc-interface", - "reqwest", - "serde", - "serde_derive", - "serde_json", - "tracing 0.1.0", -] - [[package]] name = "mime" version = "0.3.16" @@ -1761,6 +1747,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "mir-state-analysis" +version = "0.1.0" +dependencies = [ + "derive_more", + "prusti-rustc-interface", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "tracing 0.1.0", +] + [[package]] name = "multipart" version = "0.18.0" @@ -2134,7 +2133,7 @@ dependencies = [ "env_logger", "lazy_static", "log", - "micromir", + "mir-state-analysis", "prusti-common", "prusti-interface", "prusti-rustc-interface", diff --git a/Cargo.toml b/Cargo.toml index 74d88886b7d..2670d0c5e95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "prusti-common", "prusti-utils", "tracing", - "micromir", + "mir-state-analysis", "prusti-interface", "prusti-viper", "prusti-server", diff --git a/micromir/src/check/checker.rs b/micromir/src/check/checker.rs deleted file mode 100644 index 6fcf1a64575..00000000000 --- a/micromir/src/check/checker.rs +++ /dev/null @@ -1,136 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use prusti_rustc_interface::data_structures::fx::FxHashMap; - -use crate::{ - repack::triple::ModifiesFreeState, FreeState, MicroBasicBlocks, PermissionKind, - PermissionLocal, PlaceOrdering, PlaceRepacker, RepackOp, Repacks, -}; - -pub(crate) fn check<'tcx>(bbs: &MicroBasicBlocks<'tcx>, rp: PlaceRepacker<'_, 'tcx>) { - for bb in bbs.basic_blocks.iter() { - let mut curr_state = bb.get_start_state().clone(); - // Consistency - curr_state.consistency_check(rp); - for stmt in bb.statements.iter() { - // Pre-state - let pcs = stmt.repack_operands.as_ref().unwrap(); - assert_eq!(pcs.state(), &curr_state); - // Repacks - pcs.repacks().update_free(&mut curr_state, false, rp); - // Consistency - curr_state.consistency_check(rp); - // Statement - stmt.get_update(curr_state.len()) - .update_free(&mut curr_state); - // Consistency - curr_state.consistency_check(rp); - } - // Pre-state - let pcs = bb.terminator.repack_operands.as_ref().unwrap(); - assert_eq!(pcs.state(), &curr_state); - // Repacks - pcs.repacks().update_free(&mut curr_state, false, rp); - // Consistency - curr_state.consistency_check(rp); - // Terminator - bb.terminator - .get_update(curr_state.len()) - .update_free(&mut curr_state); - // Consistency - curr_state.consistency_check(rp); - // Join repacks - let pcs = bb.terminator.repack_join.as_ref().unwrap(); - assert_eq!(pcs.state(), &curr_state); - for succ in bb.terminator.original_kind.successors() { - let mut curr_state = curr_state.clone(); - // No repack means that `succ` only has one predecessor - if let Some(repack) = pcs.repacks().get(&succ) { - repack.update_free(&mut curr_state, bbs.basic_blocks[succ].is_cleanup, rp); - // Consistency - curr_state.consistency_check(rp); - } - assert_eq!( - bbs.basic_blocks[succ].get_start_state(), - &curr_state, - "{succ:?}" - ); - } - } -} - -impl<'tcx> Repacks<'tcx> { - #[tracing::instrument(level = "debug", skip(rp))] - fn update_free( - &self, - state: &mut FreeState<'tcx>, - can_dealloc: bool, - rp: PlaceRepacker<'_, 'tcx>, - ) { - for rpck in &**self { - match rpck { - RepackOp::Weaken(place, from, to) => { - let curr_state = state[place.local].get_allocated_mut(); - let old = curr_state.insert(*place, *to); - assert_eq!(old, Some(*from), "{rpck:?}, {curr_state:?}"); - } - &RepackOp::DeallocForCleanup(local) => { - assert!(can_dealloc); - let curr_state = state[local].get_allocated_mut(); - assert_eq!(curr_state.len(), 1); - assert_eq!(curr_state[&local.into()], PermissionKind::Uninit); - state[local] = PermissionLocal::Unallocated; - } - &RepackOp::DeallocUnknown(local) => { - assert!(!can_dealloc); - let curr_state = state[local].get_allocated_mut(); - assert_eq!(curr_state.len(), 1); - assert_eq!(curr_state[&local.into()], PermissionKind::Uninit); - state[local] = PermissionLocal::Unallocated; - } - RepackOp::Pack(place, guide, kind) => { - assert_eq!( - place.partial_cmp(*guide), - Some(PlaceOrdering::Prefix), - "{rpck:?}" - ); - let curr_state = state[place.local].get_allocated_mut(); - let mut removed = curr_state - .drain_filter(|p, _| place.related_to(*p)) - .collect::>(); - let (p, others) = rp.expand_one_level(*place, *guide); - assert!(others - .into_iter() - .chain(std::iter::once(p)) - .all(|p| removed.remove(&p).unwrap() == *kind)); - assert!(removed.is_empty(), "{rpck:?}, {removed:?}"); - - let old = curr_state.insert(*place, *kind); - assert_eq!(old, None); - } - RepackOp::Unpack(place, guide, kind) => { - assert_eq!( - place.partial_cmp(*guide), - Some(PlaceOrdering::Prefix), - "{rpck:?}" - ); - let curr_state = state[place.local].get_allocated_mut(); - assert_eq!( - curr_state.remove(place), - Some(*kind), - "{rpck:?} ({:?})", - &**curr_state - ); - - let (p, others) = rp.expand_one_level(*place, *guide); - curr_state.insert(p, *kind); - curr_state.extend(others.into_iter().map(|p| (p, *kind))); - } - } - } - } -} diff --git a/micromir/src/defs/body.rs b/micromir/src/defs/body.rs deleted file mode 100644 index 44d6f81e694..00000000000 --- a/micromir/src/defs/body.rs +++ /dev/null @@ -1,143 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use derive_more::{Deref, DerefMut}; -use prusti_rustc_interface::{ - index::vec::IndexVec, - middle::{ - mir::{BasicBlock, BasicBlockData, Body}, - ty::TyCtxt, - }, -}; -use std::{ - fmt::{Display, Formatter, Result}, - rc::Rc, -}; - -use crate::{ - FreeState, MicroStatement, MicroTerminator, TermDebug, TerminatorPlaceCapabilitySummary, -}; - -#[derive(Clone, Debug, Deref, DerefMut)] -pub struct MicroBody<'tcx> { - pub(crate) done_repacking: bool, - pub basic_blocks: MicroBasicBlocks<'tcx>, - #[deref] - #[deref_mut] - pub body: Rc>, -} -impl<'tcx> MicroBody<'tcx> { - pub fn new(body: Rc>, tcx: TyCtxt<'tcx>) -> Self { - let mut body = Self::from(body); - body.calculate_repacking(tcx); - body - } -} - -impl<'tcx> From>> for MicroBody<'tcx> { - /// Clones a `mir::Body` into an identical `MicroBody`. - /// Doesn't calculate any repacking information. - fn from(body: Rc>) -> Self { - let basic_blocks = MicroBasicBlocks::from(&*body); - Self { - done_repacking: false, - basic_blocks, - body, - } - } -} - -#[derive(Clone, Debug)] -pub struct MicroBasicBlocks<'tcx> { - pub basic_blocks: IndexVec>, -} - -impl<'tcx> From<&Body<'tcx>> for MicroBasicBlocks<'tcx> { - #[tracing::instrument(level = "info", skip(body), fields(body = format!("{body:#?}")))] - fn from(body: &Body<'tcx>) -> Self { - Self { - basic_blocks: body - .basic_blocks - .iter() - .map(MicroBasicBlockData::from) - .collect(), - } - } -} - -impl Display for MicroBasicBlocks<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - for (bb, data) in self.basic_blocks.iter_enumerated() { - writeln!(f, "{bb:?}: {{")?; - for stmt in &data.statements { - let repack = stmt.repack_operands.as_ref().unwrap(); - writeln!(f, " // {}", repack.state())?; - for rpck in &**repack.repacks() { - writeln!(f, " {rpck:?};")?; - } - for (tmp, operand) in stmt.operands.iter_enumerated() { - writeln!(f, " {tmp:?} <- {operand:?};")?; - } - writeln!(f, " {:?};", stmt.kind)?; - } - let repack = data.terminator.repack_operands.as_ref().unwrap(); - writeln!(f, " // {}", repack.state())?; - for rpck in &**repack.repacks() { - writeln!(f, " {rpck:?};")?; - } - for (tmp, operand) in data.terminator.operands.iter_enumerated() { - writeln!(f, " {tmp:?} <- {operand:?};")?; - } - let display = TermDebug(&data.terminator.kind, &data.terminator.original_kind); - writeln!(f, " {display:?};")?; - let repack = data.terminator.repack_join.as_ref().unwrap(); - // writeln!(f, " // {}", repack.state())?; - for (bb, repacks) in repack.repacks().iter() { - if repacks.is_empty() { - continue; - } - writeln!(f, " {bb:?}:")?; - for rpck in &**repacks { - writeln!(f, " {rpck:?};")?; - } - } - writeln!(f, "}}")?; - } - Ok(()) - } -} - -#[derive(Clone, Debug)] -pub struct MicroBasicBlockData<'tcx> { - pub statements: Vec>, - pub terminator: MicroTerminator<'tcx>, - pub is_cleanup: bool, -} - -impl<'tcx> From<&BasicBlockData<'tcx>> for MicroBasicBlockData<'tcx> { - fn from(data: &BasicBlockData<'tcx>) -> Self { - Self { - statements: data.statements.iter().map(MicroStatement::from).collect(), - terminator: data.terminator().into(), - is_cleanup: data.is_cleanup, - } - } -} - -impl<'tcx> MicroBasicBlockData<'tcx> { - pub(crate) fn get_start_state(&self) -> &FreeState<'tcx> { - if self.statements.is_empty() { - self.terminator.repack_operands.as_ref().unwrap().state() - } else { - self.statements[0].repack_operands.as_ref().unwrap().state() - } - } - pub(crate) fn get_end_pcs_mut( - &mut self, - ) -> Option<&mut TerminatorPlaceCapabilitySummary<'tcx>> { - self.terminator.repack_join.as_mut() - } -} diff --git a/micromir/src/defs/operand.rs b/micromir/src/defs/operand.rs deleted file mode 100644 index 9eb60715523..00000000000 --- a/micromir/src/defs/operand.rs +++ /dev/null @@ -1,100 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use derive_more::{Deref, DerefMut}; -use prusti_rustc_interface::{ - index::vec::{Idx, IndexVec}, - middle::mir::{Constant, Operand}, -}; -use std::fmt::{Debug, Formatter, Result}; - -use crate::Place; - -#[derive(Clone, Debug, PartialEq, Hash)] -pub enum MicroFullOperand<'tcx> { - Copy(Place<'tcx>), - Move(Place<'tcx>), - Constant(Box>), -} - -impl<'tcx> From<&Operand<'tcx>> for MicroFullOperand<'tcx> { - fn from(value: &Operand<'tcx>) -> Self { - match value { - &Operand::Copy(p) => MicroFullOperand::Copy(p.into()), - &Operand::Move(p) => MicroFullOperand::Move(p.into()), - Operand::Constant(c) => MicroFullOperand::Constant(c.clone()), - } - } -} - -/// Note that one can have the same `Local` multiple times in the `Operands` vector -/// for a single statement. For example in the following code: -/// ``` -/// struct S { a: bool, b: bool, c: bool } -/// fn true_a(s: &S) -> S { -/// S { a: true, .. *s } -/// } -/// ``` -#[derive(Clone, Debug, Deref, DerefMut)] -pub struct Operands<'tcx>(IndexVec>); -impl<'tcx> Operands<'tcx> { - pub(crate) fn new() -> Self { - Self(IndexVec::new()) - } - pub(crate) fn translate_operand(&mut self, operand: &Operand<'tcx>) -> MicroOperand { - let index = self.push(operand.into()); - MicroOperand::new(index) - } -} - -#[derive(Clone, Copy, Hash, Eq, PartialEq, Deref, DerefMut)] -pub struct MicroOperand(Temporary); -impl MicroOperand { - pub const fn new(value: Temporary) -> Self { - Self(value) - } -} -impl Debug for MicroOperand { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "{:?}", self.0) - } -} - -#[derive(Clone, Copy, Hash, Eq, PartialEq)] -pub struct Temporary { - private: u32, -} -impl Temporary { - pub const fn from_usize(value: usize) -> Self { - Self { - private: value as u32, - } - } - pub const fn from_u32(value: u32) -> Self { - Self { private: value } - } - pub const fn as_u32(self) -> u32 { - self.private - } - pub const fn as_usize(self) -> usize { - self.private as usize - } -} -impl Idx for Temporary { - fn new(value: usize) -> Self { - Self { - private: value as u32, - } - } - fn index(self) -> usize { - self.private as usize - } -} -impl Debug for Temporary { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "tmp{}", self.private) - } -} diff --git a/micromir/src/defs/rvalue.rs b/micromir/src/defs/rvalue.rs deleted file mode 100644 index 018fadbc6b6..00000000000 --- a/micromir/src/defs/rvalue.rs +++ /dev/null @@ -1,95 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::fmt::{Display, Formatter, Result}; - -use crate::{MicroOperand, Operands, Place}; -use prusti_rustc_interface::{ - middle::{ - mir::{AggregateKind, BinOp, BorrowKind, CastKind, Mutability, NullOp, Rvalue, UnOp}, - ty::{self, Region, Ty}, - }, - span::def_id::DefId, -}; - -#[derive(Clone, Debug, PartialEq, Hash)] -pub enum MicroNonDivergingIntrinsic { - Assume(MicroOperand), - CopyNonOverlapping(MicroCopyNonOverlapping), -} - -impl Display for MicroNonDivergingIntrinsic { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - Self::Assume(op) => write!(f, "assume({op:?})"), - Self::CopyNonOverlapping(MicroCopyNonOverlapping { src, dst, count }) => { - write!( - f, - "copy_nonoverlapping(dst = {dst:?}, src = {src:?}, count = {count:?})" - ) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Hash)] -pub struct MicroCopyNonOverlapping { - pub src: MicroOperand, - pub dst: MicroOperand, - pub count: MicroOperand, -} - -#[derive(Clone, Debug, PartialEq, Hash)] -pub enum MicroRvalue<'tcx> { - Use(MicroOperand), - Repeat(MicroOperand, ty::Const<'tcx>), - Ref(Region<'tcx>, BorrowKind, Place<'tcx>), - ThreadLocalRef(DefId), - AddressOf(Mutability, Place<'tcx>), - Len(Place<'tcx>), - Cast(CastKind, MicroOperand, Ty<'tcx>), - BinaryOp(BinOp, Box<(MicroOperand, MicroOperand)>), - CheckedBinaryOp(BinOp, Box<(MicroOperand, MicroOperand)>), - NullaryOp(NullOp, Ty<'tcx>), - UnaryOp(UnOp, MicroOperand), - Discriminant(Place<'tcx>), - Aggregate(Box>, Vec), - ShallowInitBox(MicroOperand, Ty<'tcx>), - CopyForDeref(Place<'tcx>), -} - -impl<'tcx> Operands<'tcx> { - pub(crate) fn translate_rvalue(&mut self, rvalue: &Rvalue<'tcx>) -> MicroRvalue<'tcx> { - match rvalue { - Rvalue::Use(o) => MicroRvalue::Use(self.translate_operand(o)), - Rvalue::Repeat(o, c) => MicroRvalue::Repeat(self.translate_operand(o), *c), - &Rvalue::Ref(r, bk, p) => MicroRvalue::Ref(r, bk, p.into()), - Rvalue::ThreadLocalRef(d) => MicroRvalue::ThreadLocalRef(*d), - &Rvalue::AddressOf(m, p) => MicroRvalue::AddressOf(m, p.into()), - &Rvalue::Len(p) => MicroRvalue::Len(p.into()), - Rvalue::Cast(ck, o, ty) => MicroRvalue::Cast(*ck, self.translate_operand(o), *ty), - Rvalue::BinaryOp(op, box (opa, opb)) => MicroRvalue::BinaryOp( - *op, - box (self.translate_operand(opa), self.translate_operand(opb)), - ), - Rvalue::CheckedBinaryOp(op, box (opa, opb)) => MicroRvalue::CheckedBinaryOp( - *op, - box (self.translate_operand(opa), self.translate_operand(opb)), - ), - Rvalue::NullaryOp(op, ty) => MicroRvalue::NullaryOp(*op, *ty), - Rvalue::UnaryOp(op, o) => MicroRvalue::UnaryOp(*op, self.translate_operand(o)), - &Rvalue::Discriminant(p) => MicroRvalue::Discriminant(p.into()), - Rvalue::Aggregate(ak, ops) => MicroRvalue::Aggregate( - ak.clone(), - ops.iter().map(|o| self.translate_operand(o)).collect(), - ), - Rvalue::ShallowInitBox(o, ty) => { - MicroRvalue::ShallowInitBox(self.translate_operand(o), *ty) - } - &Rvalue::CopyForDeref(p) => MicroRvalue::CopyForDeref(p.into()), - } - } -} diff --git a/micromir/src/defs/statement.rs b/micromir/src/defs/statement.rs deleted file mode 100644 index bec83324385..00000000000 --- a/micromir/src/defs/statement.rs +++ /dev/null @@ -1,161 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use core::fmt::{Debug, Formatter, Result}; -use prusti_rustc_interface::{ - middle::{ - mir::{ - Coverage, FakeReadCause, Local, NonDivergingIntrinsic, RetagKind, Statement, - StatementKind, UserTypeProjection, - }, - ty, - }, - target::abi::VariantIdx, -}; - -use crate::{ - MicroCopyNonOverlapping, MicroNonDivergingIntrinsic, MicroRvalue, Operands, Place, - PlaceCapabilitySummary, -}; - -#[derive(Clone)] -/// Note that in rare cases an operand and the target of kind can be the same place! -/// For example, the following function: -/// https://github.com/dtolnay/syn/blob/636509368ed9dbfad8bf3d15f84b0046804a1c14/src/bigint.rs#L13-L29 -/// generates a `MicroStatement { operands: [Copy(_5), Move(_22)], stmt: _5 = BinaryOp(BitOr, (tmp0, tmp1)) }` -pub struct MicroStatement<'tcx> { - pub repack_operands: Option>, - pub operands: Operands<'tcx>, - // pub repack_stmt: Option>, - pub kind: MicroStatementKind<'tcx>, -} - -#[derive(Clone, PartialEq, Hash)] -pub enum MicroStatementKind<'tcx> { - Assign(Box<(Place<'tcx>, MicroRvalue<'tcx>)>), - FakeRead(Box<(FakeReadCause, Place<'tcx>)>), - SetDiscriminant { - place: Box>, - variant_index: VariantIdx, - }, - Deinit(Box>), - StorageLive(Local), - StorageDead(Local), - Retag(RetagKind, Box>), - AscribeUserType(Box<(Place<'tcx>, UserTypeProjection)>, ty::Variance), - Coverage(Box), - Intrinsic(Box), - ConstEvalCounter, - Nop, -} - -impl<'tcx> From<&Statement<'tcx>> for MicroStatement<'tcx> { - fn from(stmt: &Statement<'tcx>) -> Self { - let mut operands = Operands::new(); - let kind = match &stmt.kind { - StatementKind::Assign(box (p, r)) => { - MicroStatementKind::Assign(box ((*p).into(), operands.translate_rvalue(r))) - } - &StatementKind::FakeRead(box (c, p)) => MicroStatementKind::FakeRead(box (c, p.into())), - &StatementKind::SetDiscriminant { - box place, - variant_index, - } => MicroStatementKind::SetDiscriminant { - place: box place.into(), - variant_index, - }, - &StatementKind::Deinit(box p) => MicroStatementKind::Deinit(box p.into()), - &StatementKind::StorageLive(l) => MicroStatementKind::StorageLive(l), - &StatementKind::StorageDead(l) => MicroStatementKind::StorageDead(l), - &StatementKind::Retag(k, box p) => MicroStatementKind::Retag(k, box p.into()), - StatementKind::AscribeUserType(box (p, ty), v) => { - MicroStatementKind::AscribeUserType(box ((*p).into(), ty.clone()), *v) - } - StatementKind::Coverage(box c) => MicroStatementKind::Coverage(box c.clone()), - StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(o)) => { - MicroStatementKind::Intrinsic(box MicroNonDivergingIntrinsic::Assume( - operands.translate_operand(o), - )) - } - StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(c)) => { - MicroStatementKind::Intrinsic(box MicroNonDivergingIntrinsic::CopyNonOverlapping( - MicroCopyNonOverlapping { - src: operands.translate_operand(&c.src), - dst: operands.translate_operand(&c.dst), - count: operands.translate_operand(&c.count), - }, - )) - } - StatementKind::ConstEvalCounter => MicroStatementKind::ConstEvalCounter, - StatementKind::Nop => MicroStatementKind::Nop, - }; - MicroStatement { - repack_operands: None, - operands, - // repack_stmt: None, - kind, - } - } -} - -impl Debug for MicroStatement<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - let mut dbg = fmt.debug_struct("MicroStatement"); - if let Some(repack) = &self.repack_operands { - dbg.field("pcs", repack); - } - if self.operands.len() > 0 { - dbg.field("operands", &*self.operands); - } - dbg.field("stmt", &self.kind); - dbg.finish() - } -} - -impl Debug for MicroStatementKind<'_> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - use MicroStatementKind::*; - match self { - Assign(box (ref place, ref rv)) => write!(fmt, "{place:?} = {rv:?}"), - FakeRead(box (ref cause, ref place)) => { - write!(fmt, "FakeRead({cause:?}, {place:?})") - } - Retag(ref kind, ref place) => write!( - fmt, - "Retag({}{:?})", - match kind { - RetagKind::FnEntry => "[fn entry] ", - RetagKind::TwoPhase => "[2phase] ", - RetagKind::Raw => "[raw] ", - RetagKind::Default => "", - }, - place, - ), - StorageLive(ref place) => write!(fmt, "StorageLive({place:?})"), - StorageDead(ref place) => write!(fmt, "StorageDead({place:?})"), - SetDiscriminant { - ref place, - variant_index, - } => { - write!(fmt, "discriminant({place:?}) = {variant_index:?}") - } - Deinit(ref place) => write!(fmt, "Deinit({place:?})"), - AscribeUserType(box (ref place, ref c_ty), ref variance) => { - write!(fmt, "AscribeUserType({place:?}, {variance:?}, {c_ty:?})") - } - Coverage(box self::Coverage { - ref kind, - code_region: Some(ref rgn), - }) => { - write!(fmt, "Coverage::{kind:?} for {rgn:?}") - } - Coverage(box ref coverage) => write!(fmt, "Coverage::{:?}", coverage.kind), - Intrinsic(box ref intrinsic) => write!(fmt, "{intrinsic}"), - ConstEvalCounter => write!(fmt, "ConstEvalCounter"), - Nop => write!(fmt, "nop"), - } - } -} diff --git a/micromir/src/defs/terminator.rs b/micromir/src/defs/terminator.rs deleted file mode 100644 index 36fa2f25b8c..00000000000 --- a/micromir/src/defs/terminator.rs +++ /dev/null @@ -1,401 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use core::fmt::{Debug, Formatter, Result, Write}; -use prusti_rustc_interface::{ - middle::mir::{AssertMessage, BasicBlock, SwitchTargets, Terminator, TerminatorKind}, - span::Span, -}; -use std::{borrow::Cow, iter}; - -use crate::{ - FreeState, MicroOperand, Operands, Place, PlaceCapabilitySummary, - TerminatorPlaceCapabilitySummary, -}; - -#[derive(Clone)] -pub struct MicroTerminator<'tcx> { - pub repack_operands: Option>, - pub operands: Operands<'tcx>, - pub kind: MicroTerminatorKind<'tcx>, - pub repack_join: Option>, - // TODO: debug only - pub previous_rjs: Vec>, - pub original_kind: TerminatorKind<'tcx>, -} - -#[derive(Clone, PartialEq, Hash)] -pub enum MicroTerminatorKind<'tcx> { - Goto { - target: BasicBlock, - }, - SwitchInt { - discr: MicroOperand, - targets: SwitchTargets, - }, - Resume, - Abort, - Return, - Unreachable, - Drop { - place: Place<'tcx>, - target: BasicBlock, - unwind: Option, - }, - DropAndReplace { - place: Place<'tcx>, - value: MicroOperand, - target: BasicBlock, - unwind: Option, - }, - Call { - func: MicroOperand, - args: Vec, - destination: Place<'tcx>, - target: Option, - cleanup: Option, - from_hir_call: bool, - fn_span: Span, - }, - Assert { - cond: MicroOperand, - expected: bool, - msg: AssertMessage<'tcx>, - target: BasicBlock, - cleanup: Option, - }, - Yield { - value: MicroOperand, - resume: BasicBlock, - resume_arg: Place<'tcx>, - drop: Option, - }, - GeneratorDrop, - FalseEdge { - real_target: BasicBlock, - imaginary_target: BasicBlock, - }, - FalseUnwind { - real_target: BasicBlock, - unwind: Option, - }, - // InlineAsm { - // template: &'tcx [InlineAsmTemplatePiece], - // operands: Vec>, - // options: InlineAsmOptions, - // line_spans: &'tcx [Span], - // destination: Option, - // cleanup: Option, - // }, -} - -impl<'tcx> From<&Terminator<'tcx>> for MicroTerminator<'tcx> { - fn from(term: &Terminator<'tcx>) -> Self { - let mut operands = Operands::new(); - let kind = match &term.kind { - &TerminatorKind::Goto { target } => MicroTerminatorKind::Goto { target }, - TerminatorKind::SwitchInt { discr, targets } => MicroTerminatorKind::SwitchInt { - discr: operands.translate_operand(discr), - targets: targets.clone(), - }, - TerminatorKind::Resume => MicroTerminatorKind::Resume, - TerminatorKind::Abort => MicroTerminatorKind::Abort, - TerminatorKind::Return => MicroTerminatorKind::Return, - TerminatorKind::Unreachable => MicroTerminatorKind::Unreachable, - &TerminatorKind::Drop { - place, - target, - unwind, - } => MicroTerminatorKind::Drop { - place: place.into(), - target, - unwind, - }, - TerminatorKind::DropAndReplace { - place, - value, - target, - unwind, - } => MicroTerminatorKind::DropAndReplace { - place: (*place).into(), - value: operands.translate_operand(value), - target: *target, - unwind: *unwind, - }, - TerminatorKind::Call { - func, - args, - destination, - target, - cleanup, - from_hir_call, - fn_span, - } => MicroTerminatorKind::Call { - func: operands.translate_operand(func), - args: args.iter().map(|a| operands.translate_operand(a)).collect(), - destination: (*destination).into(), - target: *target, - cleanup: *cleanup, - from_hir_call: *from_hir_call, - fn_span: *fn_span, - }, - TerminatorKind::Assert { - cond, - expected, - msg, - target, - cleanup, - } => MicroTerminatorKind::Assert { - cond: operands.translate_operand(cond), - expected: *expected, - msg: msg.clone(), - target: *target, - cleanup: *cleanup, - }, - TerminatorKind::Yield { - value, - resume, - resume_arg, - drop, - } => MicroTerminatorKind::Yield { - value: operands.translate_operand(value), - resume: *resume, - resume_arg: (*resume_arg).into(), - drop: *drop, - }, - TerminatorKind::GeneratorDrop => MicroTerminatorKind::GeneratorDrop, - &TerminatorKind::FalseEdge { - real_target, - imaginary_target, - } => MicroTerminatorKind::FalseEdge { - real_target, - imaginary_target, - }, - &TerminatorKind::FalseUnwind { - real_target, - unwind, - } => MicroTerminatorKind::FalseUnwind { - real_target, - unwind, - }, - TerminatorKind::InlineAsm { .. } => todo!(), - }; - MicroTerminator { - repack_operands: None, - operands, - kind, - repack_join: None, - previous_rjs: Vec::new(), - original_kind: term.kind.clone(), - } - } -} - -impl<'tcx> Debug for MicroTerminator<'tcx> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let mut dbg = f.debug_struct("MicroTerminator"); - if let Some(repack) = &self.repack_operands { - dbg.field("pcs", repack); - } - if self.operands.len() > 0 { - dbg.field("operands", &*self.operands); - } - dbg.field("term", &TermDebug(&self.kind, &self.original_kind)); - if let Some(repack) = &self.repack_join { - dbg.field("pcs_join", repack); - } - dbg.finish() - } -} - -pub(crate) struct TermDebug<'a, 'tcx>( - pub(crate) &'a MicroTerminatorKind<'tcx>, - pub(crate) &'a TerminatorKind<'tcx>, -); -impl<'a, 'tcx> Debug for TermDebug<'a, 'tcx> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result { - self.0.fmt_head(fmt)?; - let successor_count = self.1.successors().count(); - let labels = self.0.fmt_successor_labels(); - assert_eq!(successor_count, labels.len()); - - match successor_count { - 0 => Ok(()), - 1 => write!(fmt, " -> {:?}", self.1.successors().next().unwrap()), - _ => { - write!(fmt, " -> [")?; - for (i, target) in self.1.successors().enumerate() { - if i > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{}: {:?}", labels[i], target)?; - } - write!(fmt, "]") - } - } - } -} - -impl<'tcx> MicroTerminatorKind<'tcx> { - pub fn fmt_head(&self, fmt: &mut W) -> Result { - use MicroTerminatorKind::*; - match self { - Goto { .. } => write!(fmt, "goto"), - SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"), - Return => write!(fmt, "return"), - GeneratorDrop => write!(fmt, "generator_drop"), - Resume => write!(fmt, "resume"), - Abort => write!(fmt, "abort"), - Yield { - value, resume_arg, .. - } => write!(fmt, "{resume_arg:?} = yield({value:?})"), - Unreachable => write!(fmt, "unreachable"), - Drop { place, .. } => write!(fmt, "drop({place:?})"), - DropAndReplace { place, value, .. } => { - write!(fmt, "replace({place:?} <- {value:?})") - } - Call { - func, - args, - destination, - .. - } => { - write!(fmt, "{destination:?} = ")?; - write!(fmt, "{func:?}(")?; - for (index, arg) in args.iter().enumerate() { - if index > 0 { - write!(fmt, ", ")?; - } - write!(fmt, "{arg:?}")?; - } - write!(fmt, ")") - } - Assert { - cond, - expected, - msg, - .. - } => { - write!(fmt, "assert(")?; - if !expected { - write!(fmt, "!")?; - } - write!(fmt, "{cond:?}, ")?; - msg.fmt_assert_args(fmt)?; - write!(fmt, ")") - } - FalseEdge { .. } => write!(fmt, "falseEdge"), - FalseUnwind { .. } => write!(fmt, "falseUnwind"), - // InlineAsm { template, ref operands, options, .. } => { - // write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; - // for op in operands { - // write!(fmt, ", ")?; - // let print_late = |&late| if late { "late" } else { "" }; - // match op { - // InlineAsmOperand::In { reg, value } => { - // write!(fmt, "in({}) {:?}", reg, value)?; - // } - // InlineAsmOperand::Out { reg, late, place: Some(place) } => { - // write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; - // } - // InlineAsmOperand::Out { reg, late, place: None } => { - // write!(fmt, "{}out({}) _", print_late(late), reg)?; - // } - // InlineAsmOperand::InOut { - // reg, - // late, - // in_value, - // out_place: Some(out_place), - // } => { - // write!( - // fmt, - // "in{}out({}) {:?} => {:?}", - // print_late(late), - // reg, - // in_value, - // out_place - // )?; - // } - // InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { - // write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; - // } - // InlineAsmOperand::Const { value } => { - // write!(fmt, "const {:?}", value)?; - // } - // InlineAsmOperand::SymFn { value } => { - // write!(fmt, "sym_fn {:?}", value)?; - // } - // InlineAsmOperand::SymStatic { def_id } => { - // write!(fmt, "sym_static {:?}", def_id)?; - // } - // } - // } - // write!(fmt, ", options({:?}))", options) - // } - } - } - - pub fn fmt_successor_labels(&self) -> Vec> { - use MicroTerminatorKind::*; - match *self { - Return | Resume | Abort | Unreachable | GeneratorDrop => vec![], - Goto { .. } => vec!["".into()], - SwitchInt { ref targets, .. } => targets - .iter() - .map(|(u, _)| Cow::Owned(u.to_string())) - .chain(iter::once("otherwise".into())) - .collect(), - Call { - target: Some(_), - cleanup: Some(_), - .. - } => { - vec!["return".into(), "unwind".into()] - } - Call { - target: Some(_), - cleanup: None, - .. - } => vec!["return".into()], - Call { - target: None, - cleanup: Some(_), - .. - } => vec!["unwind".into()], - Call { - target: None, - cleanup: None, - .. - } => vec![], - Yield { drop: Some(_), .. } => vec!["resume".into(), "drop".into()], - Yield { drop: None, .. } => vec!["resume".into()], - DropAndReplace { unwind: None, .. } | Drop { unwind: None, .. } => { - vec!["return".into()] - } - DropAndReplace { - unwind: Some(_), .. - } - | Drop { - unwind: Some(_), .. - } => { - vec!["return".into(), "unwind".into()] - } - Assert { cleanup: None, .. } => vec!["".into()], - Assert { .. } => vec!["success".into(), "unwind".into()], - FalseEdge { .. } => vec!["real".into(), "imaginary".into()], - FalseUnwind { - unwind: Some(_), .. - } => vec!["real".into(), "cleanup".into()], - FalseUnwind { unwind: None, .. } => vec!["real".into()], - // InlineAsm { destination: Some(_), cleanup: Some(_), .. } => { - // vec!["return".into(), "unwind".into()] - // } - // InlineAsm { destination: Some(_), cleanup: None, .. } => vec!["return".into()], - // InlineAsm { destination: None, cleanup: Some(_), .. } => vec!["unwind".into()], - // InlineAsm { destination: None, cleanup: None, .. } => vec![], - } - } -} diff --git a/micromir/src/free_pcs/permission.rs b/micromir/src/free_pcs/permission.rs deleted file mode 100644 index 2b0b5422fd3..00000000000 --- a/micromir/src/free_pcs/permission.rs +++ /dev/null @@ -1,517 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::{ - cmp::Ordering, - fmt::{Debug, Display, Formatter, Result}, -}; - -use derive_more::{Deref, DerefMut}; - -use prusti_rustc_interface::{ - data_structures::fx::{FxHashMap, FxHashSet}, - index::vec::IndexVec, - middle::mir::Local, -}; - -use crate::{Place, PlaceOrdering, PlaceRepacker}; - -pub type FreeStateUpdate<'tcx> = LocalsState>; -#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut, Default)] -pub struct LocalUpdate<'tcx>( - ( - Option>, - Option>, - ), -); - -#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut, Default)] -pub struct LocalRequirement<'tcx> { - unalloc_allowed: bool, - #[deref] - #[deref_mut] - place_reqs: FxHashMap, FxHashSet>, -} - -impl<'tcx> LocalUpdate<'tcx> { - fn init_pre(&mut self) -> &mut LocalRequirement<'tcx> { - assert!(self.0 .0.is_none()); - self.0 .0 = Some(LocalRequirement::default()); - self.0 .0.as_mut().unwrap() - } - pub(crate) fn requires_unalloc_or_uninit(&mut self, local: Local) { - let req = self.init_pre(); - req.unalloc_allowed = true; - self.requires_alloc(local.into(), &[PermissionKind::Uninit]); - } - pub(crate) fn requires_alloc(&mut self, place: Place<'tcx>, perms: &[PermissionKind]) { - let req = if self.0 .0.is_none() { - self.init_pre() - } else { - self.0 .0.as_mut().unwrap() - }; - assert!( - req.keys().all(|other| !place.related_to(*other)), - "{req:?} {place:?} {perms:?}" - ); - req.insert(place, perms.iter().copied().collect()); - } - pub(crate) fn requires_unalloc(&mut self) { - let req = self.init_pre(); - req.unalloc_allowed = true; - } - pub(crate) fn requires_alloc_one(&mut self, place: Place<'tcx>, perm: PermissionKind) { - self.requires_alloc(place, &[perm]); - } - - pub(crate) fn ensures_unalloc(&mut self) { - assert!(self.0 .1.is_none()); - self.0 .1 = Some(PermissionLocal::Unallocated); - } - pub(crate) fn ensures_alloc(&mut self, place: Place<'tcx>, perm: PermissionKind) { - if let Some(pre) = &mut self.0 .1 { - let pre = pre.get_allocated_mut(); - assert!(pre.keys().all(|other| !place.related_to(*other))); - pre.insert(place, perm); - } else { - self.0 .1 = Some(PermissionLocal::Allocated( - PermissionProjections::new_update(place, perm), - )); - } - } - - /// Used for the edge case of assigning to the same place you copy from, do not use otherwise! - pub(crate) fn get_pre_for(&self, place: Place<'tcx>) -> Option<&FxHashSet> { - let pre = self.0 .0.as_ref()?; - pre.get(&place) - } - - pub(crate) fn get_pre(&self, state: &PermissionLocal<'tcx>) -> Option> { - let pre = self.0 .0.as_ref()?; - match state { - PermissionLocal::Unallocated => { - assert!(pre.unalloc_allowed); - Some(PermissionLocal::Unallocated) - } - PermissionLocal::Allocated(state) => { - let mut achievable = PermissionProjections(FxHashMap::default()); - for (place, allowed_perms) in pre.iter() { - let related_set = state.find_all_related(*place, None); - let mut perm = None; - for &ap in allowed_perms { - if related_set.minimum >= ap { - perm = Some(ap); - } - if related_set.minimum == ap { - break; - } - } - assert!( - perm.is_some(), - "{place:?}, {allowed_perms:?}, {state:?}, {:?}, {:?}", - related_set.minimum, - related_set.from - ); - achievable.insert(*place, perm.unwrap()); - } - Some(PermissionLocal::Allocated(achievable)) - } - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deref, DerefMut)] -/// Generic state of a set of locals -pub struct LocalsState(IndexVec); - -/// The free pcs of all locals -pub type FreeState<'tcx> = LocalsState>; - -impl FromIterator for LocalsState { - fn from_iter>(iter: I) -> Self { - Self(IndexVec::from_iter(iter)) - } -} -impl Display for FreeState<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "{{")?; - let mut first = true; - for state in self.iter() { - if let PermissionLocal::Allocated(state) = state { - if !first { - write!(f, ", ")?; - } - first = false; - for (i, (place, perm)) in state.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{perm:?} {place:?}")?; - } - } - } - write!(f, "}}") - } -} - -impl<'tcx> LocalsState> { - pub fn initial(local_count: usize, initial: impl Fn(Local) -> Option) -> Self { - Self(IndexVec::from_fn_n( - |local: Local| { - if let Some(perm) = initial(local) { - let places = PermissionProjections::new(local, perm); - PermissionLocal::Allocated(places) - } else { - PermissionLocal::Unallocated - } - }, - local_count, - )) - } - #[tracing::instrument(level = "trace", skip(rp))] - pub(crate) fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { - for p in self.iter() { - p.consistency_check(rp); - } - } -} -impl LocalsState { - pub fn default(local_count: usize) -> Self - where - T: Default + Clone, - { - Self(IndexVec::from_elem_n(T::default(), local_count)) - } - pub fn empty(local_count: usize, initial: T) -> Self - where - T: Clone, - { - Self(IndexVec::from_elem_n(initial, local_count)) - } -} -impl<'tcx> LocalsState> { - pub fn update_free(self, state: &mut FreeState<'tcx>) { - for (local, update) in self.0.into_iter_enumerated() { - if cfg!(debug_assertions) { - use PermissionLocal::*; - match (&state[local], update.get_pre(&state[local])) { - (_, None) => {} - (Unallocated, Some(Unallocated)) => {} - (Allocated(local_state), Some(Allocated(pre))) => { - for (place, required_perm) in pre.0 { - let perm = *local_state.get(&place).unwrap(); - let is_read = required_perm.is_shared() && perm.is_exclusive(); - assert!( - perm == required_perm || is_read, - "Have\n{state:#?}\n{place:#?}\n{perm:#?}\n{required_perm:#?}\n" - ); - } - } - _ => unreachable!(), - } - } - if let Some(post) = update.0 .1 { - match (post, &mut state[local]) { - (post @ PermissionLocal::Unallocated, _) - | (post, PermissionLocal::Unallocated) => state[local] = post, - (PermissionLocal::Allocated(post), PermissionLocal::Allocated(state)) => { - state.extend(post.0) - } - } - } - } - } -} - -#[derive(Clone, PartialEq, Eq)] -/// The permissions of a local -pub enum PermissionLocal<'tcx> { - Unallocated, - Allocated(PermissionProjections<'tcx>), -} -impl Debug for PermissionLocal<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - PermissionLocal::Unallocated => write!(f, "U"), - PermissionLocal::Allocated(a) => write!(f, "{a:?}"), - } - } -} - -impl<'tcx> PermissionLocal<'tcx> { - pub fn get_allocated(&self) -> &PermissionProjections<'tcx> { - match self { - PermissionLocal::Allocated(places) => places, - _ => panic!(), - } - } - pub fn get_allocated_mut(&mut self) -> &mut PermissionProjections<'tcx> { - match self { - PermissionLocal::Allocated(places) => places, - _ => panic!(), - } - } - - fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { - match self { - PermissionLocal::Unallocated => {} - PermissionLocal::Allocated(places) => { - places.consistency_check(rp); - } - } - } -} - -#[derive(Clone, PartialEq, Eq, Deref, DerefMut)] -/// The permissions for all the projections of a place -// We only need the projection part of the place -pub struct PermissionProjections<'tcx>(FxHashMap, PermissionKind>); - -impl<'tcx> Debug for PermissionProjections<'tcx> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - self.0.fmt(f) - } -} - -#[derive(Debug)] -pub(crate) struct RelatedSet<'tcx> { - pub(crate) from: Vec<(Place<'tcx>, PermissionKind)>, - pub(crate) to: Place<'tcx>, - pub(crate) minimum: PermissionKind, - pub(crate) relation: PlaceOrdering, -} -impl<'tcx> RelatedSet<'tcx> { - pub fn get_from(&self) -> FxHashSet> { - assert!(matches!( - self.relation, - PlaceOrdering::Suffix | PlaceOrdering::Both - )); - self.from.iter().map(|(p, _)| *p).collect() - } -} - -impl<'tcx> PermissionProjections<'tcx> { - pub fn new(local: Local, perm: PermissionKind) -> Self { - Self([(local.into(), perm)].into_iter().collect()) - } - pub fn new_uninit(local: Local) -> Self { - Self::new(local, PermissionKind::Uninit) - } - /// Should only be called when creating an update within `ModifiesFreeState` - pub(crate) fn new_update(place: Place<'tcx>, perm: PermissionKind) -> Self { - Self([(place, perm)].into_iter().collect()) - } - - /// Returns all related projections of the given place that are contained in this map. - /// A `Ordering::Less` means that the given `place` is a prefix of the iterator place. - /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] - /// It also checks that the ordering conforms to the expected ordering (the above would - /// fail in any situation since all orderings need to be the same) - #[tracing::instrument(level = "trace", ret)] - pub(crate) fn find_all_related( - &self, - to: Place<'tcx>, - mut expected: Option, - ) -> RelatedSet<'tcx> { - let mut minimum = None::; - let mut related = Vec::new(); - for (&from, &perm) in &**self { - if let Some(ord) = from.partial_cmp(to) { - minimum = if let Some(min) = minimum { - Some(min.minimum(perm).unwrap()) - } else { - Some(perm) - }; - if let Some(expected) = expected { - assert_eq!(ord, expected); - } else { - expected = Some(ord); - } - related.push((from, perm)); - } - } - assert!( - !related.is_empty(), - "Cannot find related of {to:?} in {self:?}" - ); - let relation = expected.unwrap(); - if matches!(relation, PlaceOrdering::Prefix | PlaceOrdering::Equal) { - assert_eq!(related.len(), 1); - } - RelatedSet { - from: related, - to, - minimum: minimum.unwrap(), - relation, - } - } - // pub fn all_related_with_minimum( - // &self, - // place: Place<'tcx>, - // ) -> (PermissionKind, PlaceOrdering, Vec<(Place<'tcx>, PermissionKind)>) { - // let mut ord = None; - // let related: Vec<_> = self - // .find_all_related(place, &mut ord) - // .map(|(_, p, k)| (p, k)) - // .collect(); - // let mut minimum = related.iter().map(|(_, k)| *k).reduce(|acc, k| { - // acc.minimum(k).unwrap() - // }); - // (minimum.unwrap(), ord.unwrap(), related) - // } - - #[tracing::instrument(name = "PermissionProjections::unpack", level = "trace", skip(rp), ret)] - pub(crate) fn unpack( - &mut self, - from: Place<'tcx>, - to: Place<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> Vec<(Place<'tcx>, Place<'tcx>)> { - debug_assert!(!self.contains_key(&to)); - let (expanded, others) = rp.expand(from, to); - let perm = self.remove(&from).unwrap(); - self.extend(others.into_iter().map(|p| (p, perm))); - self.insert(to, perm); - expanded - } - - // TODO: this could be implemented more efficiently, by assuming that a valid - // state can always be packed up to the root - #[tracing::instrument(name = "PermissionProjections::pack", level = "trace", skip(rp), ret)] - pub(crate) fn pack( - &mut self, - mut from: FxHashSet>, - to: Place<'tcx>, - perm: PermissionKind, - rp: PlaceRepacker<'_, 'tcx>, - ) -> Vec<(Place<'tcx>, Place<'tcx>)> { - debug_assert!(!self.contains_key(&to)); - for place in &from { - let p = self.remove(place).unwrap(); - assert_eq!(p, perm, "Cannot pack {place:?} with {p:?} into {to:?}"); - } - let collapsed = rp.collapse(to, &mut from); - assert!(from.is_empty()); - self.insert(to, perm); - collapsed - } - - #[tracing::instrument(name = "PermissionProjections::join", level = "info", skip(rp))] - pub(crate) fn join(&self, other: &mut Self, rp: PlaceRepacker<'_, 'tcx>) { - for (&place, &kind) in &**self { - let related = other.find_all_related(place, None); - match related.relation { - PlaceOrdering::Prefix => { - let from = related.from[0].0; - let joinable_place = rp.joinable_to(from, place); - if joinable_place != from { - other.unpack(from, joinable_place, rp); - } - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - other.insert(joinable_place, new_min); - } - } - PlaceOrdering::Equal => { - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - other.insert(place, new_min); - } - } - PlaceOrdering::Suffix => { - // Downgrade the permission if needed - for &(p, k) in &related.from { - let new_min = kind.minimum(k).unwrap(); - if new_min != k { - other.insert(p, new_min); - } - } - } - PlaceOrdering::Both => { - // Downgrade the permission if needed - let min = kind.minimum(related.minimum).unwrap(); - for &(p, k) in &related.from { - let new_min = min.minimum(k).unwrap(); - if new_min != k { - other.insert(p, new_min); - } - } - let cp = rp.common_prefix(related.from[0].0, place); - other.pack(related.get_from(), cp, min, rp); - } - } - } - } - - fn consistency_check(&self, rp: PlaceRepacker<'_, 'tcx>) { - // All keys unrelated to each other - let keys = self.keys().copied().collect::>(); - for (i, p1) in keys.iter().enumerate() { - for p2 in keys[i + 1..].iter() { - assert!(!p1.related_to(*p2), "{p1:?} {p2:?}",); - } - } - // Can always pack up to the root - let root: Place = self.iter().next().unwrap().0.local.into(); - let mut keys = self.keys().copied().collect(); - rp.collapse(root, &mut keys); - assert!(keys.is_empty()); - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub enum PermissionKind { - Shared, - Exclusive, - Uninit, -} - -impl Debug for PermissionKind { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - PermissionKind::Shared => write!(f, "s"), - PermissionKind::Exclusive => write!(f, "e"), - PermissionKind::Uninit => write!(f, "u"), - } - } -} - -impl PartialOrd for PermissionKind { - fn partial_cmp(&self, other: &Self) -> Option { - if *self == *other { - return Some(Ordering::Equal); - } - match (self, other) { - (PermissionKind::Shared, PermissionKind::Exclusive) - | (PermissionKind::Uninit, PermissionKind::Exclusive) => Some(Ordering::Less), - (PermissionKind::Exclusive, PermissionKind::Shared) - | (PermissionKind::Exclusive, PermissionKind::Uninit) => Some(Ordering::Greater), - (PermissionKind::Shared, PermissionKind::Uninit) - | (PermissionKind::Uninit, PermissionKind::Shared) => None, - _ => unreachable!(), - } - } -} - -impl PermissionKind { - pub fn is_shared(self) -> bool { - self == PermissionKind::Shared - } - pub fn is_exclusive(self) -> bool { - self == PermissionKind::Exclusive - } - pub fn is_uninit(self) -> bool { - self == PermissionKind::Uninit - } - pub fn minimum(self, other: Self) -> Option { - match self.partial_cmp(&other)? { - Ordering::Greater => Some(other), - _ => Some(self), - } - } -} diff --git a/micromir/src/lib.rs b/micromir/src/lib.rs deleted file mode 100644 index a40c4bccfcd..00000000000 --- a/micromir/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#![feature(rustc_private)] -#![feature(box_syntax, box_patterns)] -#![feature(drain_filter, hash_drain_filter)] -#![feature(type_alias_impl_trait)] - -mod check; -mod defs; -mod repack; -mod free_pcs; -mod utils; - -pub use defs::*; -pub use free_pcs::*; -pub use repack::*; -pub use utils::place::*; - -use prusti_interface::environment::Environment; - -pub fn test_free_pcs(env: &Environment) { - for proc_id in env.get_annotated_procedures_and_types().0.iter() { - let name = env.name.get_unique_item_name(*proc_id); - // if name != "syn::ty::parsing::ambig_ty" { - // continue; - // } - println!("id: {name}"); - let current_procedure = env.get_procedure(*proc_id); - let mir = current_procedure.get_mir_rc(); - let _ = MicroBody::new(mir, env.tcx()); - } -} diff --git a/micromir/src/repack/calculate.rs b/micromir/src/repack/calculate.rs deleted file mode 100644 index df626086e35..00000000000 --- a/micromir/src/repack/calculate.rs +++ /dev/null @@ -1,295 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use prusti_rustc_interface::{ - data_structures::{fx::FxHashSet, graph::WithStartNode}, - dataflow::storage, - index::vec::{Idx, IndexVec}, - middle::{ - mir::{BasicBlock, HasLocalDecls, Local, RETURN_PLACE}, - ty::TyCtxt, - }, -}; - -use crate::{ - check::checker, FreeState, MicroBasicBlockData, MicroBasicBlocks, MicroBody, MicroStatement, - MicroTerminator, PermissionKind, TerminatorPlaceCapabilitySummary, -}; - -use super::{place::PlaceRepacker, triple::ModifiesFreeState}; - -impl<'tcx> MicroBody<'tcx> { - fn initial_free_state(&self) -> FreeState<'tcx> { - let always_live = storage::always_storage_live_locals(&self.body); - let return_local = RETURN_PLACE; - let last_arg = Local::new(self.body.arg_count); - FreeState::initial(self.body.local_decls().len(), |local: Local| { - if local == return_local { - Some(PermissionKind::Uninit) - // TODO: figure out if `always_live` should start as `Uninit` or `Exclusive` - } else if local <= last_arg || always_live.contains(local) { - Some(PermissionKind::Exclusive) - } else { - None - } - }) - } - pub fn calculate_repacking(&mut self, tcx: TyCtxt<'tcx>) { - // Safety check - assert!(!self.done_repacking); - self.done_repacking = true; - - // Calculate initial state - let state = self.initial_free_state(); - let preds = self.body.basic_blocks.predecessors(); - let rp = PlaceRepacker::new(&self.body, tcx); - let start_node = self.body.basic_blocks.start_node(); - - // Do the actual repacking calculation - self.basic_blocks - .calculate_repacking(start_node, state, |bb| &preds[bb], rp); - - if cfg!(debug_assertions) { - // println!("--------\n{}\n--------", &self.basic_blocks); - checker::check(&self.basic_blocks, rp); - } - } -} - -#[derive(Debug)] -struct Queue { - queue: Vec, - dirty_queue: FxHashSet, - done: IndexVec, - can_redo: IndexVec, - recompute_count: IndexVec, -} -impl Queue { - fn new(start_node: BasicBlock, len: usize) -> Self { - let mut done = IndexVec::from_elem_n(false, len); - done[start_node] = true; - Self { - queue: Vec::new(), - dirty_queue: FxHashSet::default(), - done, - can_redo: IndexVec::from_elem_n(true, len), - recompute_count: IndexVec::from_elem_n(0, len), - } - } - fn add_succs<'a>( - &mut self, - term: &MicroTerminator, - preds: impl Fn(BasicBlock) -> &'a [BasicBlock], - ) { - for succ in term.original_kind.successors() { - if preds(succ).iter().all(|pred| self.done[*pred]) { - debug_assert!(!self.done[succ]); - self.queue.push(succ); - } else { - self.can_redo[succ] = true; - self.dirty_queue.insert(succ); - } - } - } - #[tracing::instrument(name = "Queue::pop", level = "warn", skip(min_by), ret)] - fn pop(&mut self, min_by: impl Fn(&BasicBlock) -> usize) -> Option { - if let Some(bb) = self.queue.pop() { - self.done[bb] = true; - Some(bb) - } else { - if self.dirty_queue.is_empty() { - debug_assert!((0..self.done.len()) - .map(BasicBlock::from_usize) - .all(|bb| self.done[bb] || !self.can_redo[bb])); - return None; - } - let bb = self - .dirty_queue - .iter() - .copied() - .filter(|bb| self.can_redo[*bb]) - .min_by_key(min_by) - .unwrap(); // Can this happen? If so probably a bug - self.can_redo[bb] = false; - self.dirty_queue.remove(&bb); - self.recompute_count[bb] += 1; - // TODO: assert that recompute count is low - assert!(self.recompute_count[bb] < 200); - Some(bb) - } - } -} - -impl<'tcx> MicroBasicBlocks<'tcx> { - pub(crate) fn calculate_repacking<'a>( - &mut self, - start_node: BasicBlock, - initial: FreeState<'tcx>, - preds: impl Fn(BasicBlock) -> &'a [BasicBlock], - rp: PlaceRepacker<'_, 'tcx>, - ) { - debug_assert!(self - .basic_blocks - .indices() - .all(|bb| bb == start_node || !preds(bb).is_empty())); - - self.basic_blocks[start_node].calculate_repacking(initial, rp); - let mut queue = Queue::new(start_node, self.basic_blocks.len()); - queue.add_succs(&self.basic_blocks[start_node].terminator, &preds); - while let Some(can_do) = queue.pop(|bb: &BasicBlock| { - let preds = preds(*bb); - preds.len() - self.get_valid_pred_count(preds) - }) { - // if can_do.as_u32() == 27 { - // tracing::warn!("IJOFD"); - // } - let is_cleanup = self.basic_blocks[can_do].is_cleanup; - let predecessors = self.get_pred_pcs(preds(can_do)); - let initial = if predecessors.len() == 1 { - predecessors[0].state().clone() - } else { - Self::calculate_join(can_do, predecessors, is_cleanup, rp) - }; - // TODO: A better way to do this might be to calculate a pre/post for entire basic blocks; - // start with pre/post of all `None` and walk over the statements collecting all the - // pre/posts, ignoring some (e.g. if we already have `x.f` in our pre then if we ran into - // `x.f.g` we'd ignore it, and if we ran into `x` we'd add `rp.expand(`x`, `x.f`).1`). - // And then calculate the fixpoint from that (rather than having to go through all the - // statements again each time). Then, once we have the state for the start and end of each - // bb, we simply calculate intermediate states along with repacking for all straight-line - // code within each bb. - let changed = self.basic_blocks[can_do].calculate_repacking(initial, rp); - if changed { - queue.add_succs(&self.basic_blocks[can_do].terminator, &preds); - } - } - } - - fn get_pred_pcs( - &mut self, - predecessors: &[BasicBlock], - ) -> Vec<&mut TerminatorPlaceCapabilitySummary<'tcx>> { - let predecessors = self - .basic_blocks - .iter_enumerated_mut() - .filter(|(bb, _)| predecessors.contains(bb)); - predecessors - .filter_map(|(_, bb)| bb.get_end_pcs_mut()) - .collect::>() - } - - fn get_valid_pred_count(&self, predecessors: &[BasicBlock]) -> usize { - predecessors - .iter() - .map(|bb| &self.basic_blocks[*bb]) - .filter(|bb| bb.terminator.repack_join.is_some()) - .count() - } - - #[tracing::instrument(level = "info", skip(rp))] - fn calculate_join( - bb: BasicBlock, - predecessors: Vec<&mut TerminatorPlaceCapabilitySummary<'tcx>>, - is_cleanup: bool, - rp: PlaceRepacker<'_, 'tcx>, - ) -> FreeState<'tcx> { - let mut join = predecessors[0].state().clone(); - for pred in predecessors.iter().skip(1) { - pred.state().join(&mut join, is_cleanup, rp); - if cfg!(debug_assertions) { - join.consistency_check(rp); - } - } - for pred in predecessors { - pred.join(&join, bb, is_cleanup, rp); - } - join - } -} - -impl<'tcx> MicroBasicBlockData<'tcx> { - #[tracing::instrument(level = "info", skip(rp))] - pub(crate) fn calculate_repacking( - &mut self, - mut incoming: FreeState<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> bool { - // Check that we haven't already calculated this - let pre_pcs = self - .statements - .first() - .map(|stmt| &stmt.repack_operands) - .unwrap_or_else(|| &self.terminator.repack_operands); - if pre_pcs - .as_ref() - .map(|pcs| pcs.state() == &incoming) - .unwrap_or_default() - { - return false; - } - // Do calculation for statements - for stmt in &mut self.statements { - incoming = stmt.calculate_repacking(incoming, rp); - } - // Do calculation for terminator - self.terminator.calculate_repacking(incoming, rp) - } -} - -impl<'tcx> MicroStatement<'tcx> { - #[tracing::instrument(level = "debug", skip(rp))] - pub(crate) fn calculate_repacking( - &mut self, - incoming: FreeState<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> FreeState<'tcx> { - let update = self.get_update(incoming.len()); - let (pcs, mut pre) = incoming.bridge(&update, rp); - if cfg!(debug_assertions) { - pre.consistency_check(rp); - } - self.repack_operands = Some(pcs); - update.update_free(&mut pre); - if cfg!(debug_assertions) { - pre.consistency_check(rp); - } - pre - } -} - -impl<'tcx> MicroTerminator<'tcx> { - #[tracing::instrument(level = "debug", skip(rp))] - pub(crate) fn calculate_repacking( - &mut self, - incoming: FreeState<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> bool { - let update = self.get_update(incoming.len()); - let (pcs, mut pre) = incoming.bridge(&update, rp); - if cfg!(debug_assertions) { - pre.consistency_check(rp); - } - self.repack_operands = Some(pcs); - update.update_free(&mut pre); - if cfg!(debug_assertions) { - pre.consistency_check(rp); - } - if let Some(pcs) = self.repack_join.as_mut() { - let changed = pcs.state() != ⪯ - debug_assert!(!(changed && self.previous_rjs.contains(&pre))); - if cfg!(debug_assertions) { - let old = std::mem::replace(pcs.state_mut(), pre); - self.previous_rjs.push(old); - } else { - *pcs.state_mut() = pre; - } - changed - } else { - self.repack_join = Some(TerminatorPlaceCapabilitySummary::empty(pre)); - true - } - } -} diff --git a/micromir/src/repack/repacker.rs b/micromir/src/repack/repacker.rs deleted file mode 100644 index eea39625c9a..00000000000 --- a/micromir/src/repack/repacker.rs +++ /dev/null @@ -1,294 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use std::fmt::{Debug, Formatter, Result}; - -use derive_more::{Deref, DerefMut}; -use prusti_rustc_interface::{ - data_structures::fx::FxHashMap, - middle::mir::{BasicBlock, Local}, -}; - -use crate::{ - repack::place::PlaceRepacker, FreeState, FreeStateUpdate, PermissionKind, PermissionLocal, - PermissionProjections, Place, PlaceOrdering, RelatedSet, -}; - -#[derive(Clone, Default, Deref, DerefMut)] -pub struct Repacks<'tcx>(Vec>); -impl Debug for Repacks<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - self.0.fmt(f) - } -} - -#[derive(Clone)] -pub struct PlaceCapabilitySummary<'tcx> { - state_before: FreeState<'tcx>, - repacks: Repacks<'tcx>, -} - -impl Debug for PlaceCapabilitySummary<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - if self.repacks.len() == 0 { - write!(f, "{:?}", &self.state_before) - } else { - f.debug_struct("PCS") - .field("state", &self.state_before) - .field("repacks", &self.repacks) - .finish() - } - } -} - -impl<'tcx> PlaceCapabilitySummary<'tcx> { - pub fn state(&self) -> &FreeState<'tcx> { - &self.state_before - } - pub fn state_mut(&mut self) -> &mut FreeState<'tcx> { - &mut self.state_before - } - pub fn repacks(&self) -> &Repacks<'tcx> { - &self.repacks - } -} - -#[derive(Clone)] -pub struct TerminatorPlaceCapabilitySummary<'tcx> { - state_before: FreeState<'tcx>, - repacks: FxHashMap>, -} - -impl Debug for TerminatorPlaceCapabilitySummary<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - if self.repacks.is_empty() { - write!(f, "{:?}", &self.state_before) - } else { - f.debug_struct("PCS") - .field("state", &self.state_before) - .field("repacks", &self.repacks) - .finish() - } - } -} - -impl<'tcx> TerminatorPlaceCapabilitySummary<'tcx> { - pub(crate) fn empty(state_before: FreeState<'tcx>) -> Self { - Self { - state_before, - repacks: FxHashMap::default(), - } - } - pub fn state(&self) -> &FreeState<'tcx> { - &self.state_before - } - pub fn state_mut(&mut self) -> &mut FreeState<'tcx> { - &mut self.state_before - } - pub fn repacks(&self) -> &FxHashMap> { - &self.repacks - } - - #[tracing::instrument(name = "PCS::join", level = "debug", skip(rp))] - pub(crate) fn join( - &mut self, - to: &FreeState<'tcx>, - bb: BasicBlock, - is_cleanup: bool, - rp: PlaceRepacker<'_, 'tcx>, - ) { - let repacks = self.repacks.entry(bb).or_default(); - repacks.clear(); - for (l, to) in to.iter_enumerated() { - let new = - PermissionLocal::bridge(&self.state_before[l], Some(to), repacks, is_cleanup, rp); - debug_assert_eq!(&new, to); - } - } -} - -impl<'tcx> FreeState<'tcx> { - /// The `from` state should never contain any `DontCare` permissions - #[tracing::instrument(level = "debug", skip(rp), ret)] - pub(crate) fn bridge( - self, - update: &FreeStateUpdate<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> (PlaceCapabilitySummary<'tcx>, FreeState<'tcx>) { - let mut repacks = Repacks::default(); - let pre = update - .iter_enumerated() - .map(|(l, update)| { - PermissionLocal::bridge( - &self[l], - update.get_pre(&self[l]).as_ref(), - &mut repacks, - false, - rp, - ) - }) - .collect(); - ( - PlaceCapabilitySummary { - state_before: self, - repacks, - }, - pre, - ) - } - - #[tracing::instrument(name = "FreeState::join", level = "info", skip(rp))] - pub(crate) fn join(&self, to: &mut Self, is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { - for (l, to) in to.iter_enumerated_mut() { - PermissionLocal::join(&self[l], to, is_cleanup, rp); - } - } -} - -impl<'tcx> PermissionLocal<'tcx> { - #[tracing::instrument(level = "trace", skip(rp), ret)] - fn bridge( - &self, - to: Option<&PermissionLocal<'tcx>>, - repacks: &mut Repacks<'tcx>, - is_cleanup: bool, - rp: PlaceRepacker<'_, 'tcx>, - ) -> PermissionLocal<'tcx> { - use PermissionLocal::*; - match (self, to) { - (_, None) | (Unallocated, Some(Unallocated)) => self.clone(), - (Allocated(from_places), Some(Allocated(places))) => { - let mut from_places = from_places.clone(); - for (&to_place, &to_kind) in &**places { - from_places.repack_op(to_place, repacks, rp); - let from_kind = *from_places.get(&to_place).unwrap(); - assert!(from_kind >= to_kind, "!({from_kind:?} >= {to_kind:?})"); - if from_kind > to_kind { - from_places.insert(to_place, to_kind); - repacks.push(RepackOp::Weaken(to_place, from_kind, to_kind)); - } - } - Allocated(from_places) - } - (Allocated(a), Some(Unallocated)) => { - let local = a.iter().next().unwrap().0.local; - let root_place = local.into(); - let mut a = a.clone(); - a.repack_op(root_place, repacks, rp); - if a[&root_place] != PermissionKind::Uninit { - assert_eq!(a[&root_place], PermissionKind::Exclusive); - repacks.push(RepackOp::Weaken( - root_place, - a[&root_place], - PermissionKind::Uninit, - )); - } - if is_cleanup { - repacks.push(RepackOp::DeallocForCleanup(local)); - } else { - println!("TODO: figure out why this happens and if it's ok"); - repacks.push(RepackOp::DeallocUnknown(local)); - } - Unallocated - } - a @ (Unallocated, Some(Allocated(..))) => unreachable!("{:?}", a), - } - } - #[tracing::instrument(level = "info", skip(rp))] - fn join(&self, to: &mut Self, _is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>) { - match (self, &mut *to) { - (PermissionLocal::Unallocated, PermissionLocal::Unallocated) => (), - (PermissionLocal::Allocated(from_places), PermissionLocal::Allocated(places)) => { - from_places.join(places, rp); - } - // Can jump to a `is_cleanup` block with some paths being alloc and other not - (PermissionLocal::Allocated(..), PermissionLocal::Unallocated) => (), - (PermissionLocal::Unallocated, PermissionLocal::Allocated(..)) => { - *to = PermissionLocal::Unallocated - } - }; - } -} - -impl<'tcx> PermissionProjections<'tcx> { - #[tracing::instrument(level = "debug", skip(rp))] - pub(crate) fn repack_op( - &mut self, - to: Place<'tcx>, - repacks: &mut Vec>, - rp: PlaceRepacker<'_, 'tcx>, - ) { - let mut related = self.find_all_related(to, None); - match related.relation { - PlaceOrdering::Prefix => self.unpack_op(related, repacks, rp), - PlaceOrdering::Equal => {} - PlaceOrdering::Suffix => self.pack_op(related, repacks, rp), - PlaceOrdering::Both => { - let cp = rp.common_prefix(related.from[0].0, to); - let minimum = related.minimum; - // Pack - related.to = cp; - related.relation = PlaceOrdering::Both; - self.pack_op(related, repacks, rp); - // Unpack - let related = RelatedSet { - from: vec![(cp, minimum)], - to, - minimum, - relation: PlaceOrdering::Prefix, - }; - self.unpack_op(related, repacks, rp); - } - } - } - pub(crate) fn unpack_op( - &mut self, - related: RelatedSet<'tcx>, - repacks: &mut Vec>, - rp: PlaceRepacker<'_, 'tcx>, - ) { - let unpacks = self.unpack(related.from[0].0, related.to, rp); - repacks.extend( - unpacks - .into_iter() - .map(move |(place, to)| RepackOp::Unpack(place, to, related.minimum)), - ); - } - pub(crate) fn pack_op( - &mut self, - related: RelatedSet<'tcx>, - repacks: &mut Vec>, - rp: PlaceRepacker<'_, 'tcx>, - ) { - let more_than_min = related.from.iter().filter(|(_, k)| *k != related.minimum); - // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` - // the exclusive permission will never be able to be recovered anymore! - for &(p, k) in more_than_min { - let old = self.insert(p, related.minimum); - assert_eq!(old, Some(k)); - repacks.push(RepackOp::Weaken(p, k, related.minimum)); - } - - let packs = self.pack(related.get_from(), related.to, related.minimum, rp); - repacks.extend( - packs - .into_iter() - .map(move |(place, to)| RepackOp::Pack(place, to, related.minimum)), - ); - } -} - -#[derive(Clone, Debug)] -pub enum RepackOp<'tcx> { - Weaken(Place<'tcx>, PermissionKind, PermissionKind), - // TODO: figure out when and why this happens - DeallocUnknown(Local), - DeallocForCleanup(Local), - // First place is packed up, second is guide place to pack up from - Pack(Place<'tcx>, Place<'tcx>, PermissionKind), - // First place is packed up, second is guide place to unpack to - Unpack(Place<'tcx>, Place<'tcx>, PermissionKind), -} diff --git a/micromir/src/repack/triple.rs b/micromir/src/repack/triple.rs deleted file mode 100644 index 048f7079083..00000000000 --- a/micromir/src/repack/triple.rs +++ /dev/null @@ -1,124 +0,0 @@ -// © 2023, ETH Zurich -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -use prusti_rustc_interface::middle::mir::RETURN_PLACE; - -use crate::{ - FreeStateUpdate, MicroFullOperand, MicroStatement, MicroStatementKind, MicroTerminator, - MicroTerminatorKind, Operands, PermissionKind, -}; - -pub(crate) trait ModifiesFreeState<'tcx> { - fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx>; -} - -impl<'tcx> ModifiesFreeState<'tcx> for Operands<'tcx> { - #[tracing::instrument(level = "trace")] - fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { - let mut update = FreeStateUpdate::default(locals); - for operand in &**self { - match *operand { - MicroFullOperand::Copy(place) => { - update[place.local].requires_alloc( - place, - &[PermissionKind::Exclusive, PermissionKind::Shared], - ); - } - MicroFullOperand::Move(place) => { - update[place.local].requires_alloc_one(place, PermissionKind::Exclusive); - update[place.local].ensures_alloc(place, PermissionKind::Uninit); - } - MicroFullOperand::Constant(..) => (), - } - } - update - } -} - -impl<'tcx> ModifiesFreeState<'tcx> for MicroStatement<'tcx> { - #[tracing::instrument(level = "trace")] - fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { - let mut update = self.operands.get_update(locals); - match &self.kind { - &MicroStatementKind::Assign(box (place, _)) => { - if let Some(pre) = update[place.local].get_pre_for(place) { - assert_eq!(pre.len(), 2); - assert!(pre.contains(&PermissionKind::Exclusive)); - assert!(pre.contains(&PermissionKind::Shared)); - } else { - update[place.local].requires_alloc_one(place, PermissionKind::Uninit); - } - update[place.local].ensures_alloc(place, PermissionKind::Exclusive); - } - MicroStatementKind::FakeRead(box (_, place)) => update[place.local] - .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Shared]), - MicroStatementKind::SetDiscriminant { box place, .. } => { - update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive) - } - MicroStatementKind::Deinit(box place) => { - // TODO: Maybe OK to also allow `Uninit` here? - update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive); - update[place.local].ensures_alloc(*place, PermissionKind::Uninit); - } - &MicroStatementKind::StorageLive(local) => { - update[local].requires_unalloc(); - update[local].ensures_alloc(local.into(), PermissionKind::Uninit); - } - &MicroStatementKind::StorageDead(local) => { - update[local].requires_unalloc_or_uninit(local); - update[local].ensures_unalloc(); - } - MicroStatementKind::Retag(_, box place) => { - update[place.local].requires_alloc_one(*place, PermissionKind::Exclusive) - } - MicroStatementKind::AscribeUserType(..) - | MicroStatementKind::Coverage(..) - | MicroStatementKind::Intrinsic(..) - | MicroStatementKind::ConstEvalCounter - | MicroStatementKind::Nop => (), - }; - update - } -} - -impl<'tcx> ModifiesFreeState<'tcx> for MicroTerminator<'tcx> { - #[tracing::instrument(level = "trace")] - fn get_update(&self, locals: usize) -> FreeStateUpdate<'tcx> { - let mut update = self.operands.get_update(locals); - match &self.kind { - MicroTerminatorKind::Goto { .. } - | MicroTerminatorKind::SwitchInt { .. } - | MicroTerminatorKind::Resume - | MicroTerminatorKind::Abort - | MicroTerminatorKind::Unreachable - | MicroTerminatorKind::Assert { .. } - | MicroTerminatorKind::GeneratorDrop - | MicroTerminatorKind::FalseEdge { .. } - | MicroTerminatorKind::FalseUnwind { .. } => (), - MicroTerminatorKind::Return => update[RETURN_PLACE] - .requires_alloc_one(RETURN_PLACE.into(), PermissionKind::Exclusive), - MicroTerminatorKind::Drop { place, .. } => { - update[place.local] - .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Uninit]); - update[place.local].ensures_alloc(*place, PermissionKind::Uninit); - } - MicroTerminatorKind::DropAndReplace { place, .. } => { - update[place.local] - .requires_alloc(*place, &[PermissionKind::Exclusive, PermissionKind::Uninit]); - update[place.local].ensures_alloc(*place, PermissionKind::Exclusive); - } - MicroTerminatorKind::Call { destination, .. } => { - update[destination.local].requires_alloc_one(*destination, PermissionKind::Uninit); - update[destination.local].ensures_alloc(*destination, PermissionKind::Exclusive); - } - MicroTerminatorKind::Yield { resume_arg, .. } => { - update[resume_arg.local].requires_alloc_one(*resume_arg, PermissionKind::Uninit); - update[resume_arg.local].ensures_alloc(*resume_arg, PermissionKind::Exclusive); - } - }; - update - } -} diff --git a/micromir/Cargo.toml b/mir-state-analysis/Cargo.toml similarity index 83% rename from micromir/Cargo.toml rename to mir-state-analysis/Cargo.toml index 847715dface..958fe750062 100644 --- a/micromir/Cargo.toml +++ b/mir-state-analysis/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "micromir" +name = "mir-state-analysis" version = "0.1.0" authors = ["Prusti Devs "] edition = "2021" @@ -9,9 +9,6 @@ derive_more = "0.99" tracing = { path = "../tracing" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } -# TODO: remove this dep -prusti-interface = { path = "../prusti-interface" } - [dev-dependencies] reqwest = { version = "^0.11", features = ["blocking"] } serde = "^1.0" diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs new file mode 100644 index 00000000000..7e6e4cfa485 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -0,0 +1,159 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + data_structures::fx::FxHashMap, + dataflow::Results, + middle::mir::{visit::Visitor, Location}, +}; + +use crate::{ + engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, + utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilitySummary, PlaceOrdering, + RepackOp, +}; + +use super::consistency::CapabilityConistency; + +pub(crate) fn check<'tcx>(results: Results<'tcx, FreePlaceCapabilitySummary<'_, 'tcx>>) { + let rp = results.analysis.0; + let body = rp.body(); + let mut cursor = results.into_results_cursor(body); + for (block, data) in body.basic_blocks.iter_enumerated() { + cursor.seek_to_block_start(block); + let mut fpcs = cursor.get().clone(); + // Consistency + fpcs.summary.consistency_check(rp); + for (statement_index, stmt) in data.statements.iter().enumerate() { + let loc = Location { + block, + statement_index, + }; + cursor.seek_after_primary_effect(loc); + let fpcs_after = cursor.get(); + // Repacks + for op in &fpcs_after.repackings { + op.update_free(&mut fpcs.summary, false, rp); + } + // Consistency + fpcs.summary.consistency_check(rp); + // Statement + assert!(fpcs.repackings.is_empty()); + fpcs.visit_statement(stmt, loc); + assert!(fpcs.repackings.is_empty()); + // Consistency + fpcs.summary.consistency_check(rp); + } + let loc = Location { + block, + statement_index: data.statements.len(), + }; + cursor.seek_after_primary_effect(loc); + let fpcs_after = cursor.get(); + // Repacks + for op in &fpcs_after.repackings { + op.update_free(&mut fpcs.summary, false, rp); + } + // Consistency + fpcs.summary.consistency_check(rp); + // Statement + assert!(fpcs.repackings.is_empty()); + fpcs.visit_terminator(data.terminator(), loc); + assert!(fpcs.repackings.is_empty()); + // Consistency + fpcs.summary.consistency_check(rp); + assert_eq!(&fpcs, fpcs_after); + + for succ in data.terminator().successors() { + // Get repacks + let to = cursor.results().entry_set_for_block(succ); + let repacks = fpcs.summary.bridge(&to.summary, rp); + + // Repacks + let mut from = fpcs.clone(); + for op in repacks { + op.update_free(&mut from.summary, body.basic_blocks[succ].is_cleanup, rp); + } + assert_eq!(&from, to); + } + } +} + +impl<'tcx> RepackOp<'tcx> { + #[tracing::instrument(level = "debug", skip(rp))] + fn update_free( + &self, + state: &mut CapabilitySummary<'tcx>, + can_dealloc: bool, + rp: PlaceRepacker<'_, 'tcx>, + ) { + match self { + RepackOp::Weaken(place, from, to) => { + assert!(from >= to, "{self:?}"); + let curr_state = state[place.local].get_allocated_mut(); + let old = curr_state.insert(*place, *to); + assert_eq!(old, Some(*from), "{self:?}, {curr_state:?}"); + } + &RepackOp::DeallocForCleanup(local) => { + assert!(can_dealloc); + let curr_state = state[local].get_allocated_mut(); + assert_eq!(curr_state.len(), 1); + assert!( + curr_state.contains_key(&local.into()), + "{self:?}, {curr_state:?}" + ); + assert_eq!(curr_state[&local.into()], CapabilityKind::Write); + state[local] = CapabilityLocal::Unallocated; + } + // &RepackOp::DeallocUnknown(local) => { + // assert!(!can_dealloc); + // let curr_state = state[local].get_allocated_mut(); + // assert_eq!(curr_state.len(), 1); + // assert_eq!(curr_state[&local.into()], CapabilityKind::Write); + // state[local] = CapabilityLocal::Unallocated; + // } + RepackOp::Pack(place, guide, kind) => { + assert_eq!( + place.partial_cmp(*guide), + Some(PlaceOrdering::Prefix), + "{self:?}" + ); + let curr_state = state[place.local].get_allocated_mut(); + let mut removed = curr_state + .drain() + .filter(|(p, _)| place.related_to(*p)) + .collect::>(); + let (p, others) = place.expand_one_level(*guide, rp); + assert!(others + .into_iter() + .chain(std::iter::once(p)) + .all(|p| removed.remove(&p).unwrap() == *kind)); + assert!(removed.is_empty(), "{self:?}, {removed:?}"); + + let old = curr_state.insert(*place, *kind); + assert_eq!(old, None); + } + RepackOp::Unpack(place, guide, kind) => { + assert_eq!( + place.partial_cmp(*guide), + Some(PlaceOrdering::Prefix), + "{self:?}" + ); + let curr_state = state[place.local].get_allocated_mut(); + assert_eq!( + curr_state.remove(place), + Some(*kind), + "{self:?} ({:?})", + &**curr_state + ); + + let (p, others) = place.expand_one_level(*guide, rp); + curr_state.insert(p, *kind); + curr_state.extend(others.into_iter().map(|p| (p, *kind))); + } + } + } +} diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs new file mode 100644 index 00000000000..b5a77a5c693 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -0,0 +1,45 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use crate::{utils::PlaceRepacker, CapabilityLocal, CapabilityProjections, Place, Summary}; + +pub trait CapabilityConistency<'tcx> { + fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>); +} + +impl<'tcx, T: CapabilityConistency<'tcx>> CapabilityConistency<'tcx> for Summary { + fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { + for p in self.iter() { + p.consistency_check(repacker) + } + } +} + +impl<'tcx> CapabilityConistency<'tcx> for CapabilityLocal<'tcx> { + fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { + match self { + CapabilityLocal::Unallocated => {} + CapabilityLocal::Allocated(cp) => cp.consistency_check(repacker), + } + } +} + +impl<'tcx> CapabilityConistency<'tcx> for CapabilityProjections<'tcx> { + fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { + // All keys unrelated to each other + let keys = self.keys().copied().collect::>(); + for (i, p1) in keys.iter().enumerate() { + for p2 in keys[i + 1..].iter() { + assert!(!p1.related_to(*p2), "{p1:?} {p2:?}",); + } + } + // Can always pack up to the root + let root: Place = self.get_local().into(); + let mut keys = self.keys().copied().collect(); + root.collapse(&mut keys, repacker); + assert!(keys.is_empty()); + } +} diff --git a/micromir/src/free_pcs/mod.rs b/mir-state-analysis/src/free_pcs/check/mod.rs similarity index 79% rename from micromir/src/free_pcs/mod.rs rename to mir-state-analysis/src/free_pcs/check/mod.rs index 4d469804e68..6a308a2b833 100644 --- a/micromir/src/free_pcs/mod.rs +++ b/mir-state-analysis/src/free_pcs/check/mod.rs @@ -4,6 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -mod permission; +mod checker; +mod consistency; -pub use permission::*; +pub(crate) use checker::check; diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs new file mode 100644 index 00000000000..77230e0a37c --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -0,0 +1,96 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + dataflow::{Analysis, AnalysisDomain, CallReturnPlaces}, + index::vec::Idx, + middle::{ + mir::{ + visit::Visitor, BasicBlock, Body, Local, Location, Statement, Terminator, RETURN_PLACE, + }, + ty::TyCtxt, + }, +}; + +use crate::{utils::PlaceRepacker, CapabilityKind, CapabilityLocal, Fpcs}; + +pub(crate) struct FreePlaceCapabilitySummary<'a, 'tcx>(pub(crate) PlaceRepacker<'a, 'tcx>); +impl<'a, 'tcx> FreePlaceCapabilitySummary<'a, 'tcx> { + pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> Self { + let repacker = PlaceRepacker::new(body, tcx); + FreePlaceCapabilitySummary(repacker) + } +} + +impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { + type Domain = Fpcs<'a, 'tcx>; + const NAME: &'static str = "free_pcs"; + + fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { + Fpcs::new(self.0) + } + + fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + let always_live = self.0.always_live_locals(); + let return_local = RETURN_PLACE; + let last_arg = Local::new(body.arg_count); + for (local, cap) in state.summary.iter_enumerated_mut() { + if local == return_local { + let old = cap + .get_allocated_mut() + .insert(local.into(), CapabilityKind::Write); + assert!(old.is_some()); + } else if local <= last_arg { + let old = cap + .get_allocated_mut() + .insert(local.into(), CapabilityKind::Exclusive); + assert!(old.is_some()); + } else if always_live.contains(local) { + // TODO: figure out if `always_live` should start as `Uninit` or `Exclusive` + let al_cap = if true { + CapabilityKind::Write + } else { + CapabilityKind::Exclusive + }; + let old = cap.get_allocated_mut().insert(local.into(), al_cap); + assert!(old.is_some()); + } else { + *cap = CapabilityLocal::Unallocated; + } + } + } +} + +impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { + fn apply_statement_effect( + &self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { + state.repackings.clear(); + state.visit_statement(statement, location); + } + + fn apply_terminator_effect( + &self, + state: &mut Self::Domain, + terminator: &Terminator<'tcx>, + location: Location, + ) { + state.repackings.clear(); + state.visit_terminator(terminator, location); + } + + fn apply_call_return_effect( + &self, + _state: &mut Self::Domain, + _block: BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + // Nothing to do here + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs new file mode 100644 index 00000000000..952b5236243 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -0,0 +1,148 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::fmt::{Debug, Formatter, Result}; + +use derive_more::{Deref, DerefMut}; +use prusti_rustc_interface::{ + dataflow::fmt::DebugWithContext, index::vec::IndexVec, middle::mir::Local, +}; + +use crate::{ + engine::FreePlaceCapabilitySummary, utils::PlaceRepacker, CapabilityKind, CapabilityLocal, + CapabilityProjections, RepackOp, +}; + +#[derive(Clone)] +pub struct Fpcs<'a, 'tcx> { + pub(crate) repacker: PlaceRepacker<'a, 'tcx>, + pub summary: CapabilitySummary<'tcx>, + pub repackings: Vec>, +} +impl<'a, 'tcx> Fpcs<'a, 'tcx> { + pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>) -> Self { + let summary = CapabilitySummary::bottom_value(repacker.local_count()); + Self { + repacker, + summary, + repackings: Vec::new(), + } + } +} + +impl PartialEq for Fpcs<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.summary == other.summary + } +} +impl Eq for Fpcs<'_, '_> {} + +impl<'a, 'tcx> Debug for Fpcs<'a, 'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.summary.fmt(f) + } +} +impl<'a, 'tcx> DebugWithContext> for Fpcs<'a, 'tcx> { + fn fmt_diff_with( + &self, + old: &Self, + _ctxt: &FreePlaceCapabilitySummary<'a, 'tcx>, + f: &mut Formatter<'_>, + ) -> Result { + assert_eq!(self.summary.len(), old.summary.len()); + for (new, old) in self.summary.iter().zip(old.summary.iter()) { + let changed = match (new, old) { + (CapabilityLocal::Unallocated, CapabilityLocal::Unallocated) => false, + (CapabilityLocal::Unallocated, CapabilityLocal::Allocated(a)) => { + write!(f, "\u{001f}-{:?}", a.get_local())?; + true + } + (CapabilityLocal::Allocated(a), CapabilityLocal::Unallocated) => { + write!(f, "\u{001f}+{a:?}")?; + true + } + (CapabilityLocal::Allocated(new), CapabilityLocal::Allocated(old)) => { + if new != old { + let mut new_set = CapabilityProjections::empty(); + let mut old_set = CapabilityProjections::empty(); + for (&p, &nk) in new.iter() { + match old.get(&p) { + Some(&ok) => { + if let Some(d) = nk - ok { + new_set.insert(p, d); + } + } + None => { + new_set.insert(p, nk); + } + } + } + for (&p, &ok) in old.iter() { + match new.get(&p) { + Some(&nk) => { + if let Some(d) = ok - nk { + old_set.insert(p, d); + } + } + None => { + old_set.insert(p, ok); + } + } + } + if !new_set.is_empty() { + write!(f, "\u{001f}+{new_set:?}")? + } + if !old_set.is_empty() { + write!(f, "\u{001f}-{old_set:?}")? + } + true + } else { + false + } + } + }; + if changed { + if f.alternate() { + writeln!(f)?; + } else { + write!(f, "\t")?; + } + } + } + Ok(()) + } +} + +#[derive(Clone, PartialEq, Eq, Deref, DerefMut)] +/// Generic state of a set of locals +pub struct Summary(IndexVec); + +impl Debug for Summary { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.fmt(f) + } +} + +// impl Summary { +// pub fn default(local_count: usize) -> Self +// where +// T: Default + Clone, +// { +// Self(IndexVec::from_elem_n(T::default(), local_count)) +// } +// } + +/// The free pcs of all locals +pub type CapabilitySummary<'tcx> = Summary>; + +impl<'tcx> CapabilitySummary<'tcx> { + pub fn bottom_value(local_count: usize) -> Self { + Self(IndexVec::from_fn_n( + |local: Local| CapabilityLocal::new(local, CapabilityKind::Exclusive), + local_count, + )) + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs new file mode 100644 index 00000000000..dff9066d09c --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -0,0 +1,197 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::dataflow::JoinSemiLattice; + +use crate::{ + utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilityProjections, + CapabilitySummary, Fpcs, PlaceOrdering, RepackOp, +}; + +impl JoinSemiLattice for Fpcs<'_, '_> { + fn join(&mut self, other: &Self) -> bool { + self.summary.join(&other.summary, self.repacker) + } +} + +pub trait RepackingJoinSemiLattice<'tcx> { + fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool; + fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec>; +} +impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilitySummary<'tcx> { + fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { + let mut changed = false; + for (l, to) in self.iter_enumerated_mut() { + let local_changed = to.join(&other[l], repacker); + changed = changed || local_changed; + } + changed + } + fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { + let mut repacks = Vec::new(); + for (l, to) in self.iter_enumerated() { + let local_repacks = to.bridge(&other[l], repacker); + repacks.extend(local_repacks); + } + repacks + } +} + +impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityLocal<'tcx> { + #[tracing::instrument(name = "CapabilityLocal::join", level = "debug", skip(repacker))] + fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { + match (&mut *self, other) { + (CapabilityLocal::Unallocated, CapabilityLocal::Unallocated) => false, + (CapabilityLocal::Allocated(to_places), CapabilityLocal::Allocated(from_places)) => { + to_places.join(from_places, repacker) + } + (CapabilityLocal::Allocated(..), CapabilityLocal::Unallocated) => { + *self = CapabilityLocal::Unallocated; + true + } + // Can jump to a `is_cleanup` block with some paths being alloc and other not + (CapabilityLocal::Unallocated, CapabilityLocal::Allocated(..)) => false, + } + } + fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { + match (self, other) { + (CapabilityLocal::Unallocated, CapabilityLocal::Unallocated) => Vec::new(), + (CapabilityLocal::Allocated(to_places), CapabilityLocal::Allocated(from_places)) => { + to_places.bridge(from_places, repacker) + } + (CapabilityLocal::Allocated(cps), CapabilityLocal::Unallocated) => { + // TODO: remove need for clone + let mut cps = cps.clone(); + let local = cps.get_local(); + let mut repacks = Vec::new(); + for (&p, k) in cps.iter_mut() { + if *k > CapabilityKind::Write { + repacks.push(RepackOp::Weaken(p, *k, CapabilityKind::Write)); + *k = CapabilityKind::Write; + } + } + if !cps.contains_key(&local.into()) { + let packs = cps.pack_ops( + cps.keys().copied().collect(), + local.into(), + CapabilityKind::Write, + repacker, + ); + repacks.extend(packs); + }; + repacks.push(RepackOp::DeallocForCleanup(local)); + repacks + } + (CapabilityLocal::Unallocated, CapabilityLocal::Allocated(..)) => unreachable!(), + } + } +} + +impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { + #[tracing::instrument(name = "CapabilityProjections::join", level = "trace", skip(repacker))] + fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { + let mut changed = false; + for (&place, &kind) in &**other { + let related = self.find_all_related(place, None); + match related.relation { + PlaceOrdering::Prefix => { + changed = true; + + let from = related.from[0].0; + let joinable_place = from.joinable_to(place, repacker); + if joinable_place != from { + self.unpack(from, joinable_place, repacker); + } + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + self.insert(joinable_place, new_min); + } + } + PlaceOrdering::Equal => { + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + changed = true; + self.insert(place, new_min); + } + } + PlaceOrdering::Suffix => { + // Downgrade the permission if needed + for &(p, k) in &related.from { + let new_min = kind.minimum(k).unwrap(); + if new_min != k { + changed = true; + self.insert(p, new_min); + } + } + } + PlaceOrdering::Both => { + changed = true; + + // Downgrade the permission if needed + let min = kind.minimum(related.minimum).unwrap(); + for &(p, k) in &related.from { + let new_min = min.minimum(k).unwrap(); + if new_min != k { + self.insert(p, new_min); + } + } + let cp = related.from[0].0.common_prefix(place, repacker); + self.pack(related.get_from(), cp, min, repacker); + } + } + } + changed + } + fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { + // TODO: remove need for clone + let mut cps = self.clone(); + + let mut repacks = Vec::new(); + for (&place, &kind) in &**other { + let related = cps.find_all_related(place, None); + match related.relation { + PlaceOrdering::Prefix => { + let from = related.from[0].0; + // TODO: remove need for clone + let unpacks = cps.unpack_ops(from, place, related.minimum, repacker); + repacks.extend(unpacks); + + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + cps.insert(place, new_min); + repacks.push(RepackOp::Weaken(place, related.minimum, new_min)); + } + } + PlaceOrdering::Equal => { + // Downgrade the permission if needed + let new_min = kind.minimum(related.minimum).unwrap(); + if new_min != related.minimum { + cps.insert(place, new_min); + repacks.push(RepackOp::Weaken(place, related.minimum, new_min)); + } + } + PlaceOrdering::Suffix => { + // Downgrade the permission if needed + for &(p, k) in &related.from { + let new_min = kind.minimum(k).unwrap(); + if new_min != k { + cps.insert(p, new_min); + repacks.push(RepackOp::Weaken(p, k, new_min)); + } + } + let packs = + cps.pack_ops(related.get_from(), related.to, related.minimum, repacker); + repacks.extend(packs); + } + PlaceOrdering::Both => unreachable!(), + } + } + repacks + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs new file mode 100644 index 00000000000..732be1d3909 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -0,0 +1,163 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::fmt::{Debug, Formatter, Result}; + +use derive_more::{Deref, DerefMut}; +use prusti_rustc_interface::{ + data_structures::fx::{FxHashMap, FxHashSet}, + middle::mir::Local, +}; + +use crate::{utils::PlaceRepacker, CapabilityKind, Place, PlaceOrdering, RelatedSet}; + +#[derive(Clone, PartialEq, Eq)] +/// The permissions of a local, each key in the hashmap is a "root" projection of the local +/// Examples of root projections are: `_1`, `*_1.f`, `*(*_.f).g` (i.e. either a local or a deref) +pub enum CapabilityLocal<'tcx> { + Unallocated, + Allocated(CapabilityProjections<'tcx>), +} + +impl Debug for CapabilityLocal<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + Self::Unallocated => write!(f, "U"), + Self::Allocated(cps) => write!(f, "{cps:?}"), + } + } +} + +impl<'tcx> CapabilityLocal<'tcx> { + pub fn get_allocated_mut(&mut self) -> &mut CapabilityProjections<'tcx> { + let Self::Allocated(cps) = self else { panic!("Expected allocated local") }; + cps + } + pub fn new(local: Local, perm: CapabilityKind) -> Self { + Self::Allocated(CapabilityProjections::new(local, perm)) + } + pub fn is_unallocated(&self) -> bool { + matches!(self, Self::Unallocated) + } +} + +#[derive(Clone, PartialEq, Eq, Deref, DerefMut)] +/// The permissions for all the projections of a place +// We only need the projection part of the place +pub struct CapabilityProjections<'tcx>(FxHashMap, CapabilityKind>); + +impl<'tcx> Debug for CapabilityProjections<'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.fmt(f) + } +} + +impl<'tcx> CapabilityProjections<'tcx> { + pub fn new(local: Local, perm: CapabilityKind) -> Self { + Self([(local.into(), perm)].into_iter().collect()) + } + pub fn new_uninit(local: Local) -> Self { + Self::new(local, CapabilityKind::Write) + } + /// Should only be called when creating an update within `ModifiesFreeState` + pub(crate) fn empty() -> Self { + Self(FxHashMap::default()) + } + + pub(crate) fn get_local(&self) -> Local { + self.iter().next().unwrap().0.local + } + + /// Returns all related projections of the given place that are contained in this map. + /// A `Ordering::Less` means that the given `place` is a prefix of the iterator place. + /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] + /// It also checks that the ordering conforms to the expected ordering (the above would + /// fail in any situation since all orderings need to be the same) + #[tracing::instrument(level = "debug", skip(self))] + pub(crate) fn find_all_related( + &self, + to: Place<'tcx>, + mut expected: Option, + ) -> RelatedSet<'tcx> { + let mut minimum = None::; + let mut related = Vec::new(); + for (&from, &cap) in &**self { + if let Some(ord) = from.partial_cmp(to) { + minimum = if let Some(min) = minimum { + Some(min.minimum(cap).unwrap()) + } else { + Some(cap) + }; + if let Some(expected) = expected { + assert_eq!(ord, expected); + } else { + expected = Some(ord); + } + related.push((from, cap)); + } + } + assert!( + !related.is_empty(), + "Cannot find related of {to:?} in {self:?}" + ); + let relation = expected.unwrap(); + if matches!(relation, PlaceOrdering::Prefix | PlaceOrdering::Equal) { + assert_eq!(related.len(), 1); + } + RelatedSet { + from: related, + to, + minimum: minimum.unwrap(), + relation, + } + } + + #[tracing::instrument( + name = "CapabilityProjections::unpack", + level = "trace", + skip(repacker), + ret + )] + pub(crate) fn unpack( + &mut self, + from: Place<'tcx>, + to: Place<'tcx>, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Vec<(Place<'tcx>, Place<'tcx>)> { + debug_assert!(!self.contains_key(&to)); + let (expanded, others) = from.expand(to, repacker); + let perm = self.remove(&from).unwrap(); + self.extend(others.into_iter().map(|p| (p, perm))); + self.insert(to, perm); + expanded + } + + // TODO: this could be implemented more efficiently, by assuming that a valid + // state can always be packed up to the root + #[tracing::instrument( + name = "CapabilityProjections::pack", + level = "trace", + skip(repacker), + ret + )] + pub(crate) fn pack( + &mut self, + mut from: FxHashSet>, + to: Place<'tcx>, + perm: CapabilityKind, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Vec<(Place<'tcx>, Place<'tcx>)> { + debug_assert!(!self.contains_key(&to), "{to:?} already exists in {self:?}"); + for place in &from { + let p = self.remove(place).unwrap(); + assert_eq!(p, perm, "Cannot pack {place:?} with {p:?} into {to:?}"); + } + let collapsed = to.collapse(&mut from, repacker); + assert!(from.is_empty()); + self.insert(to, perm); + collapsed + } +} diff --git a/micromir/src/defs/mod.rs b/mir-state-analysis/src/free_pcs/impl/mod.rs similarity index 56% rename from micromir/src/defs/mod.rs rename to mir-state-analysis/src/free_pcs/impl/mod.rs index 2cbc0dd6806..3ee083a3dc9 100644 --- a/micromir/src/defs/mod.rs +++ b/mir-state-analysis/src/free_pcs/impl/mod.rs @@ -4,14 +4,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -mod operand; -mod rvalue; -mod terminator; -mod statement; -mod body; +mod fpcs; +mod local; +mod place; +pub(crate) mod engine; +pub(crate) mod join_semi_lattice; +mod triple; +mod update; -pub use body::*; -pub use operand::*; -pub use rvalue::*; -pub use statement::*; -pub use terminator::*; +pub(crate) use fpcs::*; +pub(crate) use local::*; +pub use place::*; diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs new file mode 100644 index 00000000000..edd9ab1a657 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -0,0 +1,92 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::{ + cmp::Ordering, + fmt::{Debug, Formatter, Result}, + ops::Sub, +}; + +use prusti_rustc_interface::data_structures::fx::FxHashSet; + +use crate::{Place, PlaceOrdering}; + +#[derive(Debug)] +pub(crate) struct RelatedSet<'tcx> { + pub(crate) from: Vec<(Place<'tcx>, CapabilityKind)>, + pub(crate) to: Place<'tcx>, + pub(crate) minimum: CapabilityKind, + pub(crate) relation: PlaceOrdering, +} +impl<'tcx> RelatedSet<'tcx> { + pub fn get_from(&self) -> FxHashSet> { + assert!(matches!( + self.relation, + PlaceOrdering::Suffix | PlaceOrdering::Both + )); + self.from.iter().map(|(p, _)| *p).collect() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum CapabilityKind { + Read, + Write, + Exclusive, +} +impl Debug for CapabilityKind { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + CapabilityKind::Read => write!(f, "R"), + CapabilityKind::Write => write!(f, "W"), + CapabilityKind::Exclusive => write!(f, "E"), + } + } +} + +impl PartialOrd for CapabilityKind { + fn partial_cmp(&self, other: &Self) -> Option { + if *self == *other { + return Some(Ordering::Equal); + } + match (self, other) { + (CapabilityKind::Read, CapabilityKind::Exclusive) + | (CapabilityKind::Write, CapabilityKind::Exclusive) => Some(Ordering::Less), + (CapabilityKind::Exclusive, CapabilityKind::Read) + | (CapabilityKind::Exclusive, CapabilityKind::Write) => Some(Ordering::Greater), + _ => None, + } + } +} + +impl Sub for CapabilityKind { + type Output = Option; + fn sub(self, other: Self) -> Self::Output { + match (self, other) { + (CapabilityKind::Exclusive, CapabilityKind::Read) => Some(CapabilityKind::Write), + (CapabilityKind::Exclusive, CapabilityKind::Write) => Some(CapabilityKind::Read), + _ => None, + } + } +} + +impl CapabilityKind { + pub fn is_read(self) -> bool { + matches!(self, CapabilityKind::Read) + } + pub fn is_exclusive(self) -> bool { + matches!(self, CapabilityKind::Exclusive) + } + pub fn is_write(self) -> bool { + matches!(self, CapabilityKind::Write) + } + pub fn minimum(self, other: Self) -> Option { + match self.partial_cmp(&other)? { + Ordering::Greater => Some(other), + _ => Some(self), + } + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs new file mode 100644 index 00000000000..f9e34fd511f --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -0,0 +1,148 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + hir::Mutability, + middle::mir::{ + visit::Visitor, BorrowKind, Local, Location, Operand, Rvalue, Statement, StatementKind, + Terminator, TerminatorKind, RETURN_PLACE, + }, +}; + +use crate::Fpcs; + +impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { + fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { + self.super_operand(operand, location); + match *operand { + Operand::Copy(place) => { + self.requires_read(place); + } + Operand::Move(place) => { + self.requires_exclusive(place); + self.ensures_write(place); + } + Operand::Constant(..) => (), + } + } + + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + self.super_statement(statement, location); + use StatementKind::*; + match &statement.kind { + &Assign(box (place, _)) => { + self.requires_write(place); + self.ensures_exclusive(place); + } + &FakeRead(box (_, place)) => self.requires_read(place), + &SetDiscriminant { box place, .. } => self.requires_exclusive(place), + &Deinit(box place) => { + // TODO: Maybe OK to also allow `Write` here? + self.requires_exclusive(place); + self.ensures_write(place); + } + &StorageLive(local) => { + self.requires_unalloc(local); + self.ensures_allocates(local); + } + &StorageDead(local) => { + self.requires_unalloc_or_uninit(local); + self.ensures_unalloc(local); + } + &Retag(_, box place) => self.requires_exclusive(place), + AscribeUserType(..) | Coverage(..) | Intrinsic(..) | ConstEvalCounter | Nop => (), + }; + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + self.super_terminator(terminator, location); + use TerminatorKind::*; + match &terminator.kind { + Goto { .. } + | SwitchInt { .. } + | Resume + | Abort + | Unreachable + | Assert { .. } + | GeneratorDrop + | FalseEdge { .. } + | FalseUnwind { .. } => (), + Return => { + let always_live = self.repacker.always_live_locals(); + for local in 0..self.repacker.local_count() { + let local = Local::from_usize(local); + if always_live.contains(local) { + self.requires_write(local); + } else { + self.requires_unalloc(local); + } + } + self.requires_exclusive(RETURN_PLACE); + } + &Drop { place, .. } => { + self.requires_write(place); + self.ensures_write(place); + } + &DropAndReplace { place, .. } => { + self.requires_write(place); + self.ensures_exclusive(place); + } + &Call { destination, .. } => { + self.requires_write(destination); + self.ensures_exclusive(destination); + } + &Yield { resume_arg, .. } => { + self.requires_write(resume_arg); + self.ensures_exclusive(resume_arg); + } + InlineAsm { .. } => todo!("{terminator:?}"), + }; + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + self.super_rvalue(rvalue, location); + use Rvalue::*; + match rvalue { + Use(_) + | Repeat(_, _) + | ThreadLocalRef(_) + | Cast(_, _, _) + | BinaryOp(_, _) + | CheckedBinaryOp(_, _) + | NullaryOp(_, _) + | UnaryOp(_, _) + | Aggregate(_, _) => {} + + &Ref(_, bk, place) => match bk { + BorrowKind::Shared => { + self.requires_read(place); + // self.ensures_blocked_read(place); + } + // TODO: this should allow `Shallow Shared` as well + BorrowKind::Shallow => { + self.requires_read(place); + // self.ensures_blocked_read(place); + } + BorrowKind::Unique => { + self.requires_exclusive(place); + // self.ensures_blocked_exclusive(place); + } + BorrowKind::Mut { .. } => { + self.requires_exclusive(place); + // self.ensures_blocked_exclusive(place); + } + }, + &AddressOf(m, place) => match m { + Mutability::Not => self.requires_read(place), + Mutability::Mut => self.requires_exclusive(place), + }, + &Len(place) => self.requires_read(place), + &Discriminant(place) => self.requires_read(place), + ShallowInitBox(_, _) => todo!(), + &CopyForDeref(place) => self.requires_read(place), + } + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs new file mode 100644 index 00000000000..6c7f38d7fa3 --- /dev/null +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -0,0 +1,130 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{data_structures::fx::FxHashSet, middle::mir::Local}; + +use crate::{ + utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilityProjections, Fpcs, Place, + PlaceOrdering, RelatedSet, RepackOp, +}; + +impl<'tcx> Fpcs<'_, 'tcx> { + pub(crate) fn requires_unalloc(&mut self, local: Local) { + assert!( + self.summary[local].is_unallocated(), + "local: {local:?}, fpcs: {self:?}\n" + ); + } + pub(crate) fn requires_unalloc_or_uninit(&mut self, local: Local) { + if !self.summary[local].is_unallocated() { + self.requires_write(local) + } + } + pub(crate) fn requires_read(&mut self, place: impl Into>) { + self.requires(place, CapabilityKind::Read) + } + /// May obtain write _or_ exclusive, if one should only have write afterwards, + /// make sure to also call `ensures_write`! + pub(crate) fn requires_write(&mut self, place: impl Into>) { + self.requires(place, CapabilityKind::Write) + } + pub(crate) fn requires_exclusive(&mut self, place: impl Into>) { + self.requires(place, CapabilityKind::Exclusive) + } + fn requires(&mut self, place: impl Into>, cap: CapabilityKind) { + let place = place.into(); + let cp: &mut CapabilityProjections = self.summary[place.local].get_allocated_mut(); + let ops = cp.repack(place, self.repacker); + self.repackings.extend(ops); + let kind = (*cp)[&place]; + assert!(kind >= cap); + } + + pub(crate) fn ensures_unalloc(&mut self, local: Local) { + self.summary[local] = CapabilityLocal::Unallocated; + } + pub(crate) fn ensures_allocates(&mut self, local: Local) { + assert_eq!(self.summary[local], CapabilityLocal::Unallocated); + self.summary[local] = CapabilityLocal::Allocated(CapabilityProjections::new_uninit(local)); + } + fn ensures_alloc(&mut self, place: impl Into>, cap: CapabilityKind) { + let place = place.into(); + let cp: &mut CapabilityProjections = self.summary[place.local].get_allocated_mut(); + let old = cp.insert(place, cap); + assert!(old.is_some()); + } + pub(crate) fn ensures_exclusive(&mut self, place: impl Into>) { + self.ensures_alloc(place, CapabilityKind::Exclusive) + } + pub(crate) fn ensures_write(&mut self, place: impl Into>) { + self.ensures_alloc(place, CapabilityKind::Write) + } +} + +impl<'tcx> CapabilityProjections<'tcx> { + fn repack( + &mut self, + to: Place<'tcx>, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Vec> { + let related = self.find_all_related(to, None); + match related.relation { + PlaceOrdering::Prefix => self + .unpack_ops(related.from[0].0, related.to, related.minimum, repacker) + .collect(), + PlaceOrdering::Equal => Vec::new(), + PlaceOrdering::Suffix => self + .pack_ops(related.get_from(), related.to, related.minimum, repacker) + .collect(), + PlaceOrdering::Both => { + let cp = related.from[0].0.common_prefix(to, repacker); + // Pack + let mut ops = self.weaken(&related); + let packs = self.pack_ops(related.get_from(), cp, related.minimum, repacker); + ops.extend(packs); + // Unpack + let unpacks = self.unpack_ops(cp, related.to, related.minimum, repacker); + ops.extend(unpacks); + ops + } + } + } + pub(crate) fn unpack_ops( + &mut self, + from: Place<'tcx>, + to: Place<'tcx>, + kind: CapabilityKind, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> impl Iterator> { + self.unpack(from, to, repacker) + .into_iter() + .map(move |(f, t)| RepackOp::Unpack(f, t, kind)) + } + pub(crate) fn pack_ops( + &mut self, + from: FxHashSet>, + to: Place<'tcx>, + perm: CapabilityKind, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> impl Iterator> { + self.pack(from, to, perm, repacker) + .into_iter() + .map(move |(f, t)| RepackOp::Pack(f, t, perm)) + } + + pub(crate) fn weaken(&mut self, related: &RelatedSet<'tcx>) -> Vec> { + let mut ops = Vec::new(); + let more_than_min = related.from.iter().filter(|(_, k)| *k != related.minimum); + // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` + // the exclusive permission will never be able to be recovered anymore! + for &(p, k) in more_than_min { + let old = self.insert(p, related.minimum); + assert_eq!(old, Some(k)); + ops.push(RepackOp::Weaken(p, k, related.minimum)); + } + ops + } +} diff --git a/micromir/src/repack/mod.rs b/mir-state-analysis/src/free_pcs/mod.rs similarity index 63% rename from micromir/src/repack/mod.rs rename to mir-state-analysis/src/free_pcs/mod.rs index dd474de9c75..fe388adbc52 100644 --- a/micromir/src/repack/mod.rs +++ b/mir-state-analysis/src/free_pcs/mod.rs @@ -4,11 +4,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -mod repacker; -mod calculate; -pub(crate) mod triple; -mod place; +mod check; +mod r#impl; +mod results; -pub use calculate::*; -pub(crate) use place::*; -pub use repacker::*; +pub(crate) use check::*; +pub use r#impl::*; +pub use results::*; diff --git a/micromir/src/check/mod.rs b/mir-state-analysis/src/free_pcs/results/mod.rs similarity index 87% rename from micromir/src/check/mod.rs rename to mir-state-analysis/src/free_pcs/results/mod.rs index a5f8a9cc024..7d5043c22da 100644 --- a/micromir/src/check/mod.rs +++ b/mir-state-analysis/src/free_pcs/results/mod.rs @@ -4,4 +4,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -pub(crate) mod checker; +mod repacks; + +pub use repacks::*; diff --git a/mir-state-analysis/src/free_pcs/results/repacking.rs b/mir-state-analysis/src/free_pcs/results/repacking.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs new file mode 100644 index 00000000000..9cb61b8027c --- /dev/null +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -0,0 +1,21 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::middle::mir::Local; + +use crate::{CapabilityKind, Place}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RepackOp<'tcx> { + Weaken(Place<'tcx>, CapabilityKind, CapabilityKind), + // TODO: figure out when and why this happens + // DeallocUnknown(Local), + DeallocForCleanup(Local), + // First place is packed up, second is guide place to pack up from + Pack(Place<'tcx>, Place<'tcx>, CapabilityKind), + // First place is packed up, second is guide place to unpack to + Unpack(Place<'tcx>, Place<'tcx>, CapabilityKind), +} diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs new file mode 100644 index 00000000000..b03090e6103 --- /dev/null +++ b/mir-state-analysis/src/lib.rs @@ -0,0 +1,28 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#![feature(rustc_private)] +#![feature(box_patterns)] + +mod free_pcs; +mod utils; + +pub use free_pcs::*; +pub use utils::place::*; + +use prusti_rustc_interface::{ + dataflow::Analysis, + middle::{mir::Body, ty::TyCtxt}, +}; + +pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { + let fpcs = free_pcs::engine::FreePlaceCapabilitySummary::new(tcx, mir); + let analysis = fpcs + .into_engine(tcx, mir) + .pass_name("free_pcs") + .iterate_to_fixpoint(); + free_pcs::check(analysis); +} diff --git a/micromir/src/utils/mod.rs b/mir-state-analysis/src/utils/mod.rs similarity index 81% rename from micromir/src/utils/mod.rs rename to mir-state-analysis/src/utils/mod.rs index 6f5a94a7085..e0bd10f8b91 100644 --- a/micromir/src/utils/mod.rs +++ b/mir-state-analysis/src/utils/mod.rs @@ -5,3 +5,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. pub mod place; +pub(crate) mod repacker; + +pub(crate) use repacker::*; diff --git a/micromir/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs similarity index 63% rename from micromir/src/utils/place.rs rename to mir-state-analysis/src/utils/place.rs index 672c7e5d5cd..139fa466c6c 100644 --- a/micromir/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -18,6 +18,38 @@ use prusti_rustc_interface::middle::{ ty::List, }; +// #[derive(Clone, Copy, Deref, DerefMut, Hash, PartialEq, Eq)] +// pub struct RootPlace<'tcx>(Place<'tcx>); +// impl<'tcx> RootPlace<'tcx> { +// pub(super) fn new(place: Place<'tcx>) -> Self { +// assert!(place.projection.last().copied().map(Self::is_indirect).unwrap_or(true)); +// Self(place) +// } + +// pub fn is_indirect(p: ProjectionElem) -> bool { +// match p { +// ProjectionElem::Deref => true, + +// ProjectionElem::Field(_, _) +// | ProjectionElem::Index(_) +// | ProjectionElem::OpaqueCast(_) +// | ProjectionElem::ConstantIndex { .. } +// | ProjectionElem::Subslice { .. } +// | ProjectionElem::Downcast(_, _) => false, +// } +// } +// } +// impl Debug for RootPlace<'_> { +// fn fmt(&self, fmt: &mut Formatter) -> Result { +// self.0.fmt(fmt) +// } +// } +// impl From for RootPlace<'_> { +// fn from(local: Local) -> Self { +// Self(local.into()) +// } +// } + fn elem_eq<'tcx>(to_cmp: (PlaceElem<'tcx>, PlaceElem<'tcx>)) -> bool { use ProjectionElem::*; match to_cmp { @@ -144,8 +176,90 @@ impl<'tcx> Place<'tcx> { } impl Debug for Place<'_> { - fn fmt(&self, f: &mut Formatter) -> Result { - self.0.fmt(f) + fn fmt(&self, fmt: &mut Formatter) -> Result { + for elem in self.projection.iter().rev() { + match elem { + ProjectionElem::OpaqueCast(_) | ProjectionElem::Downcast(_, _) => { + write!(fmt, "(").unwrap(); + } + ProjectionElem::Deref => { + write!(fmt, "(*").unwrap(); + } + ProjectionElem::Field(_, _) + | ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => {} + } + } + + write!(fmt, "{:?}", self.local)?; + + for elem in self.projection.iter() { + match elem { + ProjectionElem::OpaqueCast(ty) => { + write!(fmt, " as {})", ty)?; + } + ProjectionElem::Downcast(Some(name), _index) => { + write!(fmt, " as {})", name)?; + } + ProjectionElem::Downcast(None, index) => { + write!(fmt, " as variant#{:?})", index)?; + } + ProjectionElem::Deref => { + write!(fmt, ")")?; + } + ProjectionElem::Field(field, _ty) => { + write!(fmt, ".{:?}", field.index())?; + } + ProjectionElem::Index(ref index) => { + write!(fmt, "[{:?}]", index)?; + } + ProjectionElem::ConstantIndex { + offset, + min_length, + from_end: false, + } => { + write!(fmt, "[{:?} of {:?}]", offset, min_length)?; + } + ProjectionElem::ConstantIndex { + offset, + min_length, + from_end: true, + } => { + write!(fmt, "[-{:?} of {:?}]", offset, min_length)?; + } + ProjectionElem::Subslice { + from, + to, + from_end: true, + } if to == 0 => { + write!(fmt, "[{:?}:]", from)?; + } + ProjectionElem::Subslice { + from, + to, + from_end: true, + } if from == 0 => { + write!(fmt, "[:-{:?}]", to)?; + } + ProjectionElem::Subslice { + from, + to, + from_end: true, + } => { + write!(fmt, "[{:?}:-{:?}]", from, to)?; + } + ProjectionElem::Subslice { + from, + to, + from_end: false, + } => { + write!(fmt, "[{:?}..{:?}]", from, to)?; + } + } + } + + Ok(()) } } @@ -214,6 +328,21 @@ pub enum PlaceOrdering { Both, } +impl PlaceOrdering { + pub fn is_eq(self) -> bool { + matches!(self, PlaceOrdering::Equal) + } + pub fn is_prefix(self) -> bool { + matches!(self, PlaceOrdering::Prefix) + } + pub fn is_suffix(self) -> bool { + matches!(self, PlaceOrdering::Suffix) + } + pub fn is_both(self) -> bool { + matches!(self, PlaceOrdering::Both) + } +} + impl From for PlaceOrdering { fn from(ordering: Ordering) -> Self { match ordering { diff --git a/micromir/src/repack/place.rs b/mir-state-analysis/src/utils/repacker.rs similarity index 55% rename from micromir/src/repack/place.rs rename to mir-state-analysis/src/utils/repacker.rs index e4362cf7d8d..0ebd17a8eab 100644 --- a/micromir/src/repack/place.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -6,8 +6,10 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashSet, + dataflow::storage, + index::bit_set::BitSet, middle::{ - mir::{Body, Field, ProjectionElem}, + mir::{Body, Field, HasLocalDecls, Local, Mutability, ProjectionElem}, ty::{TyCtxt, TyKind}, }, }; @@ -16,7 +18,7 @@ use crate::Place; #[derive(Copy, Clone)] // TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate -pub(crate) struct PlaceRepacker<'a, 'tcx: 'a> { +pub struct PlaceRepacker<'a, 'tcx: 'a> { mir: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, } @@ -26,26 +28,105 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { Self { mir, tcx } } - /// Expand `current_place` one level down by following the `guide_place`. - /// Returns the new `current_place` and a vector containing other places that + pub fn local_count(self) -> usize { + self.mir.local_decls().len() + } + + pub fn always_live_locals(self) -> BitSet { + storage::always_storage_live_locals(self.mir) + } + + pub fn body(self) -> &'a Body<'tcx> { + self.mir + } +} + +impl<'tcx> Place<'tcx> { + /// Subtract the `to` place from the `self` place. The + /// subtraction is defined as set minus between `self` place replaced + /// with a set of places that are unrolled up to the same level as + /// `to` and the singleton `to` set. For example, + /// `expand(x.f, x.f.g.h)` is performed by unrolling `x.f` into + /// `{x.g, x.h, x.f.f, x.f.h, x.f.g.f, x.f.g.g, x.f.g.h}` and + /// subtracting `{x.f.g.h}` from it, which results into (`{x.f, x.f.g}`, `{x.g, x.h, + /// x.f.f, x.f.h, x.f.g.f, x.f.g.g}`). The first vector contains the chain of + /// places that were expanded along with the target to of each expansion. + #[tracing::instrument(level = "trace", skip(repacker), ret)] + pub fn expand( + mut self, + to: Self, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> (Vec<(Self, Self)>, Vec) { + assert!( + self.is_prefix(to), + "The minuend ({self:?}) must be the prefix of the subtrahend ({to:?})." + ); + let mut place_set = Vec::new(); + let mut expanded = Vec::new(); + while self.projection.len() < to.projection.len() { + expanded.push((self, to)); + let (new_minuend, places) = self.expand_one_level(to, repacker); + self = new_minuend; + place_set.extend(places); + } + (expanded, place_set) + } + + /// Try to collapse all places in `from` by following the + /// `guide_place`. This function is basically the reverse of + /// `expand`. + #[tracing::instrument(level = "trace", skip(repacker), ret)] + pub fn collapse( + self, + from: &mut FxHashSet, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Vec<(Self, Self)> { + let mut collapsed = Vec::new(); + let mut guide_places = vec![self]; + while let Some(guide_place) = guide_places.pop() { + if !from.remove(&guide_place) { + let expand_guide = *from + .iter() + .find(|p| guide_place.is_prefix(**p)) + .unwrap_or_else(|| { + panic!( + "The `from` set didn't contain all \ + the places required to construct the \ + `guide_place`. Currently tried to find \ + `{guide_place:?}` in `{from:?}`." + ) + }); + let (expanded, new_places) = guide_place.expand(expand_guide, repacker); + // Doing `collapsed.extend(expanded)` would result in a reversed order. + // Could also change this to `collapsed.push(expanded)` and return Vec>. + collapsed.extend(expanded); + guide_places.extend(new_places); + from.remove(&expand_guide); + } + } + collapsed.reverse(); + collapsed + } + + /// Expand `self` one level down by following the `guide_place`. + /// Returns the new `self` and a vector containing other places that /// could have resulted from the expansion. - #[tracing::instrument(level = "trace", skip(self), ret)] + #[tracing::instrument(level = "trace", skip(repacker), ret)] pub(crate) fn expand_one_level( self, - current_place: Place<'tcx>, - guide_place: Place<'tcx>, - ) -> (Place<'tcx>, Vec>) { - let index = current_place.projection.len(); - let new_projection = self.tcx.mk_place_elems( - current_place - .projection + guide_place: Self, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> (Self, Vec) { + let index = self.projection.len(); + let new_projection = repacker.tcx.mk_place_elems_from_iter( + self.projection .iter() .chain([guide_place.projection[index]]), ); - let new_current_place = Place::new(current_place.local, new_projection); + let new_current_place = Place::new(self.local, new_projection); let other_places = match guide_place.projection[index] { ProjectionElem::Field(projected_field, _field_ty) => { - self.expand_place(current_place, Some(projected_field.index())) + self.expand_field(Some(projected_field.index()), repacker) } ProjectionElem::ConstantIndex { offset, @@ -60,9 +141,10 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { } }) .map(|i| { - self.tcx + repacker + .tcx .mk_place_elem( - *current_place, + *self, ProjectionElem::ConstantIndex { offset: i, min_length, @@ -85,13 +167,13 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { /// each of the struct's fields `{x.f.g.f, x.f.g.g, x.f.g.h}`. If /// `without_field` is not `None`, then omits that field from the final /// vector. - pub fn expand_place( + pub fn expand_field( self, - place: Place<'tcx>, without_field: Option, - ) -> Vec> { + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Vec { let mut places = Vec::new(); - let typ = place.ty(self.mir, self.tcx); + let typ = self.ty(repacker.mir, repacker.tcx); if !matches!(typ.ty.kind(), TyKind::Adt(..)) { assert!( typ.variant_index.is_none(), @@ -107,9 +189,11 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { for (index, field_def) in variant.fields.iter().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = - self.tcx - .mk_place_field(*place, field, field_def.ty(self.tcx, substs)); + let field_place = repacker.tcx.mk_place_field( + *self, + field, + field_def.ty(repacker.tcx, substs), + ); places.push(field_place.into()); } } @@ -118,7 +202,7 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { for (index, arg) in slice.iter().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = self.tcx.mk_place_field(*place, field, arg); + let field_place = repacker.tcx.mk_place_field(*self, field, arg); places.push(field_place.into()); } } @@ -127,7 +211,7 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { for (index, subst_ty) in substs.as_closure().upvar_tys().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = self.tcx.mk_place_field(*place, field, subst_ty); + let field_place = repacker.tcx.mk_place_field(*self, field, subst_ty); places.push(field_place.into()); } } @@ -136,7 +220,7 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { for (index, subst_ty) in substs.as_generator().upvar_tys().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = self.tcx.mk_place_field(*place, field, subst_ty); + let field_place = repacker.tcx.mk_place_field(*self, field, subst_ty); places.push(field_place.into()); } } @@ -146,72 +230,6 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { places } - /// Subtract the `subtrahend` place from the `minuend` place. The - /// subtraction is defined as set minus between `minuend` place replaced - /// with a set of places that are unrolled up to the same level as - /// `subtrahend` and the singleton `subtrahend` set. For example, - /// `expand(x.f, x.f.g.h)` is performed by unrolling `x.f` into - /// `{x.g, x.h, x.f.f, x.f.h, x.f.g.f, x.f.g.g, x.f.g.h}` and - /// subtracting `{x.f.g.h}` from it, which results into (`{x.f, x.f.g}`, `{x.g, x.h, - /// x.f.f, x.f.h, x.f.g.f, x.f.g.g}`). The first vector contains the chain of - /// places that were expanded along with the target subtrahend of each expansion. - #[tracing::instrument(level = "trace", skip(self), ret)] - pub fn expand( - self, - mut minuend: Place<'tcx>, - subtrahend: Place<'tcx>, - ) -> (Vec<(Place<'tcx>, Place<'tcx>)>, Vec>) { - assert!( - minuend.is_prefix(subtrahend), - "The minuend ({minuend:?}) must be the prefix of the subtrahend ({subtrahend:?})." - ); - let mut place_set = Vec::new(); - let mut expanded = Vec::new(); - while minuend.projection.len() < subtrahend.projection.len() { - expanded.push((minuend, subtrahend)); - let (new_minuend, places) = self.expand_one_level(minuend, subtrahend); - minuend = new_minuend; - place_set.extend(places); - } - (expanded, place_set) - } - - /// Try to collapse all places in `places` by following the - /// `guide_place`. This function is basically the reverse of - /// `expand`. - #[tracing::instrument(level = "trace", skip(self), ret)] - pub fn collapse( - self, - guide_place: Place<'tcx>, - places: &mut FxHashSet>, - ) -> Vec<(Place<'tcx>, Place<'tcx>)> { - let mut collapsed = Vec::new(); - let mut guide_places = vec![guide_place]; - while let Some(guide_place) = guide_places.pop() { - if !places.remove(&guide_place) { - let expand_guide = *places - .iter() - .find(|p| guide_place.is_prefix(**p)) - .unwrap_or_else(|| { - panic!( - "The `places` set didn't contain all \ - the places required to construct the \ - `guide_place`. Currently tried to find \ - `{guide_place:?}` in `{places:?}`." - ) - }); - let (expanded, new_places) = self.expand(guide_place, expand_guide); - // Doing `collapsed.extend(expanded)` would result in a reversed order. - // Could also change this to `collapsed.push(expanded)` and return Vec>. - collapsed.extend(expanded); - guide_places.extend(new_places); - places.remove(&expand_guide); - } - } - collapsed.reverse(); - collapsed - } - // /// Pop the last projection from the place and return the new place with the popped element. // pub fn pop_one_level(self, place: Place<'tcx>) -> (PlaceElem<'tcx>, Place<'tcx>) { // assert!(place.projection.len() > 0); @@ -222,23 +240,37 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { // Place::new(place.local, projection), // ) // } +} + +// impl<'tcx> RootPlace<'tcx> { +// pub fn get_parent(self, repacker: PlaceRepacker<'_, 'tcx>) -> Place<'tcx> { +// assert!(self.projection.len() > 0); +// let idx = self.projection.len() - 1; +// let projection = repacker.tcx.intern_place_elems(&self.projection[..idx]); +// Place::new(self.local, projection) +// } +// } - #[tracing::instrument(level = "debug", skip(self), ret, fields(lp = ?left.projection, rp = ?right.projection))] - pub fn common_prefix(self, left: Place<'tcx>, right: Place<'tcx>) -> Place<'tcx> { - assert_eq!(left.local, right.local); +impl<'tcx> Place<'tcx> { + #[tracing::instrument(level = "debug", skip(repacker), ret, fields(lp = ?self.projection, rp = ?other.projection))] + pub fn common_prefix(self, other: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { + assert_eq!(self.local, other.local); - let common_prefix = left - .compare_projections(right) + let common_prefix = self + .compare_projections(other) .take_while(|(eq, _, _)| *eq) .map(|(_, e1, _)| e1); - Place::new(left.local, self.tcx.mk_place_elems(common_prefix)) + Self::new( + self.local, + repacker.tcx.mk_place_elems_from_iter(common_prefix), + ) } - #[tracing::instrument(level = "info", skip(self), ret)] - pub fn joinable_to(self, from: Place<'tcx>, to: Place<'tcx>) -> Place<'tcx> { - assert!(from.is_prefix(to)); - let proj = from.projection.iter(); - let to_proj = to.projection[from.projection.len()..] + #[tracing::instrument(level = "info", skip(repacker), ret)] + pub fn joinable_to(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { + assert!(self.is_prefix(to)); + let proj = self.projection.iter(); + let to_proj = to.projection[self.projection.len()..] .iter() .copied() .take_while(|p| { @@ -249,7 +281,28 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { | ProjectionElem::ConstantIndex { .. } ) }); - let projection = self.tcx.mk_place_elems(proj.chain(to_proj)); - Place::new(from.local, projection) + let projection = repacker.tcx.mk_place_elems_from_iter(proj.chain(to_proj)); + Self::new(self.local, projection) + } + + // pub fn get_root(self, repacker: PlaceRepacker<'_, 'tcx>) -> RootPlace<'tcx> { + // if let Some(idx) = self.projection.iter().rev().position(RootPlace::is_indirect) { + // let idx = self.projection.len() - idx; + // let projection = repacker.tcx.intern_place_elems(&self.projection[..idx]); + // let new = Self::new(self.local, projection); + // RootPlace::new(new) + // } else { + // RootPlace::new(self.local.into()) + // } + // } + + /// Should only be called on a `Place` obtained from `RootPlace::get_parent`. + pub fn get_ref_mutability(self, repacker: PlaceRepacker<'_, 'tcx>) -> Mutability { + let typ = self.ty(repacker.mir, repacker.tcx); + if let TyKind::Ref(_, _, mutability) = typ.ty.kind() { + *mutability + } else { + unreachable!("get_ref_mutability called on non-ref type: {:?}", typ.ty); + } } } diff --git a/micromir/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs similarity index 100% rename from micromir/tests/top_crates.rs rename to mir-state-analysis/tests/top_crates.rs diff --git a/prusti/Cargo.toml b/prusti/Cargo.toml index 86a70addec0..c42681fc002 100644 --- a/prusti/Cargo.toml +++ b/prusti/Cargo.toml @@ -21,7 +21,7 @@ lazy_static = "1.4.0" tracing = { path = "../tracing" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-chrome = "0.7" -micromir = { path = "../micromir" } +mir-state-analysis = { path = "../mir-state-analysis" } [build-dependencies] chrono = { version = "0.4.22", default-features = false, features = ["clock"] } diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 587f3b79be0..94eb61f7e5a 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,5 +1,5 @@ use crate::verifier::verify; -use micromir::test_free_pcs; +use mir_state_analysis::test_free_pcs; use prusti_common::config; use prusti_interface::{ environment::{mir_storage, Environment}, @@ -140,7 +140,14 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { CrossCrateSpecs::import_export_cross_crate(&mut env, &mut def_spec); if !config::no_verify() { if config::test_free_pcs() { - test_free_pcs(&env); + for proc_id in env.get_annotated_procedures_and_types().0.iter() { + let name = env.name.get_unique_item_name(*proc_id); + println!("Calculating FPCS for: {name}"); + + let current_procedure = env.get_procedure(*proc_id); + let mir = current_procedure.get_mir_rc(); + test_free_pcs(&mir, tcx); + } } else { verify(env, def_spec); } diff --git a/x.py b/x.py index 8cba90906e7..4d02093296e 100755 --- a/x.py +++ b/x.py @@ -37,7 +37,7 @@ RUSTFMT_CRATES = [ 'analysis', 'jni-gen', - 'micromir', + 'mir-state-analysis', 'prusti', 'prusti-common', 'prusti-contracts/prusti-contracts', From a64df485e4d374219bfb32fb04ffd290cc1028a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 18 Apr 2023 20:26:22 +0200 Subject: [PATCH 06/32] Add results cursor --- .../src/free_pcs/check/checker.rs | 51 ++++---- .../src/free_pcs/results/cursor.rs | 113 ++++++++++++++++++ .../src/free_pcs/results/mod.rs | 2 + .../src/free_pcs/results/repacking.rs | 0 mir-state-analysis/src/lib.rs | 10 +- 5 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 mir-state-analysis/src/free_pcs/results/cursor.rs delete mode 100644 mir-state-analysis/src/free_pcs/results/repacking.rs diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index 7e6e4cfa485..3fe8bd1d03e 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -6,25 +6,26 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashMap, - dataflow::Results, middle::mir::{visit::Visitor, Location}, }; use crate::{ - engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, - utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilitySummary, PlaceOrdering, - RepackOp, + utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, + FreePcsAnalysis, PlaceOrdering, RepackOp, }; use super::consistency::CapabilityConistency; -pub(crate) fn check<'tcx>(results: Results<'tcx, FreePlaceCapabilitySummary<'_, 'tcx>>) { - let rp = results.analysis.0; +pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { + let rp = results.repacker(); let body = rp.body(); - let mut cursor = results.into_results_cursor(body); for (block, data) in body.basic_blocks.iter_enumerated() { - cursor.seek_to_block_start(block); - let mut fpcs = cursor.get().clone(); + let mut cursor = results.analysis_for_bb(block); + let mut fpcs = Fpcs { + summary: cursor.initial_state().clone(), + repackings: Vec::new(), + repacker: rp, + }; // Consistency fpcs.summary.consistency_check(rp); for (statement_index, stmt) in data.statements.iter().enumerate() { @@ -32,10 +33,10 @@ pub(crate) fn check<'tcx>(results: Results<'tcx, FreePlaceCapabilitySummary<'_, block, statement_index, }; - cursor.seek_after_primary_effect(loc); - let fpcs_after = cursor.get(); + let fpcs_after = cursor.next().unwrap(); + assert_eq!(fpcs_after.location, loc); // Repacks - for op in &fpcs_after.repackings { + for op in fpcs_after.repacks { op.update_free(&mut fpcs.summary, false, rp); } // Consistency @@ -51,10 +52,10 @@ pub(crate) fn check<'tcx>(results: Results<'tcx, FreePlaceCapabilitySummary<'_, block, statement_index: data.statements.len(), }; - cursor.seek_after_primary_effect(loc); - let fpcs_after = cursor.get(); + let fpcs_after = cursor.next().unwrap(); + assert_eq!(fpcs_after.location, loc); // Repacks - for op in &fpcs_after.repackings { + for op in fpcs_after.repacks { op.update_free(&mut fpcs.summary, false, rp); } // Consistency @@ -65,19 +66,23 @@ pub(crate) fn check<'tcx>(results: Results<'tcx, FreePlaceCapabilitySummary<'_, assert!(fpcs.repackings.is_empty()); // Consistency fpcs.summary.consistency_check(rp); - assert_eq!(&fpcs, fpcs_after); + assert_eq!(&fpcs.summary, fpcs_after.state); - for succ in data.terminator().successors() { - // Get repacks - let to = cursor.results().entry_set_for_block(succ); - let repacks = fpcs.summary.bridge(&to.summary, rp); + let Err(fpcs_end) = cursor.next() else { + panic!("Expected error at the end of the block"); + }; + for succ in fpcs_end.succs { // Repacks let mut from = fpcs.clone(); - for op in repacks { - op.update_free(&mut from.summary, body.basic_blocks[succ].is_cleanup, rp); + for op in succ.repacks { + op.update_free( + &mut from.summary, + body.basic_blocks[succ.location.block].is_cleanup, + rp, + ); } - assert_eq!(&from, to); + assert_eq!(&from.summary, succ.state); } } } diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs new file mode 100644 index 00000000000..d1589a8d1ae --- /dev/null +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -0,0 +1,113 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + dataflow::ResultsCursor, + middle::mir::{BasicBlock, Body, Location}, +}; + +use crate::{ + engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, + utils::PlaceRepacker, CapabilitySummary, RepackOp, +}; + +type Cursor<'mir, 'tcx> = ResultsCursor<'mir, 'tcx, FreePlaceCapabilitySummary<'mir, 'tcx>>; + +pub struct FreePcsAnalysis<'mir, 'tcx>(pub(crate) Cursor<'mir, 'tcx>); + +impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { + pub fn analysis_for_bb(&mut self, block: BasicBlock) -> FreePcsCursor<'_, 'mir, 'tcx> { + self.0.seek_to_block_start(block); + let end_stmt = self + .0 + .analysis() + .0 + .body() + .terminator_loc(block) + .successor_within_block(); + FreePcsCursor { + analysis: self, + curr_stmt: Location { + block, + statement_index: 0, + }, + end_stmt, + } + } + + pub(crate) fn body(&self) -> &'mir Body<'tcx> { + self.0.analysis().0.body() + } + pub(crate) fn repacker(&self) -> PlaceRepacker<'mir, 'tcx> { + self.0.results().analysis.0 + } +} + +pub struct FreePcsCursor<'a, 'mir, 'tcx> { + analysis: &'a mut FreePcsAnalysis<'mir, 'tcx>, + curr_stmt: Location, + end_stmt: Location, +} + +impl<'a, 'mir, 'tcx> FreePcsCursor<'a, 'mir, 'tcx> { + pub fn initial_state(&self) -> &CapabilitySummary<'tcx> { + &self.analysis.0.get().summary + } + pub fn next<'b>( + &'b mut self, + ) -> Result, FreePcsTerminator<'b, 'tcx>> { + let location = self.curr_stmt; + assert!(location <= self.end_stmt); + self.curr_stmt = location.successor_within_block(); + + if location == self.end_stmt { + // TODO: cleanup + let cursor = &self.analysis.0; + let state = cursor.get(); + let rp = self.analysis.repacker(); + let block = &self.analysis.body()[location.block]; + let succs = block + .terminator() + .successors() + .map(|succ| { + // Get repacks + let to = cursor.results().entry_set_for_block(succ); + FreePcsLocation { + location: Location { + block: succ, + statement_index: 0, + }, + state: &to.summary, + repacks: state.summary.bridge(&to.summary, rp), + } + }) + .collect(); + Err(FreePcsTerminator { succs }) + } else { + self.analysis.0.seek_after_primary_effect(location); + let state = self.analysis.0.get(); + Ok(FreePcsLocation { + location, + state: &state.summary, + repacks: state.repackings.clone(), + }) + } + } +} + +#[derive(Debug)] +pub struct FreePcsLocation<'a, 'tcx> { + pub location: Location, + /// Repacks before the statement + pub repacks: Vec>, + /// State after the statement + pub state: &'a CapabilitySummary<'tcx>, +} + +#[derive(Debug)] +pub struct FreePcsTerminator<'a, 'tcx> { + pub succs: Vec>, +} diff --git a/mir-state-analysis/src/free_pcs/results/mod.rs b/mir-state-analysis/src/free_pcs/results/mod.rs index 7d5043c22da..3d949c4f182 100644 --- a/mir-state-analysis/src/free_pcs/results/mod.rs +++ b/mir-state-analysis/src/free_pcs/results/mod.rs @@ -5,5 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. mod repacks; +mod cursor; +pub use cursor::*; pub use repacks::*; diff --git a/mir-state-analysis/src/free_pcs/results/repacking.rs b/mir-state-analysis/src/free_pcs/results/repacking.rs deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index b03090e6103..62ff756207e 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -18,11 +18,19 @@ use prusti_rustc_interface::{ middle::{mir::Body, ty::TyCtxt}, }; -pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { +pub fn run_free_pcs<'mir, 'tcx>( + mir: &'mir Body<'tcx>, + tcx: TyCtxt<'tcx>, +) -> FreePcsAnalysis<'mir, 'tcx> { let fpcs = free_pcs::engine::FreePlaceCapabilitySummary::new(tcx, mir); let analysis = fpcs .into_engine(tcx, mir) .pass_name("free_pcs") .iterate_to_fixpoint(); + FreePcsAnalysis(analysis.into_results_cursor(mir)) +} + +pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { + let analysis = run_free_pcs(mir, tcx); free_pcs::check(analysis); } From d85ed76b2bfd814a40d6711206cef3f63eb4627f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 18 Apr 2023 20:51:09 +0200 Subject: [PATCH 07/32] Display repacks in graphviz --- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 3 +++ mir-state-analysis/src/free_pcs/results/repacks.rs | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index 952b5236243..f21c6627080 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -53,6 +53,9 @@ impl<'a, 'tcx> DebugWithContext> for Fpcs<' f: &mut Formatter<'_>, ) -> Result { assert_eq!(self.summary.len(), old.summary.len()); + for op in &self.repackings { + writeln!(f, "{op}")?; + } for (new, old) in self.summary.iter().zip(old.summary.iter()) { let changed = match (new, old) { (CapabilityLocal::Unallocated, CapabilityLocal::Unallocated) => false, diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs index 9cb61b8027c..53e92b0fb7f 100644 --- a/mir-state-analysis/src/free_pcs/results/repacks.rs +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -4,6 +4,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +use std::fmt::{Display, Formatter, Result}; + use prusti_rustc_interface::middle::mir::Local; use crate::{CapabilityKind, Place}; @@ -19,3 +21,14 @@ pub enum RepackOp<'tcx> { // First place is packed up, second is guide place to unpack to Unpack(Place<'tcx>, Place<'tcx>, CapabilityKind), } + +impl Display for RepackOp<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + RepackOp::Weaken(place, from, to) => write!(f, "Weaken({place:?}, {:?})", (*from - *to).unwrap()), + RepackOp::DeallocForCleanup(_) => todo!(), + RepackOp::Pack(to, _, kind) => write!(f, "Pack({to:?}, {kind:?})"), + RepackOp::Unpack(from, _, kind) => write!(f, "Unpack({from:?}, {kind:?})"), + } + } +} From b914d79586caabe396becbc92ca255820d5da3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 19 Apr 2023 21:48:29 +0200 Subject: [PATCH 08/32] Add `ShallowExclusive` capability TODO: remove read capability --- .../src/free_pcs/check/checker.rs | 134 ++++++++----- .../src/free_pcs/check/consistency.rs | 12 +- .../src/free_pcs/impl/engine.rs | 27 ++- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 39 ++-- .../src/free_pcs/impl/join_semi_lattice.rs | 148 +++++++-------- mir-state-analysis/src/free_pcs/impl/local.rs | 120 +++++++++--- mir-state-analysis/src/free_pcs/impl/place.rs | 36 +++- .../src/free_pcs/impl/triple.rs | 43 ++++- .../src/free_pcs/impl/update.rs | 68 ++----- .../src/free_pcs/results/cursor.rs | 7 +- .../src/free_pcs/results/repacks.rs | 77 ++++++-- mir-state-analysis/src/lib.rs | 13 +- mir-state-analysis/src/utils/mod.rs | 3 +- mir-state-analysis/src/utils/place.rs | 38 +++- mir-state-analysis/src/utils/repacker.rs | 177 +++++++++++++----- 15 files changed, 636 insertions(+), 306 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index 3fe8bd1d03e..ce0d27a7fec 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -6,12 +6,14 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashMap, - middle::mir::{visit::Visitor, Location}, + middle::mir::{visit::Visitor, Location, ProjectionElem}, }; use crate::{ - utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, - FreePcsAnalysis, PlaceOrdering, RepackOp, + free_pcs::{ + CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, + }, + utils::PlaceRepacker, }; use super::consistency::CapabilityConistency; @@ -23,6 +25,7 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { let mut cursor = results.analysis_for_bb(block); let mut fpcs = Fpcs { summary: cursor.initial_state().clone(), + bottom: false, repackings: Vec::new(), repacker: rp, }; @@ -90,20 +93,13 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { impl<'tcx> RepackOp<'tcx> { #[tracing::instrument(level = "debug", skip(rp))] fn update_free( - &self, + self, state: &mut CapabilitySummary<'tcx>, - can_dealloc: bool, + is_cleanup: bool, rp: PlaceRepacker<'_, 'tcx>, ) { match self { - RepackOp::Weaken(place, from, to) => { - assert!(from >= to, "{self:?}"); - let curr_state = state[place.local].get_allocated_mut(); - let old = curr_state.insert(*place, *to); - assert_eq!(old, Some(*from), "{self:?}, {curr_state:?}"); - } - &RepackOp::DeallocForCleanup(local) => { - assert!(can_dealloc); + RepackOp::StorageDead(local) => { let curr_state = state[local].get_allocated_mut(); assert_eq!(curr_state.len(), 1); assert!( @@ -113,51 +109,97 @@ impl<'tcx> RepackOp<'tcx> { assert_eq!(curr_state[&local.into()], CapabilityKind::Write); state[local] = CapabilityLocal::Unallocated; } - // &RepackOp::DeallocUnknown(local) => { - // assert!(!can_dealloc); - // let curr_state = state[local].get_allocated_mut(); - // assert_eq!(curr_state.len(), 1); - // assert_eq!(curr_state[&local.into()], CapabilityKind::Write); - // state[local] = CapabilityLocal::Unallocated; - // } - RepackOp::Pack(place, guide, kind) => { + RepackOp::IgnoreStorageDead(local) => { + assert_eq!(state[local], CapabilityLocal::Unallocated); + // Add write permission so that the `mir::StatementKind::StorageDead` can + // deallocate without producing any repacks, which would cause the + // `assert!(fpcs.repackings.is_empty());` above to fail. + state[local] = CapabilityLocal::new(local, CapabilityKind::Write); + } + RepackOp::Weaken(place, from, to) => { + assert!(from >= to, "{self:?}"); + let curr_state = state[place.local].get_allocated_mut(); + let old = curr_state.insert(place, to); + assert_eq!(old, Some(from), "{self:?}, {curr_state:?}"); + } + RepackOp::Expand(place, guide, kind) => { + assert!(place.is_prefix_exact(guide), "{self:?}"); + assert_ne!(*guide.projection.last().unwrap(), ProjectionElem::Deref); + let curr_state = state[place.local].get_allocated_mut(); assert_eq!( - place.partial_cmp(*guide), - Some(PlaceOrdering::Prefix), - "{self:?}" + curr_state.remove(&place), + Some(kind), + "{self:?} ({curr_state:?})" ); + + let (p, others, pkind) = place.expand_one_level(guide, rp); + assert!(!pkind.is_deref()); + curr_state.insert(p, kind); + curr_state.extend(others.into_iter().map(|p| (p, kind))); + } + RepackOp::Collapse(place, guide, kind) => { + assert!(place.is_prefix_exact(guide), "{self:?}"); + assert_ne!(*guide.projection.last().unwrap(), ProjectionElem::Deref); let curr_state = state[place.local].get_allocated_mut(); let mut removed = curr_state - .drain() - .filter(|(p, _)| place.related_to(*p)) + .drain_filter(|p, _| place.related_to(*p)) .collect::>(); - let (p, others) = place.expand_one_level(*guide, rp); - assert!(others - .into_iter() - .chain(std::iter::once(p)) - .all(|p| removed.remove(&p).unwrap() == *kind)); - assert!(removed.is_empty(), "{self:?}, {removed:?}"); - let old = curr_state.insert(*place, *kind); + let (p, mut others, pkind) = place.expand_one_level(guide, rp); + assert!(!pkind.is_deref()); + others.push(p); + for other in others { + assert_eq!(removed.remove(&other), Some(kind), "{self:?}"); + } + assert!(removed.is_empty(), "{self:?}, {removed:?}"); + let old = curr_state.insert(place, kind); assert_eq!(old, None); } - RepackOp::Unpack(place, guide, kind) => { - assert_eq!( - place.partial_cmp(*guide), - Some(PlaceOrdering::Prefix), - "{self:?}" - ); + RepackOp::Deref(place, kind, guide, to_kind) => { + assert!(place.is_prefix_exact(guide), "{self:?}"); + assert_eq!(*guide.projection.last().unwrap(), ProjectionElem::Deref); let curr_state = state[place.local].get_allocated_mut(); assert_eq!( - curr_state.remove(place), - Some(*kind), - "{self:?} ({:?})", - &**curr_state + curr_state.remove(&place), + Some(kind), + "{self:?} ({curr_state:?})" ); - let (p, others) = place.expand_one_level(*guide, rp); - curr_state.insert(p, *kind); - curr_state.extend(others.into_iter().map(|p| (p, *kind))); + let (p, others, pkind) = place.expand_one_level(guide, rp); + assert!(pkind.is_deref()); + if pkind.is_box() && kind.is_shallow_exclusive() { + assert_eq!(to_kind, CapabilityKind::Write); + } else { + assert_eq!(to_kind, kind); + } + + curr_state.insert(p, to_kind); + assert!(others.is_empty()); + } + RepackOp::Upref(place, to_kind, guide, kind) => { + assert!(place.is_prefix_exact(guide), "{self:?}"); + assert_eq!(*guide.projection.last().unwrap(), ProjectionElem::Deref); + let curr_state = state[place.local].get_allocated_mut(); + let mut removed = curr_state + .drain_filter(|p, _| place.related_to(*p)) + .collect::>(); + + let (p, mut others, pkind) = place.expand_one_level(guide, rp); + assert!(pkind.is_deref()); + others.push(p); + for other in others { + assert_eq!(removed.remove(&other), Some(kind)); + } + assert!(removed.is_empty(), "{self:?}, {removed:?}"); + + if pkind.is_shared_ref() && !place.projects_shared_ref(rp) { + assert_eq!(kind, CapabilityKind::Read); + assert_eq!(to_kind, CapabilityKind::Exclusive); + } else { + assert_eq!(to_kind, kind); + } + let old = curr_state.insert(place, to_kind); + assert_eq!(old, None); } } } diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs index b5a77a5c693..ad9e2aac964 100644 --- a/mir-state-analysis/src/free_pcs/check/consistency.rs +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -4,7 +4,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use crate::{utils::PlaceRepacker, CapabilityLocal, CapabilityProjections, Place, Summary}; +use crate::{ + free_pcs::{CapabilityKind, CapabilityLocal, CapabilityProjections, Summary}, + utils::{Place, PlaceRepacker}, +}; pub trait CapabilityConistency<'tcx> { fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>); @@ -35,6 +38,13 @@ impl<'tcx> CapabilityConistency<'tcx> for CapabilityProjections<'tcx> { for p2 in keys[i + 1..].iter() { assert!(!p1.related_to(*p2), "{p1:?} {p2:?}",); } + // Cannot pack or unpack through uninitialized pointers. + if p1.projection_contains_deref() { + assert!( + matches!(self[p1], CapabilityKind::Exclusive | CapabilityKind::Read), + "{self:?}" + ); + } } // Can always pack up to the root let root: Place = self.get_local().into(); diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 77230e0a37c..8d15e967ae7 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -15,7 +15,10 @@ use prusti_rustc_interface::{ }, }; -use crate::{utils::PlaceRepacker, CapabilityKind, CapabilityLocal, Fpcs}; +use crate::{ + free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, + utils::PlaceRepacker, +}; pub(crate) struct FreePlaceCapabilitySummary<'a, 'tcx>(pub(crate) PlaceRepacker<'a, 'tcx>); impl<'a, 'tcx> FreePlaceCapabilitySummary<'a, 'tcx> { @@ -34,20 +37,16 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + state.bottom = false; let always_live = self.0.always_live_locals(); let return_local = RETURN_PLACE; let last_arg = Local::new(body.arg_count); for (local, cap) in state.summary.iter_enumerated_mut() { - if local == return_local { - let old = cap - .get_allocated_mut() - .insert(local.into(), CapabilityKind::Write); - assert!(old.is_some()); + assert!(cap.is_unallocated()); + let new_cap = if local == return_local { + CapabilityLocal::new(local, CapabilityKind::Write) } else if local <= last_arg { - let old = cap - .get_allocated_mut() - .insert(local.into(), CapabilityKind::Exclusive); - assert!(old.is_some()); + CapabilityLocal::new(local, CapabilityKind::Exclusive) } else if always_live.contains(local) { // TODO: figure out if `always_live` should start as `Uninit` or `Exclusive` let al_cap = if true { @@ -55,11 +54,11 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } else { CapabilityKind::Exclusive }; - let old = cap.get_allocated_mut().insert(local.into(), al_cap); - assert!(old.is_some()); + CapabilityLocal::new(local, al_cap) } else { - *cap = CapabilityLocal::Unallocated; - } + CapabilityLocal::Unallocated + }; + *cap = new_cap; } } } diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index f21c6627080..10d032336c8 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -12,21 +12,25 @@ use prusti_rustc_interface::{ }; use crate::{ - engine::FreePlaceCapabilitySummary, utils::PlaceRepacker, CapabilityKind, CapabilityLocal, - CapabilityProjections, RepackOp, + free_pcs::{ + engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, + }, + utils::PlaceRepacker, }; #[derive(Clone)] pub struct Fpcs<'a, 'tcx> { pub(crate) repacker: PlaceRepacker<'a, 'tcx>, + pub(crate) bottom: bool, pub summary: CapabilitySummary<'tcx>, pub repackings: Vec>, } impl<'a, 'tcx> Fpcs<'a, 'tcx> { pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>) -> Self { - let summary = CapabilitySummary::bottom_value(repacker.local_count()); + let summary = CapabilitySummary::default(repacker.local_count()); Self { repacker, + bottom: true, summary, repackings: Vec::new(), } @@ -35,7 +39,9 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { impl PartialEq for Fpcs<'_, '_> { fn eq(&self, other: &Self) -> bool { - self.summary == other.summary + self.bottom == other.bottom + && self.summary == other.summary + && self.repackings == other.repackings } } impl Eq for Fpcs<'_, '_> {} @@ -129,23 +135,14 @@ impl Debug for Summary { } } -// impl Summary { -// pub fn default(local_count: usize) -> Self -// where -// T: Default + Clone, -// { -// Self(IndexVec::from_elem_n(T::default(), local_count)) -// } -// } +impl Summary { + pub fn default(local_count: usize) -> Self + where + T: Default + Clone, + { + Self(IndexVec::from_elem_n(T::default(), local_count)) + } +} /// The free pcs of all locals pub type CapabilitySummary<'tcx> = Summary>; - -impl<'tcx> CapabilitySummary<'tcx> { - pub fn bottom_value(local_count: usize) -> Self { - Self(IndexVec::from_fn_n( - |local: Local| CapabilityLocal::new(local, CapabilityKind::Exclusive), - local_count, - )) - } -} diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index dff9066d09c..6e96e8f36de 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -7,13 +7,21 @@ use prusti_rustc_interface::dataflow::JoinSemiLattice; use crate::{ - utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilityProjections, - CapabilitySummary, Fpcs, PlaceOrdering, RepackOp, + free_pcs::{ + CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, + }, + utils::{PlaceOrdering, PlaceRepacker}, }; impl JoinSemiLattice for Fpcs<'_, '_> { fn join(&mut self, other: &Self) -> bool { - self.summary.join(&other.summary, self.repacker) + assert!(!other.bottom); + if self.bottom { + self.clone_from(other); + true + } else { + self.summary.join(&other.summary, self.repacker) + } } } @@ -32,8 +40,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilitySummary<'tcx> { } fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { let mut repacks = Vec::new(); - for (l, to) in self.iter_enumerated() { - let local_repacks = to.bridge(&other[l], repacker); + for (l, from) in self.iter_enumerated() { + let local_repacks = from.bridge(&other[l], repacker); repacks.extend(local_repacks); } repacks @@ -59,8 +67,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityLocal<'tcx> { fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { match (self, other) { (CapabilityLocal::Unallocated, CapabilityLocal::Unallocated) => Vec::new(), - (CapabilityLocal::Allocated(to_places), CapabilityLocal::Allocated(from_places)) => { - to_places.bridge(from_places, repacker) + (CapabilityLocal::Allocated(from_places), CapabilityLocal::Allocated(to_places)) => { + from_places.bridge(to_places, repacker) } (CapabilityLocal::Allocated(cps), CapabilityLocal::Unallocated) => { // TODO: remove need for clone @@ -74,15 +82,10 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityLocal<'tcx> { } } if !cps.contains_key(&local.into()) { - let packs = cps.pack_ops( - cps.keys().copied().collect(), - local.into(), - CapabilityKind::Write, - repacker, - ); + let packs = cps.collapse(cps.keys().copied().collect(), local.into(), repacker); repacks.extend(packs); }; - repacks.push(RepackOp::DeallocForCleanup(local)); + repacks.push(RepackOp::StorageDead(local)); repacks } (CapabilityLocal::Unallocated, CapabilityLocal::Allocated(..)) => unreachable!(), @@ -95,53 +98,67 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { let mut changed = false; for (&place, &kind) in &**other { + let want = kind.read_as_exclusive(); let related = self.find_all_related(place, None); - match related.relation { + let final_place = match related.relation { PlaceOrdering::Prefix => { changed = true; - let from = related.from[0].0; - let joinable_place = from.joinable_to(place, repacker); + let from = related.get_only_from(); + let not_through_ref = self[&from] != CapabilityKind::Exclusive; + let joinable_place = from.joinable_to(place, not_through_ref, repacker); if joinable_place != from { - self.unpack(from, joinable_place, repacker); - } - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - self.insert(joinable_place, new_min); - } - } - PlaceOrdering::Equal => { - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - changed = true; - self.insert(place, new_min); + self.expand(from, joinable_place, repacker); } + Some(joinable_place) } + PlaceOrdering::Equal => Some(place), PlaceOrdering::Suffix => { // Downgrade the permission if needed for &(p, k) in &related.from { - let new_min = kind.minimum(k).unwrap(); - if new_min != k { + if !self.contains_key(&p) { + continue; + } + let k = k.read_as_exclusive(); + let p = if want != CapabilityKind::Exclusive { + // TODO: we may want to allow going through Box derefs here? + if let Some(to) = p.projects_ty( + |typ| typ.ty.is_ref() || typ.ty.is_unsafe_ptr() || typ.ty.is_box(), + repacker, + ) { + changed = true; + let related = self.find_all_related(to, None); + assert_eq!(related.relation, PlaceOrdering::Suffix); + self.collapse(related.get_from(), related.to, repacker); + to + } else { + p + } + } else { + p + }; + if k > want { changed = true; - self.insert(p, new_min); + self.insert(p, want); + } else { + assert_eq!(k, want); } } + None } PlaceOrdering::Both => { changed = true; - // Downgrade the permission if needed - let min = kind.minimum(related.minimum).unwrap(); - for &(p, k) in &related.from { - let new_min = min.minimum(k).unwrap(); - if new_min != k { - self.insert(p, new_min); - } - } - let cp = related.from[0].0.common_prefix(place, repacker); - self.pack(related.get_from(), cp, min, repacker); + let cp = related.common_prefix(place, repacker); + self.collapse(related.get_from(), cp, repacker); + Some(cp) + } + }; + if let Some(place) = final_place { + // Downgrade the permission if needed + let curr = self[&place].read_as_exclusive(); + if curr > want { + self.insert(place, want); } } } @@ -149,48 +166,33 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { } fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { // TODO: remove need for clone - let mut cps = self.clone(); + let mut from = self.clone(); let mut repacks = Vec::new(); for (&place, &kind) in &**other { - let related = cps.find_all_related(place, None); + let related = from.find_all_related(place, None); match related.relation { PlaceOrdering::Prefix => { - let from = related.from[0].0; + let from_place = related.get_only_from(); // TODO: remove need for clone - let unpacks = cps.unpack_ops(from, place, related.minimum, repacker); + let unpacks = from.expand(from_place, place, repacker); repacks.extend(unpacks); - - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - cps.insert(place, new_min); - repacks.push(RepackOp::Weaken(place, related.minimum, new_min)); - } - } - PlaceOrdering::Equal => { - // Downgrade the permission if needed - let new_min = kind.minimum(related.minimum).unwrap(); - if new_min != related.minimum { - cps.insert(place, new_min); - repacks.push(RepackOp::Weaken(place, related.minimum, new_min)); - } } + PlaceOrdering::Equal => (), PlaceOrdering::Suffix => { - // Downgrade the permission if needed - for &(p, k) in &related.from { - let new_min = kind.minimum(k).unwrap(); - if new_min != k { - cps.insert(p, new_min); - repacks.push(RepackOp::Weaken(p, k, new_min)); - } - } - let packs = - cps.pack_ops(related.get_from(), related.to, related.minimum, repacker); + let packs = from.collapse(related.get_from(), related.to, repacker); repacks.extend(packs); } PlaceOrdering::Both => unreachable!(), } + // Downgrade the permission if needed + let want = kind.read_as_exclusive(); + let curr = from[&place].read_as_exclusive(); + if curr != want { + assert!(curr > want); + from.insert(place, want); + repacks.push(RepackOp::Weaken(place, curr, want)); + } } repacks } diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index 732be1d3909..7f472503fec 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -12,7 +12,10 @@ use prusti_rustc_interface::{ middle::mir::Local, }; -use crate::{utils::PlaceRepacker, CapabilityKind, Place, PlaceOrdering, RelatedSet}; +use crate::{ + free_pcs::{CapabilityKind, RelatedSet, RepackOp}, + utils::{Place, PlaceOrdering, PlaceRepacker}, +}; #[derive(Clone, PartialEq, Eq)] /// The permissions of a local, each key in the hashmap is a "root" projection of the local @@ -31,6 +34,12 @@ impl Debug for CapabilityLocal<'_> { } } +impl Default for CapabilityLocal<'_> { + fn default() -> Self { + Self::Unallocated + } +} + impl<'tcx> CapabilityLocal<'tcx> { pub fn get_allocated_mut(&mut self) -> &mut CapabilityProjections<'tcx> { let Self::Allocated(cps) = self else { panic!("Expected allocated local") }; @@ -82,15 +91,16 @@ impl<'tcx> CapabilityProjections<'tcx> { to: Place<'tcx>, mut expected: Option, ) -> RelatedSet<'tcx> { - let mut minimum = None::; + // let mut minimum = None::; let mut related = Vec::new(); for (&from, &cap) in &**self { if let Some(ord) = from.partial_cmp(to) { - minimum = if let Some(min) = minimum { - Some(min.minimum(cap).unwrap()) - } else { - Some(cap) - }; + // let cap_no_read = cap.read_as_exclusive(); + // minimum = if let Some(min) = minimum { + // Some(min.minimum(cap_no_read).unwrap()) + // } else { + // Some(cap_no_read) + // }; if let Some(expected) = expected { assert_eq!(ord, expected); } else { @@ -110,7 +120,7 @@ impl<'tcx> CapabilityProjections<'tcx> { RelatedSet { from: related, to, - minimum: minimum.unwrap(), + // minimum: minimum.unwrap(), relation, } } @@ -121,18 +131,35 @@ impl<'tcx> CapabilityProjections<'tcx> { skip(repacker), ret )] - pub(crate) fn unpack( + pub(crate) fn expand( &mut self, from: Place<'tcx>, to: Place<'tcx>, repacker: PlaceRepacker<'_, 'tcx>, - ) -> Vec<(Place<'tcx>, Place<'tcx>)> { + ) -> Vec> { debug_assert!(!self.contains_key(&to)); - let (expanded, others) = from.expand(to, repacker); - let perm = self.remove(&from).unwrap(); + let (expanded, mut others) = from.expand(to, repacker); + let mut perm = self.remove(&from).unwrap(); + others.push(to); + let mut ops = Vec::new(); + for (from, to, kind) in expanded { + let others = others.drain_filter(|other| !to.is_prefix(*other)); + self.extend(others.map(|p| (p, perm))); + if kind.is_deref() { + let new_perm = if perm.is_shallow_exclusive() && kind.is_box() { + CapabilityKind::Write + } else { + perm + }; + ops.push(RepackOp::Deref(from, perm, to, new_perm)); + perm = new_perm; + } else { + ops.push(RepackOp::Expand(from, to, perm)); + } + } self.extend(others.into_iter().map(|p| (p, perm))); - self.insert(to, perm); - expanded + // assert!(self.contains_key(&to), "{self:?}\n{to:?}"); + ops } // TODO: this could be implemented more efficiently, by assuming that a valid @@ -143,21 +170,68 @@ impl<'tcx> CapabilityProjections<'tcx> { skip(repacker), ret )] - pub(crate) fn pack( + pub(crate) fn collapse( &mut self, mut from: FxHashSet>, to: Place<'tcx>, - perm: CapabilityKind, repacker: PlaceRepacker<'_, 'tcx>, - ) -> Vec<(Place<'tcx>, Place<'tcx>)> { + ) -> Vec> { debug_assert!(!self.contains_key(&to), "{to:?} already exists in {self:?}"); - for place in &from { - let p = self.remove(place).unwrap(); - assert_eq!(p, perm, "Cannot pack {place:?} with {p:?} into {to:?}"); - } + let mut old_caps: FxHashMap<_, _> = from + .iter() + .map(|&p| (p, self.remove(&p).unwrap())) + .collect(); let collapsed = to.collapse(&mut from, repacker); assert!(from.is_empty()); - self.insert(to, perm); - collapsed + let mut exclusive_at = Vec::new(); + if !to.projects_shared_ref(repacker) { + for (to, _, kind) in &collapsed { + if kind.is_shared_ref() { + let mut is_prefixed = false; + exclusive_at.drain_filter(|old| { + let cmp = to.either_prefix(*old); + if matches!(cmp, Some(false)) { + is_prefixed = true; + } + cmp.unwrap_or_default() + }); + if !is_prefixed { + exclusive_at.push(*to); + } + } + } + } + let mut ops = Vec::new(); + for (to, from, kind) in collapsed { + let removed_perms: Vec<_> = + old_caps.drain_filter(|old, _| to.is_prefix(*old)).collect(); + let perm = removed_perms + .iter() + .fold(CapabilityKind::Exclusive, |acc, (_, p)| { + acc.minimum(*p).unwrap() + }); + for (from, from_perm) in removed_perms { + if perm != from_perm { + assert!(from_perm > perm); + ops.push(RepackOp::Weaken(from, from_perm, perm)); + } + } + let op = if kind.is_deref() { + let new_perm = if kind.is_shared_ref() && exclusive_at.contains(&to) { + assert_eq!(perm, CapabilityKind::Read); + CapabilityKind::Exclusive + } else { + perm + }; + old_caps.insert(to, new_perm); + RepackOp::Upref(to, new_perm, from, perm) + } else { + old_caps.insert(to, perm); + RepackOp::Collapse(to, from, perm) + }; + ops.push(op); + } + self.insert(to, old_caps[&to]); + ops } } diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index edd9ab1a657..8ab6e87592b 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -12,13 +12,13 @@ use std::{ use prusti_rustc_interface::data_structures::fx::FxHashSet; -use crate::{Place, PlaceOrdering}; +use crate::utils::{Place, PlaceOrdering, PlaceRepacker}; #[derive(Debug)] pub(crate) struct RelatedSet<'tcx> { pub(crate) from: Vec<(Place<'tcx>, CapabilityKind)>, pub(crate) to: Place<'tcx>, - pub(crate) minimum: CapabilityKind, + // pub(crate) minimum: CapabilityKind, pub(crate) relation: PlaceOrdering, } impl<'tcx> RelatedSet<'tcx> { @@ -29,6 +29,13 @@ impl<'tcx> RelatedSet<'tcx> { )); self.from.iter().map(|(p, _)| *p).collect() } + pub fn get_only_from(&self) -> Place<'tcx> { + assert_eq!(self.from.len(), 1); + self.from[0].0 + } + pub fn common_prefix(&self, to: Place<'tcx>, repacker: PlaceRepacker<'_, 'tcx>) -> Place<'tcx> { + self.from[0].0.common_prefix(to, repacker) + } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -36,6 +43,9 @@ pub enum CapabilityKind { Read, Write, Exclusive, + /// [`CapabilityKind::Exclusive`] for everything not through a dereference, + /// [`CapabilityKind::Write`] for everything through a dereference. + ShallowExclusive, } impl Debug for CapabilityKind { fn fmt(&self, f: &mut Formatter<'_>) -> Result { @@ -43,6 +53,7 @@ impl Debug for CapabilityKind { CapabilityKind::Read => write!(f, "R"), CapabilityKind::Write => write!(f, "W"), CapabilityKind::Exclusive => write!(f, "E"), + CapabilityKind::ShallowExclusive => write!(f, "e"), } } } @@ -53,10 +64,14 @@ impl PartialOrd for CapabilityKind { return Some(Ordering::Equal); } match (self, other) { + // R < E, W < E, W < e (CapabilityKind::Read, CapabilityKind::Exclusive) - | (CapabilityKind::Write, CapabilityKind::Exclusive) => Some(Ordering::Less), + | (CapabilityKind::Write, CapabilityKind::Exclusive) + | (CapabilityKind::Write, CapabilityKind::ShallowExclusive) => Some(Ordering::Less), + // E > R, E > W, e > W (CapabilityKind::Exclusive, CapabilityKind::Read) - | (CapabilityKind::Exclusive, CapabilityKind::Write) => Some(Ordering::Greater), + | (CapabilityKind::Exclusive, CapabilityKind::Write) + | (CapabilityKind::ShallowExclusive, CapabilityKind::Write) => Some(Ordering::Greater), _ => None, } } @@ -83,10 +98,23 @@ impl CapabilityKind { pub fn is_write(self) -> bool { matches!(self, CapabilityKind::Write) } + pub fn is_shallow_exclusive(self) -> bool { + matches!(self, CapabilityKind::ShallowExclusive) + } pub fn minimum(self, other: Self) -> Option { match self.partial_cmp(&other)? { Ordering::Greater => Some(other), _ => Some(self), } } + pub fn read_as_exclusive(self) -> Self { + match self { + CapabilityKind::Read => CapabilityKind::Exclusive, + _ => self, + } + } + // pub fn minimum_with_read_as_exclusive(self, other: Self) -> Option { + // let (adj_self, adj_other) = (self.read_as_exclusive(), other.read_as_exclusive()); + // adj_self.minimum(adj_other) + // } } diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index f9e34fd511f..a1336d14fac 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -12,7 +12,7 @@ use prusti_rustc_interface::{ }, }; -use crate::Fpcs; +use crate::free_pcs::{CapabilityKind, Fpcs}; impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { @@ -33,9 +33,14 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { self.super_statement(statement, location); use StatementKind::*; match &statement.kind { - &Assign(box (place, _)) => { - self.requires_write(place); - self.ensures_exclusive(place); + Assign(box (place, rvalue)) => { + self.requires_write(*place); + let ensures = rvalue.capability(); + match ensures { + CapabilityKind::Exclusive => self.ensures_exclusive(*place), + CapabilityKind::ShallowExclusive => self.ensures_shallow_exclusive(*place), + _ => unreachable!(), + } } &FakeRead(box (_, place)) => self.requires_read(place), &SetDiscriminant { box place, .. } => self.requires_exclusive(place), @@ -141,8 +146,36 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { }, &Len(place) => self.requires_read(place), &Discriminant(place) => self.requires_read(place), - ShallowInitBox(_, _) => todo!(), + ShallowInitBox(op, ty) => todo!("{op:?}, {ty:?}"), &CopyForDeref(place) => self.requires_read(place), } } } + +trait ProducesCapability { + fn capability(&self) -> CapabilityKind; +} + +impl ProducesCapability for Rvalue<'_> { + fn capability(&self) -> CapabilityKind { + use Rvalue::*; + match self { + Use(_) + | Repeat(_, _) + | Ref(_, _, _) + | ThreadLocalRef(_) + | AddressOf(_, _) + | Len(_) + | Cast(_, _, _) + | BinaryOp(_, _) + | CheckedBinaryOp(_, _) + | NullaryOp(_, _) + | UnaryOp(_, _) + | Discriminant(_) + | Aggregate(_, _) + | CopyForDeref(_) => CapabilityKind::Exclusive, + // TODO: + ShallowInitBox(_, _) => CapabilityKind::Read, + } + } +} diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 6c7f38d7fa3..5fb85bc8cc4 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -4,11 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use prusti_rustc_interface::{data_structures::fx::FxHashSet, middle::mir::Local}; +use prusti_rustc_interface::middle::mir::Local; use crate::{ - utils::PlaceRepacker, CapabilityKind, CapabilityLocal, CapabilityProjections, Fpcs, Place, - PlaceOrdering, RelatedSet, RepackOp, + free_pcs::{CapabilityKind, CapabilityLocal, CapabilityProjections, Fpcs, RepackOp}, + utils::{Place, PlaceOrdering, PlaceRepacker}, }; impl<'tcx> Fpcs<'_, 'tcx> { @@ -21,6 +21,8 @@ impl<'tcx> Fpcs<'_, 'tcx> { pub(crate) fn requires_unalloc_or_uninit(&mut self, local: Local) { if !self.summary[local].is_unallocated() { self.requires_write(local) + } else { + self.repackings.push(RepackOp::IgnoreStorageDead(local)) } } pub(crate) fn requires_read(&mut self, place: impl Into>) { @@ -59,72 +61,34 @@ impl<'tcx> Fpcs<'_, 'tcx> { pub(crate) fn ensures_exclusive(&mut self, place: impl Into>) { self.ensures_alloc(place, CapabilityKind::Exclusive) } + pub(crate) fn ensures_shallow_exclusive(&mut self, place: impl Into>) { + self.ensures_alloc(place, CapabilityKind::ShallowExclusive) + } pub(crate) fn ensures_write(&mut self, place: impl Into>) { self.ensures_alloc(place, CapabilityKind::Write) } } impl<'tcx> CapabilityProjections<'tcx> { - fn repack( + pub(super) fn repack( &mut self, to: Place<'tcx>, repacker: PlaceRepacker<'_, 'tcx>, ) -> Vec> { let related = self.find_all_related(to, None); match related.relation { - PlaceOrdering::Prefix => self - .unpack_ops(related.from[0].0, related.to, related.minimum, repacker) - .collect(), + PlaceOrdering::Prefix => self.expand(related.get_only_from(), related.to, repacker), PlaceOrdering::Equal => Vec::new(), - PlaceOrdering::Suffix => self - .pack_ops(related.get_from(), related.to, related.minimum, repacker) - .collect(), + PlaceOrdering::Suffix => self.collapse(related.get_from(), related.to, repacker), PlaceOrdering::Both => { - let cp = related.from[0].0.common_prefix(to, repacker); - // Pack - let mut ops = self.weaken(&related); - let packs = self.pack_ops(related.get_from(), cp, related.minimum, repacker); - ops.extend(packs); - // Unpack - let unpacks = self.unpack_ops(cp, related.to, related.minimum, repacker); + let cp = related.common_prefix(to, repacker); + // Collapse + let mut ops = self.collapse(related.get_from(), cp, repacker); + // Expand + let unpacks = self.expand(cp, related.to, repacker); ops.extend(unpacks); ops } } } - pub(crate) fn unpack_ops( - &mut self, - from: Place<'tcx>, - to: Place<'tcx>, - kind: CapabilityKind, - repacker: PlaceRepacker<'_, 'tcx>, - ) -> impl Iterator> { - self.unpack(from, to, repacker) - .into_iter() - .map(move |(f, t)| RepackOp::Unpack(f, t, kind)) - } - pub(crate) fn pack_ops( - &mut self, - from: FxHashSet>, - to: Place<'tcx>, - perm: CapabilityKind, - repacker: PlaceRepacker<'_, 'tcx>, - ) -> impl Iterator> { - self.pack(from, to, perm, repacker) - .into_iter() - .map(move |(f, t)| RepackOp::Pack(f, t, perm)) - } - - pub(crate) fn weaken(&mut self, related: &RelatedSet<'tcx>) -> Vec> { - let mut ops = Vec::new(); - let more_than_min = related.from.iter().filter(|(_, k)| *k != related.minimum); - // TODO: This will replace `PermissionKind::Exclusive` with `PermissionKind::Shared` - // the exclusive permission will never be able to be recovered anymore! - for &(p, k) in more_than_min { - let old = self.insert(p, related.minimum); - assert_eq!(old, Some(k)); - ops.push(RepackOp::Weaken(p, k, related.minimum)); - } - ops - } } diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index d1589a8d1ae..5dee534629e 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -10,8 +10,11 @@ use prusti_rustc_interface::{ }; use crate::{ - engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, - utils::PlaceRepacker, CapabilitySummary, RepackOp, + free_pcs::{ + engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, + CapabilitySummary, RepackOp, + }, + utils::PlaceRepacker, }; type Cursor<'mir, 'tcx> = ResultsCursor<'mir, 'tcx, FreePlaceCapabilitySummary<'mir, 'tcx>>; diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs index 53e92b0fb7f..da745db2842 100644 --- a/mir-state-analysis/src/free_pcs/results/repacks.rs +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -8,27 +8,78 @@ use std::fmt::{Display, Formatter, Result}; use prusti_rustc_interface::middle::mir::Local; -use crate::{CapabilityKind, Place}; +use crate::{free_pcs::CapabilityKind, utils::Place}; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RepackOp<'tcx> { + /// Rust will sometimes join two BasicBlocks where a local is live in one and dead in the other. + /// Our analysis will join these two into a state where the local is dead, and this Op marks the + /// edge from where it was live. + /// + /// This is not an issue in the MIR since it generally has a + /// [`mir::StatementKind::StorageDead`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.StorageDead) + /// right after the merge point, which is fine in Rust semantics, since + /// [`mir::StatementKind::StorageDead`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.StorageDead) + /// is a no-op if the local is already (conditionally) dead. + /// + /// This Op only appears for edges between basic blocks. It is often emitted for edges to panic + /// handling blocks, but can also appear in regular code for example in the MIR of + /// [this function](https://github.com/dtolnay/syn/blob/3da56a712abf7933b91954dbfb5708b452f88504/src/attr.rs#L623-L628). + StorageDead(Local), + /// This Op only appears within a BasicBlock and is attached to a + /// [`mir::StatementKind::StorageDead`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.StorageDead) + /// statement. We emit it for any such statement where the local may already be dead. We + /// guarantee to have inserted a [`RepackOp::StorageDead`] before this Op so that one can + /// safely ignore the statement this is attached to. + IgnoreStorageDead(Local), + /// Instructs that the current capability to the place (first [`CapabilityKind`]) should + /// be weakened to the second given capability. We guarantee that `_.1 > _.2`. + /// + /// This Op is used prior to a [`RepackOp::Collapse`] to ensure that all packed up places have + /// the same capability. It can also appear at basic block join points, where one branch has + /// a weaker capability than the other. Weaken(Place<'tcx>, CapabilityKind, CapabilityKind), - // TODO: figure out when and why this happens - // DeallocUnknown(Local), - DeallocForCleanup(Local), - // First place is packed up, second is guide place to pack up from - Pack(Place<'tcx>, Place<'tcx>, CapabilityKind), - // First place is packed up, second is guide place to unpack to - Unpack(Place<'tcx>, Place<'tcx>, CapabilityKind), + /// Instructs that one should unpack the first place with the capability. + /// We guarantee that the current state holds exactly the given capability for the given place. + /// The second place is the guide, denoting e.g. the enum variant to unpack to. One can use + /// [`Place::expand_one_level(_.0, _.1, ..)`](Place::expand_one_level) to get the set of all + /// places which will be obtained by unpacking. + Expand(Place<'tcx>, Place<'tcx>, CapabilityKind), + /// Instructs that one should pack up to the given (first) place with the given capability. + /// The second place is the guide, denoting e.g. the enum variant to pack from. One can use + /// [`Place::expand_one_level(_.0, _.1, ..)`](Place::expand_one_level) to get the set of all + /// places which should be packed up. We guarantee that the current state holds exactly the + /// given capability for all places in this set. + Collapse(Place<'tcx>, Place<'tcx>, CapabilityKind), + /// TODO + Deref(Place<'tcx>, CapabilityKind, Place<'tcx>, CapabilityKind), + Upref(Place<'tcx>, CapabilityKind, Place<'tcx>, CapabilityKind), } impl Display for RepackOp<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { - RepackOp::Weaken(place, from, to) => write!(f, "Weaken({place:?}, {:?})", (*from - *to).unwrap()), - RepackOp::DeallocForCleanup(_) => todo!(), - RepackOp::Pack(to, _, kind) => write!(f, "Pack({to:?}, {kind:?})"), - RepackOp::Unpack(from, _, kind) => write!(f, "Unpack({from:?}, {kind:?})"), + RepackOp::StorageDead(place) => write!(f, "StorageDead({place:?})"), + RepackOp::IgnoreStorageDead(_) => write!(f, "IgnoreSD"), + RepackOp::Weaken(place, from, to) => { + write!(f, "Weaken({place:?}, {:?})", (*from - *to).unwrap()) + } + RepackOp::Collapse(to, _, kind) => write!(f, "CollapseTo({to:?}, {kind:?})"), + RepackOp::Expand(from, _, kind) => write!(f, "Expand({from:?}, {kind:?})"), + RepackOp::Upref(to, to_kind, _, from_kind) => { + if to_kind == from_kind { + write!(f, "UprefTo({to:?}, {to_kind:?})") + } else { + write!(f, "UprefTo({to:?}, {from_kind:?} -> {to_kind:?})") + } + } + RepackOp::Deref(from, from_kind, _, to_kind) => { + if to_kind == from_kind { + write!(f, "Deref({from:?}, {to_kind:?})") + } else { + write!(f, "Deref({from:?}, {from_kind:?} -> {to_kind:?})") + } + } } } } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 62ff756207e..061bb6c0ced 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -5,13 +5,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![feature(rustc_private)] -#![feature(box_patterns)] +#![feature(box_patterns, hash_drain_filter, drain_filter)] -mod free_pcs; -mod utils; - -pub use free_pcs::*; -pub use utils::place::*; +pub mod free_pcs; +pub mod utils; use prusti_rustc_interface::{ dataflow::Analysis, @@ -21,13 +18,13 @@ use prusti_rustc_interface::{ pub fn run_free_pcs<'mir, 'tcx>( mir: &'mir Body<'tcx>, tcx: TyCtxt<'tcx>, -) -> FreePcsAnalysis<'mir, 'tcx> { +) -> free_pcs::FreePcsAnalysis<'mir, 'tcx> { let fpcs = free_pcs::engine::FreePlaceCapabilitySummary::new(tcx, mir); let analysis = fpcs .into_engine(tcx, mir) .pass_name("free_pcs") .iterate_to_fixpoint(); - FreePcsAnalysis(analysis.into_results_cursor(mir)) + free_pcs::FreePcsAnalysis(analysis.into_results_cursor(mir)) } pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { diff --git a/mir-state-analysis/src/utils/mod.rs b/mir-state-analysis/src/utils/mod.rs index e0bd10f8b91..7eb552309af 100644 --- a/mir-state-analysis/src/utils/mod.rs +++ b/mir-state-analysis/src/utils/mod.rs @@ -7,4 +7,5 @@ pub mod place; pub(crate) mod repacker; -pub(crate) use repacker::*; +pub use place::*; +pub use repacker::*; diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 139fa466c6c..346c1469224 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -155,7 +155,7 @@ impl<'tcx> Place<'tcx> { } } - /// Check if the place `potential_prefix` is a prefix of `place`. For example: + /// Check if the place `self` is a prefix of `place`. For example: /// /// + `is_prefix(x.f, x.f) == true` /// + `is_prefix(x.f, x.f.g) == true` @@ -166,6 +166,36 @@ impl<'tcx> Place<'tcx> { .unwrap_or(false) } + /// Check if the place `self` is an exact prefix of `place`. For example: + /// + /// + `is_prefix(x.f, x.f) == false` + /// + `is_prefix(x.f, x.f.g) == true` + /// + `is_prefix(x.f, x.f.g.h) == false` + pub(crate) fn is_prefix_exact(self, place: Self) -> bool { + self.0.projection.len() + 1 == place.0.projection.len() + && Self::partial_cmp(self, place) + .map(|o| o == PlaceOrdering::Prefix) + .unwrap_or(false) + } + + /// Check if the place `self` is a prefix of `place` or vice versa. For example: + /// + /// + `is_prefix(x.f, x.f) == None` + /// + `is_prefix(x.f, x.f.g) == Some(true)` + /// + `is_prefix(x.f.g, x.f) == Some(false)` + /// + `is_prefix(x.g, x.f) == None` + pub(crate) fn either_prefix(self, place: Self) -> Option { + Self::partial_cmp(self, place).and_then(|o| { + if o == PlaceOrdering::Prefix { + Some(true) + } else if o == PlaceOrdering::Suffix { + Some(false) + } else { + None + } + }) + } + /// Returns `true` if either of the places can reach the other /// with a series of expand/collapse operations. Note that /// both operations are allowed and so e.g. @@ -173,6 +203,12 @@ impl<'tcx> Place<'tcx> { pub fn related_to(self, right: Self) -> bool { self.partial_cmp(right).is_some() } + + pub fn projection_contains_deref(self) -> bool { + self.projection + .iter() + .any(|proj| matches!(proj, ProjectionElem::Deref)) + } } impl Debug for Place<'_> { diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 0ebd17a8eab..d805a6d8023 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -9,12 +9,37 @@ use prusti_rustc_interface::{ dataflow::storage, index::bit_set::BitSet, middle::{ - mir::{Body, Field, HasLocalDecls, Local, Mutability, ProjectionElem}, + mir::{tcx::PlaceTy, Body, Field, HasLocalDecls, Local, Mutability, ProjectionElem}, ty::{TyCtxt, TyKind}, }, }; -use crate::Place; +use super::Place; + +#[derive(Debug, Clone, Copy)] +pub enum ProjectionRefKind { + Ref(Mutability), + RawPtr(Mutability), + Box, + Other, +} +impl ProjectionRefKind { + pub fn is_ref(self) -> bool { + matches!(self, ProjectionRefKind::Ref(_)) + } + pub fn is_raw_ptr(self) -> bool { + matches!(self, ProjectionRefKind::RawPtr(_)) + } + pub fn is_box(self) -> bool { + matches!(self, ProjectionRefKind::Box) + } + pub fn is_deref(self) -> bool { + self.is_ref() || self.is_raw_ptr() || self.is_box() + } + pub fn is_shared_ref(self) -> bool { + matches!(self, ProjectionRefKind::Ref(Mutability::Not)) + } +} #[derive(Copy, Clone)] // TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate @@ -56,7 +81,7 @@ impl<'tcx> Place<'tcx> { mut self, to: Self, repacker: PlaceRepacker<'_, 'tcx>, - ) -> (Vec<(Self, Self)>, Vec) { + ) -> (Vec<(Self, Self, ProjectionRefKind)>, Vec) { assert!( self.is_prefix(to), "The minuend ({self:?}) must be the prefix of the subtrahend ({to:?})." @@ -64,10 +89,10 @@ impl<'tcx> Place<'tcx> { let mut place_set = Vec::new(); let mut expanded = Vec::new(); while self.projection.len() < to.projection.len() { - expanded.push((self, to)); - let (new_minuend, places) = self.expand_one_level(to, repacker); - self = new_minuend; + let (new_minuend, places, kind) = self.expand_one_level(to, repacker); + expanded.push((self, new_minuend, kind)); place_set.extend(places); + self = new_minuend; } (expanded, place_set) } @@ -80,7 +105,7 @@ impl<'tcx> Place<'tcx> { self, from: &mut FxHashSet, repacker: PlaceRepacker<'_, 'tcx>, - ) -> Vec<(Self, Self)> { + ) -> Vec<(Self, Self, ProjectionRefKind)> { let mut collapsed = Vec::new(); let mut guide_places = vec![self]; while let Some(guide_place) = guide_places.pop() { @@ -112,11 +137,11 @@ impl<'tcx> Place<'tcx> { /// Returns the new `self` and a vector containing other places that /// could have resulted from the expansion. #[tracing::instrument(level = "trace", skip(repacker), ret)] - pub(crate) fn expand_one_level( + pub fn expand_one_level( self, guide_place: Self, repacker: PlaceRepacker<'_, 'tcx>, - ) -> (Self, Vec) { + ) -> (Self, Vec, ProjectionRefKind) { let index = self.projection.len(); let new_projection = repacker.tcx.mk_place_elems_from_iter( self.projection @@ -124,43 +149,56 @@ impl<'tcx> Place<'tcx> { .chain([guide_place.projection[index]]), ); let new_current_place = Place::new(self.local, new_projection); - let other_places = match guide_place.projection[index] { + let (other_places, kind) = match guide_place.projection[index] { ProjectionElem::Field(projected_field, _field_ty) => { - self.expand_field(Some(projected_field.index()), repacker) + let other_places = self.expand_field(Some(projected_field.index()), repacker); + (other_places, ProjectionRefKind::Other) } ProjectionElem::ConstantIndex { offset, min_length, from_end, - } => (0..min_length) - .filter(|&i| { - if from_end { - i != min_length - offset - } else { - i != offset - } - }) - .map(|i| { - repacker - .tcx - .mk_place_elem( - *self, - ProjectionElem::ConstantIndex { - offset: i, - min_length, - from_end, - }, - ) - .into() - }) - .collect(), - ProjectionElem::Deref - | ProjectionElem::Index(..) + } => { + let other_places = (0..min_length) + .filter(|&i| { + if from_end { + i != min_length - offset + } else { + i != offset + } + }) + .map(|i| { + repacker + .tcx + .mk_place_elem( + *self, + ProjectionElem::ConstantIndex { + offset: i, + min_length, + from_end, + }, + ) + .into() + }) + .collect(); + (other_places, ProjectionRefKind::Other) + } + ProjectionElem::Deref => { + let typ = self.ty(repacker.mir, repacker.tcx); + let kind = match typ.ty.kind() { + TyKind::Ref(_, _, mutbl) => ProjectionRefKind::Ref(*mutbl), + TyKind::RawPtr(ptr) => ProjectionRefKind::RawPtr(ptr.mutbl), + _ if typ.ty.is_box() => ProjectionRefKind::Box, + _ => unreachable!(), + }; + (Vec::new(), kind) + } + ProjectionElem::Index(..) | ProjectionElem::Subslice { .. } | ProjectionElem::Downcast(..) - | ProjectionElem::OpaqueCast(..) => vec![], + | ProjectionElem::OpaqueCast(..) => (Vec::new(), ProjectionRefKind::Other), }; - (new_current_place, other_places) + (new_current_place, other_places, kind) } /// Expands a place `x.f.g` of type struct into a vector of places for @@ -267,7 +305,12 @@ impl<'tcx> Place<'tcx> { } #[tracing::instrument(level = "info", skip(repacker), ret)] - pub fn joinable_to(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { + pub fn joinable_to( + self, + to: Self, + not_through_ref: bool, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Self { assert!(self.is_prefix(to)); let proj = self.projection.iter(); let to_proj = to.projection[self.projection.len()..] @@ -276,10 +319,8 @@ impl<'tcx> Place<'tcx> { .take_while(|p| { matches!( p, - ProjectionElem::Deref - | ProjectionElem::Field(..) - | ProjectionElem::ConstantIndex { .. } - ) + ProjectionElem::Field(..) | ProjectionElem::ConstantIndex { .. } // TODO: we may want to allow going through Box derefs here? + ) || (!not_through_ref && matches!(p, ProjectionElem::Deref)) }); let projection = repacker.tcx.mk_place_elems_from_iter(proj.chain(to_proj)); Self::new(self.local, projection) @@ -305,4 +346,56 @@ impl<'tcx> Place<'tcx> { unreachable!("get_ref_mutability called on non-ref type: {:?}", typ.ty); } } + + // /// Calculates if we dereference through a mutable/immutable reference. For example, if we have\ + // /// `x: (i32, & &mut i32)` then we would get the following results: + // /// + // /// * `"x".through_ref_mutability("x.0")` -> `None` + // /// * `"x".through_ref_mutability("x.1")` -> `None` + // /// * `"x".through_ref_mutability("*x.1")` -> `Some(Mutability::Not)` + // /// * `"x".through_ref_mutability("**x.1")` -> `Some(Mutability::Not)` + // /// * `"*x.1".through_ref_mutability("**x.1")` -> `Some(Mutability::Mut)` + // pub fn between_ref_mutability(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Option { + // assert!(self.is_prefix(to)); + // let mut typ = self.ty(repacker.mir, repacker.tcx); + // let mut mutbl = None; + // for &elem in &to.projection[self.projection.len()..] { + // match typ.ty.kind() { + // TyKind::Ref(_, _, Mutability::Not) => return Some(Mutability::Not), + // TyKind::Ref(_, _, Mutability::Mut) => mutbl = Some(Mutability::Mut), + // _ => () + // }; + // typ = typ.projection_ty(repacker.tcx, elem); + // } + // mutbl + // } + + pub fn projects_shared_ref(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { + self.projects_ty( + |typ| { + typ.ty + .ref_mutability() + .map(|m| m.is_not()) + .unwrap_or_default() + }, + repacker, + ) + .is_some() + } + + pub fn projects_ty( + self, + predicate: impl Fn(PlaceTy<'tcx>) -> bool, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Option> { + let mut typ = PlaceTy::from_ty(repacker.mir.local_decls()[self.local].ty); + for (idx, elem) in self.projection.iter().enumerate() { + if predicate(typ) { + let projection = repacker.tcx.mk_place_elems(&self.projection[0..idx]); + return Some(Self::new(self.local, projection)); + } + typ = typ.projection_ty(repacker.tcx, elem); + } + None + } } From d176fb8444709501f4bdac51ba1930ecf0c8a65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 24 Apr 2023 12:01:21 +0200 Subject: [PATCH 09/32] Update --- .../src/free_pcs/check/checker.rs | 65 ++------ .../src/free_pcs/check/consistency.rs | 9 +- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 17 +- .../src/free_pcs/impl/join_semi_lattice.rs | 42 +++-- mir-state-analysis/src/free_pcs/impl/local.rs | 29 +--- mir-state-analysis/src/free_pcs/impl/place.rs | 37 +---- .../src/free_pcs/impl/triple.rs | 7 +- .../src/free_pcs/impl/update.rs | 11 +- .../src/free_pcs/results/cursor.rs | 152 ++++++++++-------- .../src/free_pcs/results/repacks.rs | 22 +-- mir-state-analysis/src/lib.rs | 2 +- mir-state-analysis/src/utils/place.rs | 20 +-- mir-state-analysis/src/utils/repacker.rs | 50 ++---- 13 files changed, 183 insertions(+), 280 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index ce0d27a7fec..d19cd9b8b43 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -18,11 +18,11 @@ use crate::{ use super::consistency::CapabilityConistency; -pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { - let rp = results.repacker(); +pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { + let rp = cursor.repacker(); let body = rp.body(); for (block, data) in body.basic_blocks.iter_enumerated() { - let mut cursor = results.analysis_for_bb(block); + cursor.analysis_for_bb(block); let mut fpcs = Fpcs { summary: cursor.initial_state().clone(), bottom: false, @@ -36,7 +36,7 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { block, statement_index, }; - let fpcs_after = cursor.next().unwrap(); + let fpcs_after = cursor.next(loc); assert_eq!(fpcs_after.location, loc); // Repacks for op in fpcs_after.repacks { @@ -55,7 +55,7 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { block, statement_index: data.statements.len(), }; - let fpcs_after = cursor.next().unwrap(); + let fpcs_after = cursor.next(loc); assert_eq!(fpcs_after.location, loc); // Repacks for op in fpcs_after.repacks { @@ -69,11 +69,9 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { assert!(fpcs.repackings.is_empty()); // Consistency fpcs.summary.consistency_check(rp); - assert_eq!(&fpcs.summary, fpcs_after.state); + assert_eq!(fpcs.summary, fpcs_after.state); - let Err(fpcs_end) = cursor.next() else { - panic!("Expected error at the end of the block"); - }; + let fpcs_end = cursor.terminator(); for succ in fpcs_end.succs { // Repacks @@ -85,7 +83,7 @@ pub(crate) fn check(mut results: FreePcsAnalysis<'_, '_>) { rp, ); } - assert_eq!(&from.summary, succ.state); + assert_eq!(from.summary, succ.state); } } } @@ -124,7 +122,6 @@ impl<'tcx> RepackOp<'tcx> { } RepackOp::Expand(place, guide, kind) => { assert!(place.is_prefix_exact(guide), "{self:?}"); - assert_ne!(*guide.projection.last().unwrap(), ProjectionElem::Deref); let curr_state = state[place.local].get_allocated_mut(); assert_eq!( curr_state.remove(&place), @@ -132,21 +129,18 @@ impl<'tcx> RepackOp<'tcx> { "{self:?} ({curr_state:?})" ); - let (p, others, pkind) = place.expand_one_level(guide, rp); - assert!(!pkind.is_deref()); + let (p, others, _) = place.expand_one_level(guide, rp); curr_state.insert(p, kind); curr_state.extend(others.into_iter().map(|p| (p, kind))); } RepackOp::Collapse(place, guide, kind) => { assert!(place.is_prefix_exact(guide), "{self:?}"); - assert_ne!(*guide.projection.last().unwrap(), ProjectionElem::Deref); let curr_state = state[place.local].get_allocated_mut(); let mut removed = curr_state .drain_filter(|p, _| place.related_to(*p)) .collect::>(); - let (p, mut others, pkind) = place.expand_one_level(guide, rp); - assert!(!pkind.is_deref()); + let (p, mut others, _) = place.expand_one_level(guide, rp); others.push(p); for other in others { assert_eq!(removed.remove(&other), Some(kind), "{self:?}"); @@ -155,52 +149,21 @@ impl<'tcx> RepackOp<'tcx> { let old = curr_state.insert(place, kind); assert_eq!(old, None); } - RepackOp::Deref(place, kind, guide, to_kind) => { + RepackOp::DerefShallowInit(place, guide) => { assert!(place.is_prefix_exact(guide), "{self:?}"); assert_eq!(*guide.projection.last().unwrap(), ProjectionElem::Deref); let curr_state = state[place.local].get_allocated_mut(); assert_eq!( curr_state.remove(&place), - Some(kind), + Some(CapabilityKind::ShallowExclusive), "{self:?} ({curr_state:?})" ); let (p, others, pkind) = place.expand_one_level(guide, rp); - assert!(pkind.is_deref()); - if pkind.is_box() && kind.is_shallow_exclusive() { - assert_eq!(to_kind, CapabilityKind::Write); - } else { - assert_eq!(to_kind, kind); - } - - curr_state.insert(p, to_kind); + assert!(pkind.is_box()); + curr_state.insert(p, CapabilityKind::Write); assert!(others.is_empty()); } - RepackOp::Upref(place, to_kind, guide, kind) => { - assert!(place.is_prefix_exact(guide), "{self:?}"); - assert_eq!(*guide.projection.last().unwrap(), ProjectionElem::Deref); - let curr_state = state[place.local].get_allocated_mut(); - let mut removed = curr_state - .drain_filter(|p, _| place.related_to(*p)) - .collect::>(); - - let (p, mut others, pkind) = place.expand_one_level(guide, rp); - assert!(pkind.is_deref()); - others.push(p); - for other in others { - assert_eq!(removed.remove(&other), Some(kind)); - } - assert!(removed.is_empty(), "{self:?}, {removed:?}"); - - if pkind.is_shared_ref() && !place.projects_shared_ref(rp) { - assert_eq!(kind, CapabilityKind::Read); - assert_eq!(to_kind, CapabilityKind::Exclusive); - } else { - assert_eq!(to_kind, kind); - } - let old = curr_state.insert(place, to_kind); - assert_eq!(old, None); - } } } } diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs index ad9e2aac964..77a1ba62462 100644 --- a/mir-state-analysis/src/free_pcs/check/consistency.rs +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -38,12 +38,9 @@ impl<'tcx> CapabilityConistency<'tcx> for CapabilityProjections<'tcx> { for p2 in keys[i + 1..].iter() { assert!(!p1.related_to(*p2), "{p1:?} {p2:?}",); } - // Cannot pack or unpack through uninitialized pointers. - if p1.projection_contains_deref() { - assert!( - matches!(self[p1], CapabilityKind::Exclusive | CapabilityKind::Read), - "{self:?}" - ); + // Cannot be inside of uninitialized pointers. + if !p1.can_deinit(repacker) { + assert!(matches!(self[p1], CapabilityKind::Exclusive), "{self:?}"); } } // Can always pack up to the root diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index 10d032336c8..54a405e64a0 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -58,6 +58,7 @@ impl<'a, 'tcx> DebugWithContext> for Fpcs<' _ctxt: &FreePlaceCapabilitySummary<'a, 'tcx>, f: &mut Formatter<'_>, ) -> Result { + // let rp = self.repacker; assert_eq!(self.summary.len(), old.summary.len()); for op in &self.repackings { writeln!(f, "{op}")?; @@ -79,24 +80,16 @@ impl<'a, 'tcx> DebugWithContext> for Fpcs<' let mut old_set = CapabilityProjections::empty(); for (&p, &nk) in new.iter() { match old.get(&p) { - Some(&ok) => { - if let Some(d) = nk - ok { - new_set.insert(p, d); - } - } - None => { + Some(&ok) if nk == ok => (), + _ => { new_set.insert(p, nk); } } } for (&p, &ok) in old.iter() { match new.get(&p) { - Some(&nk) => { - if let Some(d) = ok - nk { - old_set.insert(p, d); - } - } - None => { + Some(&nk) if nk == ok => (), + _ => { old_set.insert(p, ok); } } diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 6e96e8f36de..c68a5cb6186 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -98,15 +98,20 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { fn join(&mut self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { let mut changed = false; for (&place, &kind) in &**other { - let want = kind.read_as_exclusive(); let related = self.find_all_related(place, None); let final_place = match related.relation { PlaceOrdering::Prefix => { changed = true; let from = related.get_only_from(); - let not_through_ref = self[&from] != CapabilityKind::Exclusive; - let joinable_place = from.joinable_to(place, not_through_ref, repacker); + let joinable_place = if self[&from] != CapabilityKind::Exclusive { + place + .projects_ptr(repacker) + .unwrap_or_else(|| from.joinable_to(place, repacker)) + } else { + from.joinable_to(place, repacker) + }; + assert!(from.is_prefix(joinable_place)); if joinable_place != from { self.expand(from, joinable_place, repacker); } @@ -119,13 +124,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { if !self.contains_key(&p) { continue; } - let k = k.read_as_exclusive(); - let p = if want != CapabilityKind::Exclusive { - // TODO: we may want to allow going through Box derefs here? - if let Some(to) = p.projects_ty( - |typ| typ.ty.is_ref() || typ.ty.is_unsafe_ptr() || typ.ty.is_box(), - repacker, - ) { + let p = if kind != CapabilityKind::Exclusive { + if let Some(to) = p.projects_ptr(repacker) { changed = true; let related = self.find_all_related(to, None); assert_eq!(related.relation, PlaceOrdering::Suffix); @@ -137,11 +137,9 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { } else { p }; - if k > want { + if k > kind { changed = true; - self.insert(p, want); - } else { - assert_eq!(k, want); + self.insert(p, kind); } } None @@ -156,9 +154,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { }; if let Some(place) = final_place { // Downgrade the permission if needed - let curr = self[&place].read_as_exclusive(); - if curr > want { - self.insert(place, want); + if self[&place] > kind { + self.insert(place, kind); } } } @@ -186,12 +183,11 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { PlaceOrdering::Both => unreachable!(), } // Downgrade the permission if needed - let want = kind.read_as_exclusive(); - let curr = from[&place].read_as_exclusive(); - if curr != want { - assert!(curr > want); - from.insert(place, want); - repacks.push(RepackOp::Weaken(place, curr, want)); + let curr = from[&place]; + if curr != kind { + assert!(curr > kind); + from.insert(place, kind); + repacks.push(RepackOp::Weaken(place, curr, kind)); } } repacks diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index 7f472503fec..f7b3b069754 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -145,14 +145,9 @@ impl<'tcx> CapabilityProjections<'tcx> { for (from, to, kind) in expanded { let others = others.drain_filter(|other| !to.is_prefix(*other)); self.extend(others.map(|p| (p, perm))); - if kind.is_deref() { - let new_perm = if perm.is_shallow_exclusive() && kind.is_box() { - CapabilityKind::Write - } else { - perm - }; - ops.push(RepackOp::Deref(from, perm, to, new_perm)); - perm = new_perm; + if kind.is_box() && perm.is_shallow_exclusive() { + ops.push(RepackOp::DerefShallowInit(from, to)); + perm = CapabilityKind::Write; } else { ops.push(RepackOp::Expand(from, to, perm)); } @@ -202,7 +197,7 @@ impl<'tcx> CapabilityProjections<'tcx> { } } let mut ops = Vec::new(); - for (to, from, kind) in collapsed { + for (to, from, _) in collapsed { let removed_perms: Vec<_> = old_caps.drain_filter(|old, _| to.is_prefix(*old)).collect(); let perm = removed_perms @@ -216,20 +211,8 @@ impl<'tcx> CapabilityProjections<'tcx> { ops.push(RepackOp::Weaken(from, from_perm, perm)); } } - let op = if kind.is_deref() { - let new_perm = if kind.is_shared_ref() && exclusive_at.contains(&to) { - assert_eq!(perm, CapabilityKind::Read); - CapabilityKind::Exclusive - } else { - perm - }; - old_caps.insert(to, new_perm); - RepackOp::Upref(to, new_perm, from, perm) - } else { - old_caps.insert(to, perm); - RepackOp::Collapse(to, from, perm) - }; - ops.push(op); + old_caps.insert(to, perm); + ops.push(RepackOp::Collapse(to, from, perm)); } self.insert(to, old_caps[&to]); ops diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index 8ab6e87592b..bbbd3e8b3ac 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -7,7 +7,6 @@ use std::{ cmp::Ordering, fmt::{Debug, Formatter, Result}, - ops::Sub, }; use prusti_rustc_interface::data_structures::fx::FxHashSet; @@ -40,7 +39,6 @@ impl<'tcx> RelatedSet<'tcx> { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum CapabilityKind { - Read, Write, Exclusive, /// [`CapabilityKind::Exclusive`] for everything not through a dereference, @@ -50,7 +48,6 @@ pub enum CapabilityKind { impl Debug for CapabilityKind { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { - CapabilityKind::Read => write!(f, "R"), CapabilityKind::Write => write!(f, "W"), CapabilityKind::Exclusive => write!(f, "E"), CapabilityKind::ShallowExclusive => write!(f, "e"), @@ -64,34 +61,18 @@ impl PartialOrd for CapabilityKind { return Some(Ordering::Equal); } match (self, other) { - // R < E, W < E, W < e - (CapabilityKind::Read, CapabilityKind::Exclusive) - | (CapabilityKind::Write, CapabilityKind::Exclusive) + // W < E, W < e + (CapabilityKind::Write, CapabilityKind::Exclusive) | (CapabilityKind::Write, CapabilityKind::ShallowExclusive) => Some(Ordering::Less), - // E > R, E > W, e > W - (CapabilityKind::Exclusive, CapabilityKind::Read) - | (CapabilityKind::Exclusive, CapabilityKind::Write) + // E > W, e > W + (CapabilityKind::Exclusive, CapabilityKind::Write) | (CapabilityKind::ShallowExclusive, CapabilityKind::Write) => Some(Ordering::Greater), _ => None, } } } -impl Sub for CapabilityKind { - type Output = Option; - fn sub(self, other: Self) -> Self::Output { - match (self, other) { - (CapabilityKind::Exclusive, CapabilityKind::Read) => Some(CapabilityKind::Write), - (CapabilityKind::Exclusive, CapabilityKind::Write) => Some(CapabilityKind::Read), - _ => None, - } - } -} - impl CapabilityKind { - pub fn is_read(self) -> bool { - matches!(self, CapabilityKind::Read) - } pub fn is_exclusive(self) -> bool { matches!(self, CapabilityKind::Exclusive) } @@ -107,14 +88,4 @@ impl CapabilityKind { _ => Some(self), } } - pub fn read_as_exclusive(self) -> Self { - match self { - CapabilityKind::Read => CapabilityKind::Exclusive, - _ => self, - } - } - // pub fn minimum_with_read_as_exclusive(self, other: Self) -> Option { - // let (adj_self, adj_other) = (self.read_as_exclusive(), other.read_as_exclusive()); - // adj_self.minimum(adj_other) - // } } diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index a1336d14fac..973271987cd 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -119,7 +119,8 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { | CheckedBinaryOp(_, _) | NullaryOp(_, _) | UnaryOp(_, _) - | Aggregate(_, _) => {} + | Aggregate(_, _) + | ShallowInitBox(_, _) => {} &Ref(_, bk, place) => match bk { BorrowKind::Shared => { @@ -146,7 +147,6 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { }, &Len(place) => self.requires_read(place), &Discriminant(place) => self.requires_read(place), - ShallowInitBox(op, ty) => todo!("{op:?}, {ty:?}"), &CopyForDeref(place) => self.requires_read(place), } } @@ -174,8 +174,7 @@ impl ProducesCapability for Rvalue<'_> { | Discriminant(_) | Aggregate(_, _) | CopyForDeref(_) => CapabilityKind::Exclusive, - // TODO: - ShallowInitBox(_, _) => CapabilityKind::Read, + ShallowInitBox(_, _) => CapabilityKind::ShallowExclusive, } } } diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 5fb85bc8cc4..1c48a6ceee7 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -26,14 +26,20 @@ impl<'tcx> Fpcs<'_, 'tcx> { } } pub(crate) fn requires_read(&mut self, place: impl Into>) { - self.requires(place, CapabilityKind::Read) + self.requires(place, CapabilityKind::Exclusive) } /// May obtain write _or_ exclusive, if one should only have write afterwards, /// make sure to also call `ensures_write`! pub(crate) fn requires_write(&mut self, place: impl Into>) { + let place = place.into(); + // Cannot get write on a shared ref + assert!(!place.projects_shared_ref(self.repacker)); self.requires(place, CapabilityKind::Write) } pub(crate) fn requires_exclusive(&mut self, place: impl Into>) { + let place = place.into(); + // Cannot get exclusive on a shared ref + assert!(!place.projects_shared_ref(self.repacker)); self.requires(place, CapabilityKind::Exclusive) } fn requires(&mut self, place: impl Into>, cap: CapabilityKind) { @@ -65,6 +71,9 @@ impl<'tcx> Fpcs<'_, 'tcx> { self.ensures_alloc(place, CapabilityKind::ShallowExclusive) } pub(crate) fn ensures_write(&mut self, place: impl Into>) { + let place = place.into(); + // Cannot get uninitialize behind a ref (actually drop does this) + assert!(place.can_deinit(self.repacker), "{place:?}"); self.ensures_alloc(place, CapabilityKind::Write) } } diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index 5dee534629e..928341cb6f3 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -19,98 +19,122 @@ use crate::{ type Cursor<'mir, 'tcx> = ResultsCursor<'mir, 'tcx, FreePlaceCapabilitySummary<'mir, 'tcx>>; -pub struct FreePcsAnalysis<'mir, 'tcx>(pub(crate) Cursor<'mir, 'tcx>); +pub struct FreePcsAnalysis<'mir, 'tcx> { + cursor: Cursor<'mir, 'tcx>, + curr_stmt: Option, + end_stmt: Option, +} impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { - pub fn analysis_for_bb(&mut self, block: BasicBlock) -> FreePcsCursor<'_, 'mir, 'tcx> { - self.0.seek_to_block_start(block); + pub(crate) fn new(cursor: Cursor<'mir, 'tcx>) -> Self { + Self { + cursor, + curr_stmt: None, + end_stmt: None, + } + } + + pub fn analysis_for_bb(&mut self, block: BasicBlock) { + self.cursor.seek_to_block_start(block); let end_stmt = self - .0 + .cursor .analysis() .0 .body() .terminator_loc(block) .successor_within_block(); - FreePcsCursor { - analysis: self, - curr_stmt: Location { - block, - statement_index: 0, - }, - end_stmt, - } + self.curr_stmt = Some(Location { + block, + statement_index: 0, + }); + self.end_stmt = Some(end_stmt); } - pub(crate) fn body(&self) -> &'mir Body<'tcx> { - self.0.analysis().0.body() + fn body(&self) -> &'mir Body<'tcx> { + self.cursor.analysis().0.body() } pub(crate) fn repacker(&self) -> PlaceRepacker<'mir, 'tcx> { - self.0.results().analysis.0 + self.cursor.results().analysis.0 } -} - -pub struct FreePcsCursor<'a, 'mir, 'tcx> { - analysis: &'a mut FreePcsAnalysis<'mir, 'tcx>, - curr_stmt: Location, - end_stmt: Location, -} -impl<'a, 'mir, 'tcx> FreePcsCursor<'a, 'mir, 'tcx> { pub fn initial_state(&self) -> &CapabilitySummary<'tcx> { - &self.analysis.0.get().summary + &self.cursor.get().summary } - pub fn next<'b>( - &'b mut self, - ) -> Result, FreePcsTerminator<'b, 'tcx>> { - let location = self.curr_stmt; - assert!(location <= self.end_stmt); - self.curr_stmt = location.successor_within_block(); + pub fn next(&mut self, exp_loc: Location) -> FreePcsLocation<'tcx> { + let location = self.curr_stmt.unwrap(); + assert_eq!(location, exp_loc); + assert!(location < self.end_stmt.unwrap()); + self.curr_stmt = Some(location.successor_within_block()); - if location == self.end_stmt { - // TODO: cleanup - let cursor = &self.analysis.0; - let state = cursor.get(); - let rp = self.analysis.repacker(); - let block = &self.analysis.body()[location.block]; - let succs = block - .terminator() - .successors() - .map(|succ| { - // Get repacks - let to = cursor.results().entry_set_for_block(succ); - FreePcsLocation { - location: Location { - block: succ, - statement_index: 0, - }, - state: &to.summary, - repacks: state.summary.bridge(&to.summary, rp), - } - }) - .collect(); - Err(FreePcsTerminator { succs }) - } else { - self.analysis.0.seek_after_primary_effect(location); - let state = self.analysis.0.get(); - Ok(FreePcsLocation { - location, - state: &state.summary, - repacks: state.repackings.clone(), + self.cursor.seek_after_primary_effect(location); + let state = self.cursor.get(); + FreePcsLocation { + location, + state: state.summary.clone(), + repacks: state.repackings.clone(), + } + } + pub fn terminator(&mut self) -> FreePcsTerminator<'tcx> { + let location = self.curr_stmt.unwrap(); + assert!(location == self.end_stmt.unwrap()); + self.curr_stmt = None; + self.end_stmt = None; + + // TODO: cleanup + let state = self.cursor.get(); + let rp: PlaceRepacker = self.repacker(); + let block = &self.body()[location.block]; + let succs = block + .terminator() + .successors() + .map(|succ| { + // Get repacks + let to = self.cursor.results().entry_set_for_block(succ); + FreePcsLocation { + location: Location { + block: succ, + statement_index: 0, + }, + state: to.summary.clone(), + repacks: state.summary.bridge(&to.summary, rp), + } }) + .collect(); + FreePcsTerminator { succs } + } + + /// Recommened interface. + /// Does *not* require that one calls `analysis_for_bb` first + pub fn get_all_for_bb(&mut self, block: BasicBlock) -> FreePcsBasicBlock<'tcx> { + self.analysis_for_bb(block); + let mut statements = Vec::new(); + while self.curr_stmt.unwrap() != self.end_stmt.unwrap() { + let stmt = self.next(self.curr_stmt.unwrap()); + statements.push(stmt); + } + let terminator = self.terminator(); + FreePcsBasicBlock { + statements, + terminator, } } } +pub struct FreePcsBasicBlock<'tcx> { + pub statements: Vec>, + pub terminator: FreePcsTerminator<'tcx>, +} + #[derive(Debug)] -pub struct FreePcsLocation<'a, 'tcx> { +pub struct FreePcsLocation<'tcx> { pub location: Location, /// Repacks before the statement pub repacks: Vec>, /// State after the statement - pub state: &'a CapabilitySummary<'tcx>, + pub state: CapabilitySummary<'tcx>, } #[derive(Debug)] -pub struct FreePcsTerminator<'a, 'tcx> { - pub succs: Vec>, +pub struct FreePcsTerminator<'tcx> { + pub succs: Vec>, } diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs index da745db2842..bd725d390d7 100644 --- a/mir-state-analysis/src/free_pcs/results/repacks.rs +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -52,8 +52,7 @@ pub enum RepackOp<'tcx> { /// given capability for all places in this set. Collapse(Place<'tcx>, Place<'tcx>, CapabilityKind), /// TODO - Deref(Place<'tcx>, CapabilityKind, Place<'tcx>, CapabilityKind), - Upref(Place<'tcx>, CapabilityKind, Place<'tcx>, CapabilityKind), + DerefShallowInit(Place<'tcx>, Place<'tcx>), } impl Display for RepackOp<'_> { @@ -61,25 +60,12 @@ impl Display for RepackOp<'_> { match self { RepackOp::StorageDead(place) => write!(f, "StorageDead({place:?})"), RepackOp::IgnoreStorageDead(_) => write!(f, "IgnoreSD"), - RepackOp::Weaken(place, from, to) => { - write!(f, "Weaken({place:?}, {:?})", (*from - *to).unwrap()) + RepackOp::Weaken(place, _, to) => { + write!(f, "Weaken({place:?}, {to:?})") } RepackOp::Collapse(to, _, kind) => write!(f, "CollapseTo({to:?}, {kind:?})"), RepackOp::Expand(from, _, kind) => write!(f, "Expand({from:?}, {kind:?})"), - RepackOp::Upref(to, to_kind, _, from_kind) => { - if to_kind == from_kind { - write!(f, "UprefTo({to:?}, {to_kind:?})") - } else { - write!(f, "UprefTo({to:?}, {from_kind:?} -> {to_kind:?})") - } - } - RepackOp::Deref(from, from_kind, _, to_kind) => { - if to_kind == from_kind { - write!(f, "Deref({from:?}, {to_kind:?})") - } else { - write!(f, "Deref({from:?}, {from_kind:?} -> {to_kind:?})") - } - } + RepackOp::DerefShallowInit(from, _) => write!(f, "DerefShallowInit({from:?})"), } } } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 061bb6c0ced..217b198fc77 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -24,7 +24,7 @@ pub fn run_free_pcs<'mir, 'tcx>( .into_engine(tcx, mir) .pass_name("free_pcs") .iterate_to_fixpoint(); - free_pcs::FreePcsAnalysis(analysis.into_results_cursor(mir)) + free_pcs::FreePcsAnalysis::new(analysis.into_results_cursor(mir)) } pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 346c1469224..f5c7cd19971 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -233,13 +233,13 @@ impl Debug for Place<'_> { for elem in self.projection.iter() { match elem { ProjectionElem::OpaqueCast(ty) => { - write!(fmt, " as {})", ty)?; + write!(fmt, "@{ty})")?; } ProjectionElem::Downcast(Some(name), _index) => { - write!(fmt, " as {})", name)?; + write!(fmt, "@{name})")?; } ProjectionElem::Downcast(None, index) => { - write!(fmt, " as variant#{:?})", index)?; + write!(fmt, "@variant#{index:?})")?; } ProjectionElem::Deref => { write!(fmt, ")")?; @@ -248,49 +248,49 @@ impl Debug for Place<'_> { write!(fmt, ".{:?}", field.index())?; } ProjectionElem::Index(ref index) => { - write!(fmt, "[{:?}]", index)?; + write!(fmt, "[{index:?}]")?; } ProjectionElem::ConstantIndex { offset, min_length, from_end: false, } => { - write!(fmt, "[{:?} of {:?}]", offset, min_length)?; + write!(fmt, "[{offset:?} of {min_length:?}]")?; } ProjectionElem::ConstantIndex { offset, min_length, from_end: true, } => { - write!(fmt, "[-{:?} of {:?}]", offset, min_length)?; + write!(fmt, "[-{offset:?} of {min_length:?}]")?; } ProjectionElem::Subslice { from, to, from_end: true, } if to == 0 => { - write!(fmt, "[{:?}:]", from)?; + write!(fmt, "[{from:?}:]")?; } ProjectionElem::Subslice { from, to, from_end: true, } if from == 0 => { - write!(fmt, "[:-{:?}]", to)?; + write!(fmt, "[:-{to:?}]")?; } ProjectionElem::Subslice { from, to, from_end: true, } => { - write!(fmt, "[{:?}:-{:?}]", from, to)?; + write!(fmt, "[{from:?}:-{to:?}]")?; } ProjectionElem::Subslice { from, to, from_end: false, } => { - write!(fmt, "[{:?}..{:?}]", from, to)?; + write!(fmt, "[{from:?}..{to:?}]")?; } } } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index d805a6d8023..f1a3c31fb08 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -44,8 +44,8 @@ impl ProjectionRefKind { #[derive(Copy, Clone)] // TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate pub struct PlaceRepacker<'a, 'tcx: 'a> { - mir: &'a Body<'tcx>, - tcx: TyCtxt<'tcx>, + pub(super) mir: &'a Body<'tcx>, + pub(super) tcx: TyCtxt<'tcx>, } impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { @@ -305,12 +305,7 @@ impl<'tcx> Place<'tcx> { } #[tracing::instrument(level = "info", skip(repacker), ret)] - pub fn joinable_to( - self, - to: Self, - not_through_ref: bool, - repacker: PlaceRepacker<'_, 'tcx>, - ) -> Self { + pub fn joinable_to(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { assert!(self.is_prefix(to)); let proj = self.projection.iter(); let to_proj = to.projection[self.projection.len()..] @@ -319,8 +314,10 @@ impl<'tcx> Place<'tcx> { .take_while(|p| { matches!( p, - ProjectionElem::Field(..) | ProjectionElem::ConstantIndex { .. } // TODO: we may want to allow going through Box derefs here? - ) || (!not_through_ref && matches!(p, ProjectionElem::Deref)) + ProjectionElem::Deref + | ProjectionElem::Field(..) + | ProjectionElem::ConstantIndex { .. } + ) }); let projection = repacker.tcx.mk_place_elems_from_iter(proj.chain(to_proj)); Self::new(self.local, projection) @@ -347,29 +344,6 @@ impl<'tcx> Place<'tcx> { } } - // /// Calculates if we dereference through a mutable/immutable reference. For example, if we have\ - // /// `x: (i32, & &mut i32)` then we would get the following results: - // /// - // /// * `"x".through_ref_mutability("x.0")` -> `None` - // /// * `"x".through_ref_mutability("x.1")` -> `None` - // /// * `"x".through_ref_mutability("*x.1")` -> `Some(Mutability::Not)` - // /// * `"x".through_ref_mutability("**x.1")` -> `Some(Mutability::Not)` - // /// * `"*x.1".through_ref_mutability("**x.1")` -> `Some(Mutability::Mut)` - // pub fn between_ref_mutability(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Option { - // assert!(self.is_prefix(to)); - // let mut typ = self.ty(repacker.mir, repacker.tcx); - // let mut mutbl = None; - // for &elem in &to.projection[self.projection.len()..] { - // match typ.ty.kind() { - // TyKind::Ref(_, _, Mutability::Not) => return Some(Mutability::Not), - // TyKind::Ref(_, _, Mutability::Mut) => mutbl = Some(Mutability::Mut), - // _ => () - // }; - // typ = typ.projection_ty(repacker.tcx, elem); - // } - // mutbl - // } - pub fn projects_shared_ref(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { self.projects_ty( |typ| { @@ -383,9 +357,17 @@ impl<'tcx> Place<'tcx> { .is_some() } + pub fn projects_ptr(self, repacker: PlaceRepacker<'_, 'tcx>) -> Option> { + self.projects_ty(|typ| typ.ty.is_ref() || typ.ty.is_unsafe_ptr(), repacker) + } + + pub fn can_deinit(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { + !self.projects_shared_ref(repacker) + } + pub fn projects_ty( self, - predicate: impl Fn(PlaceTy<'tcx>) -> bool, + mut predicate: impl FnMut(PlaceTy<'tcx>) -> bool, repacker: PlaceRepacker<'_, 'tcx>, ) -> Option> { let mut typ = PlaceTy::from_ty(repacker.mir.local_decls()[self.local].ty); From 2f8fbbaf76cf1ed545a3db7fbd65cb7dce839e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 25 Apr 2023 13:10:54 +0200 Subject: [PATCH 10/32] Bugfix --- .../src/free_pcs/impl/join_semi_lattice.rs | 2 +- mir-state-analysis/src/utils/repacker.rs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index c68a5cb6186..9b4f0e7b8b7 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -180,7 +180,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { let packs = from.collapse(related.get_from(), related.to, repacker); repacks.extend(packs); } - PlaceOrdering::Both => unreachable!(), + PlaceOrdering::Both => unreachable!("{self:?}\n{from:?}\n{other:?}\n{related:?}"), } // Downgrade the permission if needed let curr = from[&place]; diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index f1a3c31fb08..236876e3ea3 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -311,14 +311,7 @@ impl<'tcx> Place<'tcx> { let to_proj = to.projection[self.projection.len()..] .iter() .copied() - .take_while(|p| { - matches!( - p, - ProjectionElem::Deref - | ProjectionElem::Field(..) - | ProjectionElem::ConstantIndex { .. } - ) - }); + .take_while(|p| matches!(p, ProjectionElem::Deref | ProjectionElem::Field(..))); let projection = repacker.tcx.mk_place_elems_from_iter(proj.chain(to_proj)); Self::new(self.local, projection) } From 330e61a261e851087169699f89bf91690aeb7602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 25 Apr 2023 14:33:01 +0200 Subject: [PATCH 11/32] More changes --- .../src/free_pcs/impl/join_semi_lattice.rs | 6 +- mir-state-analysis/src/free_pcs/impl/place.rs | 6 +- .../src/free_pcs/impl/update.rs | 8 +- mir-state-analysis/src/utils/mod.rs | 3 + mir-state-analysis/src/utils/mutable.rs | 239 ++++++++++++++++++ mir-state-analysis/src/utils/place.rs | 93 ++++--- mir-state-analysis/src/utils/repacker.rs | 62 +++-- mir-state-analysis/src/utils/root_place.rs | 17 ++ 8 files changed, 362 insertions(+), 72 deletions(-) create mode 100644 mir-state-analysis/src/utils/mutable.rs create mode 100644 mir-state-analysis/src/utils/root_place.rs diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 9b4f0e7b8b7..35b277f9d51 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -107,9 +107,9 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { let joinable_place = if self[&from] != CapabilityKind::Exclusive { place .projects_ptr(repacker) - .unwrap_or_else(|| from.joinable_to(place, repacker)) + .unwrap_or_else(|| from.joinable_to(place)) } else { - from.joinable_to(place, repacker) + from.joinable_to(place) }; assert!(from.is_prefix(joinable_place)); if joinable_place != from { @@ -147,7 +147,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { PlaceOrdering::Both => { changed = true; - let cp = related.common_prefix(place, repacker); + let cp = related.common_prefix(place); self.collapse(related.get_from(), cp, repacker); Some(cp) } diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index bbbd3e8b3ac..777be5b8209 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -11,7 +11,7 @@ use std::{ use prusti_rustc_interface::data_structures::fx::FxHashSet; -use crate::utils::{Place, PlaceOrdering, PlaceRepacker}; +use crate::utils::{Place, PlaceOrdering}; #[derive(Debug)] pub(crate) struct RelatedSet<'tcx> { @@ -32,8 +32,8 @@ impl<'tcx> RelatedSet<'tcx> { assert_eq!(self.from.len(), 1); self.from[0].0 } - pub fn common_prefix(&self, to: Place<'tcx>, repacker: PlaceRepacker<'_, 'tcx>) -> Place<'tcx> { - self.from[0].0.common_prefix(to, repacker) + pub fn common_prefix(&self, to: Place<'tcx>) -> Place<'tcx> { + self.from[0].0.common_prefix(to) } } diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 1c48a6ceee7..dc4d18ded40 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -8,7 +8,7 @@ use prusti_rustc_interface::middle::mir::Local; use crate::{ free_pcs::{CapabilityKind, CapabilityLocal, CapabilityProjections, Fpcs, RepackOp}, - utils::{Place, PlaceOrdering, PlaceRepacker}, + utils::{LocalMutationIsAllowed, Place, PlaceOrdering, PlaceRepacker}, }; impl<'tcx> Fpcs<'_, 'tcx> { @@ -33,7 +33,9 @@ impl<'tcx> Fpcs<'_, 'tcx> { pub(crate) fn requires_write(&mut self, place: impl Into>) { let place = place.into(); // Cannot get write on a shared ref - assert!(!place.projects_shared_ref(self.repacker)); + assert!(place + .is_mutable(LocalMutationIsAllowed::Yes, self.repacker) + .is_ok()); self.requires(place, CapabilityKind::Write) } pub(crate) fn requires_exclusive(&mut self, place: impl Into>) { @@ -90,7 +92,7 @@ impl<'tcx> CapabilityProjections<'tcx> { PlaceOrdering::Equal => Vec::new(), PlaceOrdering::Suffix => self.collapse(related.get_from(), related.to, repacker), PlaceOrdering::Both => { - let cp = related.common_prefix(to, repacker); + let cp = related.common_prefix(to); // Collapse let mut ops = self.collapse(related.get_from(), cp, repacker); // Expand diff --git a/mir-state-analysis/src/utils/mod.rs b/mir-state-analysis/src/utils/mod.rs index 7eb552309af..fb02d7b16ab 100644 --- a/mir-state-analysis/src/utils/mod.rs +++ b/mir-state-analysis/src/utils/mod.rs @@ -6,6 +6,9 @@ pub mod place; pub(crate) mod repacker; +mod mutable; +mod root_place; +pub use mutable::*; pub use place::*; pub use repacker::*; diff --git a/mir-state-analysis/src/utils/mutable.rs b/mir-state-analysis/src/utils/mutable.rs new file mode 100644 index 00000000000..118c80244f5 --- /dev/null +++ b/mir-state-analysis/src/utils/mutable.rs @@ -0,0 +1,239 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use prusti_rustc_interface::{ + hir, + middle::{ + mir::{Field, Mutability, ProjectionElem}, + ty::{CapturedPlace, TyKind, UpvarCapture}, + }, +}; + +use super::{root_place::RootPlace, Place, PlaceRepacker}; + +struct Upvar<'tcx> { + pub(crate) place: CapturedPlace<'tcx>, + pub(crate) by_ref: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LocalMutationIsAllowed { + Yes, + ExceptUpvars, + No, +} + +impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { + fn upvars(self) -> Vec> { + let def = self.body().source.def_id().expect_local(); + self.tcx + .closure_captures(def) + .iter() + .map(|&captured_place| { + let capture = captured_place.info.capture_kind; + let by_ref = match capture { + UpvarCapture::ByValue => false, + UpvarCapture::ByRef(..) => true, + }; + Upvar { + place: captured_place.clone(), + by_ref, + } + }) + .collect() + } +} + +impl<'tcx> Place<'tcx> { + pub fn is_mutable( + self, + is_local_mutation_allowed: LocalMutationIsAllowed, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Result, Self> { + let upvars = repacker.upvars(); + self.is_mutable_helper(is_local_mutation_allowed, &upvars, repacker) + } + + /// Whether this value can be written or borrowed mutably. + /// Returns the root place if the place passed in is a projection. + fn is_mutable_helper( + self, + is_local_mutation_allowed: LocalMutationIsAllowed, + upvars: &[Upvar<'tcx>], + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Result, Self> { + match self.last_projection() { + None => { + let local = &repacker.body().local_decls[self.local]; + match local.mutability { + Mutability::Not => match is_local_mutation_allowed { + LocalMutationIsAllowed::Yes => Ok(RootPlace { + place: self, + is_local_mutation_allowed: LocalMutationIsAllowed::Yes, + }), + LocalMutationIsAllowed::ExceptUpvars => Ok(RootPlace { + place: self, + is_local_mutation_allowed: LocalMutationIsAllowed::ExceptUpvars, + }), + LocalMutationIsAllowed::No => Err(self), + }, + Mutability::Mut => Ok(RootPlace { + place: self, + is_local_mutation_allowed, + }), + } + } + Some((place_base, elem)) => { + match elem { + ProjectionElem::Deref => { + let base_ty = place_base.ty(repacker.body(), repacker.tcx).ty; + + // Check the kind of deref to decide + match base_ty.kind() { + TyKind::Ref(_, _, mutbl) => { + match mutbl { + // Shared borrowed data is never mutable + hir::Mutability::Not => Err(self), + // Mutably borrowed data is mutable, but only if we have a + // unique path to the `&mut` + hir::Mutability::Mut => { + let mode = match self + .is_upvar_field_projection(upvars, repacker) + { + Some(field) if upvars[field.index()].by_ref => { + is_local_mutation_allowed + } + _ => LocalMutationIsAllowed::Yes, + }; + + place_base.is_mutable_helper(mode, upvars, repacker) + } + } + } + TyKind::RawPtr(tnm) => { + match tnm.mutbl { + // `*const` raw pointers are not mutable + hir::Mutability::Not => Err(self), + // `*mut` raw pointers are always mutable, regardless of + // context. The users have to check by themselves. + hir::Mutability::Mut => Ok(RootPlace { + place: self, + is_local_mutation_allowed, + }), + } + } + // `Box` owns its content, so mutable if its location is mutable + _ if base_ty.is_box() => place_base.is_mutable_helper( + is_local_mutation_allowed, + upvars, + repacker, + ), + // Deref should only be for reference, pointers or boxes + _ => panic!("Deref of unexpected type: {:?}", base_ty), + } + } + // All other projections are owned by their base path, so mutable if + // base path is mutable + ProjectionElem::Field(..) + | ProjectionElem::Index(..) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::OpaqueCast { .. } + | ProjectionElem::Downcast(..) => { + let upvar_field_projection = + self.is_upvar_field_projection(upvars, repacker); + if let Some(field) = upvar_field_projection { + let upvar = &upvars[field.index()]; + match (upvar.place.mutability, is_local_mutation_allowed) { + ( + Mutability::Not, + LocalMutationIsAllowed::No + | LocalMutationIsAllowed::ExceptUpvars, + ) => Err(self), + (Mutability::Not, LocalMutationIsAllowed::Yes) + | (Mutability::Mut, _) => { + // Subtle: this is an upvar + // reference, so it looks like + // `self.foo` -- we want to double + // check that the location `*self` + // is mutable (i.e., this is not a + // `Fn` closure). But if that + // check succeeds, we want to + // *blame* the mutability on + // `place` (that is, + // `self.foo`). This is used to + // propagate the info about + // whether mutability declarations + // are used outwards, so that we register + // the outer variable as mutable. Otherwise a + // test like this fails to record the `mut` + // as needed: + // + // ``` + // fn foo(_f: F) { } + // fn main() { + // let var = Vec::new(); + // foo(move || { + // var.push(1); + // }); + // } + // ``` + let _ = place_base.is_mutable_helper( + is_local_mutation_allowed, + upvars, + repacker, + )?; + Ok(RootPlace { + place: self, + is_local_mutation_allowed, + }) + } + } + } else { + place_base.is_mutable_helper( + is_local_mutation_allowed, + upvars, + repacker, + ) + } + } + } + } + } + } + + /// If `place` is a field projection, and the field is being projected from a closure type, + /// then returns the index of the field being projected. Note that this closure will always + /// be `self` in the current MIR, because that is the only time we directly access the fields + /// of a closure type. + fn is_upvar_field_projection( + self, + upvars: &[Upvar<'tcx>], + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Option { + let mut place_ref = *self; + let mut by_ref = false; + + if let Some((place_base, ProjectionElem::Deref)) = place_ref.last_projection() { + place_ref = place_base; + by_ref = true; + } + + match place_ref.last_projection() { + Some((place_base, ProjectionElem::Field(field, _ty))) => { + let base_ty = place_base.ty(repacker.body(), repacker.tcx).ty; + if (base_ty.is_closure() || base_ty.is_generator()) + && (!by_ref || upvars[field.index()].by_ref) + { + Some(field) + } else { + None + } + } + _ => None, + } + } +} diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index f5c7cd19971..1dc12ded632 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -13,9 +13,8 @@ use std::{ use derive_more::{Deref, DerefMut}; -use prusti_rustc_interface::middle::{ - mir::{Local, Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem}, - ty::List, +use prusti_rustc_interface::middle::mir::{ + Local, Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem, }; // #[derive(Clone, Copy, Deref, DerefMut, Hash, PartialEq, Eq)] @@ -79,25 +78,19 @@ fn elem_eq<'tcx>(to_cmp: (PlaceElem<'tcx>, PlaceElem<'tcx>)) -> bool { } #[derive(Clone, Copy, Deref, DerefMut)] -pub struct Place<'tcx>(MirPlace<'tcx>); +pub struct Place<'tcx>(PlaceRef<'tcx>); impl<'tcx> Place<'tcx> { - pub(crate) fn new(local: Local, projection: &'tcx List>) -> Self { - Self(MirPlace { local, projection }) + pub(crate) fn new(local: Local, projection: &'tcx [PlaceElem<'tcx>]) -> Self { + Self(PlaceRef { local, projection }) } pub(crate) fn compare_projections( self, other: Self, ) -> impl Iterator, PlaceElem<'tcx>)> { - Self::compare_projections_ref(self.as_ref(), other.as_ref()) - } - pub(crate) fn compare_projections_ref( - left: PlaceRef<'tcx>, - right: PlaceRef<'tcx>, - ) -> impl Iterator, PlaceElem<'tcx>)> { - let left = left.projection.iter().copied(); - let right = right.projection.iter().copied(); + let left = self.projection.iter().copied(); + let right = other.projection.iter().copied(); left.zip(right).map(|(e1, e2)| (elem_eq((e1, e2)), e1, e2)) } @@ -109,10 +102,7 @@ impl<'tcx> Place<'tcx> { /// + `partial_cmp(x.f.g, x.f) == Some(Suffix)` /// + `partial_cmp(x.f, x.f.g) == Some(Prefix)` /// + `partial_cmp(x as None, x as Some.0) == Some(Both)` - #[tracing::instrument(level = "trace", ret)] - pub fn partial_cmp(self, right: Self) -> Option { - Self::partial_cmp_ref(self.as_ref(), right.as_ref()) - } + /// /// The ultimate question this answers is: are the two places mutually /// exclusive (i.e. can we have both or not)? /// For example, all of the following are mutually exclusive: @@ -124,14 +114,12 @@ impl<'tcx> Place<'tcx> { /// - `x` and `y` /// - `x.f` and `x.g.h` /// - `x[3 of 6]` and `x[4 of 6]` - pub(crate) fn partial_cmp_ref( - left: PlaceRef<'tcx>, - right: PlaceRef<'tcx>, - ) -> Option { - if left.local != right.local { + #[tracing::instrument(level = "trace", ret)] + pub(crate) fn partial_cmp(self, right: Self) -> Option { + if self.local != right.local { return None; } - let diff = Self::compare_projections_ref(left, right).find(|(eq, _, _)| !eq); + let diff = self.compare_projections(right).find(|(eq, _, _)| !eq); if let Some((_, left, right)) = diff { use ProjectionElem::*; fn is_index(elem: PlaceElem<'_>) -> bool { @@ -151,7 +139,7 @@ impl<'tcx> Place<'tcx> { diff => unreachable!("Unexpected diff: {diff:?}"), } } else { - Some(left.projection.len().cmp(&right.projection.len()).into()) + Some(self.projection.len().cmp(&right.projection.len()).into()) } } @@ -209,6 +197,36 @@ impl<'tcx> Place<'tcx> { .iter() .any(|proj| matches!(proj, ProjectionElem::Deref)) } + + #[tracing::instrument(level = "debug", ret, fields(lp = ?self.projection, rp = ?other.projection))] + pub fn common_prefix(self, other: Self) -> Self { + assert_eq!(self.local, other.local); + + let max_len = std::cmp::min(self.projection.len(), other.projection.len()); + let common_prefix = self + .compare_projections(other) + .position(|(eq, _, _)| !eq) + .unwrap_or(max_len); + Self::new(self.local, &self.projection[..common_prefix]) + } + + #[tracing::instrument(level = "info", ret)] + pub fn joinable_to(self, to: Self) -> Self { + assert!(self.is_prefix(to)); + let diff = to.projection.len() - self.projection.len(); + let to_proj = self.projection.len() + + to.projection[self.projection.len()..] + .iter() + .position(|p| !matches!(p, ProjectionElem::Deref | ProjectionElem::Field(..))) + .unwrap_or(diff); + Self::new(self.local, &to.projection[..to_proj]) + } + + pub fn last_projection(self) -> Option<(Self, PlaceElem<'tcx>)> { + self.0 + .last_projection() + .map(|(place, proj)| (place.into(), proj)) + } } impl Debug for Place<'_> { @@ -230,7 +248,7 @@ impl Debug for Place<'_> { write!(fmt, "{:?}", self.local)?; - for elem in self.projection.iter() { + for &elem in self.projection.iter() { match elem { ProjectionElem::OpaqueCast(ty) => { write!(fmt, "@{ty})")?; @@ -312,7 +330,7 @@ impl Hash for Place<'_> { fn hash(&self, state: &mut H) { self.0.local.hash(state); let projection = self.0.projection; - for pe in projection { + for &pe in projection { match pe { ProjectionElem::Field(field, _) => { discriminant(&pe).hash(state); @@ -346,9 +364,24 @@ impl Hash for Place<'_> { } } -impl<'tcx, T: Into>> From for Place<'tcx> { - fn from(value: T) -> Self { - Self(value.into()) +// impl<'tcx, T: Into>> From for Place<'tcx> { +// fn from(value: T) -> Self { +// Self(value.into()) +// } +// } +impl<'tcx> From> for Place<'tcx> { + fn from(value: PlaceRef<'tcx>) -> Self { + Self(value) + } +} +impl<'tcx> From> for Place<'tcx> { + fn from(value: MirPlace<'tcx>) -> Self { + Self(value.as_ref()) + } +} +impl<'tcx> From for Place<'tcx> { + fn from(value: Local) -> Self { + MirPlace::from(value).into() } } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 236876e3ea3..b8b59e312c1 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -9,7 +9,10 @@ use prusti_rustc_interface::{ dataflow::storage, index::bit_set::BitSet, middle::{ - mir::{tcx::PlaceTy, Body, Field, HasLocalDecls, Local, Mutability, ProjectionElem}, + mir::{ + tcx::PlaceTy, Body, Field, HasLocalDecls, Local, Mutability, Place as MirPlace, + ProjectionElem, + }, ty::{TyCtxt, TyKind}, }, }; @@ -67,6 +70,13 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { } impl<'tcx> Place<'tcx> { + pub fn to_rust_place(self, repacker: PlaceRepacker<'_, 'tcx>) -> MirPlace<'tcx> { + MirPlace { + local: self.local, + projection: repacker.tcx.mk_place_elems(self.projection), + } + } + /// Subtract the `to` place from the `self` place. The /// subtraction is defined as set minus between `self` place replaced /// with a set of places that are unrolled up to the same level as @@ -146,6 +156,7 @@ impl<'tcx> Place<'tcx> { let new_projection = repacker.tcx.mk_place_elems_from_iter( self.projection .iter() + .copied() .chain([guide_place.projection[index]]), ); let new_current_place = Place::new(self.local, new_projection); @@ -171,7 +182,7 @@ impl<'tcx> Place<'tcx> { repacker .tcx .mk_place_elem( - *self, + self.to_rust_place(repacker), ProjectionElem::ConstantIndex { offset: i, min_length, @@ -228,7 +239,7 @@ impl<'tcx> Place<'tcx> { if Some(index) != without_field { let field = Field::from_usize(index); let field_place = repacker.tcx.mk_place_field( - *self, + self.to_rust_place(repacker), field, field_def.ty(repacker.tcx, substs), ); @@ -240,7 +251,10 @@ impl<'tcx> Place<'tcx> { for (index, arg) in slice.iter().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = repacker.tcx.mk_place_field(*self, field, arg); + let field_place = + repacker + .tcx + .mk_place_field(self.to_rust_place(repacker), field, arg); places.push(field_place.into()); } } @@ -249,7 +263,11 @@ impl<'tcx> Place<'tcx> { for (index, subst_ty) in substs.as_closure().upvar_tys().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = repacker.tcx.mk_place_field(*self, field, subst_ty); + let field_place = repacker.tcx.mk_place_field( + self.to_rust_place(repacker), + field, + subst_ty, + ); places.push(field_place.into()); } } @@ -258,7 +276,11 @@ impl<'tcx> Place<'tcx> { for (index, subst_ty) in substs.as_generator().upvar_tys().enumerate() { if Some(index) != without_field { let field = Field::from_usize(index); - let field_place = repacker.tcx.mk_place_field(*self, field, subst_ty); + let field_place = repacker.tcx.mk_place_field( + self.to_rust_place(repacker), + field, + subst_ty, + ); places.push(field_place.into()); } } @@ -290,32 +312,6 @@ impl<'tcx> Place<'tcx> { // } impl<'tcx> Place<'tcx> { - #[tracing::instrument(level = "debug", skip(repacker), ret, fields(lp = ?self.projection, rp = ?other.projection))] - pub fn common_prefix(self, other: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { - assert_eq!(self.local, other.local); - - let common_prefix = self - .compare_projections(other) - .take_while(|(eq, _, _)| *eq) - .map(|(_, e1, _)| e1); - Self::new( - self.local, - repacker.tcx.mk_place_elems_from_iter(common_prefix), - ) - } - - #[tracing::instrument(level = "info", skip(repacker), ret)] - pub fn joinable_to(self, to: Self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { - assert!(self.is_prefix(to)); - let proj = self.projection.iter(); - let to_proj = to.projection[self.projection.len()..] - .iter() - .copied() - .take_while(|p| matches!(p, ProjectionElem::Deref | ProjectionElem::Field(..))); - let projection = repacker.tcx.mk_place_elems_from_iter(proj.chain(to_proj)); - Self::new(self.local, projection) - } - // pub fn get_root(self, repacker: PlaceRepacker<'_, 'tcx>) -> RootPlace<'tcx> { // if let Some(idx) = self.projection.iter().rev().position(RootPlace::is_indirect) { // let idx = self.projection.len() - idx; @@ -369,7 +365,7 @@ impl<'tcx> Place<'tcx> { let projection = repacker.tcx.mk_place_elems(&self.projection[0..idx]); return Some(Self::new(self.local, projection)); } - typ = typ.projection_ty(repacker.tcx, elem); + typ = typ.projection_ty(repacker.tcx, *elem); } None } diff --git a/mir-state-analysis/src/utils/root_place.rs b/mir-state-analysis/src/utils/root_place.rs new file mode 100644 index 00000000000..5a3d7f0912e --- /dev/null +++ b/mir-state-analysis/src/utils/root_place.rs @@ -0,0 +1,17 @@ +// © 2023, ETH Zurich +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use derive_more::{Deref, DerefMut}; + +use super::{mutable::LocalMutationIsAllowed, Place}; + +#[derive(Debug, Clone, Copy, Deref, DerefMut)] +pub struct RootPlace<'tcx> { + #[deref] + #[deref_mut] + pub(super) place: Place<'tcx>, + pub is_local_mutation_allowed: LocalMutationIsAllowed, +} From adf559b2d68e4fb275e05418530f721737a660ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 22 Aug 2023 15:55:42 +0200 Subject: [PATCH 12/32] Merge update fixes --- mir-state-analysis/src/free_pcs/check/checker.rs | 2 +- mir-state-analysis/src/free_pcs/impl/engine.rs | 8 ++++---- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 2 +- mir-state-analysis/src/free_pcs/impl/local.rs | 8 ++++---- mir-state-analysis/src/free_pcs/impl/triple.rs | 11 ++--------- mir-state-analysis/src/free_pcs/results/cursor.rs | 4 ++-- mir-state-analysis/src/lib.rs | 2 +- mir-state-analysis/src/utils/mutable.rs | 5 +++-- mir-state-analysis/src/utils/repacker.rs | 15 ++++++++------- 9 files changed, 26 insertions(+), 31 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index d19cd9b8b43..e3299398fb7 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -137,7 +137,7 @@ impl<'tcx> RepackOp<'tcx> { assert!(place.is_prefix_exact(guide), "{self:?}"); let curr_state = state[place.local].get_allocated_mut(); let mut removed = curr_state - .drain_filter(|p, _| place.related_to(*p)) + .extract_if(|p, _| place.related_to(*p)) .collect::>(); let (p, mut others, _) = place.expand_one_level(guide, rp); diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 8d15e967ae7..05d3a1040fd 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -6,7 +6,7 @@ use prusti_rustc_interface::{ dataflow::{Analysis, AnalysisDomain, CallReturnPlaces}, - index::vec::Idx, + index::Idx, middle::{ mir::{ visit::Visitor, BasicBlock, Body, Local, Location, Statement, Terminator, RETURN_PLACE, @@ -65,7 +65,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { fn apply_statement_effect( - &self, + &mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, location: Location, @@ -75,7 +75,7 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } fn apply_terminator_effect( - &self, + &mut self, state: &mut Self::Domain, terminator: &Terminator<'tcx>, location: Location, @@ -85,7 +85,7 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } fn apply_call_return_effect( - &self, + &mut self, _state: &mut Self::Domain, _block: BasicBlock, _return_places: CallReturnPlaces<'_, 'tcx>, diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index 54a405e64a0..b5bc981b678 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -8,7 +8,7 @@ use std::fmt::{Debug, Formatter, Result}; use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ - dataflow::fmt::DebugWithContext, index::vec::IndexVec, middle::mir::Local, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, }; use crate::{ diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index f7b3b069754..313d82fb228 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -143,7 +143,7 @@ impl<'tcx> CapabilityProjections<'tcx> { others.push(to); let mut ops = Vec::new(); for (from, to, kind) in expanded { - let others = others.drain_filter(|other| !to.is_prefix(*other)); + let others = others.extract_if(|other| !to.is_prefix(*other)); self.extend(others.map(|p| (p, perm))); if kind.is_box() && perm.is_shallow_exclusive() { ops.push(RepackOp::DerefShallowInit(from, to)); @@ -183,13 +183,13 @@ impl<'tcx> CapabilityProjections<'tcx> { for (to, _, kind) in &collapsed { if kind.is_shared_ref() { let mut is_prefixed = false; - exclusive_at.drain_filter(|old| { + exclusive_at.extract_if(|old| { let cmp = to.either_prefix(*old); if matches!(cmp, Some(false)) { is_prefixed = true; } cmp.unwrap_or_default() - }); + }).for_each(drop); if !is_prefixed { exclusive_at.push(*to); } @@ -199,7 +199,7 @@ impl<'tcx> CapabilityProjections<'tcx> { let mut ops = Vec::new(); for (to, from, _) in collapsed { let removed_perms: Vec<_> = - old_caps.drain_filter(|old, _| to.is_prefix(*old)).collect(); + old_caps.extract_if(|old, _| to.is_prefix(*old)).collect(); let perm = removed_perms .iter() .fold(CapabilityKind::Exclusive, |acc, (_, p)| { diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 973271987cd..c12e425e5fd 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -58,6 +58,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { self.ensures_unalloc(local); } &Retag(_, box place) => self.requires_exclusive(place), + &PlaceMention(box place) => self.requires_write(place), AscribeUserType(..) | Coverage(..) | Intrinsic(..) | ConstEvalCounter | Nop => (), }; } @@ -69,7 +70,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { Goto { .. } | SwitchInt { .. } | Resume - | Abort + | Terminate | Unreachable | Assert { .. } | GeneratorDrop @@ -91,10 +92,6 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { self.requires_write(place); self.ensures_write(place); } - &DropAndReplace { place, .. } => { - self.requires_write(place); - self.ensures_exclusive(place); - } &Call { destination, .. } => { self.requires_write(destination); self.ensures_exclusive(destination); @@ -132,10 +129,6 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { self.requires_read(place); // self.ensures_blocked_read(place); } - BorrowKind::Unique => { - self.requires_exclusive(place); - // self.ensures_blocked_exclusive(place); - } BorrowKind::Mut { .. } => { self.requires_exclusive(place); // self.ensures_blocked_exclusive(place); diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index 928341cb6f3..06f93fae2a8 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -53,7 +53,7 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { fn body(&self) -> &'mir Body<'tcx> { self.cursor.analysis().0.body() } - pub(crate) fn repacker(&self) -> PlaceRepacker<'mir, 'tcx> { + pub(crate) fn repacker(&mut self) -> PlaceRepacker<'mir, 'tcx> { self.cursor.results().analysis.0 } @@ -81,8 +81,8 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { self.end_stmt = None; // TODO: cleanup - let state = self.cursor.get(); let rp: PlaceRepacker = self.repacker(); + let state = self.cursor.get().clone(); let block = &self.body()[location.block]; let succs = block .terminator() diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 217b198fc77..84c6fb4dcb2 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -5,7 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #![feature(rustc_private)] -#![feature(box_patterns, hash_drain_filter, drain_filter)] +#![feature(box_patterns, hash_extract_if, extract_if)] pub mod free_pcs; pub mod utils; diff --git a/mir-state-analysis/src/utils/mutable.rs b/mir-state-analysis/src/utils/mutable.rs index 118c80244f5..a733acdc139 100644 --- a/mir-state-analysis/src/utils/mutable.rs +++ b/mir-state-analysis/src/utils/mutable.rs @@ -7,9 +7,10 @@ use prusti_rustc_interface::{ hir, middle::{ - mir::{Field, Mutability, ProjectionElem}, + mir::{Mutability, ProjectionElem}, ty::{CapturedPlace, TyKind, UpvarCapture}, }, + target::abi::FieldIdx, }; use super::{root_place::RootPlace, Place, PlaceRepacker}; @@ -213,7 +214,7 @@ impl<'tcx> Place<'tcx> { self, upvars: &[Upvar<'tcx>], repacker: PlaceRepacker<'_, 'tcx>, - ) -> Option { + ) -> Option { let mut place_ref = *self; let mut by_ref = false; diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index b8b59e312c1..182fb09a46e 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -10,11 +10,12 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, middle::{ mir::{ - tcx::PlaceTy, Body, Field, HasLocalDecls, Local, Mutability, Place as MirPlace, + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, ProjectionElem, }, ty::{TyCtxt, TyKind}, }, + target::abi::FieldIdx, }; use super::Place; @@ -237,7 +238,7 @@ impl<'tcx> Place<'tcx> { .unwrap_or_else(|| def.non_enum_variant()); for (index, field_def) in variant.fields.iter().enumerate() { if Some(index) != without_field { - let field = Field::from_usize(index); + let field = FieldIdx::from_usize(index); let field_place = repacker.tcx.mk_place_field( self.to_rust_place(repacker), field, @@ -250,7 +251,7 @@ impl<'tcx> Place<'tcx> { TyKind::Tuple(slice) => { for (index, arg) in slice.iter().enumerate() { if Some(index) != without_field { - let field = Field::from_usize(index); + let field = FieldIdx::from_usize(index); let field_place = repacker .tcx @@ -260,9 +261,9 @@ impl<'tcx> Place<'tcx> { } } TyKind::Closure(_, substs) => { - for (index, subst_ty) in substs.as_closure().upvar_tys().enumerate() { + for (index, subst_ty) in substs.as_closure().upvar_tys().iter().enumerate() { if Some(index) != without_field { - let field = Field::from_usize(index); + let field = FieldIdx::from_usize(index); let field_place = repacker.tcx.mk_place_field( self.to_rust_place(repacker), field, @@ -273,9 +274,9 @@ impl<'tcx> Place<'tcx> { } } TyKind::Generator(_, substs, _) => { - for (index, subst_ty) in substs.as_generator().upvar_tys().enumerate() { + for (index, subst_ty) in substs.as_generator().upvar_tys().iter().enumerate() { if Some(index) != without_field { - let field = Field::from_usize(index); + let field = FieldIdx::from_usize(index); let field_place = repacker.tcx.mk_place_field( self.to_rust_place(repacker), field, From 5933a00e79e1e974da59c01acd41a39ed1108ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 17:38:32 +0200 Subject: [PATCH 13/32] fmt --- mir-state-analysis/src/free_pcs/impl/local.rs | 23 +++++++++++-------- mir-state-analysis/src/utils/repacker.rs | 3 +-- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index 313d82fb228..025c7cc0bc3 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -42,7 +42,9 @@ impl Default for CapabilityLocal<'_> { impl<'tcx> CapabilityLocal<'tcx> { pub fn get_allocated_mut(&mut self) -> &mut CapabilityProjections<'tcx> { - let Self::Allocated(cps) = self else { panic!("Expected allocated local") }; + let Self::Allocated(cps) = self else { + panic!("Expected allocated local") + }; cps } pub fn new(local: Local, perm: CapabilityKind) -> Self { @@ -183,13 +185,15 @@ impl<'tcx> CapabilityProjections<'tcx> { for (to, _, kind) in &collapsed { if kind.is_shared_ref() { let mut is_prefixed = false; - exclusive_at.extract_if(|old| { - let cmp = to.either_prefix(*old); - if matches!(cmp, Some(false)) { - is_prefixed = true; - } - cmp.unwrap_or_default() - }).for_each(drop); + exclusive_at + .extract_if(|old| { + let cmp = to.either_prefix(*old); + if matches!(cmp, Some(false)) { + is_prefixed = true; + } + cmp.unwrap_or_default() + }) + .for_each(drop); if !is_prefixed { exclusive_at.push(*to); } @@ -198,8 +202,7 @@ impl<'tcx> CapabilityProjections<'tcx> { } let mut ops = Vec::new(); for (to, from, _) in collapsed { - let removed_perms: Vec<_> = - old_caps.extract_if(|old, _| to.is_prefix(*old)).collect(); + let removed_perms: Vec<_> = old_caps.extract_if(|old, _| to.is_prefix(*old)).collect(); let perm = removed_perms .iter() .fold(CapabilityKind::Exclusive, |acc, (_, p)| { diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 182fb09a46e..8a43a3b9e6c 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -10,8 +10,7 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, middle::{ mir::{ - tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, - ProjectionElem, + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, ProjectionElem, }, ty::{TyCtxt, TyKind}, }, From 496ebe11bf08b1503b9327b8c0f73778e8644017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 17:50:31 +0200 Subject: [PATCH 14/32] Fix merge issues --- mir-state-analysis/src/free_pcs/impl/engine.rs | 12 +++++++----- mir-state-analysis/src/free_pcs/impl/triple.rs | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 05d3a1040fd..30375c3dd5c 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -5,11 +5,12 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ - dataflow::{Analysis, AnalysisDomain, CallReturnPlaces}, + dataflow::{Analysis, AnalysisDomain}, index::Idx, middle::{ mir::{ - visit::Visitor, BasicBlock, Body, Local, Location, Statement, Terminator, RETURN_PLACE, + visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Location, Statement, + Terminator, TerminatorEdges, RETURN_PLACE, }, ty::TyCtxt, }, @@ -74,14 +75,15 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { state.visit_statement(statement, location); } - fn apply_terminator_effect( + fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, - terminator: &Terminator<'tcx>, + terminator: &'mir Terminator<'tcx>, location: Location, - ) { + ) -> TerminatorEdges<'mir, 'tcx> { state.repackings.clear(); state.visit_terminator(terminator, location); + terminator.edges() } fn apply_call_return_effect( diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index c12e425e5fd..2f1ed23dc6c 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -69,8 +69,8 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { match &terminator.kind { Goto { .. } | SwitchInt { .. } - | Resume - | Terminate + | UnwindResume + | UnwindTerminate(_) | Unreachable | Assert { .. } | GeneratorDrop From 66f7ea2e14bc2dba199bcc0939a1848b014c0450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 17:58:21 +0200 Subject: [PATCH 15/32] Clippy fixes --- mir-state-analysis/src/utils/place.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 1dc12ded632..03784be3f19 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -284,16 +284,16 @@ impl Debug for Place<'_> { } ProjectionElem::Subslice { from, - to, + to: 0, from_end: true, - } if to == 0 => { + } => { write!(fmt, "[{from:?}:]")?; } ProjectionElem::Subslice { - from, + from: 0, to, from_end: true, - } if from == 0 => { + } => { write!(fmt, "[:-{to:?}]")?; } ProjectionElem::Subslice { From 362788cc089ab7101c912a4c7656432bce09d2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 14:47:27 +0200 Subject: [PATCH 16/32] Bugfix for shallow exclusive --- mir-state-analysis/src/free_pcs/impl/engine.rs | 2 ++ mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs | 5 ++--- mir-state-analysis/src/free_pcs/impl/local.rs | 2 +- mir-state-analysis/src/free_pcs/impl/place.rs | 3 +++ mir-state-analysis/src/lib.rs | 1 + mir-state-analysis/src/utils/repacker.rs | 3 ++- prusti/src/callbacks.rs | 8 ++++---- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 30375c3dd5c..a5a555cae2c 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -65,6 +65,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { + #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] fn apply_statement_effect( &mut self, state: &mut Self::Domain, @@ -75,6 +76,7 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { state.visit_statement(statement, location); } + #[tracing::instrument(name = "apply_terminator_effect", level = "debug", skip(self))] fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 35b277f9d51..50f524c2ced 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -14,6 +14,7 @@ use crate::{ }; impl JoinSemiLattice for Fpcs<'_, '_> { + #[tracing::instrument(name = "Fpcs::join", level = "debug")] fn join(&mut self, other: &Self) -> bool { assert!(!other.bottom); if self.bottom { @@ -121,9 +122,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { PlaceOrdering::Suffix => { // Downgrade the permission if needed for &(p, k) in &related.from { - if !self.contains_key(&p) { - continue; - } + assert!(self.contains_key(&p)); let p = if kind != CapabilityKind::Exclusive { if let Some(to) = p.projects_ptr(repacker) { changed = true; diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index 025c7cc0bc3..fda663d4cd7 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -87,7 +87,7 @@ impl<'tcx> CapabilityProjections<'tcx> { /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] /// It also checks that the ordering conforms to the expected ordering (the above would /// fail in any situation since all orderings need to be the same) - #[tracing::instrument(level = "debug", skip(self))] + #[tracing::instrument(level = "debug", ret)] pub(crate) fn find_all_related( &self, to: Place<'tcx>, diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index 777be5b8209..259dd639036 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -56,6 +56,7 @@ impl Debug for CapabilityKind { } impl PartialOrd for CapabilityKind { + #[tracing::instrument(name = "CapabilityKind::partial_cmp", level = "trace", ret)] fn partial_cmp(&self, other: &Self) -> Option { if *self == *other { return Some(Ordering::Equal); @@ -63,9 +64,11 @@ impl PartialOrd for CapabilityKind { match (self, other) { // W < E, W < e (CapabilityKind::Write, CapabilityKind::Exclusive) + | (CapabilityKind::ShallowExclusive, CapabilityKind::Exclusive) | (CapabilityKind::Write, CapabilityKind::ShallowExclusive) => Some(Ordering::Less), // E > W, e > W (CapabilityKind::Exclusive, CapabilityKind::Write) + | (CapabilityKind::Exclusive, CapabilityKind::ShallowExclusive) | (CapabilityKind::ShallowExclusive, CapabilityKind::Write) => Some(Ordering::Greater), _ => None, } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 84c6fb4dcb2..cd778866e60 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -15,6 +15,7 @@ use prusti_rustc_interface::{ middle::{mir::Body, ty::TyCtxt}, }; +#[tracing::instrument(name = "run_free_pcs", level = "debug", skip(tcx))] pub fn run_free_pcs<'mir, 'tcx>( mir: &'mir Body<'tcx>, tcx: TyCtxt<'tcx>, diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 8a43a3b9e6c..0460af82b19 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -346,8 +346,9 @@ impl<'tcx> Place<'tcx> { .is_some() } + #[tracing::instrument(name = "Place::projects_ptr", level = "trace", skip(repacker), ret)] pub fn projects_ptr(self, repacker: PlaceRepacker<'_, 'tcx>) -> Option> { - self.projects_ty(|typ| typ.ty.is_ref() || typ.ty.is_unsafe_ptr(), repacker) + self.projects_ty(|typ| typ.ty.is_ref() || typ.ty.is_box() || typ.ty.is_unsafe_ptr(), repacker) } pub fn can_deinit(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index d9f3216e034..b0ca261727b 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -35,7 +35,7 @@ fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &BorrowCheckResu // when calling `get_body_with_borrowck_facts`. TODO: figure out if we need // (anon) const bodies at all, and if so, how to get them? if !is_anon_const { - let consumer_opts = if is_spec_fn(tcx, def_id.to_def_id()) || config::no_verify() { + let consumer_opts = if is_spec_fn(tcx, def_id.to_def_id()) || config::no_verify() || config::test_free_pcs() { consumers::ConsumerOptions::RegionInferenceContext } else { consumers::ConsumerOptions::PoloniusOutputFacts @@ -160,11 +160,11 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { if !config::no_verify() { if config::test_free_pcs() { for proc_id in env.get_annotated_procedures_and_types().0.iter() { - let name = env.name.get_unique_item_name(*proc_id); - println!("Calculating FPCS for: {name}"); - let current_procedure = env.get_procedure(*proc_id); let mir = current_procedure.get_mir_rc(); + + let name = env.name.get_unique_item_name(*proc_id); + println!("Calculating FPCS for: {name} ({:?})", mir.span); test_free_pcs(&mir, tcx); } } else { From 813c87f0c268e9891fa9e8114c7c186d8f8ca9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 14:52:58 +0200 Subject: [PATCH 17/32] Remove check --- mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 50f524c2ced..035910499fe 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -122,7 +122,11 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { PlaceOrdering::Suffix => { // Downgrade the permission if needed for &(p, k) in &related.from { - assert!(self.contains_key(&p)); + // Might not contain key if `p.projects_ptr(repacker)` + // returned `Some` in a previous iteration. + if !self.contains_key(&p) { + continue; + } let p = if kind != CapabilityKind::Exclusive { if let Some(to) = p.projects_ptr(repacker) { changed = true; From 55bd03941ebfe9410933afa70b0c37b7dc7e6832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 14:53:20 +0200 Subject: [PATCH 18/32] fmt --- mir-state-analysis/src/utils/repacker.rs | 5 ++++- prusti/src/callbacks.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 0460af82b19..b2b5c60b52e 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -348,7 +348,10 @@ impl<'tcx> Place<'tcx> { #[tracing::instrument(name = "Place::projects_ptr", level = "trace", skip(repacker), ret)] pub fn projects_ptr(self, repacker: PlaceRepacker<'_, 'tcx>) -> Option> { - self.projects_ty(|typ| typ.ty.is_ref() || typ.ty.is_box() || typ.ty.is_unsafe_ptr(), repacker) + self.projects_ty( + |typ| typ.ty.is_ref() || typ.ty.is_box() || typ.ty.is_unsafe_ptr(), + repacker, + ) } pub fn can_deinit(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index b0ca261727b..a430b8255d3 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -35,7 +35,10 @@ fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &BorrowCheckResu // when calling `get_body_with_borrowck_facts`. TODO: figure out if we need // (anon) const bodies at all, and if so, how to get them? if !is_anon_const { - let consumer_opts = if is_spec_fn(tcx, def_id.to_def_id()) || config::no_verify() || config::test_free_pcs() { + let consumer_opts = if is_spec_fn(tcx, def_id.to_def_id()) + || config::no_verify() + || config::test_free_pcs() + { consumers::ConsumerOptions::RegionInferenceContext } else { consumers::ConsumerOptions::PoloniusOutputFacts From 135065c3c52f9e09b2f7f3d09e50dcd7e2f68b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 16:50:04 +0200 Subject: [PATCH 19/32] Bugfix --- .../src/free_pcs/impl/join_semi_lattice.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 035910499fe..4ed76a8ebaf 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -102,10 +102,12 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { let related = self.find_all_related(place, None); let final_place = match related.relation { PlaceOrdering::Prefix => { - changed = true; - let from = related.get_only_from(); - let joinable_place = if self[&from] != CapabilityKind::Exclusive { + let perms_eq = self[&from] == kind; + let joinable_place = if self[&from] != CapabilityKind::Exclusive && !perms_eq { + // If I have `Write` or `ShallowExclusive` and the other is different, I need to join + // above any pointers I may be projected through. + // TODO: imo if `projects_ptr` ever returns `Some` we will fail the `assert` below... place .projects_ptr(repacker) .unwrap_or_else(|| from.joinable_to(place)) @@ -114,6 +116,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { }; assert!(from.is_prefix(joinable_place)); if joinable_place != from { + changed = true; self.expand(from, joinable_place, repacker); } Some(joinable_place) @@ -127,7 +130,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { if !self.contains_key(&p) { continue; } - let p = if kind != CapabilityKind::Exclusive { + let perms_eq = k == kind; + let p = if kind != CapabilityKind::Exclusive && !perms_eq { if let Some(to) = p.projects_ptr(repacker) { changed = true; let related = self.find_all_related(to, None); @@ -158,6 +162,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { if let Some(place) = final_place { // Downgrade the permission if needed if self[&place] > kind { + changed = true; self.insert(place, kind); } } From a7a892bd829841735454b747a9aaa1aac7021ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 17:45:09 +0200 Subject: [PATCH 20/32] Fix `PlaceMention` triple --- mir-state-analysis/src/free_pcs/impl/triple.rs | 3 +-- mir-state-analysis/src/free_pcs/impl/update.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 2f1ed23dc6c..0b7b82e278b 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -42,7 +42,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { _ => unreachable!(), } } - &FakeRead(box (_, place)) => self.requires_read(place), + &FakeRead(box (_, place)) | &PlaceMention(box place) => self.requires_read(place), &SetDiscriminant { box place, .. } => self.requires_exclusive(place), &Deinit(box place) => { // TODO: Maybe OK to also allow `Write` here? @@ -58,7 +58,6 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { self.ensures_unalloc(local); } &Retag(_, box place) => self.requires_exclusive(place), - &PlaceMention(box place) => self.requires_write(place), AscribeUserType(..) | Coverage(..) | Intrinsic(..) | ConstEvalCounter | Nop => (), }; } diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index dc4d18ded40..40c483ba431 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -10,14 +10,17 @@ use crate::{ free_pcs::{CapabilityKind, CapabilityLocal, CapabilityProjections, Fpcs, RepackOp}, utils::{LocalMutationIsAllowed, Place, PlaceOrdering, PlaceRepacker}, }; +use std::fmt::Debug; impl<'tcx> Fpcs<'_, 'tcx> { + #[tracing::instrument(name = "Fpcs::requires_unalloc", level = "trace")] pub(crate) fn requires_unalloc(&mut self, local: Local) { assert!( self.summary[local].is_unallocated(), "local: {local:?}, fpcs: {self:?}\n" ); } + #[tracing::instrument(name = "Fpcs::requires_unalloc_or_uninit", level = "trace")] pub(crate) fn requires_unalloc_or_uninit(&mut self, local: Local) { if !self.summary[local].is_unallocated() { self.requires_write(local) @@ -25,12 +28,14 @@ impl<'tcx> Fpcs<'_, 'tcx> { self.repackings.push(RepackOp::IgnoreStorageDead(local)) } } - pub(crate) fn requires_read(&mut self, place: impl Into>) { + #[tracing::instrument(name = "Fpcs::requires_read", level = "trace")] + pub(crate) fn requires_read(&mut self, place: impl Into> + Debug) { self.requires(place, CapabilityKind::Exclusive) } /// May obtain write _or_ exclusive, if one should only have write afterwards, /// make sure to also call `ensures_write`! - pub(crate) fn requires_write(&mut self, place: impl Into>) { + #[tracing::instrument(name = "Fpcs::requires_write", level = "trace")] + pub(crate) fn requires_write(&mut self, place: impl Into> + Debug) { let place = place.into(); // Cannot get write on a shared ref assert!(place @@ -38,7 +43,8 @@ impl<'tcx> Fpcs<'_, 'tcx> { .is_ok()); self.requires(place, CapabilityKind::Write) } - pub(crate) fn requires_exclusive(&mut self, place: impl Into>) { + #[tracing::instrument(name = "Fpcs::requires_write", level = "trace")] + pub(crate) fn requires_exclusive(&mut self, place: impl Into> + Debug) { let place = place.into(); // Cannot get exclusive on a shared ref assert!(!place.projects_shared_ref(self.repacker)); From 62f8e6225ce9b71d50decbe53529608a33b97d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 18:39:56 +0200 Subject: [PATCH 21/32] Fix `can_deinit` check --- mir-state-analysis/src/utils/repacker.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index b2b5c60b52e..d9300973593 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -355,7 +355,19 @@ impl<'tcx> Place<'tcx> { } pub fn can_deinit(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { - !self.projects_shared_ref(repacker) + let mut projects_shared_ref = false; + self.projects_ty( + |typ| { + projects_shared_ref = projects_shared_ref || typ.ty + .ref_mutability() + .map(|m| m.is_not()) + .unwrap_or_default(); + projects_shared_ref = projects_shared_ref && !typ.ty.is_unsafe_ptr(); + false + }, + repacker, + ); + !projects_shared_ref } pub fn projects_ty( From 17ed9fe367a05c8d0a6a4e6d73ee754b81581a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 29 Sep 2023 17:02:21 +0200 Subject: [PATCH 22/32] fmt --- mir-state-analysis/src/utils/repacker.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index d9300973593..a76c0a8e6c7 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -358,10 +358,12 @@ impl<'tcx> Place<'tcx> { let mut projects_shared_ref = false; self.projects_ty( |typ| { - projects_shared_ref = projects_shared_ref || typ.ty - .ref_mutability() - .map(|m| m.is_not()) - .unwrap_or_default(); + projects_shared_ref = projects_shared_ref + || typ + .ty + .ref_mutability() + .map(|m| m.is_not()) + .unwrap_or_default(); projects_shared_ref = projects_shared_ref && !typ.ty.is_unsafe_ptr(); false }, From 210d37aa3b67a97aecd8f9b2b7770d17f6d3be4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 12 Oct 2023 12:02:59 +0200 Subject: [PATCH 23/32] Fix bug in `ConstantIndex` unpacking --- .../src/free_pcs/results/repacks.rs | 5 +- mir-state-analysis/src/utils/place.rs | 65 ++++--------------- mir-state-analysis/src/utils/repacker.rs | 16 ++--- 3 files changed, 25 insertions(+), 61 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs index bd725d390d7..c32c45f5555 100644 --- a/mir-state-analysis/src/free_pcs/results/repacks.rs +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -43,7 +43,10 @@ pub enum RepackOp<'tcx> { /// We guarantee that the current state holds exactly the given capability for the given place. /// The second place is the guide, denoting e.g. the enum variant to unpack to. One can use /// [`Place::expand_one_level(_.0, _.1, ..)`](Place::expand_one_level) to get the set of all - /// places which will be obtained by unpacking. + /// places (except as noted in the documentation for that fn) which will be obtained by unpacking. + /// + /// Until rust-lang/rust#21232 lands, we guarantee that this will only have + /// [`CapabilityKind::Exclusive`]. Expand(Place<'tcx>, Place<'tcx>, CapabilityKind), /// Instructs that one should pack up to the given (first) place with the given capability. /// The second place is the guide, denoting e.g. the enum variant to pack from. One can use diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 03784be3f19..19d332ff454 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -49,34 +49,6 @@ use prusti_rustc_interface::middle::mir::{ // } // } -fn elem_eq<'tcx>(to_cmp: (PlaceElem<'tcx>, PlaceElem<'tcx>)) -> bool { - use ProjectionElem::*; - match to_cmp { - (Field(left, _), Field(right, _)) => left == right, - ( - ConstantIndex { - offset: lo, - min_length: lml, - from_end: lfe, - }, - ConstantIndex { - offset: ro, - min_length: rml, - from_end: rfe, - }, - ) => { - lml == rml - && (if lfe == rfe { - lo == ro - } else { - (lml - lo) == ro - }) - } - (Downcast(_, left), Downcast(_, right)) => left == right, - (left, right) => left == right, - } -} - #[derive(Clone, Copy, Deref, DerefMut)] pub struct Place<'tcx>(PlaceRef<'tcx>); @@ -127,8 +99,8 @@ impl<'tcx> Place<'tcx> { } match (left, right) { (Field(..), Field(..)) => None, - (ConstantIndex { min_length: l, .. }, ConstantIndex { min_length: r, .. }) - if r == l => + (ConstantIndex { min_length: l, from_end: lfe, .. }, ConstantIndex { min_length: r, from_end: rfe, .. }) + if r == l && lfe == rfe => { None } @@ -317,6 +289,15 @@ impl Debug for Place<'_> { } } +fn elem_eq<'tcx>(to_cmp: (PlaceElem<'tcx>, PlaceElem<'tcx>)) -> bool { + use ProjectionElem::*; + match to_cmp { + (Field(left, _), Field(right, _)) => left == right, + (Downcast(_, left), Downcast(_, right)) => left == right, + (left, right) => left == right, + } +} + impl PartialEq for Place<'_> { fn eq(&self, other: &Self) -> bool { self.local == other.local @@ -336,29 +317,11 @@ impl Hash for Place<'_> { discriminant(&pe).hash(state); field.hash(state); } - ProjectionElem::ConstantIndex { - offset, - min_length, - from_end, - } => { + ProjectionElem::Downcast(_, variant) => { discriminant(&pe).hash(state); - let offset = if from_end { - min_length - offset - } else { - offset - }; - offset.hash(state); - min_length.hash(state); + variant.hash(state); } - pe => { - pe.hash(state); - } - } - if let ProjectionElem::Field(field, _) = pe { - discriminant(&pe).hash(state); - field.hash(state); - } else { - pe.hash(state); + _ => pe.hash(state), } } } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index a76c0a8e6c7..c8809996461 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -145,7 +145,9 @@ impl<'tcx> Place<'tcx> { /// Expand `self` one level down by following the `guide_place`. /// Returns the new `self` and a vector containing other places that - /// could have resulted from the expansion. + /// could have resulted from the expansion. Note: this vector is always + /// incomplete when projecting with `Index` or `Subslice` and also when + /// projecting a slice type with `ConstantIndex`! #[tracing::instrument(level = "trace", skip(repacker), ret)] pub fn expand_one_level( self, @@ -170,14 +172,10 @@ impl<'tcx> Place<'tcx> { min_length, from_end, } => { - let other_places = (0..min_length) - .filter(|&i| { - if from_end { - i != min_length - offset - } else { - i != offset - } - }) + let range = if from_end { 1..min_length+1 } else { 0..min_length }; + assert!(range.contains(&offset)); + let other_places = range + .filter(|&i| i != offset) .map(|i| { repacker .tcx From 4047bebad1c2c46ba7f731336eddc31f38595e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 12 Oct 2023 14:27:11 +0200 Subject: [PATCH 24/32] Ensure that we never expand a Write capability --- .../src/free_pcs/check/checker.rs | 2 ++ .../src/free_pcs/check/consistency.rs | 6 +++++ .../src/free_pcs/impl/join_semi_lattice.rs | 26 +++++-------------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index e3299398fb7..e65f83ed99b 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -121,6 +121,7 @@ impl<'tcx> RepackOp<'tcx> { assert_eq!(old, Some(from), "{self:?}, {curr_state:?}"); } RepackOp::Expand(place, guide, kind) => { + assert_eq!(kind, CapabilityKind::Exclusive, "{self:?}"); assert!(place.is_prefix_exact(guide), "{self:?}"); let curr_state = state[place.local].get_allocated_mut(); assert_eq!( @@ -134,6 +135,7 @@ impl<'tcx> RepackOp<'tcx> { curr_state.extend(others.into_iter().map(|p| (p, kind))); } RepackOp::Collapse(place, guide, kind) => { + assert_ne!(kind, CapabilityKind::ShallowExclusive, "{self:?}"); assert!(place.is_prefix_exact(guide), "{self:?}"); let curr_state = state[place.local].get_allocated_mut(); let mut removed = curr_state diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs index 77a1ba62462..af5f2fa7a84 100644 --- a/mir-state-analysis/src/free_pcs/check/consistency.rs +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -42,6 +42,12 @@ impl<'tcx> CapabilityConistency<'tcx> for CapabilityProjections<'tcx> { if !p1.can_deinit(repacker) { assert!(matches!(self[p1], CapabilityKind::Exclusive), "{self:?}"); } + // Cannot have Read or None here + assert!(self[p1] >= CapabilityKind::Write); + // Can only have `ShallowExclusive` for box typed places + if self[p1].is_shallow_exclusive() { + assert!(p1.ty(repacker).ty.is_box()); + } } // Can always pack up to the root let root: Place = self.get_local().into(); diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index 4ed76a8ebaf..e2880d86002 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -103,14 +103,9 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { let final_place = match related.relation { PlaceOrdering::Prefix => { let from = related.get_only_from(); - let perms_eq = self[&from] == kind; - let joinable_place = if self[&from] != CapabilityKind::Exclusive && !perms_eq { - // If I have `Write` or `ShallowExclusive` and the other is different, I need to join - // above any pointers I may be projected through. - // TODO: imo if `projects_ptr` ever returns `Some` we will fail the `assert` below... - place - .projects_ptr(repacker) - .unwrap_or_else(|| from.joinable_to(place)) + let joinable_place = if self[&from] != CapabilityKind::Exclusive { + // One cannot expand a `Write` or a `ShallowInit` capability + from } else { from.joinable_to(place) }; @@ -130,17 +125,10 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { if !self.contains_key(&p) { continue; } - let perms_eq = k == kind; - let p = if kind != CapabilityKind::Exclusive && !perms_eq { - if let Some(to) = p.projects_ptr(repacker) { - changed = true; - let related = self.find_all_related(to, None); - assert_eq!(related.relation, PlaceOrdering::Suffix); - self.collapse(related.get_from(), related.to, repacker); - to - } else { - p - } + let p = if kind != CapabilityKind::Exclusive { + changed = true; + self.collapse(related.get_from(), related.to, repacker); + related.to } else { p }; From 4467796c72e8d2a8ed1e7153a8c6e80e8317ad5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 12 Oct 2023 14:28:51 +0200 Subject: [PATCH 25/32] fmt --- mir-state-analysis/src/utils/place.rs | 17 ++++++++++++----- mir-state-analysis/src/utils/repacker.rs | 6 +++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 19d332ff454..64cb9dc2b48 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -99,11 +99,18 @@ impl<'tcx> Place<'tcx> { } match (left, right) { (Field(..), Field(..)) => None, - (ConstantIndex { min_length: l, from_end: lfe, .. }, ConstantIndex { min_length: r, from_end: rfe, .. }) - if r == l && lfe == rfe => - { - None - } + ( + ConstantIndex { + min_length: l, + from_end: lfe, + .. + }, + ConstantIndex { + min_length: r, + from_end: rfe, + .. + }, + ) if r == l && lfe == rfe => None, (Downcast(_, _), Downcast(_, _)) | (OpaqueCast(_), OpaqueCast(_)) => { Some(PlaceOrdering::Both) } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index c8809996461..f3e95c92fba 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -172,7 +172,11 @@ impl<'tcx> Place<'tcx> { min_length, from_end, } => { - let range = if from_end { 1..min_length+1 } else { 0..min_length }; + let range = if from_end { + 1..min_length + 1 + } else { + 0..min_length + }; assert!(range.contains(&offset)); let other_places = range .filter(|&i| i != offset) From 8e4030cf498bb18ab85fd7830c738a6ca01f5745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 20 Oct 2023 16:11:19 +0200 Subject: [PATCH 26/32] Add weakens before write --- mir-state-analysis/src/free_pcs/impl/triple.rs | 5 +++-- mir-state-analysis/src/free_pcs/impl/update.rs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 0b7b82e278b..8696ff6d2d6 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -79,13 +79,14 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { let always_live = self.repacker.always_live_locals(); for local in 0..self.repacker.local_count() { let local = Local::from_usize(local); - if always_live.contains(local) { + if local == RETURN_PLACE { + self.requires_exclusive(RETURN_PLACE); + } else if always_live.contains(local) { self.requires_write(local); } else { self.requires_unalloc(local); } } - self.requires_exclusive(RETURN_PLACE); } &Drop { place, .. } => { self.requires_write(place); diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 40c483ba431..70933df0cd3 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -55,8 +55,11 @@ impl<'tcx> Fpcs<'_, 'tcx> { let cp: &mut CapabilityProjections = self.summary[place.local].get_allocated_mut(); let ops = cp.repack(place, self.repacker); self.repackings.extend(ops); - let kind = (*cp)[&place]; + let kind = cp.insert(place, cap).unwrap(); assert!(kind >= cap); + if kind != cap { + self.repackings.push(RepackOp::Weaken(place, kind, cap)); + } } pub(crate) fn ensures_unalloc(&mut self, local: Local) { From 7e12a2e78f772b3b8740acfc7a8cdb97c6ef9fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 25 Oct 2023 10:16:55 +0100 Subject: [PATCH 27/32] Bugfix to support other capabilities --- mir-state-analysis/src/free_pcs/impl/update.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 70933df0cd3..23bcd14981e 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -55,11 +55,15 @@ impl<'tcx> Fpcs<'_, 'tcx> { let cp: &mut CapabilityProjections = self.summary[place.local].get_allocated_mut(); let ops = cp.repack(place, self.repacker); self.repackings.extend(ops); - let kind = cp.insert(place, cap).unwrap(); + let kind = cp[&place]; + if cap.is_write() { + // Requires write should deinit an exclusive + cp.insert(place, cap); + if kind != cap { + self.repackings.push(RepackOp::Weaken(place, kind, cap)); + } + }; assert!(kind >= cap); - if kind != cap { - self.repackings.push(RepackOp::Weaken(place, kind, cap)); - } } pub(crate) fn ensures_unalloc(&mut self, local: Local) { From 786a22d05df068a2a18ccdfaabf4d92bda406766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 25 Oct 2023 14:47:02 +0100 Subject: [PATCH 28/32] Add pre and post distinction --- .../src/free_pcs/check/checker.rs | 29 ++++++++++++++++--- .../src/free_pcs/impl/engine.rs | 23 +++++++++++++-- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 2 ++ .../src/free_pcs/impl/triple.rs | 10 +++++-- .../src/free_pcs/results/cursor.rs | 6 ++++ 5 files changed, 62 insertions(+), 8 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index e65f83ed99b..63fc3c7bae2 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -25,6 +25,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { cursor.analysis_for_bb(block); let mut fpcs = Fpcs { summary: cursor.initial_state().clone(), + apply_pre_effect: true, bottom: false, repackings: Vec::new(), repacker: rp, @@ -39,13 +40,23 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { let fpcs_after = cursor.next(loc); assert_eq!(fpcs_after.location, loc); // Repacks - for op in fpcs_after.repacks { + for &op in &fpcs_after.repacks_middle { op.update_free(&mut fpcs.summary, false, rp); } // Consistency fpcs.summary.consistency_check(rp); - // Statement + // Statement pre + assert!(fpcs.repackings.is_empty()); + fpcs.apply_pre_effect = true; + fpcs.visit_statement(stmt, loc); assert!(fpcs.repackings.is_empty()); + + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Statement post + fpcs.apply_pre_effect = false; fpcs.visit_statement(stmt, loc); assert!(fpcs.repackings.is_empty()); // Consistency @@ -58,13 +69,23 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { let fpcs_after = cursor.next(loc); assert_eq!(fpcs_after.location, loc); // Repacks - for op in fpcs_after.repacks { + for op in fpcs_after.repacks_middle { op.update_free(&mut fpcs.summary, false, rp); } // Consistency fpcs.summary.consistency_check(rp); - // Statement + // Statement pre + assert!(fpcs.repackings.is_empty()); + fpcs.apply_pre_effect = true; + fpcs.visit_terminator(data.terminator(), loc); assert!(fpcs.repackings.is_empty()); + + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Statement post + fpcs.apply_pre_effect = false; fpcs.visit_terminator(data.terminator(), loc); assert!(fpcs.repackings.is_empty()); // Consistency diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index a5a555cae2c..f3f85d8a00b 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -65,7 +65,13 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { - #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] + #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_before_statement_effect", level = "debug", skip(self))] + fn apply_before_statement_effect(&mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, location: Location) { + state.repackings.clear(); + state.apply_pre_effect = true; + state.visit_statement(statement, location); + } + #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_statement_effect", level = "debug", skip(self))] fn apply_statement_effect( &mut self, state: &mut Self::Domain, @@ -73,10 +79,22 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { location: Location, ) { state.repackings.clear(); + state.apply_pre_effect = false; state.visit_statement(statement, location); } - #[tracing::instrument(name = "apply_terminator_effect", level = "debug", skip(self))] + #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_before_terminator_effect", level = "debug", skip(self))] + fn apply_before_terminator_effect( + &mut self, + state: &mut Self::Domain, + terminator: &Terminator<'tcx>, + location: Location, + ) { + state.repackings.clear(); + state.apply_pre_effect = true; + state.visit_terminator(terminator, location); + } + #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_terminator_effect", level = "debug", skip(self))] fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, @@ -84,6 +102,7 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { location: Location, ) -> TerminatorEdges<'mir, 'tcx> { state.repackings.clear(); + state.apply_pre_effect = false; state.visit_terminator(terminator, location); terminator.edges() } diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index b5bc981b678..966a3a855b4 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -22,6 +22,7 @@ use crate::{ pub struct Fpcs<'a, 'tcx> { pub(crate) repacker: PlaceRepacker<'a, 'tcx>, pub(crate) bottom: bool, + pub(crate) apply_pre_effect: bool, pub summary: CapabilitySummary<'tcx>, pub repackings: Vec>, } @@ -31,6 +32,7 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { Self { repacker, bottom: true, + apply_pre_effect: true, summary, repackings: Vec::new(), } diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 8696ff6d2d6..c995adf2df5 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -30,7 +30,10 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { } fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { - self.super_statement(statement, location); + if self.apply_pre_effect { + self.super_statement(statement, location); + return; + } use StatementKind::*; match &statement.kind { Assign(box (place, rvalue)) => { @@ -63,7 +66,10 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { } fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { - self.super_terminator(terminator, location); + if self.apply_pre_effect { + self.super_terminator(terminator, location); + return; + } use TerminatorKind::*; match &terminator.kind { Goto { .. } diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index 06f93fae2a8..26615a30c6f 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -66,12 +66,15 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { assert!(location < self.end_stmt.unwrap()); self.curr_stmt = Some(location.successor_within_block()); + self.cursor.seek_before_primary_effect(location); + let repacks_middle = self.cursor.get().repackings.clone(); self.cursor.seek_after_primary_effect(location); let state = self.cursor.get(); FreePcsLocation { location, state: state.summary.clone(), repacks: state.repackings.clone(), + repacks_middle, } } pub fn terminator(&mut self) -> FreePcsTerminator<'tcx> { @@ -97,6 +100,7 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { }, state: to.summary.clone(), repacks: state.summary.bridge(&to.summary, rp), + repacks_middle: Vec::new(), } }) .collect(); @@ -130,6 +134,8 @@ pub struct FreePcsLocation<'tcx> { pub location: Location, /// Repacks before the statement pub repacks: Vec>, + /// Repacks in the middle of the statement + pub repacks_middle: Vec>, /// State after the statement pub state: CapabilitySummary<'tcx>, } From 87d41e8202fe984511caf522c1900ec267953981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 25 Oct 2023 14:56:39 +0100 Subject: [PATCH 29/32] Add `ty` utility function --- mir-state-analysis/src/utils/mutable.rs | 2 +- mir-state-analysis/src/utils/repacker.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mir-state-analysis/src/utils/mutable.rs b/mir-state-analysis/src/utils/mutable.rs index a733acdc139..45e7bafa315 100644 --- a/mir-state-analysis/src/utils/mutable.rs +++ b/mir-state-analysis/src/utils/mutable.rs @@ -90,7 +90,7 @@ impl<'tcx> Place<'tcx> { Some((place_base, elem)) => { match elem { ProjectionElem::Deref => { - let base_ty = place_base.ty(repacker.body(), repacker.tcx).ty; + let base_ty = place_base.ty(repacker).ty; // Check the kind of deref to decide match base_ty.kind() { diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index f3e95c92fba..ad9a3ad8ba8 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -10,7 +10,7 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, middle::{ mir::{ - tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, ProjectionElem, + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, ProjectionElem, PlaceRef, }, ty::{TyCtxt, TyKind}, }, @@ -197,7 +197,7 @@ impl<'tcx> Place<'tcx> { (other_places, ProjectionRefKind::Other) } ProjectionElem::Deref => { - let typ = self.ty(repacker.mir, repacker.tcx); + let typ = self.ty(repacker); let kind = match typ.ty.kind() { TyKind::Ref(_, _, mutbl) => ProjectionRefKind::Ref(*mutbl), TyKind::RawPtr(ptr) => ProjectionRefKind::RawPtr(ptr.mutbl), @@ -224,7 +224,7 @@ impl<'tcx> Place<'tcx> { repacker: PlaceRepacker<'_, 'tcx>, ) -> Vec { let mut places = Vec::new(); - let typ = self.ty(repacker.mir, repacker.tcx); + let typ = self.ty(repacker); if !matches!(typ.ty.kind(), TyKind::Adt(..)) { assert!( typ.variant_index.is_none(), @@ -325,9 +325,13 @@ impl<'tcx> Place<'tcx> { // } // } + pub fn ty(self, repacker: PlaceRepacker<'_, 'tcx>) -> PlaceTy<'tcx> { + PlaceRef::ty(&*self, repacker.mir, repacker.tcx) + } + /// Should only be called on a `Place` obtained from `RootPlace::get_parent`. pub fn get_ref_mutability(self, repacker: PlaceRepacker<'_, 'tcx>) -> Mutability { - let typ = self.ty(repacker.mir, repacker.tcx); + let typ = self.ty(repacker); if let TyKind::Ref(_, _, mutability) = typ.ty.kind() { *mutability } else { From 09dcd1ee42cfed902f577cfe5aadb083f2f94af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 13 Nov 2023 16:11:32 +0100 Subject: [PATCH 30/32] fmt and clippy --- analysis/tests/utils.rs | 12 ++++--- .../src/free_pcs/impl/engine.rs | 31 ++++++++++++++++--- mir-state-analysis/src/utils/repacker.rs | 5 +-- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/analysis/tests/utils.rs b/analysis/tests/utils.rs index 397f53aeaa6..b9ef0a0cdd9 100644 --- a/analysis/tests/utils.rs +++ b/analysis/tests/utils.rs @@ -31,12 +31,16 @@ pub fn find_compiled_executable(name: &str) -> PathBuf { } pub fn find_sysroot() -> String { - // Taken from https://github.com/Manishearth/rust-clippy/pull/911. - let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); - let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); + // Taken from https://github.com/rust-lang/rust-clippy/commit/f5db351a1d502cb65f8807ec2c84f44756099ef3. + let home = std::env::var("RUSTUP_HOME") + .or_else(|_| std::env::var("MULTIRUST_HOME")) + .ok(); + let toolchain = std::env::var("RUSTUP_TOOLCHAIN") + .or_else(|_| std::env::var("MULTIRUST_TOOLCHAIN")) + .ok(); match (home, toolchain) { (Some(home), Some(toolchain)) => format!("{home}/toolchains/{toolchain}"), - _ => option_env!("RUST_SYSROOT") + _ => std::env::var("RUST_SYSROOT") .expect("need to specify RUST_SYSROOT env var or use rustup or multirust") .to_owned(), } diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index f3f85d8a00b..fdbcf85cc31 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -65,13 +65,26 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { } impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { - #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_before_statement_effect", level = "debug", skip(self))] - fn apply_before_statement_effect(&mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, location: Location) { + #[tracing::instrument( + name = "FreePlaceCapabilitySummary::apply_before_statement_effect", + level = "debug", + skip(self) + )] + fn apply_before_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { state.repackings.clear(); state.apply_pre_effect = true; state.visit_statement(statement, location); } - #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_statement_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "FreePlaceCapabilitySummary::apply_statement_effect", + level = "debug", + skip(self) + )] fn apply_statement_effect( &mut self, state: &mut Self::Domain, @@ -83,7 +96,11 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { state.visit_statement(statement, location); } - #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_before_terminator_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "FreePlaceCapabilitySummary::apply_before_terminator_effect", + level = "debug", + skip(self) + )] fn apply_before_terminator_effect( &mut self, state: &mut Self::Domain, @@ -94,7 +111,11 @@ impl<'a, 'tcx> Analysis<'tcx> for FreePlaceCapabilitySummary<'a, 'tcx> { state.apply_pre_effect = true; state.visit_terminator(terminator, location); } - #[tracing::instrument(name = "FreePlaceCapabilitySummary::apply_terminator_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "FreePlaceCapabilitySummary::apply_terminator_effect", + level = "debug", + skip(self) + )] fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index ad9a3ad8ba8..2bf86bdec2a 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -10,7 +10,8 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, middle::{ mir::{ - tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, ProjectionElem, PlaceRef, + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, PlaceRef, + ProjectionElem, }, ty::{TyCtxt, TyKind}, }, @@ -326,7 +327,7 @@ impl<'tcx> Place<'tcx> { // } pub fn ty(self, repacker: PlaceRepacker<'_, 'tcx>) -> PlaceTy<'tcx> { - PlaceRef::ty(&*self, repacker.mir, repacker.tcx) + PlaceRef::ty(&self, repacker.mir, repacker.tcx) } /// Should only be called on a `Place` obtained from `RootPlace::get_parent`. From 54651c97c5d382c4b9960e0eb05bd0902d4efd55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 13 Nov 2023 19:16:19 +0100 Subject: [PATCH 31/32] Make top crates iterator --- mir-state-analysis/tests/top_crates.rs | 73 +++++++++++++++++--------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/mir-state-analysis/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs index 84dee437ac0..65e4c1fdb5a 100644 --- a/mir-state-analysis/tests/top_crates.rs +++ b/mir-state-analysis/tests/top_crates.rs @@ -17,8 +17,8 @@ fn get(url: &str) -> reqwest::Result { pub fn top_crates_range(range: std::ops::Range) { std::fs::create_dir_all("tmp").unwrap(); - let top_crates = top_crates_by_download_count(range.end - 1); - for (i, krate) in top_crates.into_iter().enumerate().skip(range.start) { + let top_crates = CratesIter::top(range); + for (i, krate) in top_crates { let version = krate.version.unwrap_or(krate.newest_version); println!("Starting: {i} ({})", krate.name); run_on_crate(&krate.name, &version); @@ -92,27 +92,52 @@ struct CratesList { crates: Vec, } -/// Create a list of top ``count`` crates. -fn top_crates_by_download_count(mut count: usize) -> Vec { - const PAGE_SIZE: usize = 100; - let page_count = count / PAGE_SIZE + 2; - let mut sources = Vec::new(); - for page in 1..page_count { - let url = format!( - "https://crates.io/api/v1/crates?page={page}&per_page={PAGE_SIZE}&sort=downloads" - ); - let resp = get(&url).expect("Could not fetch top crates"); - assert!( - resp.status().is_success(), - "Response status: {}", - resp.status() - ); - let page_crates: CratesList = match serde_json::from_reader(resp) { - Ok(page_crates) => page_crates, - Err(e) => panic!("Invalid JSON {e}"), - }; - sources.extend(page_crates.crates.into_iter().take(count)); - count -= std::cmp::min(PAGE_SIZE, count); +const PAGE_SIZE: usize = 100; +struct CratesIter { + curr_idx: usize, + curr_page: usize, + crates: Vec, +} + +impl CratesIter { + pub fn new(start: usize) -> Self { + Self { + curr_idx: start, + curr_page: start / PAGE_SIZE + 1, + crates: Vec::new(), + } + } + pub fn top(range: std::ops::Range) -> impl Iterator { + Self::new(range.start).take(range.len()) + } +} + +impl Iterator for CratesIter { + type Item = (usize, Crate); + fn next(&mut self) -> Option { + if self.crates.is_empty() { + let url = format!( + "https://crates.io/api/v1/crates?page={}&per_page={PAGE_SIZE}&sort=downloads", + self.curr_page, + ); + let resp = get(&url).expect("Could not fetch top crates"); + assert!( + resp.status().is_success(), + "Response status: {}", + resp.status() + ); + let page_crates: CratesList = match serde_json::from_reader(resp) { + Ok(page_crates) => page_crates, + Err(e) => panic!("Invalid JSON {e}"), + }; + assert_eq!(page_crates.crates.len(), PAGE_SIZE); + self.crates = page_crates.crates; + self.crates.reverse(); + self.crates + .truncate(self.crates.len() - self.curr_idx % PAGE_SIZE); + self.curr_page += 1; + } + self.curr_idx += 1; + Some((self.curr_idx - 1, self.crates.pop().unwrap())) } - sources } From b9ba8789dedc8ee1c9499e92dcac7b250a7b6016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 13 Nov 2023 19:17:16 +0100 Subject: [PATCH 32/32] Disallow expanding through `Projection` at edges --- .../src/free_pcs/check/checker.rs | 18 ++++++++++++++---- .../src/free_pcs/impl/join_semi_lattice.rs | 16 ++++++++++------ mir-state-analysis/src/free_pcs/impl/local.rs | 9 +++++++-- mir-state-analysis/src/free_pcs/impl/update.rs | 3 +-- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index 63fc3c7bae2..77a9ffedea6 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -41,7 +41,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { assert_eq!(fpcs_after.location, loc); // Repacks for &op in &fpcs_after.repacks_middle { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, data.is_cleanup, true, rp); } // Consistency fpcs.summary.consistency_check(rp); @@ -53,7 +53,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { // Repacks for op in fpcs_after.repacks { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, data.is_cleanup, true, rp); } // Statement post fpcs.apply_pre_effect = false; @@ -70,7 +70,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { assert_eq!(fpcs_after.location, loc); // Repacks for op in fpcs_after.repacks_middle { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, data.is_cleanup, true, rp); } // Consistency fpcs.summary.consistency_check(rp); @@ -82,7 +82,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { // Repacks for op in fpcs_after.repacks { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, data.is_cleanup, true, rp); } // Statement post fpcs.apply_pre_effect = false; @@ -101,6 +101,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { op.update_free( &mut from.summary, body.basic_blocks[succ.location.block].is_cleanup, + false, rp, ); } @@ -115,6 +116,7 @@ impl<'tcx> RepackOp<'tcx> { self, state: &mut CapabilitySummary<'tcx>, is_cleanup: bool, + can_downcast: bool, rp: PlaceRepacker<'_, 'tcx>, ) { match self { @@ -144,6 +146,14 @@ impl<'tcx> RepackOp<'tcx> { RepackOp::Expand(place, guide, kind) => { assert_eq!(kind, CapabilityKind::Exclusive, "{self:?}"); assert!(place.is_prefix_exact(guide), "{self:?}"); + assert!( + can_downcast + || !matches!( + guide.projection.last().unwrap(), + ProjectionElem::Downcast(..) + ), + "{self:?}" + ); let curr_state = state[place.local].get_allocated_mut(); assert_eq!( curr_state.remove(&place), diff --git a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs index e2880d86002..24536ef2a79 100644 --- a/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/free_pcs/impl/join_semi_lattice.rs @@ -125,16 +125,20 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { if !self.contains_key(&p) { continue; } - let p = if kind != CapabilityKind::Exclusive { - changed = true; - self.collapse(related.get_from(), related.to, repacker); + let collapse_to = if kind != CapabilityKind::Exclusive { related.to } else { - p + related.to.joinable_to(p) }; + if collapse_to != p { + changed = true; + let mut from = related.get_from(); + from.retain(|&from| collapse_to.is_prefix(from)); + self.collapse(from, collapse_to, repacker); + } if k > kind { changed = true; - self.insert(p, kind); + self.update_cap(collapse_to, kind); } } None @@ -151,7 +155,7 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilityProjections<'tcx> { // Downgrade the permission if needed if self[&place] > kind { changed = true; - self.insert(place, kind); + self.update_cap(place, kind); } } } diff --git a/mir-state-analysis/src/free_pcs/impl/local.rs b/mir-state-analysis/src/free_pcs/impl/local.rs index fda663d4cd7..b154e6b34a5 100644 --- a/mir-state-analysis/src/free_pcs/impl/local.rs +++ b/mir-state-analysis/src/free_pcs/impl/local.rs @@ -82,6 +82,11 @@ impl<'tcx> CapabilityProjections<'tcx> { self.iter().next().unwrap().0.local } + pub(crate) fn update_cap(&mut self, place: Place<'tcx>, cap: CapabilityKind) { + let old = self.insert(place, cap); + assert!(old.is_some()); + } + /// Returns all related projections of the given place that are contained in this map. /// A `Ordering::Less` means that the given `place` is a prefix of the iterator place. /// For example: find_all_related(x.f.g) = [(Less, x.f.g.h), (Greater, x.f)] @@ -104,7 +109,7 @@ impl<'tcx> CapabilityProjections<'tcx> { // Some(cap_no_read) // }; if let Some(expected) = expected { - assert_eq!(ord, expected); + assert_eq!(ord, expected, "({self:?}) {from:?} {to:?}"); } else { expected = Some(ord); } @@ -179,7 +184,7 @@ impl<'tcx> CapabilityProjections<'tcx> { .map(|&p| (p, self.remove(&p).unwrap())) .collect(); let collapsed = to.collapse(&mut from, repacker); - assert!(from.is_empty()); + assert!(from.is_empty(), "{from:?} ({collapsed:?}) {to:?}"); let mut exclusive_at = Vec::new(); if !to.projects_shared_ref(repacker) { for (to, _, kind) in &collapsed { diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 23bcd14981e..1e59d315ecb 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -76,8 +76,7 @@ impl<'tcx> Fpcs<'_, 'tcx> { fn ensures_alloc(&mut self, place: impl Into>, cap: CapabilityKind) { let place = place.into(); let cp: &mut CapabilityProjections = self.summary[place.local].get_allocated_mut(); - let old = cp.insert(place, cap); - assert!(old.is_some()); + cp.update_cap(place, cap); } pub(crate) fn ensures_exclusive(&mut self, place: impl Into>) { self.ensures_alloc(place, CapabilityKind::Exclusive)