From 9a8e1f0aaa8e157cb68e8950a19cec63fb14184e Mon Sep 17 00:00:00 2001 From: Jonas Date: Thu, 23 Feb 2023 20:16:00 +0100 Subject: [PATCH 01/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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/58] 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 1af955e135124f5704cd4bc203f7272b7fc7d793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 22 Aug 2023 16:15:45 +0200 Subject: [PATCH 13/58] Initial experiments with coupling graph --- Cargo.lock | 8 + mir-state-analysis/Cargo.toml | 3 + .../src/coupling_graph/impl/cg.rs | 283 ++++++++++++++++ .../src/coupling_graph/impl/dot.rs | 192 +++++++++++ .../src/coupling_graph/impl/engine.rs | 316 ++++++++++++++++++ .../coupling_graph/impl/join_semi_lattice.rs | 62 ++++ .../src/coupling_graph/impl/mod.rs | 10 + mir-state-analysis/src/coupling_graph/mod.rs | 9 + .../src/free_pcs/impl/triple.rs | 3 +- .../src/free_pcs/results/repacks.rs | 3 + mir-state-analysis/src/lib.rs | 21 ++ mir-state-analysis/src/utils/display.rs | 156 +++++++++ mir-state-analysis/src/utils/mod.rs | 2 + mir-state-analysis/src/utils/mutable.rs | 2 +- mir-state-analysis/src/utils/repacker.rs | 40 ++- mir-state-analysis/src/utils/ty/mod.rs | 95 ++++++ mir-state-analysis/src/utils/ty/ty_rec.rs | 40 +++ prusti-interface/src/environment/body.rs | 19 +- .../src/environment/borrowck/facts.rs | 13 +- prusti-utils/src/config.rs | 5 + prusti/src/callbacks.rs | 15 +- 21 files changed, 1287 insertions(+), 10 deletions(-) create mode 100644 mir-state-analysis/src/coupling_graph/impl/cg.rs create mode 100644 mir-state-analysis/src/coupling_graph/impl/dot.rs create mode 100644 mir-state-analysis/src/coupling_graph/impl/engine.rs create mode 100644 mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs create mode 100644 mir-state-analysis/src/coupling_graph/impl/mod.rs create mode 100644 mir-state-analysis/src/coupling_graph/mod.rs create mode 100644 mir-state-analysis/src/utils/display.rs create mode 100644 mir-state-analysis/src/utils/ty/mod.rs create mode 100644 mir-state-analysis/src/utils/ty/ty_rec.rs diff --git a/Cargo.lock b/Cargo.lock index 4991ced6d8f..75d7e241d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -905,6 +905,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "dot" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906" + [[package]] name = "dunce" version = "1.0.4" @@ -1894,6 +1900,8 @@ name = "mir-state-analysis" version = "0.1.0" dependencies = [ "derive_more", + "dot", + "prusti-interface", "prusti-rustc-interface", "reqwest", "serde", diff --git a/mir-state-analysis/Cargo.toml b/mir-state-analysis/Cargo.toml index 958fe750062..dbf4ee2bb71 100644 --- a/mir-state-analysis/Cargo.toml +++ b/mir-state-analysis/Cargo.toml @@ -8,6 +8,9 @@ edition = "2021" derive_more = "0.99" tracing = { path = "../tracing" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } +dot = "0.1" +# TODO: remove +prusti-interface = { path = "../prusti-interface" } [dev-dependencies] reqwest = { version = "^0.11", features = ["blocking"] } diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs new file mode 100644 index 00000000000..97f229467bd --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -0,0 +1,283 @@ +// © 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}, borrow::Cow}; + +use derive_more::{Deref, DerefMut}; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + data_structures::fx::{FxHashMap, FxHashSet}, + index::bit_set::BitSet, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, + borrowck::{borrow_set::BorrowData, consumers::BorrowIndex}, + middle::{mir::ConstraintCategory, ty::{RegionVid, TyKind}}, +}; + +use crate::{ + free_pcs::{ + engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, + }, + utils::{PlaceRepacker, Place}, +}; + +use super::engine::CoupligGraph; + +#[derive(Clone)] +pub struct Regions<'a, 'tcx> { + pub borrows: FxHashMap, Vec<(Local, RegionVid)>)>, + pub(crate) subset: Vec<(RegionVid, RegionVid)>, + pub(crate) graph: Graph<'a, 'tcx>, +} + +pub type NodeId = usize; + +#[derive(Clone)] +pub struct Graph<'a, 'tcx> { + pub rp: PlaceRepacker<'a, 'tcx>, + pub facts: &'a BorrowckFacts, + pub facts2: &'a BorrowckFacts2<'tcx>, + pub nodes: Vec>>, + pub skip_empty_nodes: bool, + pub shared_borrows: Vec>, +} + +impl PartialEq for Graph<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.nodes == other.nodes + } +} + +impl<'a, 'tcx> Graph<'a, 'tcx> { + pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { + let mut result = Self { + rp, + facts, + facts2, + nodes: Vec::new(), + skip_empty_nodes: false, + shared_borrows: Vec::new(), + }; + // let input_facts = facts.input_facts.borrow(); + // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { + // result.outlives(r1, r2); + // } + let constraints = facts2.region_inference_context.outlives_constraints(); + for c in constraints { + if c.locations.from_location().is_none() { + result.outlives(c.sup, c.sub, c.category); + } + } + result + } + pub fn new_shared_borrow(&mut self, data: BorrowData<'tcx>) { + self.shared_borrows.push(data); + } + pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) { + let n1 = self.region_to_node(r1); + let n2 = self.region_to_node(r2); + if n1 == n2 { + return; + } + // println!("Adding outlives {r1:?} ({n1}): {r2:?} ({n2})"); + if let Some(path) = self.reachable(n1, n2) { + for other in path { + self.merge(other, n2); + } + } else { + self.blocks(n2, n1, reason); + } + } + // pub fn contained_by(&mut self, r: RegionVid, l: Local) { + // let n = self.region_to_node(r); + // self.get_node_mut(n).contained_by.push(l); + // } + pub fn kill(&mut self, r: RegionVid) { + let n = self.region_to_node(r); + self.kill_node(n) + } + pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool) { + for n in self.nodes.iter_mut() { + if let Some(n) = n { + if n.regions.contains(&r) { + n.regions.remove(&r); + if n.regions.is_empty() { + let id = n.id; + let n = self.remove_node(id); + for (&block, _) in &n.blocks { + for (&blocked_by, &edge) in &n.blocked_by { + self.blocks(blocked_by, block, edge.reason); + } + } + } + return; + } + } + } + assert!(maybe_already_removed, "Region {:?} not found in graph", r); + } + + fn reachable(&self, from: NodeId, to: NodeId) -> Option> { + // println!("Checking reachability from {} to {}", from, to); + let mut nodes = FxHashSet::default(); + if from == to { + return Some(nodes); + } + for (&next, _) in &self.get_node(from).blocks { + if let Some(others) = self.reachable(next, to) { + nodes.insert(from); + nodes.extend(others); + } + } + if nodes.is_empty() { + None + } else { + Some(nodes) + } + } + fn region_to_node(&mut self, r: RegionVid) -> NodeId { + let mut last_none = self.nodes.len(); + for (i, n) in self.nodes.iter().enumerate() { + if let Some(n) = n { + if n.regions.contains(&r) { + return i; + } + } else { + last_none = i; + } + } + if last_none == self.nodes.len() { + self.nodes.push(Some(Node::new(last_none, r))); + } else { + self.nodes[last_none] = Some(Node::new(last_none, r)); + } + last_none + } + fn merge(&mut self, n1: NodeId, n2: NodeId) { + assert_ne!(n1, n2); + let to_merge = self.remove_node(n1); + for (block, edge) in to_merge.blocks { + if block != n2 { + self.blocks(n2, block, edge.reason); + } + } + for (block_by, edge) in to_merge.blocked_by { + if block_by != n2 { + self.blocks(block_by, n2, edge.reason); + } + } + let n2 = self.get_node_mut(n2); + // n2.contained_by.extend(to_merge.contained_by); + n2.regions.extend(to_merge.regions); + } + fn kill_node(&mut self, n: NodeId) { + let removed = self.remove_node(n); + for (blocked_by, _) in removed.blocked_by { + self.kill_node(blocked_by); + } + } + fn remove_node(&mut self, n: NodeId) -> Node<'tcx> { + let to_remove = self.nodes[n].take().unwrap(); + for &block in to_remove.blocks.keys() { + let rem = self.get_node_mut(block).blocked_by.remove(&n); + assert!(rem.is_some()); + } + for &block_by in to_remove.blocked_by.keys() { + let rem = self.get_node_mut(block_by).blocks.remove(&n); + assert!(rem.is_some()); + } + to_remove + } + pub(crate) fn get_node(&self, n: NodeId) -> &Node<'tcx> { + self.nodes[n].as_ref().unwrap() + } + fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { + self.nodes[n].as_mut().unwrap() + } + fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: ConstraintCategory<'tcx>) { + let block = Edge::new(n1, n2, reason); + self.get_node_mut(n1).blocks.insert(n2, block); + let blocked_by = Edge::new(n2, n1, reason); + self.get_node_mut(n2).blocked_by.insert(n1, blocked_by); + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Node<'tcx> { + pub id: NodeId, + pub regions: FxHashSet, + pub blocks: FxHashMap>, + pub blocked_by: FxHashMap>, + // pub contained_by: Vec, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Edge<'tcx> { + pub from: NodeId, + pub to: NodeId, + pub reason: ConstraintCategory<'tcx>, +} + +impl<'tcx> Edge<'tcx> { + fn new(from: NodeId, to: NodeId, reason: ConstraintCategory<'tcx>) -> Self { + Self { from, to, reason } + } +} + +impl<'tcx> Node<'tcx> { + pub fn new(id: NodeId, r: RegionVid) -> Self { + Self { + id, + regions: [r].into_iter().collect(), + blocks: FxHashMap::default(), + blocked_by: FxHashMap::default(), + // contained_by: Vec::new(), + } + } +} + +#[derive(Clone)] +pub struct Cg<'a, 'tcx> { + pub(crate) repacker: PlaceRepacker<'a, 'tcx>, + // pub(crate) facts: &'a BorrowckFacts, + pub(crate) live: BitSet, + pub(crate) regions: Regions<'a, 'tcx>, + pub done: usize, +} +impl<'a, 'tcx> Cg<'a, 'tcx> { + pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { + let live = BitSet::new_empty(facts2.borrow_set.location_map.len() * 2); + let regions = Regions { + borrows: FxHashMap::default(), + subset: Vec::new(), + graph: Graph::new(repacker, facts, facts2), + }; + Cg { repacker, live, regions, done: 0 } + } +} + +impl PartialEq for Cg<'_, '_> { + fn eq(&self, other: &Self) -> bool { + true + } +} +impl Eq for Cg<'_, '_> {} + +impl<'a, 'tcx> Debug for Cg<'a, 'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + // self.summary.fmt(f) + Ok(()) + } +} +impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { + fn fmt_diff_with( + &self, + old: &Self, + _ctxt: &CoupligGraph<'a, 'tcx>, + f: &mut Formatter<'_>, + ) -> Result { + Ok(()) + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs new file mode 100644 index 00000000000..0210ca9f040 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -0,0 +1,192 @@ +// © 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::borrow::Cow; + +use prusti_rustc_interface::{ + data_structures::fx::{FxHashMap, FxHashSet}, + index::bit_set::BitSet, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, + borrowck::consumers::BorrowIndex, + middle::{mir::{BorrowKind, ConstraintCategory}, ty::{RegionVid, TyKind}}, +}; + +use crate::utils::Place; + +use super::cg::{NodeId, Edge, Graph}; + +impl<'a, 'tcx> Graph<'a, 'tcx> { + fn get_corresponding_places(&self, n: NodeId) -> Vec>> { + let node = self.get_node(n); + let mut contained_by: Vec> = Vec::new(); + let input_facts = self.facts.input_facts.borrow(); + for &(l, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { + if node.regions.contains(&r) { + contained_by.push(Perms::AllIn(r, l.into())); + } + } + for (_, data) in &self.facts2.borrow_set.location_map { + if node.regions.contains(&data.region) { + contained_by.push(Perms::Exact(data.borrowed_place.into())); + } + } + for data in &self.shared_borrows { + if node.regions.contains(&data.region) { + contained_by.push(Perms::Exact(data.borrowed_place.into())); + } + } + contained_by + } + fn is_empty_node(&self, n: NodeId) -> bool { + self.get_corresponding_places(n).is_empty() + } + fn is_borrow_only(&self, n: NodeId) -> Option { + let node = self.get_node(n); + let input_facts = self.facts.input_facts.borrow(); + for &(_, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { + if node.regions.contains(&r) { + return None; + } + } + let mut is_borrow = None; + for (_, data) in &self.facts2.borrow_set.location_map { + if node.regions.contains(&data.region) { + if is_borrow.is_some() { + return None; + } + is_borrow = Some(data.kind); + } + } + for data in &self.shared_borrows { + if node.regions.contains(&data.region) { + if is_borrow.is_some() { + return None; + } + is_borrow = Some(data.kind); + } + } + is_borrow + } +} + +#[derive(Debug, Copy, Clone)] +enum Perms { + Exact(T), + AllIn(RegionVid, T), +} + +impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example1").unwrap() } + + fn node_id(&'a self, n: &NodeId) -> dot::Id<'a> { + dot::Id::new(format!("N_{n:?}")).unwrap() + } + + fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { + if self.is_borrow_only(e.from).is_some() { + dot::Arrow::from_arrow(dot::ArrowShape::Dot(dot::Fill::Filled)) + } else { + dot::Arrow::default() + } + } + fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { + let label = match e.reason { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + ConstraintCategory::UseAsConst => "const", + ConstraintCategory::UseAsStatic => "static", + ConstraintCategory::TypeAnnotation => "type", + ConstraintCategory::Cast => "cast", + ConstraintCategory::ClosureBounds => "closure", + ConstraintCategory::CallArgument(_) => "arg", + ConstraintCategory::CopyBound => "copy", + ConstraintCategory::SizedBound => "sized", + ConstraintCategory::Assignment => "assign", + ConstraintCategory::Usage => "use", + ConstraintCategory::OpaqueType => "opaque", + ConstraintCategory::ClosureUpvar(_) => "upvar", + ConstraintCategory::Predicate(_) => "pred", + ConstraintCategory::Boring => "boring", + ConstraintCategory::BoringNoLocation => "boring_nl", + ConstraintCategory::Internal => "internal", + }; + dot::LabelText::LabelStr(Cow::Borrowed(label)) + } + fn node_shape(&'a self, n: &NodeId) -> Option> { + self.is_borrow_only(*n).map(|kind| match kind { + BorrowKind::Shared => + dot::LabelText::LabelStr(Cow::Borrowed("box")), + BorrowKind::Shallow => + dot::LabelText::LabelStr(Cow::Borrowed("triangle")), + BorrowKind::Mut { kind } => + dot::LabelText::LabelStr(Cow::Borrowed("ellipse")), + }) + } + fn node_label(&'a self, n: &NodeId) -> dot::LabelText<'a> { + let process_place = |p: Place<'tcx>| p.to_string(self.rp); + let contained_by = self.get_corresponding_places(*n); + // let process_place = |p: Place<'tcx>| p; + let contained_by = contained_by.iter().map(|&l| { + match l { + Perms::Exact(p) => Perms::Exact(process_place(p)), + Perms::AllIn(r, mut p) => { + let mut ty = p.ty(self.rp).ty; + let mut made_precise = false; + while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { + ty = inner_ty; + p = p.mk_deref(self.rp); + if rr.is_var() && rr.as_var() == r { + made_precise = true; + break; + } + } + if made_precise { + Perms::Exact(process_place(p)) + } else { + Perms::AllIn(r, process_place(p)) + } + } + } + }).collect::>(); + let node = self.get_node(*n); + let label = format!("{:?}\n{:?}", node.regions, contained_by); + dot::LabelText::LabelStr(Cow::Owned(label)) + } +} + +impl<'a, 'b, 'tcx> dot::GraphWalk<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { + fn nodes(&self) -> dot::Nodes<'a, NodeId> { + let nodes: Vec<_> = self.nodes + .iter() + .enumerate() + .filter_map(|(idx, n)| n.as_ref().map(|_| idx)) + .filter(|&idx| !self.skip_empty_nodes || !self.is_empty_node(idx)) + .collect(); + Cow::Owned(nodes) + } + + fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { + let mut edges = Vec::new(); + for (c, n) in self.nodes.iter().enumerate() { + if let Some(n) = n { + if self.skip_empty_nodes && self.is_empty_node(c) { + continue; + } + for (&b, &edge) in &n.blocks { + if self.skip_empty_nodes && self.is_empty_node(b) { + continue; + } + edges.push(edge); + } + } + } + Cow::Owned(edges) + } + + fn source(&self, e: &Edge<'tcx>) -> NodeId { e.from } + + fn target(&self, e: &Edge<'tcx>) -> NodeId { e.to } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs new file mode 100644 index 00000000000..bae30158213 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -0,0 +1,316 @@ +// © 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::cell::RefCell; + +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + data_structures::fx::{FxIndexMap}, + borrowck::{ + borrow_set::{BorrowData, TwoPhaseActivation}, + consumers::{Borrows, BorrowIndex, RichLocation, calculate_borrows_out_of_scope_at_location}, + }, + dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, ResultsCursor}, + index::{bit_set::{BitSet, HybridBitSet}, Idx}, + middle::{ + mir::{ + TerminatorKind, Operand, ConstantKind, StatementKind, Rvalue, + visit::Visitor, BasicBlock, Body, Local, Location, Statement, Terminator, RETURN_PLACE, + }, + ty::{RegionVid, TyCtxt}, + }, +}; + +use crate::{ + free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, + utils::PlaceRepacker, coupling_graph::cg::{Graph, Node}, +}; + +use super::cg::{Cg, Regions}; + +pub(crate) struct CoupligGraph<'a, 'tcx> { + pub(crate) repacker: PlaceRepacker<'a, 'tcx>, + pub(crate) facts: &'a BorrowckFacts, + pub(crate) facts2: &'a BorrowckFacts2<'tcx>, + pub(crate) flow_borrows: RefCell>>, + pub(crate) out_of_scope: FxIndexMap>, +} +impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { + pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { + std::fs::remove_dir_all("log/coupling").ok(); + std::fs::create_dir_all("log/coupling").unwrap(); + + let repacker = PlaceRepacker::new(body, tcx); + let regioncx = &*facts2.region_inference_context; + let borrow_set = &*facts2.borrow_set; + let out_of_scope = calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set); + let flow_borrows = Borrows::new(tcx, body, regioncx, borrow_set) + .into_engine(tcx, body) + .pass_name("borrowck") + .iterate_to_fixpoint() + .into_results_cursor(body); + CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope } + } + + fn handle_diff(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { + let input_facts = self.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + let location_table = self.facts.location_table.borrow(); + let location_table = location_table.as_ref().unwrap(); + for idx in delta.set.iter() { + let loan_issued_at = &input_facts.loan_issued_at; + let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); + let l = location_table.to_location(l); + let RichLocation::Mid(l) = l else { unreachable!() }; + assert_eq!(l, location); + let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); + state.borrows.insert(idx, (vec![r], locals)); + } + state.subset.extend(input_facts.subset_base.iter().filter( + |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location + ).map(|(r1, r2, _)| (*r1, *r2))); + // TODO: do a proper fixpoint here + for _ in 0..10 { + for &(r1, r2) in &state.subset { + let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); + let mut did_push = false; + for (_, s) in state.borrows.iter_mut() { + if s.0.contains(&r1) { + did_push = true; + if !s.0.contains(&r2) { + s.0.push(r2); + s.1.extend(locals.iter().copied()); + } + } + } + // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); + } + } + for r in delta.cleared.iter() { + let removed = state.borrows.remove(&r).unwrap(); + // for (_, s) in state.borrows.iter_mut() { + // s.0.retain(|r| !removed.0.contains(r)); + // s.1.retain(|l| !removed.1.contains(l)); + // } + } + // print!(" {:?}", state.borrows); + self.handle_graph(state, delta, location); + } + + fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { + let l = format!("{:?}", location).replace('[', "_").replace(']', ""); + + let input_facts = self.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + let location_table = self.facts.location_table.borrow(); + let location_table = location_table.as_ref().unwrap(); + + // let input_facts = self.facts2.region_inference_context.borrow(); + + let oos = self.out_of_scope.get(&location); + if let Some(oos) = oos { + for bi in oos { + let (r, _, l) = input_facts.loan_issued_at.iter().find( + |(_, b, _)| bi == b + ).copied().unwrap(); + println!("UGHBJS region: {r:?} location: {l:?}"); + state.graph.kill(r); + let l = rich_to_loc(location_table.to_location(l)); + let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); + let local = borrow_data.assigned_place.local; + for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + println!("IHUBJ local: {local:?} region: {region:?}"); + state.graph.remove(region, true); + } + } + } + + // let mut f = std::fs::File::create(format!("log/coupling/{l}_a.dot")).unwrap(); + // dot::render(&state.graph, &mut f).unwrap(); + + for killed in delta.cleared.iter() { + if oos.map(|oos| oos.contains(&killed)).unwrap_or_default() { + continue; + } + let (r, _, l) = input_facts.loan_issued_at.iter().find( + |(_, b, _)| killed == *b + ).copied().unwrap(); + state.graph.remove(r, false); + let l = rich_to_loc(location_table.to_location(l)); + let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); + let local = borrow_data.borrowed_place.local; + for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + state.graph.remove(region, true); + } + } + + // let mut f = std::fs::File::create(format!("log/coupling/{l}_b.dot")).unwrap(); + // dot::render(&state.graph, &mut f).unwrap(); + + // let new_subsets: Vec<_> = input_facts.subset_base.iter().filter( + // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location + // ).map(|(r1, r2, _)| (*r1, *r2)).collect(); + // for (r1, r2) in new_subsets { + // state.graph.outlives(r1, r2); + // } + let constraints = self.facts2.region_inference_context.outlives_constraints(); + for c in constraints { + if let Some(from) = c.locations.from_location() { + if from == location { + state.graph.outlives(c.sup, c.sub, c.category); + } + } + } + + if !self.repacker.body().basic_blocks[location.block].is_cleanup { + let mut f = std::fs::File::create(format!("log/coupling/{l}_c.dot")).unwrap(); + dot::render(&state.graph, &mut f).unwrap(); + } + } +} + +impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { + type Domain = Cg<'a, 'tcx>; + const NAME: &'static str = "coupling_graph"; + + fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { + Cg::new(self.repacker, self.facts, self.facts2) + } + + fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + // println!("body: {body:?}"); + println!("\ninput_facts: {:?}", self.facts.input_facts); + println!("output_facts: {:#?}\n", self.facts.output_facts); + println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); + println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); + println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); + // println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context); + // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); + let lt = self.facts.location_table.borrow(); + let lt = lt.as_ref().unwrap(); + for pt in lt.all_points() { + println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); + } + println!("out_of_scope: {:?}", self.out_of_scope); + } +} + +impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { + fn apply_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { + if location.statement_index == 0 { + println!("\nblock: {:?}", location.block); + self.flow_borrows.borrow_mut().seek_to_block_start(location.block); + state.live = self.flow_borrows.borrow().get().clone(); + } + self.flow_borrows.borrow_mut().seek_after_primary_effect(location); + let other = self.flow_borrows.borrow().get().clone(); + // print!("{statement:?} ({other:?}):"); + let delta = calculate_diff(&other, &state.live); + if delta.set.is_empty() { + match statement.kind { + StatementKind::Assign(box (assigned_place, Rvalue::Ref(region, kind, borrowed_place))) => { + + state.regions.graph.new_shared_borrow(BorrowData { + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + kind, + region: region.as_var(), + borrowed_place, + assigned_place, + }) + } + _ => (), + } + } + self.handle_diff(&mut state.regions, delta, location); + state.live = other; + // println!(); + } + + fn apply_terminator_effect( + &mut self, + state: &mut Self::Domain, + terminator: &Terminator<'tcx>, + location: Location, + ) { + if location.statement_index == 0 { + println!("\nblock: {:?}", location.block); + self.flow_borrows.borrow_mut().seek_to_block_start(location.block); + state.live = self.flow_borrows.borrow().get().clone(); + } + self.flow_borrows.borrow_mut().seek_after_primary_effect(location); + let other = self.flow_borrows.borrow().get().clone(); + if let TerminatorKind::Call { func, args, destination, target, fn_span, .. } = &terminator.kind { + if let Operand::Constant(c) = func { + println!("user_ty: {:?}", c.user_ty); + println!("call: {:?}", c.literal); + if let ConstantKind::Val(cv, ty) = c.literal { + println!("val: {:?}", cv); + println!("ty: {:?}", ty); + } + println!("\n\n\ncall: {:?}", func); + } + for arg in args { + match arg { + Operand::Copy(a) => println!("copy ({arg:?}): {:?}", a), + Operand::Move(b) => println!("move ({arg:?}): {:?}", b), + Operand::Constant(c) => println!("const ({arg:?}): {:?}", c.literal), + } + } + } + // print!("{terminator:?} ({other:?}):"); + let delta = calculate_diff(&other, &state.live); + self.handle_diff(&mut state.regions, delta, location); + state.live = other; + // println!(); + } + + fn apply_call_return_effect( + &mut self, + _state: &mut Self::Domain, + _block: BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + // Nothing to do here + } +} + +struct BorrowDelta { + set: HybridBitSet, + cleared: HybridBitSet, +} + +fn calculate_diff(curr: &BitSet, old: &BitSet) -> BorrowDelta { + let size = curr.domain_size(); + assert_eq!(size, old.domain_size()); + + let mut set_in_curr = HybridBitSet::new_empty(size); + let mut cleared_in_curr = HybridBitSet::new_empty(size); + + for i in (0..size).map(BorrowIndex::new) { + match (curr.contains(i), old.contains(i)) { + (true, false) => set_in_curr.insert(i), + (false, true) => cleared_in_curr.insert(i), + _ => continue, + }; + } + BorrowDelta { + set: set_in_curr, + cleared: cleared_in_curr, + } +} + +fn rich_to_loc(l: RichLocation) -> Location { + match l { + RichLocation::Start(l) => l, + RichLocation::Mid(l) => l, + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs new file mode 100644 index 00000000000..27c10e8cc4c --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -0,0 +1,62 @@ +// © 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::collections::hash_map::Entry; + +use prusti_rustc_interface::dataflow::JoinSemiLattice; + +use crate::{ + free_pcs::{ + CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, + }, + utils::{PlaceOrdering, PlaceRepacker}, +}; + +use super::cg::Cg; + +impl JoinSemiLattice for Cg<'_, '_> { + fn join(&mut self, other: &Self) -> bool { + if self.done == 2 { + return false; + } + self.done += 1; + let mut changed = self.live.union(&other.live); + for (idx, data) in other.regions.borrows.iter() { + match self.regions.borrows.entry(*idx) { + Entry::Occupied(mut o) => { + let (a, b) = o.get_mut(); + for r in &data.0 { + if !a.contains(r) { + changed = true; + a.push(*r); + } + } + for r in &data.1 { + if !b.contains(r) { + changed = true; + b.push(*r); + } + } + } + Entry::Vacant(v) => { + changed = true; + v.insert(data.clone()); + } + } + } + for s in &other.regions.subset { + if !self.regions.subset.contains(s) { + changed = true; + self.regions.subset.push(*s); + } + } + if self.regions.graph != other.regions.graph { + changed = true; + self.regions.graph = other.regions.graph.clone(); + } + changed + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/mod.rs b/mir-state-analysis/src/coupling_graph/impl/mod.rs new file mode 100644 index 00000000000..2c97c1ffdd6 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/mod.rs @@ -0,0 +1,10 @@ +// © 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 engine; +pub(crate) mod cg; +pub(crate) mod join_semi_lattice; +mod dot; diff --git a/mir-state-analysis/src/coupling_graph/mod.rs b/mir-state-analysis/src/coupling_graph/mod.rs new file mode 100644 index 00000000000..9456a304f3f --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/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 r#impl; + +pub use r#impl::*; diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index c12e425e5fd..c40d20cb00a 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -42,7 +42,8 @@ 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? diff --git a/mir-state-analysis/src/free_pcs/results/repacks.rs b/mir-state-analysis/src/free_pcs/results/repacks.rs index bd725d390d7..80b38815eb9 100644 --- a/mir-state-analysis/src/free_pcs/results/repacks.rs +++ b/mir-state-analysis/src/free_pcs/results/repacks.rs @@ -44,6 +44,9 @@ pub enum RepackOp<'tcx> { /// 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. + /// + /// 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/lib.rs b/mir-state-analysis/src/lib.rs index 84c6fb4dcb2..c691d08237c 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -9,7 +9,9 @@ pub mod free_pcs; pub mod utils; +pub mod coupling_graph; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ dataflow::Analysis, middle::{mir::Body, ty::TyCtxt}, @@ -31,3 +33,22 @@ pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { let analysis = run_free_pcs(mir, tcx); free_pcs::check(analysis); } + +pub fn run_coupling_graph<'mir, 'tcx>( + mir: &'mir Body<'tcx>, + facts: &'mir BorrowckFacts, + facts2: &'mir BorrowckFacts2<'tcx>, + tcx: TyCtxt<'tcx>, +) { + let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2); + let analysis = fpcs + .into_engine(tcx, mir) + .pass_name("coupling_graph") + .iterate_to_fixpoint(); + // free_pcs::FreePcsAnalysis::new(analysis.into_results_cursor(mir)) +} + +pub fn test_coupling_graph<'tcx>(mir: &Body<'tcx>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>) { + let analysis = run_coupling_graph(mir, facts, facts2, tcx); + // free_pcs::check(analysis); +} diff --git a/mir-state-analysis/src/utils/display.rs b/mir-state-analysis/src/utils/display.rs new file mode 100644 index 00000000000..d7a61fef236 --- /dev/null +++ b/mir-state-analysis/src/utils/display.rs @@ -0,0 +1,156 @@ +// © 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::{borrow::Cow, collections::VecDeque, fmt::{Debug, Formatter, Result}}; + +use prusti_rustc_interface::{ + middle::{ + mir::{ + PlaceElem, PlaceRef, ProjectionElem, VarDebugInfo, VarDebugInfoContents, RETURN_PLACE, + }, + ty::{AdtKind, TyKind}, + }, + span::Span, +}; + +use super::{Place, PlaceRepacker}; + +pub enum PlaceDisplay<'tcx> { + Temporary(Place<'tcx>), + User(String), +} + +impl<'tcx> Debug for PlaceDisplay<'tcx> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + PlaceDisplay::Temporary(place) => write!(f, "{place:?}"), + PlaceDisplay::User(s) => write!(f, "{s}"), + } + } +} + +impl<'tcx> Place<'tcx> { + pub fn to_string(&self, repacker: PlaceRepacker<'_, 'tcx>) -> PlaceDisplay<'tcx> { + // Get the local's debug name from the Body's VarDebugInfo + let local_name = if self.local == RETURN_PLACE { + Cow::Borrowed("RETURN") + } else { + fn as_local(span: Span, outer_span: Span) -> Option { + // Before we call source_callsite, we check and see if the span is already local. + // This is important b/c in print!("{}", y) if the user selects `y`, the source_callsite + // of that span is the entire macro. + if outer_span.contains(span) { + return Some(span); + } else { + let sp = span.source_callsite(); + if outer_span.contains(sp) { + return Some(sp); + } + } + + None + } + + let get_local_name = |info: &VarDebugInfo<'tcx>| match info.value { + VarDebugInfoContents::Place(place) if place.local == self.local => { + as_local(info.source_info.span, repacker.mir.span) + .map(|_| info.name.to_string()) + } + _ => None, + }; + let Some(local_name) = repacker + .mir + .var_debug_info + .iter() + .find_map(get_local_name) else { + return PlaceDisplay::Temporary(*self); + }; + Cow::Owned(local_name) + }; + + #[derive(Copy, Clone)] + enum ElemPosition { + Prefix, + Suffix, + } + + // Turn each PlaceElem into a prefix (e.g. * for deref) or a suffix + // (e.g. .field for projection). + let elem_to_string = |(index, (place, elem)): ( + usize, + (PlaceRef<'tcx>, PlaceElem<'tcx>), + )| + -> (ElemPosition, Cow<'static, str>) { + match elem { + ProjectionElem::Deref => (ElemPosition::Prefix, "*".into()), + + ProjectionElem::Field(field, _) => { + let ty = place.ty(&repacker.mir.local_decls, repacker.tcx).ty; + + let field_name = match ty.kind() { + TyKind::Adt(def, _substs) => { + let fields = match def.adt_kind() { + AdtKind::Struct => &def.non_enum_variant().fields, + AdtKind::Enum => { + let Some(PlaceElem::Downcast(_, variant_idx)) = self.projection.get(index - 1) else { unimplemented!() } ; + &def.variant(*variant_idx).fields + } + kind => unimplemented!("{kind:?}"), + }; + + fields[field].ident(repacker.tcx).to_string() + } + + TyKind::Tuple(_) => field.as_usize().to_string(), + + TyKind::Closure(def_id, _substs) => match def_id.as_local() { + Some(local_def_id) => { + let captures = repacker.tcx.closure_captures(local_def_id); + captures[field.as_usize()].var_ident.to_string() + } + None => field.as_usize().to_string(), + }, + + kind => unimplemented!("{kind:?}"), + }; + + (ElemPosition::Suffix, format!(".{field_name}").into()) + } + ProjectionElem::Downcast(sym, _) => { + let variant = sym.map(|s| s.to_string()).unwrap_or_else(|| "??".into()); + (ElemPosition::Suffix, format!("@{variant}",).into()) + } + + ProjectionElem::Index(_) => (ElemPosition::Suffix, "[_]".into()), + kind => unimplemented!("{kind:?}"), + } + }; + + let (positions, contents): (Vec<_>, Vec<_>) = self + .iter_projections() + .enumerate() + .map(elem_to_string) + .unzip(); + + // Combine the prefixes and suffixes into a corresponding sequence + let mut parts = VecDeque::from([local_name]); + for (i, string) in contents.into_iter().enumerate() { + match positions[i] { + ElemPosition::Prefix => { + parts.push_front(string); + if matches!(positions.get(i + 1), Some(ElemPosition::Suffix)) { + parts.push_front(Cow::Borrowed("(")); + parts.push_back(Cow::Borrowed(")")); + } + } + ElemPosition::Suffix => parts.push_back(string), + } + } + + let full = parts.make_contiguous().join(""); + PlaceDisplay::User(full) + } +} diff --git a/mir-state-analysis/src/utils/mod.rs b/mir-state-analysis/src/utils/mod.rs index fb02d7b16ab..d983612fba0 100644 --- a/mir-state-analysis/src/utils/mod.rs +++ b/mir-state-analysis/src/utils/mod.rs @@ -6,8 +6,10 @@ pub mod place; pub(crate) mod repacker; +pub mod display; mod mutable; mod root_place; +pub mod ty; pub use mutable::*; pub use place::*; 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 182fb09a46e..2810a6bb9c7 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -11,13 +11,15 @@ use prusti_rustc_interface::{ middle::{ mir::{ tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, - ProjectionElem, + ProjectionElem, PlaceElem, }, - ty::{TyCtxt, TyKind}, + ty::{Ty, TyCtxt, TyKind, RegionVid}, }, target::abi::FieldIdx, }; +use crate::utils::ty::{DeepTypeVisitor, Stack, DeepTypeVisitable}; + use super::Place; #[derive(Debug, Clone, Copy)] @@ -68,6 +70,10 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { pub fn body(self) -> &'a Body<'tcx> { self.mir } + + pub fn tcx(self) -> TyCtxt<'tcx> { + self.tcx + } } impl<'tcx> Place<'tcx> { @@ -196,7 +202,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), @@ -223,7 +229,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(), @@ -313,6 +319,9 @@ impl<'tcx> Place<'tcx> { // } impl<'tcx> Place<'tcx> { + pub fn ty(self, repacker: PlaceRepacker<'_, 'tcx>) -> PlaceTy<'tcx> { + (*self).ty(repacker.mir, repacker.tcx) + } // 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; @@ -326,7 +335,7 @@ impl<'tcx> Place<'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 { @@ -370,4 +379,25 @@ impl<'tcx> Place<'tcx> { } None } + + pub fn all_behind_region(self, r: RegionVid, repacker: PlaceRepacker<'_, 'tcx>) -> Vec { + struct AllBehindWalker<'tcx>(Place<'tcx>, Vec>, TyCtxt<'tcx>); + impl<'tcx> DeepTypeVisitor<'tcx> for AllBehindWalker<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.2 + } + + fn visit_rec(&mut self, ty: Ty<'tcx>, stack: &mut Stack<'tcx>) { + ty.visit_with(self, stack); + } + } + todo!() + } + + pub fn mk_deref(self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { + let elems = repacker.tcx.mk_place_elems_from_iter( + self.projection.iter().copied().chain(std::iter::once(PlaceElem::Deref)) + ); + Self::new(self.local, elems) + } } diff --git a/mir-state-analysis/src/utils/ty/mod.rs b/mir-state-analysis/src/utils/ty/mod.rs new file mode 100644 index 00000000000..3f9a7927203 --- /dev/null +++ b/mir-state-analysis/src/utils/ty/mod.rs @@ -0,0 +1,95 @@ +// © 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 ty_rec; + +pub use ty_rec::*; + +use prusti_rustc_interface::{ + data_structures::fx::FxHashSet, + dataflow::storage, + index::bit_set::BitSet, + middle::{ + mir::{ + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, + ProjectionElem, + }, + ty::{Ty, TyKind, TyCtxt}, + }, +}; + +pub struct Stack<'tcx>(Vec>); + +impl<'tcx> Stack<'tcx> { + pub fn get(&self) -> &Vec> { + &self.0 + } +} + +pub trait DeepTypeVisitor<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx>; + + fn visit(&mut self, ty: Ty<'tcx>) { + self.visit_rec(ty, &mut Stack(Vec::new())); + } + fn visit_rec(&mut self, ty: Ty<'tcx>, stack: &mut Stack<'tcx>); +} + +pub trait DeepTypeVisitable<'tcx> { + fn visit_with(&self, visitor: &mut impl DeepTypeVisitor<'tcx>, stack: &mut Stack<'tcx>); +} + +impl<'tcx> DeepTypeVisitable<'tcx> for Ty<'tcx> { + fn visit_with(&self, visitor: &mut impl DeepTypeVisitor<'tcx>, stack: &mut Stack<'tcx>) { + if stack.0.contains(self) { return; } + stack.0.push(*self); + match self.kind() { + TyKind::Bool | + TyKind::Char | + TyKind::Int(_) | + TyKind::Uint(_) | + TyKind::Float(_) | + TyKind::Str => (), + TyKind::Adt(def_id, substs) => { + for field in def_id.all_fields() { + let ty = field.ty(visitor.tcx(), substs); + visitor.visit_rec(ty, stack); + } + } + TyKind::Tuple(tys) => { + for ty in tys.iter() { + visitor.visit_rec(ty, stack); + } + } + TyKind::Ref(_, ty, _) => { + visitor.visit_rec(*ty, stack); + } + TyKind::Foreign(_) => todo!(), + TyKind::Array(_, _) => todo!(), + TyKind::Slice(_) => todo!(), + TyKind::RawPtr(_) => todo!(), + TyKind::FnDef(_, _) => todo!(), + TyKind::FnPtr(_) => todo!(), + TyKind::Dynamic(_, _, _) => todo!(), + TyKind::Closure(_, _) => todo!(), + TyKind::Generator(_, _, _) => todo!(), + TyKind::GeneratorWitness(_) => todo!(), + TyKind::GeneratorWitnessMIR(_, _) => todo!(), + TyKind::Never => todo!(), + TyKind::Alias(_, _) => todo!(), + TyKind::Param(_) => todo!(), + TyKind::Bound(_, _) => todo!(), + TyKind::Placeholder(_) => todo!(), + TyKind::Infer(_) => todo!(), + TyKind::Error(_) => todo!(), + } + stack.0.pop(); + } +} + +// pub fn is_ty_rec(ty: Ty) -> bool { +// let walker = ty.walk(); +// } diff --git a/mir-state-analysis/src/utils/ty/ty_rec.rs b/mir-state-analysis/src/utils/ty/ty_rec.rs new file mode 100644 index 00000000000..bcd49bdb2ef --- /dev/null +++ b/mir-state-analysis/src/utils/ty/ty_rec.rs @@ -0,0 +1,40 @@ +// © 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, + dataflow::storage, + index::bit_set::BitSet, + middle::{ + mir::{ + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, + ProjectionElem, + }, + ty::{Ty, TyKind, TyCtxt}, + }, +}; + +use crate::utils::ty::{DeepTypeVisitor, DeepTypeVisitable, Stack}; + +pub fn is_ty_rec<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool { + struct RecTyWalk<'tcx>(TyCtxt<'tcx>, bool); + impl<'tcx> DeepTypeVisitor<'tcx> for RecTyWalk<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.0 + } + + fn visit_rec(&mut self, ty: Ty<'tcx>, stack: &mut Stack<'tcx>) { + if stack.get().contains(&ty) { + self.1 = true; + return; + } + ty.visit_with(self, stack); + } + } + let mut walker = RecTyWalk(tcx, false); + walker.visit(ty); + walker.1 +} diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 48eea9e314a..326c0d2d5bd 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -10,7 +10,7 @@ use rustc_hash::FxHashMap; use rustc_middle::ty::GenericArgsRef; use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc}; -use crate::environment::{borrowck::facts::BorrowckFacts, mir_storage}; +use crate::environment::{borrowck::facts::{BorrowckFacts, BorrowckFacts2}, mir_storage}; /// Stores any possible MIR body (from the compiler) that /// Prusti might want to work with. Cheap to clone @@ -33,6 +33,7 @@ struct BodyWithBorrowckFacts<'tcx> { body: MirBody<'tcx>, /// Cached borrowck information. borrowck_facts: Rc, + borrowck_facts2: Rc>, } /// Bodies which need not be synched across crates and so can be @@ -138,10 +139,15 @@ impl<'tcx> EnvBody<'tcx> { output_facts: body_with_facts.output_facts, location_table: RefCell::new(body_with_facts.location_table), }; + let facts2 = BorrowckFacts2 { + borrow_set: body_with_facts.borrow_set, + region_inference_context: body_with_facts.region_inference_context, + }; BodyWithBorrowckFacts { body: MirBody(Rc::new(body_with_facts.body)), borrowck_facts: Rc::new(facts), + borrowck_facts2: Rc::new(facts2), } } @@ -315,6 +321,17 @@ impl<'tcx> EnvBody<'tcx> { .map(|body| body.borrowck_facts.clone()) } + #[tracing::instrument(level = "debug", skip(self))] + pub fn try_get_local_mir_borrowck_facts2( + &self, + def_id: LocalDefId, + ) -> Option>> { + self.local_impure_fns + .borrow() + .get(&def_id) + .map(|body| body.borrowck_facts2.clone()) + } + /// Ensures that the MIR body of a local spec is cached. This must be called on all specs, /// prior to requesting their bodies with `get_spec_body` or exporting with `CrossCrateBodies::from`! pub(crate) fn load_spec_body(&mut self, def_id: LocalDefId) { diff --git a/prusti-interface/src/environment/borrowck/facts.rs b/prusti-interface/src/environment/borrowck/facts.rs index 557c4a4fd7d..314b3477da0 100644 --- a/prusti-interface/src/environment/borrowck/facts.rs +++ b/prusti-interface/src/environment/borrowck/facts.rs @@ -5,7 +5,10 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ - borrowck::consumers::{LocationTable, RichLocation, RustcFacts}, + borrowck::{ + borrow_set::BorrowSet, + consumers::{LocationTable, RichLocation, RustcFacts, RegionInferenceContext}, + }, middle::mir, polonius_engine::FactTypes, }; @@ -32,6 +35,14 @@ impl LocationTableExt for LocationTable { } } +pub struct BorrowckFacts2<'tcx> { + /// The set of borrows occurring in `body` with data about them. + pub borrow_set: Rc>, + /// Context generated during borrowck, intended to be passed to + /// [`OutOfScopePrecomputer`](dataflow::OutOfScopePrecomputer). + pub region_inference_context: Rc>, +} + pub struct BorrowckFacts { /// Polonius input facts. pub input_facts: RefCell>, diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 4cfc879a9e2..5941025b1ef 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -132,6 +132,7 @@ lazy_static::lazy_static! { settings.set_default("num_errors_per_function", 1).unwrap(); // TODO: remove this option settings.set_default("test_free_pcs", false).unwrap(); + settings.set_default("test_coupling_graph", false).unwrap(); settings.set_default("print_desugared_specs", false).unwrap(); settings.set_default("print_typeckd_specs", false).unwrap(); @@ -1037,3 +1038,7 @@ pub fn num_errors_per_function() -> u32 { pub fn test_free_pcs() -> bool { read_setting("test_free_pcs") } + +pub fn test_coupling_graph() -> bool { + read_setting("test_coupling_graph") +} diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 172c4ff945f..16a67080f9f 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -1,5 +1,5 @@ use crate::verifier::verify; -use mir_state_analysis::test_free_pcs; +use mir_state_analysis::{test_coupling_graph, test_free_pcs}; use prusti_common::config; use prusti_interface::{ environment::{mir_storage, Environment}, @@ -168,6 +168,19 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { let mir = current_procedure.get_mir_rc(); test_free_pcs(&mir, tcx); } + } else if config::test_coupling_graph() { + for proc_id in env.get_annotated_procedures_and_types().0.iter() { + let mir = env.body.get_impure_fn_body_identity(proc_id.expect_local()); + let facts = env + .body + .try_get_local_mir_borrowck_facts(proc_id.expect_local()) + .unwrap(); + let facts2 = env + .body + .try_get_local_mir_borrowck_facts2(proc_id.expect_local()) + .unwrap(); + test_coupling_graph(&*mir, &*facts, &*facts2, tcx); + } } else { verify(env, def_spec); } From 1f9441e3faabb51ed4436daa58e5c18b646f6f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 22 Sep 2023 14:06:28 +0200 Subject: [PATCH 14/58] Add merge op --- mir-state-analysis/Cargo.toml | 1 + .../src/coupling_graph/impl/cg.rs | 169 ++++++++++--- .../src/coupling_graph/impl/dot.rs | 45 +++- .../src/coupling_graph/impl/engine.rs | 230 +++++++++++------- .../coupling_graph/impl/join_semi_lattice.rs | 45 +++- .../src/free_pcs/impl/triple.rs | 1 - mir-state-analysis/src/lib.rs | 11 +- mir-state-analysis/tests/top_crates.rs | 3 +- 8 files changed, 364 insertions(+), 141 deletions(-) diff --git a/mir-state-analysis/Cargo.toml b/mir-state-analysis/Cargo.toml index dbf4ee2bb71..fbbf497f3d3 100644 --- a/mir-state-analysis/Cargo.toml +++ b/mir-state-analysis/Cargo.toml @@ -11,6 +11,7 @@ prusti-rustc-interface = { path = "../prusti-rustc-interface" } dot = "0.1" # TODO: remove prusti-interface = { path = "../prusti-interface" } +regex = "1" [dev-dependencies] reqwest = { version = "^0.11", features = ["blocking"] } diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs index 97f229467bd..0f0c143a2b7 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -13,7 +13,7 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, borrowck::{borrow_set::BorrowData, consumers::BorrowIndex}, - middle::{mir::ConstraintCategory, ty::{RegionVid, TyKind}}, + middle::{mir::{ConstraintCategory, RETURN_PLACE}, ty::{RegionVid, TyKind}}, }; use crate::{ @@ -36,6 +36,8 @@ pub type NodeId = usize; #[derive(Clone)] pub struct Graph<'a, 'tcx> { + pub id: Option, + pub version: usize, pub rp: PlaceRepacker<'a, 'tcx>, pub facts: &'a BorrowckFacts, pub facts2: &'a BorrowckFacts2<'tcx>, @@ -49,10 +51,13 @@ impl PartialEq for Graph<'_, '_> { self.nodes == other.nodes } } +impl Eq for Graph<'_, '_> {} impl<'a, 'tcx> Graph<'a, 'tcx> { pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { let mut result = Self { + id: None, + version: 0, rp, facts, facts2, @@ -64,30 +69,51 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { // result.outlives(r1, r2); // } - let constraints = facts2.region_inference_context.outlives_constraints(); - for c in constraints { - if c.locations.from_location().is_none() { - result.outlives(c.sup, c.sub, c.category); - } - } + + //////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) + // let input_facts = facts.input_facts.borrow(); + // let input_facts = input_facts.as_ref().unwrap(); + // let constraints = facts2.region_inference_context.outlives_constraints(); + // for c in constraints { + // if c.locations.from_location().is_none() { + // let l: Vec<_> = input_facts.use_of_var_derefs_origin.iter().filter(|(_, r)| *r == c.sup).collect(); + // assert!(l.len() <= 1); + // if l.len() == 1 && l[0].0 == RETURN_PLACE { + // continue; + // } + // // println!("c: {c:?}...{:?} {:?}", c.sub, c.sup); + // result.outlives(c.sup, c.sub, c.category); + // } + // } result } pub fn new_shared_borrow(&mut self, data: BorrowData<'tcx>) { self.shared_borrows.push(data); } - pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) { + // r1 outlives r2, or `r1: r2` (i.e. r1 gets blocked) + pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) -> bool { + self.outlives_many(r1, r2, &FxHashSet::from_iter([reason].into_iter())) + } + pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { let n1 = self.region_to_node(r1); let n2 = self.region_to_node(r2); if n1 == n2 { - return; + return false; } // println!("Adding outlives {r1:?} ({n1}): {r2:?} ({n2})"); if let Some(path) = self.reachable(n1, n2) { + let mut changed = false; for other in path { + // Can only merge nodes which are not equal + changed = true; self.merge(other, n2); } + // self.sanity_check(); + changed } else { - self.blocks(n2, n1, reason); + let changed = self.blocks(n2, n1, reasons); + // self.sanity_check(); + changed } } // pub fn contained_by(&mut self, r: RegionVid, l: Local) { @@ -96,7 +122,8 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // } pub fn kill(&mut self, r: RegionVid) { let n = self.region_to_node(r); - self.kill_node(n) + self.kill_node(n); + // self.sanity_check(); } pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool) { for n in self.nodes.iter_mut() { @@ -105,18 +132,56 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { n.regions.remove(&r); if n.regions.is_empty() { let id = n.id; - let n = self.remove_node(id); - for (&block, _) in &n.blocks { - for (&blocked_by, &edge) in &n.blocked_by { - self.blocks(blocked_by, block, edge.reason); - } - } + self.remove_node_rejoin(id); } return; } } } assert!(maybe_already_removed, "Region {:?} not found in graph", r); + // self.sanity_check(); + } + // Used when merging two graphs (and we know from one graph that two regions are equal) + pub fn equate_regions(&mut self, ra: RegionVid, rb: RegionVid) -> bool { + let mut changed = self.outlives(ra, rb, ConstraintCategory::Internal); + changed = changed || self.outlives(rb, ra, ConstraintCategory::Internal); + // self.sanity_check(); + changed + } + pub fn edge_to_regions(&self, from: NodeId, to: NodeId) -> (RegionVid, RegionVid) { + let n1 = self.get_node(from); + let n2 = self.get_node(to); + let r1 = *n1.regions.iter().next().unwrap(); + let r2 = *n2.regions.iter().next().unwrap(); + (r1, r2) + } + pub fn merge_for_return(&mut self) { + let nodes: Vec<_> = self.all_nodes().map(|n| n.id).collect(); + for n in nodes { + if self.is_borrow_only(n).is_some() || self.is_empty_node(n) { + self.remove_node_rejoin(n); + } else { + let in_facts = self.facts.input_facts.borrow(); + let universal_region = &in_facts.as_ref().unwrap().universal_region; + let proof = self.facts2.region_inference_context.outlives_constraints().find(|c| { + universal_region.contains(&c.sub) + // let r = c.sub.as_u32(); // The thing that lives shorter + // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region + }); + // If None then we have something left which doesn't outlive the function region! + // if proof.is_none() { + // let in_facts = self.facts.input_facts.borrow(); + // let r = &in_facts.as_ref().unwrap().universal_region; + // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); + // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); + // // std::fs::remove_dir_all("log/coupling").ok(); + // // std::fs::create_dir_all("log/coupling/individual").unwrap(); + // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); + // dot::render(self, &mut f).unwrap(); + // } + assert!(proof.is_some(), "Found a region which does not outlive the function region: {:?}", self.get_node(n)); + } + } } fn reachable(&self, from: NodeId, to: NodeId) -> Option> { @@ -160,12 +225,12 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { let to_merge = self.remove_node(n1); for (block, edge) in to_merge.blocks { if block != n2 { - self.blocks(n2, block, edge.reason); + self.blocks(n2, block, &edge); } } for (block_by, edge) in to_merge.blocked_by { if block_by != n2 { - self.blocks(block_by, n2, edge.reason); + self.blocks(block_by, n2, &edge); } } let n2 = self.get_node_mut(n2); @@ -175,9 +240,23 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { fn kill_node(&mut self, n: NodeId) { let removed = self.remove_node(n); for (blocked_by, _) in removed.blocked_by { - self.kill_node(blocked_by); + // May have a diamond shape, so may + if self.nodes[blocked_by].is_some() { + self.kill_node(blocked_by); + } + } + } + + fn remove_node_rejoin(&mut self, id: NodeId) -> Node<'tcx> { + let n = self.remove_node(id); + for (&block, _) in &n.blocks { + for (&blocked_by, edge) in &n.blocked_by { + self.blocks(blocked_by, block, edge); + } } + n } + // Remove node without rejoining the graph fn remove_node(&mut self, n: NodeId) -> Node<'tcx> { let to_remove = self.nodes[n].take().unwrap(); for &block in to_remove.blocks.keys() { @@ -196,33 +275,57 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { self.nodes[n].as_mut().unwrap() } - fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: ConstraintCategory<'tcx>) { - let block = Edge::new(n1, n2, reason); - self.get_node_mut(n1).blocks.insert(n2, block); - let blocked_by = Edge::new(n2, n1, reason); - self.get_node_mut(n2).blocked_by.insert(n1, blocked_by); + fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { + let mut changed = false; + let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); + let old_size = reasons.len(); + reasons.extend(reason); + changed = changed || reasons.len() != old_size; + let reasons = self.get_node_mut(n2).blocked_by.entry(n1).or_default(); + let old_size = reasons.len(); + reasons.extend(reason); + changed = changed || reasons.len() != old_size; + changed + } + fn sanity_check(&self) { + for n1 in self.all_nodes().map(|n| n.id) { + for n2 in self.all_nodes().map(|n| n.id) { + if n1 != n2 { + let path_a = self.reachable(n1, n2); + let path_b = self.reachable(n2, n1); + if path_a.is_some() && path_b.is_some() { + let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); + dot::render(self, &mut f).unwrap(); + panic!("Found cycle between {} and {}", n1, n2); + } + } + } + } + } + fn all_nodes(&self) -> impl Iterator> { + self.nodes.iter().filter_map(|n| n.as_ref()) } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { pub id: NodeId, pub regions: FxHashSet, - pub blocks: FxHashMap>, - pub blocked_by: FxHashMap>, + pub blocks: FxHashMap>>, + pub blocked_by: FxHashMap>>, // pub contained_by: Vec, } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Edge<'tcx> { pub from: NodeId, pub to: NodeId, - pub reason: ConstraintCategory<'tcx>, + pub reasons: FxHashSet>, } impl<'tcx> Edge<'tcx> { - fn new(from: NodeId, to: NodeId, reason: ConstraintCategory<'tcx>) -> Self { - Self { from, to, reason } + pub(crate) fn new(from: NodeId, to: NodeId, reasons: FxHashSet>) -> Self { + Self { from, to, reasons } } } @@ -248,7 +351,7 @@ pub struct Cg<'a, 'tcx> { } impl<'a, 'tcx> Cg<'a, 'tcx> { pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { - let live = BitSet::new_empty(facts2.borrow_set.location_map.len() * 2); + let live = BitSet::new_empty(facts2.borrow_set.location_map.len()); let regions = Regions { borrows: FxHashMap::default(), subset: Vec::new(), diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index 0210ca9f040..73419d83a5b 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -19,6 +19,9 @@ use crate::utils::Place; use super::cg::{NodeId, Edge, Graph}; impl<'a, 'tcx> Graph<'a, 'tcx> { + fn get_id(&self) -> &str { + self.id.as_deref().unwrap_or("unnamed") + } fn get_corresponding_places(&self, n: NodeId) -> Vec>> { let node = self.get_node(n); let mut contained_by: Vec> = Vec::new(); @@ -40,10 +43,23 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } contained_by } - fn is_empty_node(&self, n: NodeId) -> bool { + pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { self.get_corresponding_places(n).is_empty() } - fn is_borrow_only(&self, n: NodeId) -> Option { + fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { + if !(self.skip_empty_nodes && self.is_empty_node(n)) { + return vec![Edge::new(start, n, reasons)]; + } + let mut edges = Vec::new(); + let node = self.get_node(n); + for (&b, edge) in &node.blocks { + let mut reasons = reasons.clone(); + reasons.extend(edge); + edges.extend(self.non_empty_edges(b, start, reasons)); + } + edges + } + pub(crate) fn is_borrow_only(&self, n: NodeId) -> Option { let node = self.get_node(n); let input_facts = self.facts.input_facts.borrow(); for &(_, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { @@ -79,10 +95,10 @@ enum Perms { } impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { - fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example1").unwrap() } + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.get_id()).unwrap() } fn node_id(&'a self, n: &NodeId) -> dot::Id<'a> { - dot::Id::new(format!("N_{n:?}")).unwrap() + dot::Id::new(format!("N_{n:?}_{}", self.get_id())).unwrap() } fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { @@ -92,8 +108,15 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { dot::Arrow::default() } } + fn edge_style(&'a self, e: &Edge<'tcx>) -> dot::Style { + if self.is_borrow_only(e.from).is_some() { + dot::Style::Dashed + } else { + dot::Style::Solid + } + } fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { - let label = match e.reason { + let mut label = e.reasons.iter().map(|r| match r { ConstraintCategory::Return(_) => "return", ConstraintCategory::Yield => "yield", ConstraintCategory::UseAsConst => "const", @@ -112,8 +135,9 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { ConstraintCategory::Boring => "boring", ConstraintCategory::BoringNoLocation => "boring_nl", ConstraintCategory::Internal => "internal", - }; - dot::LabelText::LabelStr(Cow::Borrowed(label)) + }).map(|s| format!("{s}, ")).collect::(); + label = label[..label.len() - 2].to_string(); + dot::LabelText::LabelStr(Cow::Owned(label)) } fn node_shape(&'a self, n: &NodeId) -> Option> { self.is_borrow_only(*n).map(|kind| match kind { @@ -175,11 +199,8 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { if self.skip_empty_nodes && self.is_empty_node(c) { continue; } - for (&b, &edge) in &n.blocks { - if self.skip_empty_nodes && self.is_empty_node(b) { - continue; - } - edges.push(edge); + for (&b, edge) in &n.blocks { + edges.extend(self.non_empty_edges(b, c, edge.clone())); } } } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index bae30158213..aca9524087c 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ - data_structures::fx::{FxIndexMap}, + data_structures::fx::{FxIndexMap, FxHashSet}, borrowck::{ borrow_set::{BorrowData, TwoPhaseActivation}, consumers::{Borrows, BorrowIndex, RichLocation, calculate_borrows_out_of_scope_at_location}, @@ -31,17 +31,60 @@ use crate::{ use super::cg::{Cg, Regions}; +pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { + let mut graph = Vec::new(); + let body = c.body(); + for (block, data) in body.basic_blocks.iter_enumerated() { + if data.is_cleanup { + continue; + } + c.seek_to_block_start(block); + let mut g = c.get().regions.graph.clone(); + g.id = Some(format!("pre_{block:?}")); + dot::render(&g, &mut graph).unwrap(); + for statement_index in 0..body.terminator_loc(block).statement_index+1 { + c.seek_after_primary_effect(Location { block, statement_index }); + let mut g = c.get().regions.graph.clone(); + g.id = Some(format!("{block:?}_{statement_index}")); + dot::render(&g, &mut graph).unwrap(); + } + } + let combined = std::str::from_utf8(graph.as_slice()).unwrap().to_string(); + + let regex = regex::Regex::new(r"digraph (([^ ])+) \{").unwrap(); + let combined = regex.replace_all(&combined, "subgraph cluster_$1 {\n label = \"$1\""); + + // let mut paths: Vec<_> = std::fs::read_dir("log/coupling/individual").unwrap().into_iter().map(|p| p.unwrap().path()).collect(); + // paths.sort_by(|a, b| { + // let a = a.file_stem().unwrap().to_string_lossy(); + // let mut a = a.split("_"); + // let a0 = a.next().unwrap().strip_prefix("bb").unwrap().parse::().unwrap(); + // let a1 = a.next().unwrap().parse::().unwrap(); + // let b = b.file_stem().unwrap().to_string_lossy(); + // let mut b = b.split("_"); + // let b0 = b.next().unwrap().strip_prefix("bb").unwrap().parse::().unwrap(); + // let b1 = b.next().unwrap().parse::().unwrap(); + // (a0, a1).cmp(&(b0, b1)) + // }); + // let combined = paths.into_iter().fold(String::new(), |acc, p| { + // let data = std::fs::read_to_string(p).expect("Unable to read file"); + // acc + ®ex.replace_all(&data, "subgraph cluster_$1 {\n label = \"$1\"") + // }); + std::fs::write("log/coupling/all.dot", format!("digraph root {{\n{combined}}}")).expect("Unable to write file"); +} + pub(crate) struct CoupligGraph<'a, 'tcx> { pub(crate) repacker: PlaceRepacker<'a, 'tcx>, pub(crate) facts: &'a BorrowckFacts, pub(crate) facts2: &'a BorrowckFacts2<'tcx>, pub(crate) flow_borrows: RefCell>>, pub(crate) out_of_scope: FxIndexMap>, + pub(crate) printed_dot: FxHashSet, } impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { - std::fs::remove_dir_all("log/coupling").ok(); - std::fs::create_dir_all("log/coupling").unwrap(); + // std::fs::remove_dir_all("log/coupling").ok(); + // std::fs::create_dir_all("log/coupling/individual").unwrap(); let repacker = PlaceRepacker::new(body, tcx); let regioncx = &*facts2.region_inference_context; @@ -52,56 +95,60 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { .pass_name("borrowck") .iterate_to_fixpoint() .into_results_cursor(body); - CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope } + let printed_dot = FxHashSet::default(); + CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope, printed_dot } } fn handle_diff(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { - let input_facts = self.facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - let location_table = self.facts.location_table.borrow(); - let location_table = location_table.as_ref().unwrap(); - for idx in delta.set.iter() { - let loan_issued_at = &input_facts.loan_issued_at; - let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); - let l = location_table.to_location(l); - let RichLocation::Mid(l) = l else { unreachable!() }; - assert_eq!(l, location); - let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); - state.borrows.insert(idx, (vec![r], locals)); - } - state.subset.extend(input_facts.subset_base.iter().filter( - |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location - ).map(|(r1, r2, _)| (*r1, *r2))); - // TODO: do a proper fixpoint here - for _ in 0..10 { - for &(r1, r2) in &state.subset { - let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); - let mut did_push = false; - for (_, s) in state.borrows.iter_mut() { - if s.0.contains(&r1) { - did_push = true; - if !s.0.contains(&r2) { - s.0.push(r2); - s.1.extend(locals.iter().copied()); - } - } - } - // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); - } - } - for r in delta.cleared.iter() { - let removed = state.borrows.remove(&r).unwrap(); - // for (_, s) in state.borrows.iter_mut() { - // s.0.retain(|r| !removed.0.contains(r)); - // s.1.retain(|l| !removed.1.contains(l)); - // } - } + // let input_facts = self.facts.input_facts.borrow(); + // let input_facts = input_facts.as_ref().unwrap(); + // let location_table = self.facts.location_table.borrow(); + // let location_table = location_table.as_ref().unwrap(); + // for idx in delta.set.iter() { + // let loan_issued_at = &input_facts.loan_issued_at; + // let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); + // let l = location_table.to_location(l); + // let RichLocation::Mid(l) = l else { unreachable!() }; + // assert_eq!(l, location); + // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); + // state.borrows.insert(idx, (vec![r], locals)); + // } + // state.subset.extend(input_facts.subset_base.iter().filter( + // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location + // ).map(|(r1, r2, _)| (*r1, *r2))); + // // TODO: do a proper fixpoint here + // for _ in 0..10 { + // for &(r1, r2) in &state.subset { + // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); + // let mut did_push = false; + // for (_, s) in state.borrows.iter_mut() { + // if s.0.contains(&r1) { + // did_push = true; + // if !s.0.contains(&r2) { + // s.0.push(r2); + // s.1.extend(locals.iter().copied()); + // } + // } + // } + // // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); + // } + // } + // for r in delta.cleared.iter() { + // // TODO: why does unwrap fail? + // let removed = state.borrows.remove(&r);//.unwrap(); + // // for (_, s) in state.borrows.iter_mut() { + // // s.0.retain(|r| !removed.0.contains(r)); + // // s.1.retain(|l| !removed.1.contains(l)); + // // } + // } // print!(" {:?}", state.borrows); self.handle_graph(state, delta, location); } fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { let l = format!("{:?}", location).replace('[', "_").replace(']', ""); + state.graph.id = Some(l.clone()); + // println!("location: {:?}", l); let input_facts = self.facts.input_facts.borrow(); let input_facts = input_facts.as_ref().unwrap(); @@ -116,19 +163,19 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { let (r, _, l) = input_facts.loan_issued_at.iter().find( |(_, b, _)| bi == b ).copied().unwrap(); - println!("UGHBJS region: {r:?} location: {l:?}"); + // println!("UGHBJS region: {r:?} location: {l:?}"); state.graph.kill(r); let l = rich_to_loc(location_table.to_location(l)); let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); let local = borrow_data.assigned_place.local; for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - println!("IHUBJ local: {local:?} region: {region:?}"); + // println!("IHUBJ local: {local:?} region: {region:?}"); state.graph.remove(region, true); } } } - // let mut f = std::fs::File::create(format!("log/coupling/{l}_a.dot")).unwrap(); + // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_a.dot")).unwrap(); // dot::render(&state.graph, &mut f).unwrap(); for killed in delta.cleared.iter() { @@ -138,16 +185,18 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { let (r, _, l) = input_facts.loan_issued_at.iter().find( |(_, b, _)| killed == *b ).copied().unwrap(); + // println!("killed: {r:?} {killed:?} {l:?}"); state.graph.remove(r, false); let l = rich_to_loc(location_table.to_location(l)); let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); let local = borrow_data.borrowed_place.local; for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // println!("killed region: {region:?}"); state.graph.remove(region, true); } } - // let mut f = std::fs::File::create(format!("log/coupling/{l}_b.dot")).unwrap(); + // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_b.dot")).unwrap(); // dot::render(&state.graph, &mut f).unwrap(); // let new_subsets: Vec<_> = input_facts.subset_base.iter().filter( @@ -166,8 +215,8 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { } if !self.repacker.body().basic_blocks[location.block].is_cleanup { - let mut f = std::fs::File::create(format!("log/coupling/{l}_c.dot")).unwrap(); - dot::render(&state.graph, &mut f).unwrap(); + // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_v{}.dot", state.graph.version)).unwrap(); + // dot::render(&state.graph, &mut f).unwrap(); } } } @@ -181,20 +230,20 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { - // println!("body: {body:?}"); - println!("\ninput_facts: {:?}", self.facts.input_facts); - println!("output_facts: {:#?}\n", self.facts.output_facts); - println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); - println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); - println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); - // println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context); - // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); - let lt = self.facts.location_table.borrow(); - let lt = lt.as_ref().unwrap(); - for pt in lt.all_points() { - println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); - } - println!("out_of_scope: {:?}", self.out_of_scope); + // // println!("body: {body:?}"); + // println!("\ninput_facts: {:?}", self.facts.input_facts); + // println!("output_facts: {:#?}\n", self.facts.output_facts); + // println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); + // println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); + // println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); + // println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context.outlives_constraints().collect::>()); + // // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); + // let lt = self.facts.location_table.borrow(); + // let lt = lt.as_ref().unwrap(); + // for pt in lt.all_points() { + // println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); + // } + // println!("out_of_scope: {:?}", self.out_of_scope); } } @@ -206,7 +255,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { location: Location, ) { if location.statement_index == 0 { - println!("\nblock: {:?}", location.block); + // println!("\nblock: {:?}", location.block); self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } @@ -217,7 +266,6 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { if delta.set.is_empty() { match statement.kind { StatementKind::Assign(box (assigned_place, Rvalue::Ref(region, kind, borrowed_place))) => { - state.regions.graph.new_shared_borrow(BorrowData { reserve_location: location, activation_location: TwoPhaseActivation::NotTwoPhase, @@ -227,6 +275,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assigned_place, }) } + // If a local was only moved out of and not reborrowed, it's region is never killed officially + StatementKind::StorageDead(local) => { + let input_facts = self.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // println!("killed region manually: {region:?}"); + state.regions.graph.remove(region, true); + } + } _ => (), } } @@ -242,34 +299,37 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { location: Location, ) { if location.statement_index == 0 { - println!("\nblock: {:?}", location.block); + // println!("\nblock: {:?}", location.block); self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } self.flow_borrows.borrow_mut().seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); - if let TerminatorKind::Call { func, args, destination, target, fn_span, .. } = &terminator.kind { - if let Operand::Constant(c) = func { - println!("user_ty: {:?}", c.user_ty); - println!("call: {:?}", c.literal); - if let ConstantKind::Val(cv, ty) = c.literal { - println!("val: {:?}", cv); - println!("ty: {:?}", ty); - } - println!("\n\n\ncall: {:?}", func); - } - for arg in args { - match arg { - Operand::Copy(a) => println!("copy ({arg:?}): {:?}", a), - Operand::Move(b) => println!("move ({arg:?}): {:?}", b), - Operand::Constant(c) => println!("const ({arg:?}): {:?}", c.literal), - } - } - } + // if let TerminatorKind::Call { func, args, destination, target, fn_span, .. } = &terminator.kind { + // if let Operand::Constant(c) = func { + // println!("user_ty: {:?}", c.user_ty); + // println!("call: {:?}", c.literal); + // if let ConstantKind::Val(cv, ty) = c.literal { + // println!("val: {:?}", cv); + // println!("ty: {:?}", ty); + // } + // println!("\n\n\ncall: {:?}", func); + // } + // for arg in args { + // match arg { + // Operand::Copy(a) => println!("copy ({arg:?}): {:?}", a), + // Operand::Move(b) => println!("move ({arg:?}): {:?}", b), + // Operand::Constant(c) => println!("const ({arg:?}): {:?}", c.literal), + // } + // } + // } // print!("{terminator:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); self.handle_diff(&mut state.regions, delta, location); state.live = other; + if let TerminatorKind::Return = &terminator.kind { + state.regions.graph.merge_for_return(); + } // println!(); } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 27c10e8cc4c..089f2b1ae6d 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,14 +15,17 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::cg::Cg; +use super::cg::{Cg, Graph}; impl JoinSemiLattice for Cg<'_, '_> { fn join(&mut self, other: &Self) -> bool { - if self.done == 2 { - return false; - } - self.done += 1; + // if self.done == 20 { + // panic!(); + // // return false; + // } + // self.done += 1; + let actually_changed = self.regions.graph.join(&other.regions.graph); + return actually_changed; let mut changed = self.live.union(&other.live); for (idx, data) in other.regions.borrows.iter() { match self.regions.borrows.entry(*idx) { @@ -53,9 +56,35 @@ impl JoinSemiLattice for Cg<'_, '_> { self.regions.subset.push(*s); } } - if self.regions.graph != other.regions.graph { - changed = true; - self.regions.graph = other.regions.graph.clone(); + // if self.regions.graph != other.regions.graph { + // changed = true; + // self.regions.graph = other.regions.graph.clone(); + // } + actually_changed + } +} + +impl JoinSemiLattice for Graph<'_, '_> { + fn join(&mut self, other: &Self) -> bool { + // println!("Joining graphs:\n{:?}: {:?}\n{:?}: {:?}", self.id, self.nodes, other.id, other.nodes); + let mut changed = false; + for node in other.nodes.iter().flat_map(|n| n) { + let rep = node.regions.iter().next().unwrap(); + for r in node.regions.iter().skip(1) { + changed = changed || self.equate_regions(*rep, *r); + } + for (to, reasons) in node.blocks.iter() { + let (from, to) = other.edge_to_regions(node.id, *to); + let was_new = self.outlives_many(to, from, reasons); + changed = changed || was_new; + // if was_new { + // println!("New edge: {:?} -> {:?} ({:?})", from, to, reasons); + // } + } + } + if changed { + self.version += 1; + assert!(self.version < 1000); } changed } diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index c40d20cb00a..a9d6c19cb2b 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -59,7 +59,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/lib.rs b/mir-state-analysis/src/lib.rs index c691d08237c..6d08e686b56 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -40,12 +40,21 @@ pub fn run_coupling_graph<'mir, 'tcx>( facts2: &'mir BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, ) { + // let name = tcx.opt_item_name(mir.source.def_id()); + // println!("Running for {}", name.as_ref().map(|n| n.as_str()).unwrap_or("unnamed")); + println!("Running for {:?}", mir.source.def_id()); + // if tcx.item_name(mir.source.def_id()).as_str() == "main" { + // return; + // } let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2); let analysis = fpcs .into_engine(tcx, mir) .pass_name("coupling_graph") .iterate_to_fixpoint(); - // free_pcs::FreePcsAnalysis::new(analysis.into_results_cursor(mir)) + let c = analysis.into_results_cursor(mir); + if cfg!(debug_assertions) { + coupling_graph::engine::draw_dots(c); + } } pub fn test_coupling_graph<'tcx>(mir: &Body<'tcx>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>) { diff --git a/mir-state-analysis/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs index 84dee437ac0..3945b1f1a37 100644 --- a/mir-state-analysis/tests/top_crates.rs +++ b/mir-state-analysis/tests/top_crates.rs @@ -64,7 +64,8 @@ fn run_on_crate(name: &str, version: &str) { ); println!("Running: {prusti:?}"); let exit = std::process::Command::new(prusti) - .env("PRUSTI_TEST_FREE_PCS", "true") + // .env("PRUSTI_TEST_FREE_PCS", "true") + .env("PRUSTI_TEST_COUPLING_GRAPH", "true") .env("PRUSTI_SKIP_UNSUPPORTED_FEATURES", "true") // .env("PRUSTI_LOG", "debug") .env("PRUSTI_NO_VERIFY_DEPS", "true") From 3d26e328241a6fe58bbd186444435f0eefc12c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 17:11:35 +0200 Subject: [PATCH 15/58] Almost done --- .../src/coupling_graph/impl/cg.rs | 379 +++++++++++++----- .../src/coupling_graph/impl/dot.rs | 137 +++---- .../src/coupling_graph/impl/engine.rs | 277 +++++++++---- .../coupling_graph/impl/join_semi_lattice.rs | 27 +- .../src/coupling_graph/impl/mod.rs | 40 ++ .../src/coupling_graph/impl/region_place.rs | 84 ++++ .../coupling_graph/impl/shared_borrow_set.rs | 100 +++++ mir-state-analysis/src/lib.rs | 12 +- mir-state-analysis/src/utils/display.rs | 7 + mir-state-analysis/src/utils/repacker.rs | 9 +- 10 files changed, 804 insertions(+), 268 deletions(-) create mode 100644 mir-state-analysis/src/coupling_graph/impl/region_place.rs create mode 100644 mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs index 0f0c143a2b7..07d9f898b67 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -9,11 +9,11 @@ use std::{fmt::{Debug, Formatter, Result}, borrow::Cow}; use derive_more::{Deref, DerefMut}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ - data_structures::fx::{FxHashMap, FxHashSet}, + data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, index::bit_set::BitSet, dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, borrowck::{borrow_set::BorrowData, consumers::BorrowIndex}, - middle::{mir::{ConstraintCategory, RETURN_PLACE}, ty::{RegionVid, TyKind}}, + middle::{mir::{ConstraintCategory, RETURN_PLACE, Location}, ty::{RegionVid, TyKind}}, }; use crate::{ @@ -23,13 +23,16 @@ use crate::{ utils::{PlaceRepacker, Place}, }; -use super::engine::CoupligGraph; +use super::{engine::CoupligGraph, shared_borrow_set::SharedBorrowSet, CgContext}; #[derive(Clone)] pub struct Regions<'a, 'tcx> { pub borrows: FxHashMap, Vec<(Local, RegionVid)>)>, pub(crate) subset: Vec<(RegionVid, RegionVid)>, + + pub version: usize, pub(crate) graph: Graph<'a, 'tcx>, + pub path_condition: Vec, } pub type NodeId = usize; @@ -37,15 +40,19 @@ pub type NodeId = usize; #[derive(Clone)] pub struct Graph<'a, 'tcx> { pub id: Option, - pub version: usize, pub rp: PlaceRepacker<'a, 'tcx>, pub facts: &'a BorrowckFacts, pub facts2: &'a BorrowckFacts2<'tcx>, + pub cgx: &'a CgContext<'a, 'tcx>, pub nodes: Vec>>, pub skip_empty_nodes: bool, - pub shared_borrows: Vec>, + // Regions equal to 'static + pub static_regions: FxHashSet, } +#[derive(Clone)] +pub struct PathCondition; + impl PartialEq for Graph<'_, '_> { fn eq(&self, other: &Self) -> bool { self.nodes == other.nodes @@ -54,67 +61,121 @@ impl PartialEq for Graph<'_, '_> { impl Eq for Graph<'_, '_> {} impl<'a, 'tcx> Graph<'a, 'tcx> { - pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { + // TODO: get it from `UniversalRegions` instead + pub fn static_region() -> RegionVid { + RegionVid::from_u32(0) + } + pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>) -> Self { let mut result = Self { id: None, - version: 0, rp, facts, facts2, + cgx, nodes: Vec::new(), skip_empty_nodes: false, - shared_borrows: Vec::new(), + static_regions: [Self::static_region()].into_iter().collect(), }; // let input_facts = facts.input_facts.borrow(); // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { // result.outlives(r1, r2); // } - //////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) - // let input_facts = facts.input_facts.borrow(); - // let input_facts = input_facts.as_ref().unwrap(); - // let constraints = facts2.region_inference_context.outlives_constraints(); - // for c in constraints { - // if c.locations.from_location().is_none() { - // let l: Vec<_> = input_facts.use_of_var_derefs_origin.iter().filter(|(_, r)| *r == c.sup).collect(); - // assert!(l.len() <= 1); - // if l.len() == 1 && l[0].0 == RETURN_PLACE { - // continue; + ////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) + let input_facts = facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + let constraints: Vec<_> = facts2.region_inference_context.outlives_constraints().collect(); + let constraints_no_loc: Vec<_> = constraints.iter().filter(|c| c.locations.from_location().is_none()).copied().collect(); + + // Make one single `'static` node + // let n = result.region_to_node(Self::static_region()); + // let node = result.get_node_mut(n); + // let mut to_make_static = vec![Self::static_region()]; + // while let Some(r) = to_make_static.pop() { + // for c in constraints.iter().filter(|c| c.sub == r) { + // if node.regions.insert(c.sup) { + // to_make_static.push(c.sup); // } - // // println!("c: {c:?}...{:?} {:?}", c.sub, c.sup); - // result.outlives(c.sup, c.sub, c.category); // } // } + // println!("Static node: {node:?}"); + let mut to_make_static = vec![Self::static_region()]; + while let Some(r) = to_make_static.pop() { + for c in constraints_no_loc.iter().filter(|c| c.sub == r) { + if result.static_regions.insert(c.sup) { + to_make_static.push(c.sup); + } + } + } + + + for c in constraints_no_loc { + // let l: Vec<_> = input_facts.use_of_var_derefs_origin.iter().filter(|(_, r)| *r == c.sup).collect(); + // assert!(l.len() <= 1); + // if l.len() == 1 && l[0].0 == RETURN_PLACE { + // continue; + // } + // println!("c: {c:?}...{:?} {:?}", c.sub, c.sup); + + // Add arguments to the graph... This doesn't seem to work + // for r in [c.sub, c.sup] { + // if let Some(p) = cgx.region_map.get(&c.sub) { + // if let Some(l) = p.place.as_local() { + // if l.as_usize() <= rp.body().arg_count && l != RETURN_PLACE { + // result.region_to_node(r); + // } + // } + // } + // } + + // result.region_to_node(c.sup); + // result.region_to_node(c.sub); + // TODO: maybe not needed + // if c.sub == Self::static_region() { + // result.static_regions.insert(c.sup); + // } + + // Avoid this to keep `result` separate from arguments + // result.outlives(c.sup, c.sub, c.category); + } result } - pub fn new_shared_borrow(&mut self, data: BorrowData<'tcx>) { - self.shared_borrows.push(data); - } // r1 outlives r2, or `r1: r2` (i.e. r1 gets blocked) pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) -> bool { self.outlives_many(r1, r2, &FxHashSet::from_iter([reason].into_iter())) } pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { - let n1 = self.region_to_node(r1); - let n2 = self.region_to_node(r2); + // eprintln!("Outlives: {:?} -> {:?} ({:?})", r1, r2, reasons); + let Some(n2) = self.region_to_node(r2) else { + // `r2` is static + return self.make_static(r1); + }; + let Some(n1) = self.region_to_node(r1) else { + let node = self.get_node_mut(n2); + let already_set = node.borrows_from_static; + node.borrows_from_static = true; + return already_set; + }; + // assert!(!self.static_regions.contains(&r1), "{:?} outlives {:?} ({:?})", r1, r2, self.static_regions); + if n1 == n2 { return false; } - // println!("Adding outlives {r1:?} ({n1}): {r2:?} ({n2})"); - if let Some(path) = self.reachable(n1, n2) { - let mut changed = false; - for other in path { - // Can only merge nodes which are not equal - changed = true; - self.merge(other, n2); - } - // self.sanity_check(); - changed - } else { + + // if let Some(path) = self.reachable(n1, n2) { + // let mut changed = false; + // for other in path { + // // Can only merge nodes which are not equal + // changed = true; + // self.merge(other, n2); + // } + // // self.sanity_check(); + // changed + // } else { let changed = self.blocks(n2, n1, reasons); // self.sanity_check(); changed - } + // } } // pub fn contained_by(&mut self, r: RegionVid, l: Local) { // let n = self.region_to_node(r); @@ -122,10 +183,13 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // } pub fn kill(&mut self, r: RegionVid) { let n = self.region_to_node(r); - self.kill_node(n); + self.kill_node(n.unwrap()); // self.sanity_check(); } pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool) { + if self.static_regions.remove(&r) { + return; + } for n in self.nodes.iter_mut() { if let Some(n) = n { if n.regions.contains(&r) { @@ -155,31 +219,39 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { let r2 = *n2.regions.iter().next().unwrap(); (r1, r2) } - pub fn merge_for_return(&mut self) { - let nodes: Vec<_> = self.all_nodes().map(|n| n.id).collect(); - for n in nodes { - if self.is_borrow_only(n).is_some() || self.is_empty_node(n) { - self.remove_node_rejoin(n); - } else { - let in_facts = self.facts.input_facts.borrow(); - let universal_region = &in_facts.as_ref().unwrap().universal_region; - let proof = self.facts2.region_inference_context.outlives_constraints().find(|c| { - universal_region.contains(&c.sub) - // let r = c.sub.as_u32(); // The thing that lives shorter - // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region - }); - // If None then we have something left which doesn't outlive the function region! - // if proof.is_none() { - // let in_facts = self.facts.input_facts.borrow(); - // let r = &in_facts.as_ref().unwrap().universal_region; - // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); - // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); - // // std::fs::remove_dir_all("log/coupling").ok(); - // // std::fs::create_dir_all("log/coupling/individual").unwrap(); - // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); - // dot::render(self, &mut f).unwrap(); - // } - assert!(proof.is_some(), "Found a region which does not outlive the function region: {:?}", self.get_node(n)); + pub fn set_borrows_from_static(&mut self, r: RegionVid) -> bool { + if let Some(n) = self.region_to_node(r) { + self.set_borrows_from_static_node(n) + } else { + false + } + } + fn set_borrows_from_static_node(&mut self, n: NodeId) -> bool { + let node = self.get_node_mut(n); + let already_set = node.borrows_from_static; + node.borrows_from_static = true; + already_set + } + pub fn make_static(&mut self, r: RegionVid) -> bool { + // TODO: instead of using `region_to_node`, do not add a node if already present? + if let Some(n) = self.region_to_node(r) { + self.make_static_node(n); + true + } else { + false + } + } + fn make_static_node(&mut self, n: NodeId) { + // If there is a cycle we could have already removed and made static + if let Some(mut node) = self.remove_node(n) { + // println!("Making static node: {node:?}"); + self.static_regions.extend(node.regions.drain()); + for (block_by, _) in node.blocked_by.drain() { + // assert!(node.blocked_by.is_empty()); + self.set_borrows_from_static_node(block_by); + } + for (block, _) in node.blocks.drain() { + self.make_static_node(block); } } } @@ -202,12 +274,15 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { Some(nodes) } } - fn region_to_node(&mut self, r: RegionVid) -> NodeId { + fn region_to_node(&mut self, r: RegionVid) -> Option { + if self.static_regions.contains(&r) { + return None; + } let mut last_none = self.nodes.len(); for (i, n) in self.nodes.iter().enumerate() { if let Some(n) = n { if n.regions.contains(&r) { - return i; + return Some(i); } } else { last_none = i; @@ -218,11 +293,12 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } else { self.nodes[last_none] = Some(Node::new(last_none, r)); } - last_none + Some(last_none) } fn merge(&mut self, n1: NodeId, n2: NodeId) { + panic!("{:?} and {:?}", self.get_node(n1), self.get_node(n2)); // Should never need to merge! assert_ne!(n1, n2); - let to_merge = self.remove_node(n1); + let to_merge = self.remove_node(n1).unwrap(); for (block, edge) in to_merge.blocks { if block != n2 { self.blocks(n2, block, &edge); @@ -238,7 +314,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { n2.regions.extend(to_merge.regions); } fn kill_node(&mut self, n: NodeId) { - let removed = self.remove_node(n); + let removed = self.remove_node(n).unwrap(); for (blocked_by, _) in removed.blocked_by { // May have a diamond shape, so may if self.nodes[blocked_by].is_some() { @@ -248,17 +324,23 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } fn remove_node_rejoin(&mut self, id: NodeId) -> Node<'tcx> { - let n = self.remove_node(id); - for (&block, _) in &n.blocks { - for (&blocked_by, edge) in &n.blocked_by { - self.blocks(blocked_by, block, edge); + let n = self.remove_node(id).unwrap(); + for (&blocked_by, edge) in &n.blocked_by { + for (&block, _) in &n.blocks { + // Do not rejoin nodes in a loop to themselves + if blocked_by != block { + self.blocks(blocked_by, block, edge); + } + } + if n.borrows_from_static { + self.set_borrows_from_static_node(blocked_by); } } n } // Remove node without rejoining the graph - fn remove_node(&mut self, n: NodeId) -> Node<'tcx> { - let to_remove = self.nodes[n].take().unwrap(); + fn remove_node(&mut self, n: NodeId) -> Option> { + let to_remove = self.nodes[n].take()?; for &block in to_remove.blocks.keys() { let rem = self.get_node_mut(block).blocked_by.remove(&n); assert!(rem.is_some()); @@ -267,7 +349,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { let rem = self.get_node_mut(block_by).blocks.remove(&n); assert!(rem.is_some()); } - to_remove + Some(to_remove) } pub(crate) fn get_node(&self, n: NodeId) -> &Node<'tcx> { self.nodes[n].as_ref().unwrap() @@ -276,6 +358,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { self.nodes[n].as_mut().unwrap() } fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { + assert_ne!(n1, n2); let mut changed = false; let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); let old_size = reasons.len(); @@ -287,21 +370,6 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { changed = changed || reasons.len() != old_size; changed } - fn sanity_check(&self) { - for n1 in self.all_nodes().map(|n| n.id) { - for n2 in self.all_nodes().map(|n| n.id) { - if n1 != n2 { - let path_a = self.reachable(n1, n2); - let path_b = self.reachable(n2, n1); - if path_a.is_some() && path_b.is_some() { - let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); - dot::render(self, &mut f).unwrap(); - panic!("Found cycle between {} and {}", n1, n2); - } - } - } - } - } fn all_nodes(&self) -> impl Iterator> { self.nodes.iter().filter_map(|n| n.as_ref()) } @@ -313,6 +381,7 @@ pub struct Node<'tcx> { pub regions: FxHashSet, pub blocks: FxHashMap>>, pub blocked_by: FxHashMap>>, + pub borrows_from_static: bool, // pub contained_by: Vec, } @@ -336,6 +405,7 @@ impl<'tcx> Node<'tcx> { regions: [r].into_iter().collect(), blocks: FxHashMap::default(), blocked_by: FxHashMap::default(), + borrows_from_static: false, // contained_by: Vec::new(), } } @@ -350,12 +420,14 @@ pub struct Cg<'a, 'tcx> { pub done: usize, } impl<'a, 'tcx> Cg<'a, 'tcx> { - pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { + pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>,) -> Self { let live = BitSet::new_empty(facts2.borrow_set.location_map.len()); let regions = Regions { borrows: FxHashMap::default(), subset: Vec::new(), - graph: Graph::new(repacker, facts, facts2), + version: 0, + graph: Graph::new(repacker, facts, facts2, cgx), + path_condition: Vec::new(), }; Cg { repacker, live, regions, done: 0 } } @@ -363,7 +435,7 @@ impl<'a, 'tcx> Cg<'a, 'tcx> { impl PartialEq for Cg<'_, '_> { fn eq(&self, other: &Self) -> bool { - true + self.regions == other.regions } } impl Eq for Cg<'_, '_> {} @@ -384,3 +456,124 @@ impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { Ok(()) } } + +impl PartialEq for Regions<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.graph == other.graph + } +} +impl Eq for Regions<'_, '_> {} + +impl<'tcx> Regions<'_, 'tcx> { + pub fn merge_for_return(&mut self) { + let outlives: Vec<_> = self.graph.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); + let in_facts = self.graph.facts.input_facts.borrow(); + let universal_region = &in_facts.as_ref().unwrap().universal_region; + + let nodes: Vec<_> = self.graph.all_nodes().map(|n| n.id).collect(); + for n in nodes { + // Skip unknown empty nodes, we may want to figure out how to deal with them in the future + if self.is_empty_node(n) { + continue; + } + let node = self.graph.get_node(n); + + if self.is_borrow_only(n).is_some() { + self.output_to_dot("log/coupling/error.dot"); + panic!("{node:?}"); + } else { + let r = *node.regions.iter().next().unwrap(); + if universal_region.contains(&r) { + continue; + } + + let proof = outlives.iter().find(|c| { + universal_region.contains(&c.sub) && c.sup == r + // let r = c.sub.as_u32(); // The thing that lives shorter + // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region + }); + // If None then we have something left which doesn't outlive the function region! + // if proof.is_none() { + // let in_facts = self.facts.input_facts.borrow(); + // let r = &in_facts.as_ref().unwrap().universal_region; + // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); + // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); + // // std::fs::remove_dir_all("log/coupling").ok(); + // // std::fs::create_dir_all("log/coupling/individual").unwrap(); + // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); + // dot::render(self, &mut f).unwrap(); + // } + // println!("Found proof: {proof:?}"); + self.output_to_dot("log/coupling/error.dot"); + assert!(proof.is_some(), "Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); + } + } + for &r in &self.graph.static_regions { + if universal_region.contains(&r) { + continue; + } + // It's possible that we get some random unnamed regions in the static set + if !self.graph.cgx.region_map.contains_key(&r) { + continue; + } + let proof = outlives.iter().find(|c| { + universal_region.contains(&c.sub) && c.sup == r + }); + self.output_to_dot("log/coupling/error.dot"); + assert!(proof.is_some(), "Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); + } + } + pub fn sanity_check(&self) { + let mut all = self.graph.static_regions.clone(); + for n in self.graph.all_nodes() { + for r in &n.regions { + let contained = all.insert(*r); + if !contained { + self.output_to_dot("log/coupling/error.dot"); + panic!(); + } + } + } + + for n1 in self.graph.all_nodes().map(|n| n.id) { + for n2 in self.graph.all_nodes().map(|n| n.id) { + if n1 != n2 { + let path_a = self.graph.reachable(n1, n2); + let path_b = self.graph.reachable(n2, n1); + if path_a.is_some() && path_b.is_some() { + self.output_to_dot("log/coupling/error.dot"); + panic!("Found cycle between {} and {}", n1, n2); + } + } + } + } + } + pub fn output_to_dot>(&self, path: P) { + // TODO: + return; + let mut f = std::fs::File::create(path).unwrap(); + dot::render(self, &mut f).unwrap(); + } +} + +#[derive(Clone)] +pub struct SharedBorrows<'tcx> { + pub location_map: FxIndexMap>, + pub local_map: FxIndexMap>, +} + +impl<'tcx> SharedBorrows<'tcx> { + fn new() -> Self { + Self { + location_map: FxIndexMap::default(), + local_map: FxIndexMap::default(), + } + } + pub(crate) fn insert(&mut self, location: Location, data: BorrowData<'tcx>) { + println!("Inserting shared borrow: {:?} -> {:?}", location, data); + let local = data.borrowed_place.local; + let (idx, _) = self.location_map.insert_full(location, data); + let idx = BorrowIndex::from(idx); + self.local_map.entry(local).or_default().insert(idx); + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index 73419d83a5b..c4fcbdec61e 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -16,59 +16,36 @@ use prusti_rustc_interface::{ use crate::utils::Place; -use super::cg::{NodeId, Edge, Graph}; +use super::{cg::{NodeId, Edge, Graph, Regions}, region_place::Perms}; impl<'a, 'tcx> Graph<'a, 'tcx> { fn get_id(&self) -> &str { self.id.as_deref().unwrap_or("unnamed") } - fn get_corresponding_places(&self, n: NodeId) -> Vec>> { - let node = self.get_node(n); - let mut contained_by: Vec> = Vec::new(); - let input_facts = self.facts.input_facts.borrow(); - for &(l, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { - if node.regions.contains(&r) { - contained_by.push(Perms::AllIn(r, l.into())); - } - } - for (_, data) in &self.facts2.borrow_set.location_map { - if node.regions.contains(&data.region) { - contained_by.push(Perms::Exact(data.borrowed_place.into())); - } - } - for data in &self.shared_borrows { - if node.regions.contains(&data.region) { - contained_by.push(Perms::Exact(data.borrowed_place.into())); - } - } - contained_by - } +} +impl<'a, 'tcx> Regions<'a, 'tcx> { pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { - self.get_corresponding_places(n).is_empty() + self.get_corresponding_places(n).is_none() } - fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { - if !(self.skip_empty_nodes && self.is_empty_node(n)) { - return vec![Edge::new(start, n, reasons)]; - } - let mut edges = Vec::new(); - let node = self.get_node(n); - for (&b, edge) in &node.blocks { - let mut reasons = reasons.clone(); - reasons.extend(edge); - edges.extend(self.non_empty_edges(b, start, reasons)); - } - edges + // fn get_corresponding_places<'s>(&'s self, n: NodeId) -> impl Iterator> + 's { + // let node = self.graph.get_node(n); + // node.regions.iter().map(|r| &self.graph.cgx.region_map[r]) + // } + fn get_corresponding_places(&self, n: NodeId) -> Option<&Perms<'tcx>> { + let node = self.graph.get_node(n); + assert!(node.regions.len() == 1); + self.graph.cgx.region_map.get(node.regions.iter().next().unwrap()) } pub(crate) fn is_borrow_only(&self, n: NodeId) -> Option { - let node = self.get_node(n); - let input_facts = self.facts.input_facts.borrow(); + let node = self.graph.get_node(n); + let input_facts = self.graph.facts.input_facts.borrow(); for &(_, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { if node.regions.contains(&r) { return None; } } let mut is_borrow = None; - for (_, data) in &self.facts2.borrow_set.location_map { + for (_, data) in &self.graph.facts2.borrow_set.location_map { if node.regions.contains(&data.region) { if is_borrow.is_some() { return None; @@ -76,7 +53,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { is_borrow = Some(data.kind); } } - for data in &self.shared_borrows { + for (_, data) in &self.graph.cgx.sbs.location_map { if node.regions.contains(&data.region) { if is_borrow.is_some() { return None; @@ -86,19 +63,26 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } is_borrow } + fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { + if !(self.graph.skip_empty_nodes && self.is_empty_node(n)) { + return vec![Edge::new(start, n, reasons)]; + } + let mut edges = Vec::new(); + let node = self.graph.get_node(n); + for (&b, edge) in &node.blocks { + let mut reasons = reasons.clone(); + reasons.extend(edge); + edges.extend(self.non_empty_edges(b, start, reasons)); + } + edges + } } -#[derive(Debug, Copy, Clone)] -enum Perms { - Exact(T), - AllIn(RegionVid, T), -} - -impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { - fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.get_id()).unwrap() } +impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.graph.get_id()).unwrap() } fn node_id(&'a self, n: &NodeId) -> dot::Id<'a> { - dot::Id::new(format!("N_{n:?}_{}", self.get_id())).unwrap() + dot::Id::new(format!("N_{n:?}_{}", self.graph.get_id())).unwrap() } fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { @@ -116,6 +100,9 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { } } fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { + if e.to == usize::MAX { + return dot::LabelText::LabelStr(Cow::Borrowed("static")); + } let mut label = e.reasons.iter().map(|r| match r { ConstraintCategory::Return(_) => "return", ConstraintCategory::Yield => "yield", @@ -140,6 +127,9 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { dot::LabelText::LabelStr(Cow::Owned(label)) } fn node_shape(&'a self, n: &NodeId) -> Option> { + if *n == usize::MAX { + return Some(dot::LabelText::LabelStr(Cow::Borrowed("house"))); + } self.is_borrow_only(*n).map(|kind| match kind { BorrowKind::Shared => dot::LabelText::LabelStr(Cow::Borrowed("box")), @@ -150,55 +140,46 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { }) } fn node_label(&'a self, n: &NodeId) -> dot::LabelText<'a> { - let process_place = |p: Place<'tcx>| p.to_string(self.rp); + if *n == usize::MAX { + let regions = &self.graph.static_regions; + let places: Vec<_> = regions.iter().flat_map(|r| self.graph.cgx.region_map.get(r)).collect(); + return dot::LabelText::LabelStr(Cow::Owned(format!("'static\n{regions:?}\n{places:?}"))); + } let contained_by = self.get_corresponding_places(*n); + let contained_by = contained_by.map(|c| format!("{c:?}")).unwrap_or_else(|| "???".to_string()); // let process_place = |p: Place<'tcx>| p; - let contained_by = contained_by.iter().map(|&l| { - match l { - Perms::Exact(p) => Perms::Exact(process_place(p)), - Perms::AllIn(r, mut p) => { - let mut ty = p.ty(self.rp).ty; - let mut made_precise = false; - while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { - ty = inner_ty; - p = p.mk_deref(self.rp); - if rr.is_var() && rr.as_var() == r { - made_precise = true; - break; - } - } - if made_precise { - Perms::Exact(process_place(p)) - } else { - Perms::AllIn(r, process_place(p)) - } - } - } - }).collect::>(); - let node = self.get_node(*n); - let label = format!("{:?}\n{:?}", node.regions, contained_by); + // let contained_by = contained_by.collect::>(); + let node = self.graph.get_node(*n); + assert!(node.regions.len() == 1); + let label = format!("{:?}\n{contained_by}", node.regions.iter().next().unwrap()); dot::LabelText::LabelStr(Cow::Owned(label)) } } -impl<'a, 'b, 'tcx> dot::GraphWalk<'a, NodeId, Edge<'tcx>> for Graph<'b, 'tcx> { +impl<'a, 'b, 'tcx> dot::GraphWalk<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { fn nodes(&self) -> dot::Nodes<'a, NodeId> { - let nodes: Vec<_> = self.nodes + let mut nodes: Vec<_> = self.graph.nodes .iter() .enumerate() .filter_map(|(idx, n)| n.as_ref().map(|_| idx)) - .filter(|&idx| !self.skip_empty_nodes || !self.is_empty_node(idx)) + .filter(|&idx| !self.graph.skip_empty_nodes || !self.is_empty_node(idx)) .collect(); + if self.graph.static_regions.len() > 1 { + nodes.push(usize::MAX); + } Cow::Owned(nodes) } fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { let mut edges = Vec::new(); - for (c, n) in self.nodes.iter().enumerate() { + for (c, n) in self.graph.nodes.iter().enumerate() { if let Some(n) = n { - if self.skip_empty_nodes && self.is_empty_node(c) { + if self.graph.skip_empty_nodes && self.is_empty_node(c) { continue; } + if n.borrows_from_static { + edges.push(Edge::new(c, usize::MAX, FxHashSet::default())); + } for (&b, edge) in &n.blocks { edges.extend(self.non_empty_edges(b, c, edge.clone())); } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index aca9524087c..c37e35d3e93 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -11,14 +11,14 @@ use prusti_rustc_interface::{ data_structures::fx::{FxIndexMap, FxHashSet}, borrowck::{ borrow_set::{BorrowData, TwoPhaseActivation}, - consumers::{Borrows, BorrowIndex, RichLocation, calculate_borrows_out_of_scope_at_location}, + consumers::{Borrows, BorrowIndex, RichLocation, PlaceConflictBias, places_conflict, calculate_borrows_out_of_scope_at_location}, }, dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, ResultsCursor}, index::{bit_set::{BitSet, HybridBitSet}, Idx}, middle::{ mir::{ TerminatorKind, Operand, ConstantKind, StatementKind, Rvalue, - visit::Visitor, BasicBlock, Body, Local, Location, Statement, Terminator, RETURN_PLACE, + visit::Visitor, BasicBlock, Body, Local, Place, Location, Statement, Terminator, RETURN_PLACE, }, ty::{RegionVid, TyCtxt}, }, @@ -29,7 +29,7 @@ use crate::{ utils::PlaceRepacker, coupling_graph::cg::{Graph, Node}, }; -use super::cg::{Cg, Regions}; +use super::{cg::{Cg, Regions}, shared_borrow_set::SharedBorrowSet, CgContext}; pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { let mut graph = Vec::new(); @@ -39,13 +39,13 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a continue; } c.seek_to_block_start(block); - let mut g = c.get().regions.graph.clone(); - g.id = Some(format!("pre_{block:?}")); + let mut g = c.get().regions.clone(); + g.graph.id = Some(format!("pre_{block:?}")); dot::render(&g, &mut graph).unwrap(); for statement_index in 0..body.terminator_loc(block).statement_index+1 { c.seek_after_primary_effect(Location { block, statement_index }); - let mut g = c.get().regions.graph.clone(); - g.id = Some(format!("{block:?}_{statement_index}")); + let mut g = c.get().regions.clone(); + g.graph.id = Some(format!("{block:?}_{statement_index}")); dot::render(&g, &mut graph).unwrap(); } } @@ -80,11 +80,14 @@ pub(crate) struct CoupligGraph<'a, 'tcx> { pub(crate) flow_borrows: RefCell>>, pub(crate) out_of_scope: FxIndexMap>, pub(crate) printed_dot: FxHashSet, + pub(crate) cgx: &'a CgContext<'a, 'tcx>, } impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { - pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>) -> Self { - // std::fs::remove_dir_all("log/coupling").ok(); - // std::fs::create_dir_all("log/coupling/individual").unwrap(); + pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>) -> Self { + if cfg!(debug_assertions) { + std::fs::remove_dir_all("log/coupling").ok(); + std::fs::create_dir_all("log/coupling/individual").unwrap(); + } let repacker = PlaceRepacker::new(body, tcx); let regioncx = &*facts2.region_inference_context; @@ -96,58 +99,56 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { .iterate_to_fixpoint() .into_results_cursor(body); let printed_dot = FxHashSet::default(); - CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope, printed_dot } + CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope, printed_dot, cgx } } - fn handle_diff(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { - // let input_facts = self.facts.input_facts.borrow(); - // let input_facts = input_facts.as_ref().unwrap(); - // let location_table = self.facts.location_table.borrow(); - // let location_table = location_table.as_ref().unwrap(); - // for idx in delta.set.iter() { - // let loan_issued_at = &input_facts.loan_issued_at; - // let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); - // let l = location_table.to_location(l); - // let RichLocation::Mid(l) = l else { unreachable!() }; - // assert_eq!(l, location); - // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); - // state.borrows.insert(idx, (vec![r], locals)); - // } - // state.subset.extend(input_facts.subset_base.iter().filter( - // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location - // ).map(|(r1, r2, _)| (*r1, *r2))); - // // TODO: do a proper fixpoint here - // for _ in 0..10 { - // for &(r1, r2) in &state.subset { - // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); - // let mut did_push = false; - // for (_, s) in state.borrows.iter_mut() { - // if s.0.contains(&r1) { - // did_push = true; - // if !s.0.contains(&r2) { - // s.0.push(r2); - // s.1.extend(locals.iter().copied()); - // } - // } - // } - // // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); - // } - // } - // for r in delta.cleared.iter() { - // // TODO: why does unwrap fail? - // let removed = state.borrows.remove(&r);//.unwrap(); - // // for (_, s) in state.borrows.iter_mut() { - // // s.0.retain(|r| !removed.0.contains(r)); - // // s.1.retain(|l| !removed.1.contains(l)); - // // } - // } - // print!(" {:?}", state.borrows); - self.handle_graph(state, delta, location); - } + // fn handle_diff(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { + // // let input_facts = self.facts.input_facts.borrow(); + // // let input_facts = input_facts.as_ref().unwrap(); + // // let location_table = self.facts.location_table.borrow(); + // // let location_table = location_table.as_ref().unwrap(); + // // for idx in delta.set.iter() { + // // let loan_issued_at = &input_facts.loan_issued_at; + // // let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); + // // let l = location_table.to_location(l); + // // let RichLocation::Mid(l) = l else { unreachable!() }; + // // assert_eq!(l, location); + // // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); + // // state.borrows.insert(idx, (vec![r], locals)); + // // } + // // state.subset.extend(input_facts.subset_base.iter().filter( + // // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location + // // ).map(|(r1, r2, _)| (*r1, *r2))); + // // // TODO: do a proper fixpoint here + // // for _ in 0..10 { + // // for &(r1, r2) in &state.subset { + // // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); + // // let mut did_push = false; + // // for (_, s) in state.borrows.iter_mut() { + // // if s.0.contains(&r1) { + // // did_push = true; + // // if !s.0.contains(&r2) { + // // s.0.push(r2); + // // s.1.extend(locals.iter().copied()); + // // } + // // } + // // } + // // // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); + // // } + // // } + // // for r in delta.cleared.iter() { + // // // TODO: why does unwrap fail? + // // let removed = state.borrows.remove(&r);//.unwrap(); + // // // for (_, s) in state.borrows.iter_mut() { + // // // s.0.retain(|r| !removed.0.contains(r)); + // // // s.1.retain(|l| !removed.1.contains(l)); + // // // } + // // } + // // print!(" {:?}", state.borrows); + // self.handle_graph(state, delta, location); + // } - fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { - let l = format!("{:?}", location).replace('[', "_").replace(']', ""); - state.graph.id = Some(l.clone()); + fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { // println!("location: {:?}", l); let input_facts = self.facts.input_facts.borrow(); @@ -205,6 +206,9 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // for (r1, r2) in new_subsets { // state.graph.outlives(r1, r2); // } + } + + fn handle_outlives(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { let constraints = self.facts2.region_inference_context.outlives_constraints(); for c in constraints { if let Some(from) = c.locations.from_location() { @@ -213,10 +217,47 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { } } } + } + + fn kill_shared_borrows_on_place(&self, state: &mut Regions<'_, 'tcx>, place: Place<'tcx>) { + // let other_borrows_of_local = state + // .shared_borrows + // .local_map + // .get(&place.local) + // .into_iter() + // .flat_map(|bs| bs.iter()) + // .copied() + // .map(|idx| &state.shared_borrows.location_map[idx.as_usize()]); + // let definitely_conflicting_borrows = other_borrows_of_local.filter(|d| { + // places_conflict( + // self.repacker.tcx(), + // self.repacker.body(), + // d.borrowed_place, + // place, + // PlaceConflictBias::NoOverlap, + // ) + // }); + // for data in definitely_conflicting_borrows { + // state.graph.remove(data.region, true); + // } + let Some(local) = place.as_local() else { + // Only remove nodes if assigned to the entire local (this is what rustc allows too) + return + }; + // println!("Killing: {local:?}"); - if !self.repacker.body().basic_blocks[location.block].is_cleanup { - // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_v{}.dot", state.graph.version)).unwrap(); - // dot::render(&state.graph, &mut f).unwrap(); + // let was_shared_borrow_from = self.cgx.sbs.local_map.contains_key(&local); + // let was_shared_borrow_to = self.cgx.sbs.location_map.values().find(|bd| bd.assigned_place.local == local); + // if !was_shared_borrow_from && was_shared_borrow_to.is_none() { + // println!("early ret: {:?}", self.cgx.sbs.local_map); + // return; + // } + // println!("killing"); + + // Also remove any overwritten borrows locals + for (®ion, _) in state.graph.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { + // println!("Killing local: {local:?}: {region:?}"); + state.graph.remove(region, true); } } } @@ -226,11 +267,21 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { const NAME: &'static str = "coupling_graph"; fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - Cg::new(self.repacker, self.facts, self.facts2) + Cg::new(self.repacker, self.facts, self.facts2, self.cgx) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { - // // println!("body: {body:?}"); + // // Sanity check (already done when creating region to place map) + // if cfg!(debug_assertions) { + // let input_facts = self.facts.input_facts.borrow(); + // let use_of_var_derefs_origin = &input_facts.as_ref().unwrap().use_of_var_derefs_origin; + // // Each region should only have a single associated local + // for (_, r) in use_of_var_derefs_origin { + // assert!(use_of_var_derefs_origin.iter().filter(|(_, ro)| r == ro).count() <= 1, "{use_of_var_derefs_origin:?}"); + // } + // } + + // println!("body: {body:#?}"); // println!("\ninput_facts: {:?}", self.facts.input_facts); // println!("output_facts: {:#?}\n", self.facts.output_facts); // println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); @@ -244,6 +295,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { // println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); // } // println!("out_of_scope: {:?}", self.out_of_scope); + // println!("region_map: {:#?}\n", self.cgx.region_map); } } @@ -254,8 +306,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { statement: &Statement<'tcx>, location: Location, ) { + let l = format!("{:?}", location).replace('[', "_").replace(']', ""); + // println!("Location: {l}"); + state.regions.graph.id = Some(l.clone()); + if location.statement_index == 0 { // println!("\nblock: {:?}", location.block); + if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); + } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } @@ -263,33 +322,50 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { let other = self.flow_borrows.borrow().get().clone(); // print!("{statement:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); - if delta.set.is_empty() { - match statement.kind { - StatementKind::Assign(box (assigned_place, Rvalue::Ref(region, kind, borrowed_place))) => { - state.regions.graph.new_shared_borrow(BorrowData { - reserve_location: location, - activation_location: TwoPhaseActivation::NotTwoPhase, - kind, - region: region.as_var(), - borrowed_place, - assigned_place, - }) - } - // If a local was only moved out of and not reborrowed, it's region is never killed officially - StatementKind::StorageDead(local) => { - let input_facts = self.facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // println!("killed region manually: {region:?}"); - state.regions.graph.remove(region, true); + + self.handle_graph(&mut state.regions, &delta, location); + match statement.kind { + StatementKind::Assign(box (assigned_place, ref lhs)) => { + self.kill_shared_borrows_on_place(&mut state.regions, assigned_place); + if let Rvalue::Use(Operand::Constant(c)) = lhs { + // println!("Checking {:?} vs {curr_loc:?}", self.facts2.region_inference_context.outlives_constraints().map(|c| format!("{:?}", c.locations)).collect::>()); + for c in self.facts2.region_inference_context.outlives_constraints().filter( + |c| c.locations.from_location() == Some(location) + ) { + // println!("Got one: {c:?}"); + state.regions.graph.make_static(c.sup); + state.regions.graph.make_static(c.sub); } } - _ => (), } + // If a local was only moved out of and not reborrowed, it's region is never killed officially + StatementKind::StorageDead(local) => { + self.kill_shared_borrows_on_place(&mut state.regions, Place::from(local)); + + // let input_facts = self.facts.input_facts.borrow(); + // let input_facts = input_facts.as_ref().unwrap(); + // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // // println!("killed region manually: {region:?}"); + // state.regions.graph.remove(region, true); + // } + // let to_rem: Vec<_> = state.regions.graph.shared_borrows.iter().filter(|(_, data)| data.borrowed_place.local == local).map(|(_, data)| data.region).collect(); + // for region in to_rem { + // // println!("killed region manually: {region:?}"); + // state.regions.graph.remove(region, true); + // } + } + _ => (), } - self.handle_diff(&mut state.regions, delta, location); + // if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + // state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_mid.dot", state.regions.version)); + // } + self.handle_outlives(&mut state.regions, &delta, location); state.live = other; // println!(); + + if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); + } } fn apply_terminator_effect( @@ -298,8 +374,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { terminator: &Terminator<'tcx>, location: Location, ) { + let l = format!("{:?}", location).replace('[', "_").replace(']', ""); + // println!("Location: {l}"); + state.regions.graph.id = Some(l.clone()); + if location.statement_index == 0 { // println!("\nblock: {:?}", location.block); + if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); + } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } @@ -325,12 +408,32 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { // } // print!("{terminator:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); - self.handle_diff(&mut state.regions, delta, location); + self.handle_graph(&mut state.regions, &delta, location); + self.handle_outlives(&mut state.regions, &delta, location); state.live = other; if let TerminatorKind::Return = &terminator.kind { - state.regions.graph.merge_for_return(); + if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", state.regions.version)); + } + + // Pretend we have a storage dead for all `always_live_locals` other than the args/return + for l in self.repacker.always_live_locals_non_args().iter() { + self.kill_shared_borrows_on_place(&mut state.regions, l.into()); + } + // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` + for r in self.facts2.borrow_set.location_map.values() { + state.regions.graph.remove(r.region, true); + } + for r in self.cgx.sbs.location_map.values() { + state.regions.graph.remove(r.region, true); + } + state.regions.merge_for_return(); } // println!(); + + if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); + } } fn apply_call_return_effect( diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 089f2b1ae6d..378d38e8b97 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,7 +15,7 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::cg::{Cg, Graph}; +use super::cg::{Cg, Graph, SharedBorrows}; impl JoinSemiLattice for Cg<'_, '_> { fn join(&mut self, other: &Self) -> bool { @@ -25,6 +25,10 @@ impl JoinSemiLattice for Cg<'_, '_> { // } // self.done += 1; let actually_changed = self.regions.graph.join(&other.regions.graph); + if actually_changed { + self.regions.version += 1; + assert!(self.regions.version < 40); + } return actually_changed; let mut changed = self.live.union(&other.live); for (idx, data) in other.regions.borrows.iter() { @@ -81,11 +85,26 @@ impl JoinSemiLattice for Graph<'_, '_> { // println!("New edge: {:?} -> {:?} ({:?})", from, to, reasons); // } } + if node.borrows_from_static { + changed = changed || self.set_borrows_from_static(*rep); + } } - if changed { - self.version += 1; - assert!(self.version < 1000); + let old_len = self.static_regions.len(); + for &r in &other.static_regions { + self.make_static(r); } + changed = changed || self.static_regions.len() != old_len; changed } } + +impl SharedBorrows<'_> { + fn join(&mut self, other: &Self) -> bool { + println!("Joining shared borrows: {:?} and {:?}", self.location_map, other.location_map); + let old_len = self.location_map.len(); + for (k, v) in other.location_map.iter() { + self.insert(*k, v.clone()); + } + self.location_map.len() != old_len + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/mod.rs b/mir-state-analysis/src/coupling_graph/impl/mod.rs index 2c97c1ffdd6..a655aefe824 100644 --- a/mir-state-analysis/src/coupling_graph/impl/mod.rs +++ b/mir-state-analysis/src/coupling_graph/impl/mod.rs @@ -4,7 +4,47 @@ // 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, Place}; +use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + data_structures::fx::FxHashMap, + middle::{mir::Body, ty::{RegionVid, TyCtxt}}, +}; + pub(crate) mod engine; pub(crate) mod cg; pub(crate) mod join_semi_lattice; +pub(crate) mod shared_borrow_set; +pub(crate) mod region_place; mod dot; + +pub struct CgContext<'a, 'tcx> { + pub rp: PlaceRepacker<'a, 'tcx>, + pub sbs: SharedBorrowSet<'tcx>, + pub region_map: FxHashMap>, +} + +impl<'a, 'tcx> CgContext<'a, 'tcx> { + pub fn new( + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + facts: &'a BorrowckFacts, + facts2: &'a BorrowckFacts2<'tcx> + ) -> Self { + let sbs = SharedBorrowSet::build(tcx, body, &facts2.borrow_set); + let rp = PlaceRepacker::new(body, tcx); + let input_facts = facts.input_facts.borrow(); + let region_map = Perms::region_place_map( + &input_facts.as_ref().unwrap().use_of_var_derefs_origin, + &facts2.borrow_set, + &sbs, + rp, + ); + Self { + rp, + sbs, + region_map, + } + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/region_place.rs b/mir-state-analysis/src/coupling_graph/impl/region_place.rs new file mode 100644 index 00000000000..a181d2cfeed --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/region_place.rs @@ -0,0 +1,84 @@ +// © 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; + +use prusti_rustc_interface::{ + data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, + index::bit_set::BitSet, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, + borrowck::{borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation, LocalsStateAtExit}, consumers::{BorrowIndex, PlaceExt}}, + middle::{mir::{ConstraintCategory, RETURN_PLACE, Location, Rvalue, Body, traversal, visit::Visitor}, ty::{RegionVid, TyKind}}, +}; + +use crate::utils::{Place, PlaceRepacker, display::PlaceDisplay}; + +use super::shared_borrow_set::SharedBorrowSet; + +#[derive(Clone)] +pub struct Perms<'tcx> { + pub place: Place<'tcx>, + pub region: Option, + pub pretty: PlaceDisplay<'tcx>, +} + +impl Debug for Perms<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let hint = if self.pretty.is_user() { + format!(" <<{:?}>>", self.pretty) + } else { + String::new() + }; + match self.region { + Some(r) => write!(f, "AllIn({r:?}, {:?}{hint})", self.place), + None => write!(f, "{:?}{hint}", self.place), + } + } +} + +impl<'tcx> Perms<'tcx> { + pub fn region_place_map( + use_of_var_derefs_origin: &Vec<(Local, RegionVid)>, + borrows: &BorrowSet<'tcx>, + shared_borrows: &SharedBorrowSet<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + ) -> FxHashMap> { + let mut map = FxHashMap::default(); + for &(l, r) in use_of_var_derefs_origin { + let place = l.into(); + let perm = if let Some(place) = Self::try_make_precise(place, r, rp) { + Perms { place, region: None, pretty: place.to_string(rp) } + } else { + Perms { place, region: Some(r), pretty: place.to_string(rp) } + }; + let existing = map.insert(r, perm); + assert!(existing.is_none(), "{existing:?} vs {:?}", map[&r]); + } + for data in shared_borrows.location_map.values().chain(borrows.location_map.values()) { + let place = data.borrowed_place.into(); + let perm = Perms { + place, + region: None, + pretty: place.to_string(rp), + }; + let existing = map.insert(data.region, perm); + assert!(existing.is_none(), "{existing:?} vs {:?}", map[&data.region]); + } + map + } + + fn try_make_precise(mut p: Place<'tcx>, r: RegionVid, rp: PlaceRepacker<'_, 'tcx>) -> Option> { + let mut ty = p.ty(rp).ty; + while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { + ty = inner_ty; + p = p.mk_deref(rp); + if rr.is_var() && rr.as_var() == r { + return Some(p); + } + } + None + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs b/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs new file mode 100644 index 00000000000..d159effabc6 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs @@ -0,0 +1,100 @@ +// © 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, FxHashSet, FxIndexMap, FxIndexSet}, + index::bit_set::BitSet, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, + borrowck::{borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation, LocalsStateAtExit}, consumers::{BorrowIndex, PlaceExt}}, + middle::{mir::{ConstraintCategory, RETURN_PLACE, Location, Place, Rvalue, Body, traversal, visit::Visitor}, ty::{TyCtxt}}, +}; + +// Identical to `rustc_borrowck/src/borrow_set.rs` but for shared borrows +#[derive(Clone)] +pub struct SharedBorrowSet<'tcx> { + pub location_map: FxIndexMap>, + pub local_map: FxIndexMap>, +} + +impl<'tcx> SharedBorrowSet<'tcx> { + pub(crate) fn build( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + borrows: &BorrowSet<'tcx>, + ) -> Self { + let mut visitor = GatherBorrows { + tcx, + body: &body, + location_map: Default::default(), + local_map: Default::default(), + locals_state_at_exit: &borrows.locals_state_at_exit, + }; + + for (block, block_data) in traversal::preorder(&body) { + visitor.visit_basic_block_data(block, block_data); + } + + Self { + location_map: visitor.location_map, + local_map: visitor.local_map, + } + } +} + +struct GatherBorrows<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + location_map: FxIndexMap>, + local_map: FxIndexMap>, + locals_state_at_exit: &'a LocalsStateAtExit, +} + +impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { + fn visit_assign( + &mut self, + assigned_place: &Place<'tcx>, + rvalue: &Rvalue<'tcx>, + location: Location, + ) { + if let &Rvalue::Ref(region, kind, borrowed_place) = rvalue { + // Gather all borrows that the normal borrow-checker misses + if !borrowed_place.ignore_borrow(self.tcx, self.body, self.locals_state_at_exit) { + return; + } + + let region = region.as_var(); + + let borrow = BorrowData { + kind, + region, + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + borrowed_place, + assigned_place: *assigned_place, + }; + let (idx, _) = self.location_map.insert_full(location, borrow); + let idx = BorrowIndex::from(idx); + + self.local_map.entry(borrowed_place.local).or_default().insert(idx); + } + + self.super_assign(assigned_place, rvalue, location) + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + if let &Rvalue::Ref(region, kind, place) = rvalue { + // double-check that we already registered a BorrowData for this + + let borrow_data = &self.location_map[&location]; + assert_eq!(borrow_data.reserve_location, location); + assert_eq!(borrow_data.kind, kind); + assert_eq!(borrow_data.region, region.as_var()); + assert_eq!(borrow_data.borrowed_place, place); + } + + self.super_rvalue(rvalue, location) + } +} diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 6d08e686b56..b965d7718a9 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -40,13 +40,15 @@ pub fn run_coupling_graph<'mir, 'tcx>( facts2: &'mir BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, ) { - // let name = tcx.opt_item_name(mir.source.def_id()); - // println!("Running for {}", name.as_ref().map(|n| n.as_str()).unwrap_or("unnamed")); - println!("Running for {:?}", mir.source.def_id()); - // if tcx.item_name(mir.source.def_id()).as_str() == "main" { + // if tcx.item_name(mir.source.def_id()).as_str().starts_with("main") { // return; // } - let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2); + // if tcx.item_name(mir.source.def_id()).as_str() != "debug" { + // return; + // } + // println!("Running for {:?} {:?}", mir.source.def_id(), mir.span); + let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); + let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2, &cgx); let analysis = fpcs .into_engine(tcx, mir) .pass_name("coupling_graph") diff --git a/mir-state-analysis/src/utils/display.rs b/mir-state-analysis/src/utils/display.rs index d7a61fef236..f6a55137782 100644 --- a/mir-state-analysis/src/utils/display.rs +++ b/mir-state-analysis/src/utils/display.rs @@ -18,6 +18,7 @@ use prusti_rustc_interface::{ use super::{Place, PlaceRepacker}; +#[derive(Clone)] pub enum PlaceDisplay<'tcx> { Temporary(Place<'tcx>), User(String), @@ -32,6 +33,12 @@ impl<'tcx> Debug for PlaceDisplay<'tcx> { } } +impl<'tcx> PlaceDisplay<'tcx> { + pub fn is_user(&self) -> bool { + matches!(self, PlaceDisplay::User(_)) + } +} + impl<'tcx> Place<'tcx> { pub fn to_string(&self, repacker: PlaceRepacker<'_, 'tcx>) -> PlaceDisplay<'tcx> { // Get the local's debug name from the Body's VarDebugInfo diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 2810a6bb9c7..18b6d06a1e9 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -7,7 +7,7 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashSet, dataflow::storage, - index::bit_set::BitSet, + index::{Idx, bit_set::BitSet}, middle::{ mir::{ tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, @@ -66,6 +66,13 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { pub fn always_live_locals(self) -> BitSet { storage::always_storage_live_locals(self.mir) } + pub fn always_live_locals_non_args(self) -> BitSet { + let mut all = self.always_live_locals(); + for arg in 0..self.mir.arg_count+1 { // Includes `RETURN_PLACE` + all.remove(Local::new(arg)); + } + all + } pub fn body(self) -> &'a Body<'tcx> { self.mir 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 16/58] 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 17/58] 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 185a8be438380ffe7bccfac8e6836f2b0449a2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 17:52:48 +0200 Subject: [PATCH 18/58] Merge fixes --- Cargo.lock | 1 + mir-state-analysis/src/coupling_graph/impl/engine.rs | 11 ++++++----- mir-state-analysis/src/free_pcs/impl/engine.rs | 11 ++++++----- mir-state-analysis/src/free_pcs/impl/triple.rs | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81cec8515e9..beb20cf4bdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1891,6 +1891,7 @@ dependencies = [ "dot", "prusti-interface", "prusti-rustc-interface", + "regex", "reqwest", "serde", "serde_derive", diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index c37e35d3e93..794e908a4af 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -13,12 +13,12 @@ use prusti_rustc_interface::{ borrow_set::{BorrowData, TwoPhaseActivation}, consumers::{Borrows, BorrowIndex, RichLocation, PlaceConflictBias, places_conflict, calculate_borrows_out_of_scope_at_location}, }, - dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, ResultsCursor}, + dataflow::{Analysis, AnalysisDomain, ResultsCursor}, index::{bit_set::{BitSet, HybridBitSet}, Idx}, middle::{ mir::{ TerminatorKind, Operand, ConstantKind, StatementKind, Rvalue, - visit::Visitor, BasicBlock, Body, Local, Place, Location, Statement, Terminator, RETURN_PLACE, + visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Place, Location, Statement, Terminator, TerminatorEdges, RETURN_PLACE, }, ty::{RegionVid, TyCtxt}, }, @@ -368,12 +368,12 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } } - 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> { let l = format!("{:?}", location).replace('[', "_").replace(']', ""); // println!("Location: {l}"); state.regions.graph.id = Some(l.clone()); @@ -434,6 +434,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); } + terminator.edges() } fn apply_call_return_effect( diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 05d3a1040fd..9c74c5fa884 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -5,11 +5,11 @@ // 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 +74,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 a9d6c19cb2b..4fc1e66d9f1 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 19/58] 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 16a944ee7a96f65328566e0dc77b6a432f4bcc0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 18:04:17 +0200 Subject: [PATCH 20/58] Add top crates test --- .github/workflows/test.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 614482482ae..a71392584bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -110,6 +110,29 @@ jobs: - name: Run quick tests run: python x.py test --all quick + top-crates: + runs-on: ubuntu-latest + env: + PRUSTI_CACHE_PATH: ${{ github.workspace }}/prusti_cache.bin + steps: + - name: Check out the repo + uses: actions/checkout@v3 + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: '15' + distribution: 'zulu' + - name: Set up the environment + run: python x.py setup + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + with: + shared-key: "shared" + - name: Build with cargo + run: python x.py build --all + - name: Run quick tests + run: python x.py test --package mir-state-analysis --test top_crates -- top_crates --exact --nocapture + # Run a subset of the tests with the purification optimization enabled # to ensure that we do not introduce regressions. purification-tests: From 77326d4100a2600d291e18336c9c6f42aa8898da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 18:20:20 +0200 Subject: [PATCH 21/58] Debug which method we're running on --- mir-state-analysis/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index b965d7718a9..39bc1af2ff7 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -46,7 +46,7 @@ pub fn run_coupling_graph<'mir, 'tcx>( // if tcx.item_name(mir.source.def_id()).as_str() != "debug" { // return; // } - // println!("Running for {:?} {:?}", mir.source.def_id(), mir.span); + println!("Running for {:?} {:?}", mir.source.def_id(), mir.span); let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2, &cgx); let analysis = fpcs From 1a9102e1de6adf346d85fe24dcd68f2d9cf0e761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 27 Sep 2023 18:32:47 +0200 Subject: [PATCH 22/58] Change repetition tracking --- mir-state-analysis/src/coupling_graph/impl/engine.rs | 6 ++++++ .../src/coupling_graph/impl/join_semi_lattice.rs | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index 794e908a4af..972c200ab17 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -311,6 +311,9 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { state.regions.graph.id = Some(l.clone()); if location.statement_index == 0 { + state.regions.version += 1; + assert!(state.regions.version < 10); + // println!("\nblock: {:?}", location.block); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); @@ -379,6 +382,9 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { state.regions.graph.id = Some(l.clone()); if location.statement_index == 0 { + state.regions.version += 1; + assert!(state.regions.version < 10); + // println!("\nblock: {:?}", location.block); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 378d38e8b97..15104c21756 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -25,10 +25,6 @@ impl JoinSemiLattice for Cg<'_, '_> { // } // self.done += 1; let actually_changed = self.regions.graph.join(&other.regions.graph); - if actually_changed { - self.regions.version += 1; - assert!(self.regions.version < 40); - } return actually_changed; let mut changed = self.live.union(&other.live); for (idx, data) in other.regions.borrows.iter() { 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 23/58] 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 24/58] 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 25/58] 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 fc4c881b347dad6141bd983da1947c628abdfbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 15:13:04 +0200 Subject: [PATCH 26/58] Add tracing --- .../src/coupling_graph/impl/cg.rs | 27 ++++++++++++++++--- .../src/coupling_graph/impl/engine.rs | 6 +++++ .../coupling_graph/impl/join_semi_lattice.rs | 2 ++ .../src/coupling_graph/impl/mod.rs | 9 +++++++ .../coupling_graph/impl/shared_borrow_set.rs | 2 +- mir-state-analysis/src/lib.rs | 2 +- prusti/src/callbacks.rs | 5 ++++ 7 files changed, 47 insertions(+), 6 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs index 07d9f898b67..115ca63d427 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -25,7 +25,7 @@ use crate::{ use super::{engine::CoupligGraph, shared_borrow_set::SharedBorrowSet, CgContext}; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct Regions<'a, 'tcx> { pub borrows: FxHashMap, Vec<(Local, RegionVid)>)>, pub(crate) subset: Vec<(RegionVid, RegionVid)>, @@ -50,7 +50,18 @@ pub struct Graph<'a, 'tcx> { pub static_regions: FxHashSet, } -#[derive(Clone)] +impl Debug for Graph<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("Graph") + .field("id", &self.id) + .field("nodes", &self.nodes) + .field("skip_empty_nodes", &self.skip_empty_nodes) + .field("static_regions", &self.static_regions) + .finish() + } +} + +#[derive(Debug, Clone)] pub struct PathCondition; impl PartialEq for Graph<'_, '_> { @@ -141,9 +152,11 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { result } // r1 outlives r2, or `r1: r2` (i.e. r1 gets blocked) + #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) -> bool { self.outlives_many(r1, r2, &FxHashSet::from_iter([reason].into_iter())) } + #[tracing::instrument(name = "Graph::outlives_many", level = "trace", skip(self), ret)] pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { // eprintln!("Outlives: {:?} -> {:?} ({:?})", r1, r2, reasons); let Some(n2) = self.region_to_node(r2) else { @@ -181,11 +194,13 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // let n = self.region_to_node(r); // self.get_node_mut(n).contained_by.push(l); // } + #[tracing::instrument(name = "Graph::kill", level = "trace", skip(self))] pub fn kill(&mut self, r: RegionVid) { let n = self.region_to_node(r); self.kill_node(n.unwrap()); // self.sanity_check(); } + #[tracing::instrument(name = "Graph::remove", level = "trace", skip(self))] pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool) { if self.static_regions.remove(&r) { return; @@ -206,12 +221,14 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // self.sanity_check(); } // Used when merging two graphs (and we know from one graph that two regions are equal) + #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] pub fn equate_regions(&mut self, ra: RegionVid, rb: RegionVid) -> bool { let mut changed = self.outlives(ra, rb, ConstraintCategory::Internal); changed = changed || self.outlives(rb, ra, ConstraintCategory::Internal); // self.sanity_check(); changed } + #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] pub fn edge_to_regions(&self, from: NodeId, to: NodeId) -> (RegionVid, RegionVid) { let n1 = self.get_node(from); let n2 = self.get_node(to); @@ -219,6 +236,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { let r2 = *n2.regions.iter().next().unwrap(); (r1, r2) } + #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] pub fn set_borrows_from_static(&mut self, r: RegionVid) -> bool { if let Some(n) = self.region_to_node(r) { self.set_borrows_from_static_node(n) @@ -232,6 +250,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { node.borrows_from_static = true; already_set } + #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] pub fn make_static(&mut self, r: RegionVid) -> bool { // TODO: instead of using `region_to_node`, do not add a node if already present? if let Some(n) = self.region_to_node(r) { @@ -442,8 +461,7 @@ impl Eq for Cg<'_, '_> {} impl<'a, 'tcx> Debug for Cg<'a, 'tcx> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - // self.summary.fmt(f) - Ok(()) + f.debug_struct("Cg").field("regions", &self.regions).finish() } } impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { @@ -465,6 +483,7 @@ impl PartialEq for Regions<'_, '_> { impl Eq for Regions<'_, '_> {} impl<'tcx> Regions<'_, 'tcx> { + #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] pub fn merge_for_return(&mut self) { let outlives: Vec<_> = self.graph.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); let in_facts = self.graph.facts.input_facts.borrow(); diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index 972c200ab17..ed9152f80d0 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -148,6 +148,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // self.handle_graph(state, delta, location); // } + #[tracing::instrument(name = "handle_graph", level = "debug", skip(self))] fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { // println!("location: {:?}", l); @@ -208,6 +209,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // } } + #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] fn handle_outlives(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { let constraints = self.facts2.region_inference_context.outlives_constraints(); for c in constraints { @@ -219,6 +221,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { } } + #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] fn kill_shared_borrows_on_place(&self, state: &mut Regions<'_, 'tcx>, place: Place<'tcx>) { // let other_borrows_of_local = state // .shared_borrows @@ -300,6 +303,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { } impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { + #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] fn apply_statement_effect( &mut self, state: &mut Self::Domain, @@ -371,6 +375,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } } + #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, @@ -453,6 +458,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } } +#[derive(Debug)] struct BorrowDelta { set: HybridBitSet, cleared: HybridBitSet, diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 15104c21756..bd2d4b62ebb 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -18,6 +18,7 @@ use crate::{ use super::cg::{Cg, Graph, SharedBorrows}; impl JoinSemiLattice for Cg<'_, '_> { + #[tracing::instrument(name = "Cg::join", level = "debug", skip_all)] fn join(&mut self, other: &Self) -> bool { // if self.done == 20 { // panic!(); @@ -65,6 +66,7 @@ impl JoinSemiLattice for Cg<'_, '_> { } impl JoinSemiLattice for Graph<'_, '_> { + #[tracing::instrument(name = "Graph::join", level = "debug", ret)] fn join(&mut self, other: &Self) -> bool { // println!("Joining graphs:\n{:?}: {:?}\n{:?}: {:?}", self.id, self.nodes, other.id, other.nodes); let mut changed = false; diff --git a/mir-state-analysis/src/coupling_graph/impl/mod.rs b/mir-state-analysis/src/coupling_graph/impl/mod.rs index a655aefe824..2d60e302b82 100644 --- a/mir-state-analysis/src/coupling_graph/impl/mod.rs +++ b/mir-state-analysis/src/coupling_graph/impl/mod.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; + use crate::utils::{PlaceRepacker, Place}; use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; @@ -25,7 +27,14 @@ pub struct CgContext<'a, 'tcx> { pub region_map: FxHashMap>, } +impl fmt::Debug for CgContext<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CgContext").field("sbs", &self.sbs).field("region_map", &self.region_map).finish() + } +} + impl<'a, 'tcx> CgContext<'a, 'tcx> { + #[tracing::instrument(name = "CgContext::new", level = "debug", skip_all, ret)] pub fn new( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, diff --git a/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs b/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs index d159effabc6..d4613062873 100644 --- a/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs +++ b/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs @@ -13,7 +13,7 @@ use prusti_rustc_interface::{ }; // Identical to `rustc_borrowck/src/borrow_set.rs` but for shared borrows -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct SharedBorrowSet<'tcx> { pub location_map: FxIndexMap>, pub local_map: FxIndexMap>, diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 709f5f6b7c2..249926fad2f 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -35,6 +35,7 @@ pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { free_pcs::check(analysis); } +#[tracing::instrument(name = "run_coupling_graph", level = "debug", skip(facts, facts2, tcx))] pub fn run_coupling_graph<'mir, 'tcx>( mir: &'mir Body<'tcx>, facts: &'mir BorrowckFacts, @@ -47,7 +48,6 @@ pub fn run_coupling_graph<'mir, 'tcx>( // if tcx.item_name(mir.source.def_id()).as_str() != "debug" { // return; // } - println!("Running for {:?} {:?}", mir.source.def_id(), mir.span); let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2, &cgx); let analysis = fpcs diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index bce3e495995..f4e5ce92312 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -40,6 +40,8 @@ fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &BorrowCheckResu || config::test_free_pcs() { consumers::ConsumerOptions::RegionInferenceContext + } else if config::test_coupling_graph() { + consumers::ConsumerOptions::PoloniusInputFacts } else { consumers::ConsumerOptions::PoloniusOutputFacts }; @@ -181,6 +183,9 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { .body .try_get_local_mir_borrowck_facts2(proc_id.expect_local()) .unwrap(); + + let name = env.name.get_unique_item_name(*proc_id); + println!("Calculating CG for: {name} ({:?})", mir.span); test_coupling_graph(&*mir, &*facts, &*facts2, tcx); } } else { From 64e742d5704a26fea95d62d5c7414e33d7a1a9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 16:10:34 +0200 Subject: [PATCH 27/58] Add loop detection analysis --- mir-state-analysis/Cargo.toml | 1 + .../src/coupling_graph/impl/mod.rs | 5 +- mir-state-analysis/src/lib.rs | 1 + mir-state-analysis/src/loop/mod.rs | 166 ++++++++++++++++++ 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 mir-state-analysis/src/loop/mod.rs diff --git a/mir-state-analysis/Cargo.toml b/mir-state-analysis/Cargo.toml index fbbf497f3d3..64830293800 100644 --- a/mir-state-analysis/Cargo.toml +++ b/mir-state-analysis/Cargo.toml @@ -9,6 +9,7 @@ derive_more = "0.99" tracing = { path = "../tracing" } prusti-rustc-interface = { path = "../prusti-rustc-interface" } dot = "0.1" +smallvec = { version = "^1.11", features = ["union", "const_new"] } # TODO: remove prusti-interface = { path = "../prusti-interface" } regex = "1" diff --git a/mir-state-analysis/src/coupling_graph/impl/mod.rs b/mir-state-analysis/src/coupling_graph/impl/mod.rs index 2d60e302b82..1cb9af3f65e 100644 --- a/mir-state-analysis/src/coupling_graph/impl/mod.rs +++ b/mir-state-analysis/src/coupling_graph/impl/mod.rs @@ -6,7 +6,7 @@ use std::fmt; -use crate::utils::{PlaceRepacker, Place}; +use crate::{utils::{PlaceRepacker, Place}, r#loop::LoopAnalysis}; use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ @@ -25,6 +25,7 @@ pub struct CgContext<'a, 'tcx> { pub rp: PlaceRepacker<'a, 'tcx>, pub sbs: SharedBorrowSet<'tcx>, pub region_map: FxHashMap>, + pub loops: LoopAnalysis, } impl fmt::Debug for CgContext<'_, '_> { @@ -50,10 +51,12 @@ impl<'a, 'tcx> CgContext<'a, 'tcx> { &sbs, rp, ); + let loops = LoopAnalysis::find_loops(body); Self { rp, sbs, region_map, + loops, } } } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 249926fad2f..66b4d5c27dd 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -10,6 +10,7 @@ pub mod free_pcs; pub mod utils; pub mod coupling_graph; +pub mod r#loop; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ diff --git a/mir-state-analysis/src/loop/mod.rs b/mir-state-analysis/src/loop/mod.rs new file mode 100644 index 00000000000..5ece5a60734 --- /dev/null +++ b/mir-state-analysis/src/loop/mod.rs @@ -0,0 +1,166 @@ +// © 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/. + +// © 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::{Idx, IndexVec}, + middle::mir::{BasicBlock, Body, START_BLOCK} +}; + +#[derive(Clone, Debug)] +struct LoopSet { + // Tracks up to 64 loops inline + data: smallvec::SmallVec<[u8; 8]>, +} +impl LoopSet { + const OFFSET: usize = 8 * std::mem::size_of::(); + + fn new() -> Self { + Self { + data: smallvec::SmallVec::from_const([0; 8]), + } + } + fn add(&mut self, loop_idx: LoopId) { + let loop_idx = loop_idx.index(); + let idx = loop_idx / Self::OFFSET; + if idx >= self.data.len() { + self.data.resize(idx + 1, 0); + } + self.data[idx] |= 1 << (loop_idx % Self::OFFSET); + } + fn merge(&mut self, other: &Self) { + if other.data.len() > self.data.len() { + self.data.resize(other.data.len(), 0); + } + for (idx, val) in other.data.iter().enumerate() { + self.data[idx] |= val; + } + } + fn clear(&mut self, loop_idx: LoopId) { + let loop_idx = loop_idx.index(); + let idx = loop_idx / Self::OFFSET; + assert!(idx < self.data.len()); + self.data[idx] &= !(1 << (loop_idx % Self::OFFSET)); + } + fn iter(&self) -> impl DoubleEndedIterator + '_ { + self.data + .iter() + .enumerate() + .flat_map(|(idx, &val)| { + let to = if val == 0 { 0 } else { Self::OFFSET }; + (0..to) + .filter(move |&i| val & (1 << i) != 0) + .map(move |i| LoopId::new(idx * Self::OFFSET + i)) + }) + } +} + +#[derive(Debug)] +pub struct LoopAnalysis { + bb_data: IndexVec, + loop_heads: IndexVec, +} + +impl LoopAnalysis { + pub fn find_loops(body: &Body) -> Self { + let mut analysis = LoopAnalysis { + bb_data: IndexVec::from_elem_n(LoopSet::new(), body.basic_blocks.len()), + loop_heads: IndexVec::new(), + }; + let raw_bb_data: *const _ = &analysis.bb_data; + + let mut visited_bbs: IndexVec = IndexVec::from_elem_n(false, body.basic_blocks.len()); + + let mut loop_head_bb_index: IndexVec = IndexVec::from_elem_n(NO_LOOP, body.basic_blocks.len()); + for bb in body.basic_blocks.reverse_postorder().iter().copied().rev() { + let data = &mut analysis.bb_data[bb]; + for succ in body.basic_blocks[bb].terminator().successors() { + if visited_bbs[succ] { + // Merge in loops of this succ + assert_ne!(bb, succ); + // SAFETY: Index is different to mutably borrowed index + let other = unsafe { &(*raw_bb_data)[succ] }; + data.merge(other); + // If `succ` is a loop head, we are no longer in that loop + let loop_idx = loop_head_bb_index[succ]; + if loop_idx != NO_LOOP { + assert_eq!(analysis.loop_heads[loop_idx], succ); + data.clear(loop_idx); + } + } else { + // Create new loop + let loop_idx = &mut loop_head_bb_index[succ]; + if *loop_idx == NO_LOOP { + *loop_idx = LoopId::new(analysis.loop_heads.len()); + analysis.loop_heads.push(succ); + } + data.add(*loop_idx); + } + } + visited_bbs[bb] = true; + } + if cfg!(debug_assertions) { + analysis.consistency_check(); + } + analysis + } + pub fn loops(&self, bb: BasicBlock) -> impl DoubleEndedIterator + '_ { + self.bb_data[bb].iter() + } + pub fn loop_depth(&self, bb: BasicBlock) -> usize { + self.loops(bb).count() + } + pub fn loop_nest_depth(&self, l: LoopId) -> usize { + self.loop_depth(self[l]) - 1 + } + /// Returns the loop which contains `bb` as well as all other loops of `bb`. + pub fn outermost_loop(&self, bb: BasicBlock) -> Option { + self.loops(bb).next() + } + /// Returns the loop which contains `bb` but no other loops of `bb`. + pub fn innermost_loop(&self, bb: BasicBlock) -> Option { + self.loops(bb).next_back() + } + + fn consistency_check(&self) { + // Start block can be in a maximum of one loop, of which it is the head + let mut start_loops: Vec<_> = self.loops(START_BLOCK).collect(); + if let Some(l) = start_loops.pop() { + assert_eq!(self[l], START_BLOCK); + } + assert!(start_loops.is_empty()); + // Check that `innermost_loop` and `outermost_loop` are correct (TODO: remove this check) + for bb in self.bb_data.indices() { + let innermost_depth = self.innermost_loop(bb).map(|l| self.loop_nest_depth(l)).unwrap_or_default(); + let outermost_depth = self.outermost_loop(bb).map(|l| self.loop_nest_depth(l)).unwrap_or_default(); + assert!(self.loops(bb).map(|l| self.loop_nest_depth(l)).all(|d| outermost_depth <= d && d <= innermost_depth)); + } + } +} + +impl std::ops::Index for LoopAnalysis { + type Output = BasicBlock; + fn index(&self, index: LoopId) -> &Self::Output { + &self.loop_heads[index] + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LoopId(usize); +impl Idx for LoopId { + fn new(idx: usize) -> Self { + Self(idx) + } + fn index(self) -> usize { + self.0 + } +} +const NO_LOOP: LoopId = LoopId(usize::MAX); 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 28/58] 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 29/58] 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 0e5f84f1c177ef86bc9591bb6afd3acdde5733de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 17:48:19 +0200 Subject: [PATCH 30/58] Merge --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index beb20cf4bdc..76588def9b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1896,6 +1896,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "smallvec", "tracing 0.1.0", ] From 7610848cb58f82e96d5946d02c47ce73df2338f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 28 Sep 2023 18:29:03 +0200 Subject: [PATCH 31/58] Only one region per Node --- .../src/coupling_graph/impl/cg.rs | 216 +++++++++++++----- .../src/coupling_graph/impl/dot.rs | 64 +++--- .../src/coupling_graph/impl/engine.rs | 30 ++- .../coupling_graph/impl/join_semi_lattice.rs | 25 +- 4 files changed, 239 insertions(+), 96 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs index 115ca63d427..50e34435f43 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -13,7 +13,7 @@ use prusti_rustc_interface::{ index::bit_set::BitSet, dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, borrowck::{borrow_set::BorrowData, consumers::BorrowIndex}, - middle::{mir::{ConstraintCategory, RETURN_PLACE, Location}, ty::{RegionVid, TyKind}}, + middle::{mir::{BasicBlock, ConstraintCategory, RETURN_PLACE, Location, Operand}, ty::{RegionVid, TyKind}}, }; use crate::{ @@ -32,11 +32,93 @@ pub struct Regions<'a, 'tcx> { pub version: usize, pub(crate) graph: Graph<'a, 'tcx>, - pub path_condition: Vec, + // If I'm here, + pub path_condition: Vec>, } pub type NodeId = usize; +#[derive(Debug, Clone)] +pub struct PathConditionMap<'tcx> { + pub branch_arg: Operand<'tcx>, + pub branch_tgts: FxHashMap>, +} + +pub struct PathConditionCollection(FxHashMap, Vec>); +impl PathConditionCollection { + pub fn new() -> Self { + Self(FxHashMap::default()) + } + pub fn push(&mut self, pc: PathCondition) { + // let new = self.0.drain().flat_map(|(mut k, mut v)| { + // match v.pop() { + // None => Vec::new(), + // Some(last) => { + // v.into_iter().map(|mut v| { + // let mut k = k.clone(); + // k.push(v); + // (k, pc.clone()) + // }).collect() + // k.push(last); + + // } + // } + // if v.len() == 0 { + // Vec::new() + // } else { + // v.pop().un + // } + // if v.len() == 1 { + // k.push(v.pop().unwrap()); + // std::iter::once((k, pc.clone())) + // } + // k.push(v); + // (k, pc.clone()) + // }).collect(); + // self.0 = new; + } + // pub fn set_value_all(&mut self, value: Option) { + // self.0. + // } +} + +pub type BitSetValues = u128; +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct PathCondition { + pub bb: BasicBlock, + pub values: BitSetValues, + pub all_values: u128, + pub other_values: Vec, +} + +impl PathCondition { + pub fn new(bb: BasicBlock, values_len: usize) -> Self { + let (all_values, other_values) = if values_len < 127 { + ((1 << (values_len+1)) - 1, Vec::new()) + } else { + (u128::MAX, vec![false; values_len - 127]) + }; + Self { + bb, + values: 0, + all_values, + other_values, + } + } + pub fn set_value(&mut self, value: Option) -> bool { + match value { + None => self.values |= 1, + Some(v) if v < 127 => self.values |= 1 << (v+1), + Some(v) => self.other_values[TryInto::::try_into(v).unwrap() - 127] = true, + }; + self.all_values_set() + } + fn all_values_set(&self) -> bool { + assert!(self.values <= self.all_values); + self.values == self.all_values && self.other_values.iter().all(|b| *b) + } +} + #[derive(Clone)] pub struct Graph<'a, 'tcx> { pub id: Option, @@ -61,9 +143,6 @@ impl Debug for Graph<'_, '_> { } } -#[derive(Debug, Clone)] -pub struct PathCondition; - impl PartialEq for Graph<'_, '_> { fn eq(&self, other: &Self) -> bool { self.nodes == other.nodes @@ -153,11 +232,11 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } // r1 outlives r2, or `r1: r2` (i.e. r1 gets blocked) #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] - pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: ConstraintCategory<'tcx>) -> bool { + pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: EdgeInfo<'tcx>) -> bool { self.outlives_many(r1, r2, &FxHashSet::from_iter([reason].into_iter())) } #[tracing::instrument(name = "Graph::outlives_many", level = "trace", skip(self), ret)] - pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { + pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { // eprintln!("Outlives: {:?} -> {:?} ({:?})", r1, r2, reasons); let Some(n2) = self.region_to_node(r2) else { // `r2` is static @@ -201,17 +280,30 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // self.sanity_check(); } #[tracing::instrument(name = "Graph::remove", level = "trace", skip(self))] - pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool) { + // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, + // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). + // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). + pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool, remove_dangling_children: bool) { if self.static_regions.remove(&r) { return; } for n in self.nodes.iter_mut() { if let Some(n) = n { - if n.regions.contains(&r) { - n.regions.remove(&r); - if n.regions.is_empty() { - let id = n.id; - self.remove_node_rejoin(id); + // if n.regions.contains(&r) { + // n.regions.remove(&r); + // if n.regions.is_empty() { + // let id = n.id; + // self.remove_node_rejoin(id); + // } + // return; + // } + if n.region == r { + let id = n.id; + let n = self.remove_node_rejoin(id); + if remove_dangling_children { + for (block, _) in n.blocks { + self.remove_if_dangling_and_sh_borrow(block); + } } return; } @@ -220,20 +312,29 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { assert!(maybe_already_removed, "Region {:?} not found in graph", r); // self.sanity_check(); } - // Used when merging two graphs (and we know from one graph that two regions are equal) - #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - pub fn equate_regions(&mut self, ra: RegionVid, rb: RegionVid) -> bool { - let mut changed = self.outlives(ra, rb, ConstraintCategory::Internal); - changed = changed || self.outlives(rb, ra, ConstraintCategory::Internal); - // self.sanity_check(); - changed + fn remove_if_dangling_and_sh_borrow(&mut self, n: NodeId) { + let node = self.get_node(n); + if node.blocked_by.is_empty() && self.cgx.sbs.location_map.values().any(|data| data.region == node.region) { + self.remove_node(n); + } } + + // // Used when merging two graphs (and we know from one graph that two regions are equal) + // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] + // pub fn equate_regions(&mut self, ra: RegionVid, rb: RegionVid) -> bool { + // let mut changed = self.outlives(ra, rb, ConstraintCategory::Internal); + // changed = changed || self.outlives(rb, ra, ConstraintCategory::Internal); + // // self.sanity_check(); + // changed + // } #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] pub fn edge_to_regions(&self, from: NodeId, to: NodeId) -> (RegionVid, RegionVid) { let n1 = self.get_node(from); let n2 = self.get_node(to); - let r1 = *n1.regions.iter().next().unwrap(); - let r2 = *n2.regions.iter().next().unwrap(); + // let r1 = *n1.regions.iter().next().unwrap(); + // let r2 = *n2.regions.iter().next().unwrap(); + let r1 = n1.region; + let r2 = n2.region; (r1, r2) } #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] @@ -264,7 +365,8 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // If there is a cycle we could have already removed and made static if let Some(mut node) = self.remove_node(n) { // println!("Making static node: {node:?}"); - self.static_regions.extend(node.regions.drain()); + // self.static_regions.extend(node.regions.drain()); + self.static_regions.insert(node.region); for (block_by, _) in node.blocked_by.drain() { // assert!(node.blocked_by.is_empty()); self.set_borrows_from_static_node(block_by); @@ -300,7 +402,8 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { let mut last_none = self.nodes.len(); for (i, n) in self.nodes.iter().enumerate() { if let Some(n) = n { - if n.regions.contains(&r) { + // if n.regions.contains(&r) { + if n.region == r { return Some(i); } } else { @@ -316,21 +419,21 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } fn merge(&mut self, n1: NodeId, n2: NodeId) { panic!("{:?} and {:?}", self.get_node(n1), self.get_node(n2)); // Should never need to merge! - assert_ne!(n1, n2); - let to_merge = self.remove_node(n1).unwrap(); - for (block, edge) in to_merge.blocks { - if block != n2 { - self.blocks(n2, block, &edge); - } - } - for (block_by, edge) in to_merge.blocked_by { - if block_by != n2 { - self.blocks(block_by, n2, &edge); - } - } - let n2 = self.get_node_mut(n2); - // n2.contained_by.extend(to_merge.contained_by); - n2.regions.extend(to_merge.regions); + // assert_ne!(n1, n2); + // let to_merge = self.remove_node(n1).unwrap(); + // for (block, edge) in to_merge.blocks { + // if block != n2 { + // self.blocks(n2, block, &edge); + // } + // } + // for (block_by, edge) in to_merge.blocked_by { + // if block_by != n2 { + // self.blocks(block_by, n2, &edge); + // } + // } + // let n2 = self.get_node_mut(n2); + // // n2.contained_by.extend(to_merge.contained_by); + // n2.regions.extend(to_merge.regions); } fn kill_node(&mut self, n: NodeId) { let removed = self.remove_node(n).unwrap(); @@ -376,7 +479,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { self.nodes[n].as_mut().unwrap() } - fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { + fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { assert_ne!(n1, n2); let mut changed = false; let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); @@ -397,31 +500,39 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { pub id: NodeId, - pub regions: FxHashSet, - pub blocks: FxHashMap>>, - pub blocked_by: FxHashMap>>, + // pub regions: FxHashSet, + pub region: RegionVid, + pub blocks: FxHashMap>>, + pub blocked_by: FxHashMap>>, pub borrows_from_static: bool, // pub contained_by: Vec, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct EdgeInfo<'tcx> { + pub creation: BasicBlock, + pub reason: ConstraintCategory<'tcx>, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Edge<'tcx> { pub from: NodeId, pub to: NodeId, - pub reasons: FxHashSet>, + pub reasons: FxHashSet>, } impl<'tcx> Edge<'tcx> { - pub(crate) fn new(from: NodeId, to: NodeId, reasons: FxHashSet>) -> Self { + pub(crate) fn new(from: NodeId, to: NodeId, reasons: FxHashSet>) -> Self { Self { from, to, reasons } } } impl<'tcx> Node<'tcx> { - pub fn new(id: NodeId, r: RegionVid) -> Self { + pub fn new(id: NodeId, region: RegionVid) -> Self { Self { id, - regions: [r].into_iter().collect(), + // regions: [r].into_iter().collect(), + region, blocks: FxHashMap::default(), blocked_by: FxHashMap::default(), borrows_from_static: false, @@ -501,13 +612,13 @@ impl<'tcx> Regions<'_, 'tcx> { self.output_to_dot("log/coupling/error.dot"); panic!("{node:?}"); } else { - let r = *node.regions.iter().next().unwrap(); - if universal_region.contains(&r) { + // let r = *node.regions.iter().next().unwrap(); + if universal_region.contains(&node.region) { continue; } let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == r + universal_region.contains(&c.sub) && c.sup == node.region // let r = c.sub.as_u32(); // The thing that lives shorter // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region }); @@ -545,13 +656,14 @@ impl<'tcx> Regions<'_, 'tcx> { pub fn sanity_check(&self) { let mut all = self.graph.static_regions.clone(); for n in self.graph.all_nodes() { - for r in &n.regions { + // for r in &n.regions { + let r = &n.region; let contained = all.insert(*r); if !contained { self.output_to_dot("log/coupling/error.dot"); panic!(); } - } + // } } for n1 in self.graph.all_nodes().map(|n| n.id) { diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index c4fcbdec61e..d4aa76c7b17 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -16,7 +16,7 @@ use prusti_rustc_interface::{ use crate::utils::Place; -use super::{cg::{NodeId, Edge, Graph, Regions}, region_place::Perms}; +use super::{cg::{NodeId, Edge, Graph, Regions, EdgeInfo}, region_place::Perms}; impl<'a, 'tcx> Graph<'a, 'tcx> { fn get_id(&self) -> &str { @@ -33,20 +33,21 @@ impl<'a, 'tcx> Regions<'a, 'tcx> { // } fn get_corresponding_places(&self, n: NodeId) -> Option<&Perms<'tcx>> { let node = self.graph.get_node(n); - assert!(node.regions.len() == 1); - self.graph.cgx.region_map.get(node.regions.iter().next().unwrap()) + // assert!(node.regions.len() == 1); + // self.graph.cgx.region_map.get(node.regions.iter().next().unwrap()) + self.graph.cgx.region_map.get(&node.region) } pub(crate) fn is_borrow_only(&self, n: NodeId) -> Option { let node = self.graph.get_node(n); let input_facts = self.graph.facts.input_facts.borrow(); for &(_, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { - if node.regions.contains(&r) { + if node.region == r { return None; } } let mut is_borrow = None; for (_, data) in &self.graph.facts2.borrow_set.location_map { - if node.regions.contains(&data.region) { + if node.region == data.region { if is_borrow.is_some() { return None; } @@ -54,7 +55,7 @@ impl<'a, 'tcx> Regions<'a, 'tcx> { } } for (_, data) in &self.graph.cgx.sbs.location_map { - if node.regions.contains(&data.region) { + if node.region == data.region { if is_borrow.is_some() { return None; } @@ -63,7 +64,7 @@ impl<'a, 'tcx> Regions<'a, 'tcx> { } is_borrow } - fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { + fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { if !(self.graph.skip_empty_nodes && self.is_empty_node(n)) { return vec![Edge::new(start, n, reasons)]; } @@ -103,27 +104,30 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { if e.to == usize::MAX { return dot::LabelText::LabelStr(Cow::Borrowed("static")); } - let mut label = e.reasons.iter().map(|r| match r { - ConstraintCategory::Return(_) => "return", - ConstraintCategory::Yield => "yield", - ConstraintCategory::UseAsConst => "const", - ConstraintCategory::UseAsStatic => "static", - ConstraintCategory::TypeAnnotation => "type", - ConstraintCategory::Cast => "cast", - ConstraintCategory::ClosureBounds => "closure", - ConstraintCategory::CallArgument(_) => "arg", - ConstraintCategory::CopyBound => "copy", - ConstraintCategory::SizedBound => "sized", - ConstraintCategory::Assignment => "assign", - ConstraintCategory::Usage => "use", - ConstraintCategory::OpaqueType => "opaque", - ConstraintCategory::ClosureUpvar(_) => "upvar", - ConstraintCategory::Predicate(_) => "pred", - ConstraintCategory::Boring => "boring", - ConstraintCategory::BoringNoLocation => "boring_nl", - ConstraintCategory::Internal => "internal", - }).map(|s| format!("{s}, ")).collect::(); - label = label[..label.len() - 2].to_string(); + let mut label = e.reasons.iter().map(|r| { + let reason = match r.reason { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + ConstraintCategory::UseAsConst => "const", + ConstraintCategory::UseAsStatic => "static", + ConstraintCategory::TypeAnnotation => "type", + ConstraintCategory::Cast => "cast", + ConstraintCategory::ClosureBounds => "closure", + ConstraintCategory::CallArgument(_) => "arg", + ConstraintCategory::CopyBound => "copy", + ConstraintCategory::SizedBound => "sized", + ConstraintCategory::Assignment => "assign", + ConstraintCategory::Usage => "use", + ConstraintCategory::OpaqueType => "opaque", + ConstraintCategory::ClosureUpvar(_) => "upvar", + ConstraintCategory::Predicate(_) => "pred", + ConstraintCategory::Boring => "boring", + ConstraintCategory::BoringNoLocation => "boring_nl", + ConstraintCategory::Internal => "internal", + }; + format!("{:?} ({reason})", r.creation) + }).map(|s| format!("{s}\n")).collect::(); + label = label[..label.len() - 1].to_string(); dot::LabelText::LabelStr(Cow::Owned(label)) } fn node_shape(&'a self, n: &NodeId) -> Option> { @@ -150,8 +154,8 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { // let process_place = |p: Place<'tcx>| p; // let contained_by = contained_by.collect::>(); let node = self.graph.get_node(*n); - assert!(node.regions.len() == 1); - let label = format!("{:?}\n{contained_by}", node.regions.iter().next().unwrap()); + // assert!(node.regions.len() == 1); + let label = format!("{:?}\n{contained_by}", node.region); dot::LabelText::LabelStr(Cow::Owned(label)) } } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index ed9152f80d0..8293ae0b28d 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -29,7 +29,7 @@ use crate::{ utils::PlaceRepacker, coupling_graph::cg::{Graph, Node}, }; -use super::{cg::{Cg, Regions}, shared_borrow_set::SharedBorrowSet, CgContext}; +use super::{cg::{Cg, Regions, PathCondition, EdgeInfo}, shared_borrow_set::SharedBorrowSet, CgContext}; pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { let mut graph = Vec::new(); @@ -172,7 +172,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { let local = borrow_data.assigned_place.local; for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { // println!("IHUBJ local: {local:?} region: {region:?}"); - state.graph.remove(region, true); + state.graph.remove(region, true, false); } } } @@ -188,13 +188,13 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { |(_, b, _)| killed == *b ).copied().unwrap(); // println!("killed: {r:?} {killed:?} {l:?}"); - state.graph.remove(r, false); + state.graph.remove(r, false, false); let l = rich_to_loc(location_table.to_location(l)); let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); let local = borrow_data.borrowed_place.local; for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { // println!("killed region: {region:?}"); - state.graph.remove(region, true); + state.graph.remove(region, true, false); } } @@ -215,7 +215,11 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { for c in constraints { if let Some(from) = c.locations.from_location() { if from == location { - state.graph.outlives(c.sup, c.sub, c.category); + let reason = EdgeInfo { + reason: c.category, + creation: location.block, + }; + state.graph.outlives(c.sup, c.sub, reason); } } } @@ -260,7 +264,9 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // Also remove any overwritten borrows locals for (®ion, _) in state.graph.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { // println!("Killing local: {local:?}: {region:?}"); - state.graph.remove(region, true); + + // TODO: could set `remove_dangling_children` to false here + state.graph.remove(region, true, true); } } } @@ -433,10 +439,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` for r in self.facts2.borrow_set.location_map.values() { - state.regions.graph.remove(r.region, true); + state.regions.graph.remove(r.region, true, false); } for r in self.cgx.sbs.location_map.values() { - state.regions.graph.remove(r.region, true); + state.regions.graph.remove(r.region, true, false); } state.regions.merge_for_return(); } @@ -445,6 +451,14 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); } + match terminator.edges() { + TerminatorEdges::SwitchInt { targets, discr } => { + let _ = PathCondition::new(location.block, targets.all_targets().len()); + // let map = targets.iter().map(|(v, bb)| (bb, Some(v))).chain(std::iter::once((targets.otherwise(), None))).collect(); + // state.regions.path_condition.push() + } + _ => () + } terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index bd2d4b62ebb..2422d576dd5 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,7 +15,7 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::cg::{Cg, Graph, SharedBorrows}; +use super::cg::{Cg, Graph, SharedBorrows, PathCondition}; impl JoinSemiLattice for Cg<'_, '_> { #[tracing::instrument(name = "Cg::join", level = "debug", skip_all)] @@ -71,10 +71,8 @@ impl JoinSemiLattice for Graph<'_, '_> { // println!("Joining graphs:\n{:?}: {:?}\n{:?}: {:?}", self.id, self.nodes, other.id, other.nodes); let mut changed = false; for node in other.nodes.iter().flat_map(|n| n) { - let rep = node.regions.iter().next().unwrap(); - for r in node.regions.iter().skip(1) { - changed = changed || self.equate_regions(*rep, *r); - } + // let rep = node.regions.iter().next().unwrap(); + let rep = node.region; for (to, reasons) in node.blocks.iter() { let (from, to) = other.edge_to_regions(node.id, *to); let was_new = self.outlives_many(to, from, reasons); @@ -84,7 +82,7 @@ impl JoinSemiLattice for Graph<'_, '_> { // } } if node.borrows_from_static { - changed = changed || self.set_borrows_from_static(*rep); + changed = changed || self.set_borrows_from_static(rep); } } let old_len = self.static_regions.len(); @@ -106,3 +104,18 @@ impl SharedBorrows<'_> { self.location_map.len() != old_len } } + +impl JoinSemiLattice for PathCondition { + fn join(&mut self, other: &Self) -> bool { + assert_eq!(self.bb, other.bb); + let old_values = self.values; + self.values |= other.values; + let mut changed = old_values != self.values; + for (i, v) in self.other_values.iter_mut().enumerate() { + let old_value = *v; + *v |= other.other_values[i]; + changed = changed || old_value != *v; + } + changed + } +} 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 32/58] 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 33/58] 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 452c31758b527badc7a07fcd030585a76b40769e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 29 Sep 2023 17:03:34 +0200 Subject: [PATCH 34/58] Clean things up --- .../src/coupling_graph/impl/cg.rs | 802 +++++++----------- .../src/coupling_graph/impl/dot.rs | 220 +++-- .../src/coupling_graph/impl/engine.rs | 335 ++++---- .../coupling_graph/impl/join_semi_lattice.rs | 100 +-- mir-state-analysis/src/utils/place.rs | 4 + 5 files changed, 552 insertions(+), 909 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/cg.rs index 50e34435f43..f4ee9e9e1ce 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/cg.rs @@ -4,7 +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/. -use std::{fmt::{Debug, Formatter, Result}, borrow::Cow}; +use std::{fmt::{Debug, Formatter, Result, Display}, borrow::Cow}; use derive_more::{Deref, DerefMut}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; @@ -12,7 +12,7 @@ use prusti_rustc_interface::{ data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, index::bit_set::BitSet, dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, - borrowck::{borrow_set::BorrowData, consumers::BorrowIndex}, + borrowck::{borrow_set::BorrowData, consumers::{BorrowIndex, OutlivesConstraint}}, middle::{mir::{BasicBlock, ConstraintCategory, RETURN_PLACE, Location, Operand}, ty::{RegionVid, TyKind}}, }; @@ -23,101 +23,7 @@ use crate::{ utils::{PlaceRepacker, Place}, }; -use super::{engine::CoupligGraph, shared_borrow_set::SharedBorrowSet, CgContext}; - -#[derive(Debug, Clone)] -pub struct Regions<'a, 'tcx> { - pub borrows: FxHashMap, Vec<(Local, RegionVid)>)>, - pub(crate) subset: Vec<(RegionVid, RegionVid)>, - - pub version: usize, - pub(crate) graph: Graph<'a, 'tcx>, - // If I'm here, - pub path_condition: Vec>, -} - -pub type NodeId = usize; - -#[derive(Debug, Clone)] -pub struct PathConditionMap<'tcx> { - pub branch_arg: Operand<'tcx>, - pub branch_tgts: FxHashMap>, -} - -pub struct PathConditionCollection(FxHashMap, Vec>); -impl PathConditionCollection { - pub fn new() -> Self { - Self(FxHashMap::default()) - } - pub fn push(&mut self, pc: PathCondition) { - // let new = self.0.drain().flat_map(|(mut k, mut v)| { - // match v.pop() { - // None => Vec::new(), - // Some(last) => { - // v.into_iter().map(|mut v| { - // let mut k = k.clone(); - // k.push(v); - // (k, pc.clone()) - // }).collect() - // k.push(last); - - // } - // } - // if v.len() == 0 { - // Vec::new() - // } else { - // v.pop().un - // } - // if v.len() == 1 { - // k.push(v.pop().unwrap()); - // std::iter::once((k, pc.clone())) - // } - // k.push(v); - // (k, pc.clone()) - // }).collect(); - // self.0 = new; - } - // pub fn set_value_all(&mut self, value: Option) { - // self.0. - // } -} - -pub type BitSetValues = u128; -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct PathCondition { - pub bb: BasicBlock, - pub values: BitSetValues, - pub all_values: u128, - pub other_values: Vec, -} - -impl PathCondition { - pub fn new(bb: BasicBlock, values_len: usize) -> Self { - let (all_values, other_values) = if values_len < 127 { - ((1 << (values_len+1)) - 1, Vec::new()) - } else { - (u128::MAX, vec![false; values_len - 127]) - }; - Self { - bb, - values: 0, - all_values, - other_values, - } - } - pub fn set_value(&mut self, value: Option) -> bool { - match value { - None => self.values |= 1, - Some(v) if v < 127 => self.values |= 1 << (v+1), - Some(v) => self.other_values[TryInto::::try_into(v).unwrap() - 127] = true, - }; - self.all_values_set() - } - fn all_values_set(&self) -> bool { - assert!(self.values <= self.all_values); - self.values == self.all_values && self.other_values.iter().all(|b| *b) - } -} +use super::{engine::CoupligGraph, shared_borrow_set::SharedBorrowSet, CgContext, region_place::Perms}; #[derive(Clone)] pub struct Graph<'a, 'tcx> { @@ -126,8 +32,12 @@ pub struct Graph<'a, 'tcx> { pub facts: &'a BorrowckFacts, pub facts2: &'a BorrowckFacts2<'tcx>, pub cgx: &'a CgContext<'a, 'tcx>, - pub nodes: Vec>>, + + pub(crate) live: BitSet, + pub version: usize, pub skip_empty_nodes: bool, + + pub nodes: IndexVec>, // Regions equal to 'static pub static_regions: FxHashSet, } @@ -138,33 +48,36 @@ impl Debug for Graph<'_, '_> { .field("id", &self.id) .field("nodes", &self.nodes) .field("skip_empty_nodes", &self.skip_empty_nodes) - .field("static_regions", &self.static_regions) + // .field("static_regions", &self.static_regions) .finish() } } impl PartialEq for Graph<'_, '_> { fn eq(&self, other: &Self) -> bool { - self.nodes == other.nodes + self.nodes == other.nodes //&& self.static_regions == other.static_regions } } impl Eq for Graph<'_, '_> {} -impl<'a, 'tcx> Graph<'a, 'tcx> { +impl<'a, 'tcx: 'a> Graph<'a, 'tcx> { // TODO: get it from `UniversalRegions` instead pub fn static_region() -> RegionVid { RegionVid::from_u32(0) } pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>) -> Self { + let live = BitSet::new_empty(facts2.borrow_set.location_map.len()); let mut result = Self { id: None, rp, facts, facts2, cgx, - nodes: Vec::new(), + live, + version: 0, skip_empty_nodes: false, - static_regions: [Self::static_region()].into_iter().collect(), + nodes: IndexVec::from_elem_n(Node::new(), facts2.region_inference_context.var_infos.len()), + static_regions: FxHashSet::from_iter([Self::static_region()]), }; // let input_facts = facts.input_facts.borrow(); // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { @@ -189,393 +102,321 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { // } // } // println!("Static node: {node:?}"); - let mut to_make_static = vec![Self::static_region()]; - while let Some(r) = to_make_static.pop() { - for c in constraints_no_loc.iter().filter(|c| c.sub == r) { - if result.static_regions.insert(c.sup) { - to_make_static.push(c.sup); - } - } - } - - - for c in constraints_no_loc { - // let l: Vec<_> = input_facts.use_of_var_derefs_origin.iter().filter(|(_, r)| *r == c.sup).collect(); - // assert!(l.len() <= 1); - // if l.len() == 1 && l[0].0 == RETURN_PLACE { - // continue; - // } - // println!("c: {c:?}...{:?} {:?}", c.sub, c.sup); - - // Add arguments to the graph... This doesn't seem to work - // for r in [c.sub, c.sup] { - // if let Some(p) = cgx.region_map.get(&c.sub) { - // if let Some(l) = p.place.as_local() { - // if l.as_usize() <= rp.body().arg_count && l != RETURN_PLACE { - // result.region_to_node(r); - // } - // } - // } - // } - - // result.region_to_node(c.sup); - // result.region_to_node(c.sub); - // TODO: maybe not needed - // if c.sub == Self::static_region() { - // result.static_regions.insert(c.sup); - // } + // let mut to_make_static = vec![Self::static_region()]; + // while let Some(r) = to_make_static.pop() { + // for &c in constraints_no_loc.iter().filter(|c| c.sub == r) { + // if result.outlives(c) { + // to_make_static.push(c.sup); + // } + // } + // } - // Avoid this to keep `result` separate from arguments - // result.outlives(c.sup, c.sub, c.category); - } result } - // r1 outlives r2, or `r1: r2` (i.e. r1 gets blocked) #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] - pub fn outlives(&mut self, r1: RegionVid, r2: RegionVid, reason: EdgeInfo<'tcx>) -> bool { - self.outlives_many(r1, r2, &FxHashSet::from_iter([reason].into_iter())) - } - #[tracing::instrument(name = "Graph::outlives_many", level = "trace", skip(self), ret)] - pub fn outlives_many(&mut self, r1: RegionVid, r2: RegionVid, reasons: &FxHashSet>) -> bool { - // eprintln!("Outlives: {:?} -> {:?} ({:?})", r1, r2, reasons); - let Some(n2) = self.region_to_node(r2) else { - // `r2` is static - return self.make_static(r1); - }; - let Some(n1) = self.region_to_node(r1) else { - let node = self.get_node_mut(n2); - let already_set = node.borrows_from_static; - node.borrows_from_static = true; - return already_set; + pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> bool { + let edge = EdgeInfo { + creation: c.locations.from_location().map(|l| l.block), + reason: c.category, }; - // assert!(!self.static_regions.contains(&r1), "{:?} outlives {:?} ({:?})", r1, r2, self.static_regions); - - if n1 == n2 { - return false; - } - - // if let Some(path) = self.reachable(n1, n2) { - // let mut changed = false; - // for other in path { - // // Can only merge nodes which are not equal - // changed = true; - // self.merge(other, n2); - // } - // // self.sanity_check(); - // changed - // } else { - let changed = self.blocks(n2, n1, reasons); - // self.sanity_check(); - changed - // } - } - // pub fn contained_by(&mut self, r: RegionVid, l: Local) { - // let n = self.region_to_node(r); - // self.get_node_mut(n).contained_by.push(l); - // } - #[tracing::instrument(name = "Graph::kill", level = "trace", skip(self))] - pub fn kill(&mut self, r: RegionVid) { - let n = self.region_to_node(r); - self.kill_node(n.unwrap()); - // self.sanity_check(); - } - #[tracing::instrument(name = "Graph::remove", level = "trace", skip(self))] - // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, - // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). - // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). - pub fn remove(&mut self, r: RegionVid, maybe_already_removed: bool, remove_dangling_children: bool) { - if self.static_regions.remove(&r) { - return; - } - for n in self.nodes.iter_mut() { - if let Some(n) = n { - // if n.regions.contains(&r) { - // n.regions.remove(&r); - // if n.regions.is_empty() { - // let id = n.id; - // self.remove_node_rejoin(id); - // } - // return; - // } - if n.region == r { - let id = n.id; - let n = self.remove_node_rejoin(id); - if remove_dangling_children { - for (block, _) in n.blocks { - self.remove_if_dangling_and_sh_borrow(block); - } - } - return; - } - } - } - assert!(maybe_already_removed, "Region {:?} not found in graph", r); - // self.sanity_check(); + self.outlives_inner(c.sup, c.sub, edge) } - fn remove_if_dangling_and_sh_borrow(&mut self, n: NodeId) { - let node = self.get_node(n); - if node.blocked_by.is_empty() && self.cgx.sbs.location_map.values().any(|data| data.region == node.region) { - self.remove_node(n); - } + pub fn outlives_static(&mut self, r: RegionVid, l: Location) { + let edge = EdgeInfo { creation: Some(l.block), reason: ConstraintCategory::Internal }; + self.outlives_inner(r, Self::static_region(), edge); } - - // // Used when merging two graphs (and we know from one graph that two regions are equal) - // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - // pub fn equate_regions(&mut self, ra: RegionVid, rb: RegionVid) -> bool { - // let mut changed = self.outlives(ra, rb, ConstraintCategory::Internal); - // changed = changed || self.outlives(rb, ra, ConstraintCategory::Internal); - // // self.sanity_check(); - // changed - // } - #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - pub fn edge_to_regions(&self, from: NodeId, to: NodeId) -> (RegionVid, RegionVid) { - let n1 = self.get_node(from); - let n2 = self.get_node(to); - // let r1 = *n1.regions.iter().next().unwrap(); - // let r2 = *n2.regions.iter().next().unwrap(); - let r1 = n1.region; - let r2 = n2.region; - (r1, r2) - } - #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - pub fn set_borrows_from_static(&mut self, r: RegionVid) -> bool { - if let Some(n) = self.region_to_node(r) { - self.set_borrows_from_static_node(n) - } else { - false + // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) + #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] + pub(crate) fn outlives_inner(&mut self, sup: RegionVid, sub: RegionVid, edge: EdgeInfo<'tcx>) -> bool { + if sup == sub { + panic!(); + return false; } - } - fn set_borrows_from_static_node(&mut self, n: NodeId) -> bool { - let node = self.get_node_mut(n); - let already_set = node.borrows_from_static; - node.borrows_from_static = true; - already_set - } - #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - pub fn make_static(&mut self, r: RegionVid) -> bool { - // TODO: instead of using `region_to_node`, do not add a node if already present? - if let Some(n) = self.region_to_node(r) { - self.make_static_node(n); - true - } else { - false + if self.static_regions.contains(&sub) { + Self::set_static_region(&self.nodes, &mut self.static_regions, sup); } + self.nodes[sup].blocked_by.entry(sub).or_default().insert(edge); + self.nodes[sub].blocks.entry(sup).or_default().insert(edge) } - fn make_static_node(&mut self, n: NodeId) { - // If there is a cycle we could have already removed and made static - if let Some(mut node) = self.remove_node(n) { - // println!("Making static node: {node:?}"); - // self.static_regions.extend(node.regions.drain()); - self.static_regions.insert(node.region); - for (block_by, _) in node.blocked_by.drain() { - // assert!(node.blocked_by.is_empty()); - self.set_borrows_from_static_node(block_by); - } - for (block, _) in node.blocks.drain() { - self.make_static_node(block); + fn set_static_region(nodes: &IndexVec>, static_regions: &mut FxHashSet, r: RegionVid) { + if static_regions.insert(r) { + for &sup in nodes[r].blocks.keys() { + Self::set_static_region(nodes, static_regions, sup); } } } - fn reachable(&self, from: NodeId, to: NodeId) -> Option> { - // println!("Checking reachability from {} to {}", from, to); - let mut nodes = FxHashSet::default(); - if from == to { - return Some(nodes); - } - for (&next, _) in &self.get_node(from).blocks { - if let Some(others) = self.reachable(next, to) { - nodes.insert(from); - nodes.extend(others); - } - } - if nodes.is_empty() { - None - } else { - Some(nodes) - } - } - fn region_to_node(&mut self, r: RegionVid) -> Option { - if self.static_regions.contains(&r) { - return None; - } - let mut last_none = self.nodes.len(); - for (i, n) in self.nodes.iter().enumerate() { - if let Some(n) = n { - // if n.regions.contains(&r) { - if n.region == r { - return Some(i); - } - } else { - last_none = i; - } - } - if last_none == self.nodes.len() { - self.nodes.push(Some(Node::new(last_none, r))); - } else { - self.nodes[last_none] = Some(Node::new(last_none, r)); + #[tracing::instrument(name = "Graph::kill_borrow", level = "debug", skip(self))] + /// Remove borrow from graph and all nodes that block it and the node it blocks + pub fn kill_borrow(&mut self, data: &BorrowData<'tcx>) { + if cfg!(debug_assertions) { + let blocks = &self.nodes[data.region].blocks; + assert_eq!(blocks.len(), 1); + // A borrow should have places associated with it (i.e. be in the `region_map`)! + assert_eq!( + self.cgx.region_map[&data.region].place.local, + self.cgx.region_map[blocks.keys().next().unwrap()].place.local + ); } - Some(last_none) + self.kill(data.region); } - fn merge(&mut self, n1: NodeId, n2: NodeId) { - panic!("{:?} and {:?}", self.get_node(n1), self.get_node(n2)); // Should never need to merge! - // assert_ne!(n1, n2); - // let to_merge = self.remove_node(n1).unwrap(); - // for (block, edge) in to_merge.blocks { - // if block != n2 { - // self.blocks(n2, block, &edge); - // } - // } - // for (block_by, edge) in to_merge.blocked_by { - // if block_by != n2 { - // self.blocks(block_by, n2, &edge); - // } - // } - // let n2 = self.get_node_mut(n2); - // // n2.contained_by.extend(to_merge.contained_by); - // n2.regions.extend(to_merge.regions); - } - fn kill_node(&mut self, n: NodeId) { - let removed = self.remove_node(n).unwrap(); - for (blocked_by, _) in removed.blocked_by { - // May have a diamond shape, so may - if self.nodes[blocked_by].is_some() { - self.kill_node(blocked_by); - } + + #[tracing::instrument(name = "Graph::kill", level = "trace", skip(self))] + fn kill(&mut self, r: RegionVid) { + assert!(!self.static_regions.contains(&r)); + let (_, blocked_by) = self.remove_all_edges(r); + for (blocked_by, _) in blocked_by { + self.kill(blocked_by); } } - fn remove_node_rejoin(&mut self, id: NodeId) -> Node<'tcx> { - let n = self.remove_node(id).unwrap(); - for (&blocked_by, edge) in &n.blocked_by { - for (&block, _) in &n.blocks { + #[tracing::instrument(name = "Graph::remove", level = "trace")] + /// Remove node from graph and rejoin all blockers and blocked by. + // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, + // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). + // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). + pub fn remove(&mut self, r: RegionVid, l: Location) -> bool { + let (blocks, blocked_by) = self.remove_all_edges(r); + let changed = !(blocks.is_empty() && blocked_by.is_empty()); + for &block in blocks.keys() { + for &blocked_by in blocked_by.keys() { // Do not rejoin nodes in a loop to themselves if blocked_by != block { - self.blocks(blocked_by, block, edge); + let edge = EdgeInfo { creation: Some(l.block), reason: ConstraintCategory::Internal }; + self.outlives_inner(block, blocked_by, edge); } } - if n.borrows_from_static { - self.set_borrows_from_static_node(blocked_by); - } + // if remove_dangling_children { + // let node = &self.nodes[block]; + // if node.blocked_by.is_empty() && self.cgx.sbs.location_map.values().any(|data| data.region == block) { + // self.remove(block, l, false, remove_dangling_children); + // } + // } } - n + let was_static = self.static_regions.remove(&r); + debug_assert!(!was_static || changed); // Check that `was_static ==> changed` + changed + } + + // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] + // pub fn set_borrows_from_static(&mut self, r: RegionVid) -> bool { + // if let Some(n) = self.region_to_node(r) { + // self.set_borrows_from_static_node(n) + // } else { + // false + // } + // } + // fn set_borrows_from_static_node(&mut self, n: NodeId) -> bool { + // let node = self.get_node_mut(n); + // let already_set = node.borrows_from_static; + // node.borrows_from_static = true; + // already_set + // } + // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] + // pub fn make_static(&mut self, r: RegionVid) -> bool { + // // TODO: instead of using `region_to_node`, do not add a node if already present? + // if let Some(n) = self.region_to_node(r) { + // self.make_static_node(n); + // true + // } else { + // false + // } + // } + // fn make_static_node(&mut self, n: NodeId) { + // // If there is a cycle we could have already removed and made static + // if let Some(mut node) = self.remove_node(n) { + // // println!("Making static node: {node:?}"); + // // self.static_regions.extend(node.regions.drain()); + // self.static_regions.insert(node.region); + // for (block_by, _) in node.blocked_by.drain() { + // // assert!(node.blocked_by.is_empty()); + // self.set_borrows_from_static_node(block_by); + // } + // for (block, _) in node.blocks.drain() { + // self.make_static_node(block); + // } + // } + // } + + // fn reachable(&self, from: NodeId, to: NodeId) -> Option> { + // // println!("Checking reachability from {} to {}", from, to); + // let mut nodes = FxHashSet::default(); + // if from == to { + // return Some(nodes); + // } + // for (&next, _) in &self.get_node(from).blocks { + // if let Some(others) = self.reachable(next, to) { + // nodes.insert(from); + // nodes.extend(others); + // } + // } + // if nodes.is_empty() { + // None + // } else { + // Some(nodes) + // } + // } + // fn region_to_node(&mut self, r: RegionVid) -> Option { + // if self.static_regions.contains(&r) { + // return None; + // } + // let mut last_none = self.nodes.len(); + // for (i, n) in self.nodes.iter().enumerate() { + // if let Some(n) = n { + // // if n.regions.contains(&r) { + // if n.region == r { + // return Some(i); + // } + // } else { + // last_none = i; + // } + // } + // if last_none == self.nodes.len() { + // self.nodes.push(Some(Node::new(last_none, r))); + // } else { + // self.nodes[last_none] = Some(Node::new(last_none, r)); + // } + // Some(last_none) + // } + // fn kill_node(&mut self, n: NodeId) { + // let removed = self.remove_node(n).unwrap(); + // for (blocked_by, _) in removed.blocked_by { + // // May have a diamond shape, so may + // if self.nodes[blocked_by].is_some() { + // self.kill_node(blocked_by); + // } + // } + // } + + // fn remove_node_rejoin(&mut self, id: NodeId) -> Node<'tcx> { + // let n = self.remove_node(id).unwrap(); + // for (&blocked_by, edge) in &n.blocked_by { + // for (&block, _) in &n.blocks { + // // Do not rejoin nodes in a loop to themselves + // if blocked_by != block { + // self.blocks(blocked_by, block, edge); + // } + // } + // if n.borrows_from_static { + // self.set_borrows_from_static_node(blocked_by); + // } + // } + // n + // } + // // Remove node without rejoining the graph + // fn remove_node(&mut self, n: NodeId) -> Option> { + // let to_remove = self.nodes[n].take()?; + // for &block in to_remove.blocks.keys() { + // let rem = self.get_node_mut(block).blocked_by.remove(&n); + // assert!(rem.is_some()); + // } + // for &block_by in to_remove.blocked_by.keys() { + // let rem = self.get_node_mut(block_by).blocks.remove(&n); + // assert!(rem.is_some()); + // } + // Some(to_remove) + // } + // pub(crate) fn get_node(&self, n: NodeId) -> &Node<'tcx> { + // self.nodes[n].as_ref().unwrap() + // } + // fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { + // self.nodes[n].as_mut().unwrap() + // } + // fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { + // assert_ne!(n1, n2); + // let mut changed = false; + // let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); + // let old_size = reasons.len(); + // reasons.extend(reason); + // changed = changed || reasons.len() != old_size; + // let reasons = self.get_node_mut(n2).blocked_by.entry(n1).or_default(); + // let old_size = reasons.len(); + // reasons.extend(reason); + // changed = changed || reasons.len() != old_size; + // changed + // } + pub(crate) fn all_nodes(&self) -> impl Iterator)> { + self.nodes.iter_enumerated().filter(|(_, n)| !n.blocked_by.is_empty() || !n.blocks.is_empty()) } - // Remove node without rejoining the graph - fn remove_node(&mut self, n: NodeId) -> Option> { - let to_remove = self.nodes[n].take()?; - for &block in to_remove.blocks.keys() { - let rem = self.get_node_mut(block).blocked_by.remove(&n); - assert!(rem.is_some()); + + fn remove_all_edges(&mut self, r: RegionVid) -> ( + FxHashMap>>, + FxHashMap>>, + ) { + let blocks = std::mem::replace(&mut self.nodes[r].blocks, FxHashMap::default()); + for block in blocks.keys() { + self.nodes[*block].blocked_by.remove(&r); } - for &block_by in to_remove.blocked_by.keys() { - let rem = self.get_node_mut(block_by).blocks.remove(&n); - assert!(rem.is_some()); + let blocked_by = std::mem::replace(&mut self.nodes[r].blocked_by, FxHashMap::default()); + for block_by in blocked_by.keys() { + self.nodes[*block_by].blocks.remove(&r); } - Some(to_remove) - } - pub(crate) fn get_node(&self, n: NodeId) -> &Node<'tcx> { - self.nodes[n].as_ref().unwrap() - } - fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { - self.nodes[n].as_mut().unwrap() + (blocks, blocked_by) } - fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { - assert_ne!(n1, n2); - let mut changed = false; - let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); - let old_size = reasons.len(); - reasons.extend(reason); - changed = changed || reasons.len() != old_size; - let reasons = self.get_node_mut(n2).blocked_by.entry(n1).or_default(); - let old_size = reasons.len(); - reasons.extend(reason); - changed = changed || reasons.len() != old_size; - changed + + pub(crate) fn get_associated_place(&self, r: RegionVid) -> Option<&Perms<'tcx>> { + self.cgx.region_map.get(&r) } - fn all_nodes(&self) -> impl Iterator> { - self.nodes.iter().filter_map(|n| n.as_ref()) + pub(crate) fn has_associated_place(&self, r: RegionVid) -> bool { + self.cgx.region_map.contains_key(&r) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { - pub id: NodeId, + // pub id: NodeId, // pub regions: FxHashSet, - pub region: RegionVid, - pub blocks: FxHashMap>>, - pub blocked_by: FxHashMap>>, - pub borrows_from_static: bool, + // pub region: RegionVid, + pub blocks: FxHashMap>>, + pub blocked_by: FxHashMap>>, + // pub borrows_from_static: bool, // pub contained_by: Vec, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EdgeInfo<'tcx> { - pub creation: BasicBlock, + pub creation: Option, pub reason: ConstraintCategory<'tcx>, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Edge<'tcx> { - pub from: NodeId, - pub to: NodeId, - pub reasons: FxHashSet>, -} - -impl<'tcx> Edge<'tcx> { - pub(crate) fn new(from: NodeId, to: NodeId, reasons: FxHashSet>) -> Self { - Self { from, to, reasons } +impl Display for EdgeInfo<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let reason = match self.reason { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + ConstraintCategory::UseAsConst => "const", + ConstraintCategory::UseAsStatic => "static", + ConstraintCategory::TypeAnnotation => "type", + ConstraintCategory::Cast => "cast", + ConstraintCategory::ClosureBounds => "closure", + ConstraintCategory::CallArgument(_) => "arg", + ConstraintCategory::CopyBound => "copy", + ConstraintCategory::SizedBound => "sized", + ConstraintCategory::Assignment => "assign", + ConstraintCategory::Usage => "use", + ConstraintCategory::OpaqueType => "opaque", + ConstraintCategory::ClosureUpvar(_) => "upvar", + ConstraintCategory::Predicate(_) => "pred", + ConstraintCategory::Boring => "boring", + ConstraintCategory::BoringNoLocation => "boring_nl", + ConstraintCategory::Internal => "internal", + }; + let creation = self.creation.map(|c| format!("{c:?}")).unwrap_or_else(|| "sig".to_string()); + write!(f, "{creation} ({reason})") } } impl<'tcx> Node<'tcx> { - pub fn new(id: NodeId, region: RegionVid) -> Self { + pub fn new() -> Self { Self { - id, - // regions: [r].into_iter().collect(), - region, blocks: FxHashMap::default(), blocked_by: FxHashMap::default(), - borrows_from_static: false, // contained_by: Vec::new(), } } } -#[derive(Clone)] -pub struct Cg<'a, 'tcx> { - pub(crate) repacker: PlaceRepacker<'a, 'tcx>, - // pub(crate) facts: &'a BorrowckFacts, - pub(crate) live: BitSet, - pub(crate) regions: Regions<'a, 'tcx>, - pub done: usize, -} -impl<'a, 'tcx> Cg<'a, 'tcx> { - pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>,) -> Self { - let live = BitSet::new_empty(facts2.borrow_set.location_map.len()); - let regions = Regions { - borrows: FxHashMap::default(), - subset: Vec::new(), - version: 0, - graph: Graph::new(repacker, facts, facts2, cgx), - path_condition: Vec::new(), - }; - Cg { repacker, live, regions, done: 0 } - } -} - -impl PartialEq for Cg<'_, '_> { - fn eq(&self, other: &Self) -> bool { - self.regions == other.regions - } -} -impl Eq for Cg<'_, '_> {} - -impl<'a, 'tcx> Debug for Cg<'a, 'tcx> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.debug_struct("Cg").field("regions", &self.regions).finish() - } -} -impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { +impl<'a, 'tcx> DebugWithContext> for Graph<'a, 'tcx> { fn fmt_diff_with( &self, old: &Self, @@ -586,39 +427,30 @@ impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { } } -impl PartialEq for Regions<'_, '_> { - fn eq(&self, other: &Self) -> bool { - self.graph == other.graph - } -} -impl Eq for Regions<'_, '_> {} - -impl<'tcx> Regions<'_, 'tcx> { +impl<'tcx> Graph<'_, 'tcx> { #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] - pub fn merge_for_return(&mut self) { - let outlives: Vec<_> = self.graph.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); - let in_facts = self.graph.facts.input_facts.borrow(); + pub fn merge_for_return(&self) { + let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); + let in_facts = self.facts.input_facts.borrow(); let universal_region = &in_facts.as_ref().unwrap().universal_region; - let nodes: Vec<_> = self.graph.all_nodes().map(|n| n.id).collect(); - for n in nodes { + for (r, node) in self.all_nodes() { // Skip unknown empty nodes, we may want to figure out how to deal with them in the future - if self.is_empty_node(n) { + if !self.has_associated_place(r) { continue; } - let node = self.graph.get_node(n); - if self.is_borrow_only(n).is_some() { + if self.borrow_kind(r).is_some() { self.output_to_dot("log/coupling/error.dot"); panic!("{node:?}"); } else { // let r = *node.regions.iter().next().unwrap(); - if universal_region.contains(&node.region) { + if universal_region.contains(&r) { continue; } let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == node.region + universal_region.contains(&c.sub) && c.sup == r // let r = c.sub.as_u32(); // The thing that lives shorter // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region }); @@ -634,77 +466,33 @@ impl<'tcx> Regions<'_, 'tcx> { // dot::render(self, &mut f).unwrap(); // } // println!("Found proof: {proof:?}"); - self.output_to_dot("log/coupling/error.dot"); - assert!(proof.is_some(), "Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); + if proof.is_none() { + self.output_to_dot("log/coupling/error.dot"); + panic!("Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); + } } } - for &r in &self.graph.static_regions { + for &r in &self.static_regions { if universal_region.contains(&r) { continue; } // It's possible that we get some random unnamed regions in the static set - if !self.graph.cgx.region_map.contains_key(&r) { + if !self.has_associated_place(r) { continue; } let proof = outlives.iter().find(|c| { universal_region.contains(&c.sub) && c.sup == r }); - self.output_to_dot("log/coupling/error.dot"); - assert!(proof.is_some(), "Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); - } - } - pub fn sanity_check(&self) { - let mut all = self.graph.static_regions.clone(); - for n in self.graph.all_nodes() { - // for r in &n.regions { - let r = &n.region; - let contained = all.insert(*r); - if !contained { - self.output_to_dot("log/coupling/error.dot"); - panic!(); - } - // } - } - - for n1 in self.graph.all_nodes().map(|n| n.id) { - for n2 in self.graph.all_nodes().map(|n| n.id) { - if n1 != n2 { - let path_a = self.graph.reachable(n1, n2); - let path_b = self.graph.reachable(n2, n1); - if path_a.is_some() && path_b.is_some() { - self.output_to_dot("log/coupling/error.dot"); - panic!("Found cycle between {} and {}", n1, n2); - } - } + if proof.is_none() { + self.output_to_dot("log/coupling/error.dot"); + panic!("Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); } } } pub fn output_to_dot>(&self, path: P) { // TODO: - return; + // return; let mut f = std::fs::File::create(path).unwrap(); dot::render(self, &mut f).unwrap(); } } - -#[derive(Clone)] -pub struct SharedBorrows<'tcx> { - pub location_map: FxIndexMap>, - pub local_map: FxIndexMap>, -} - -impl<'tcx> SharedBorrows<'tcx> { - fn new() -> Self { - Self { - location_map: FxIndexMap::default(), - local_map: FxIndexMap::default(), - } - } - pub(crate) fn insert(&mut self, location: Location, data: BorrowData<'tcx>) { - println!("Inserting shared borrow: {:?} -> {:?}", location, data); - let local = data.borrowed_place.local; - let (idx, _) = self.location_map.insert_full(location, data); - let idx = BorrowIndex::from(idx); - self.local_map.entry(local).or_default().insert(idx); - } -} diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index d4aa76c7b17..09b05a52cc6 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -16,125 +16,97 @@ use prusti_rustc_interface::{ use crate::utils::Place; -use super::{cg::{NodeId, Edge, Graph, Regions, EdgeInfo}, region_place::Perms}; +use super::{cg::{Graph, EdgeInfo}, region_place::Perms}; -impl<'a, 'tcx> Graph<'a, 'tcx> { - fn get_id(&self) -> &str { - self.id.as_deref().unwrap_or("unnamed") +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Edge<'tcx> { + pub from: RegionVid, + pub to: RegionVid, + pub reasons: FxHashSet>, +} + +impl<'tcx> Edge<'tcx> { + pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>) -> Self { + Self { from, to, reasons } } } -impl<'a, 'tcx> Regions<'a, 'tcx> { - pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { - self.get_corresponding_places(n).is_none() + +impl<'a, 'tcx> Graph<'a, 'tcx> { + fn get_id(&self) -> String { + if let Some(id) = &self.id { + id.replace('[', "_").replace(']', "") + } else { + "unnamed".to_string() + } } - // fn get_corresponding_places<'s>(&'s self, n: NodeId) -> impl Iterator> + 's { - // let node = self.graph.get_node(n); - // node.regions.iter().map(|r| &self.graph.cgx.region_map[r]) +} +impl<'a, 'tcx> Graph<'a, 'tcx> { + // pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { + // self.get_corresponding_places(n).is_none() // } - fn get_corresponding_places(&self, n: NodeId) -> Option<&Perms<'tcx>> { - let node = self.graph.get_node(n); - // assert!(node.regions.len() == 1); - // self.graph.cgx.region_map.get(node.regions.iter().next().unwrap()) - self.graph.cgx.region_map.get(&node.region) + // fn get_corresponding_places(&self, n: NodeId) -> Option<&Perms<'tcx>> { + // let node = self.get_node(n); + // self.cgx.region_map.get(&node.region) + // } + /// For regions created by `... = &'r ...`, find the kind of borrow. + pub(crate) fn borrow_kind(&self, r: RegionVid) -> Option { + // TODO: we could put this into a `FxHashMap` in `cgx`. + self.facts2.borrow_set.location_map.iter() + .chain(&self.cgx.sbs.location_map) + .find(|(_, data)| data.region == r) + .map(|(_, data)| data.kind) } - pub(crate) fn is_borrow_only(&self, n: NodeId) -> Option { - let node = self.graph.get_node(n); - let input_facts = self.graph.facts.input_facts.borrow(); - for &(_, r) in &input_facts.as_ref().unwrap().use_of_var_derefs_origin { - if node.region == r { - return None; - } - } - let mut is_borrow = None; - for (_, data) in &self.graph.facts2.borrow_set.location_map { - if node.region == data.region { - if is_borrow.is_some() { - return None; - } - is_borrow = Some(data.kind); - } - } - for (_, data) in &self.graph.cgx.sbs.location_map { - if node.region == data.region { - if is_borrow.is_some() { - return None; - } - is_borrow = Some(data.kind); - } + fn non_empty_edges(&self, r: RegionVid, start: RegionVid, reasons: FxHashSet>, visited: &mut FxHashSet) -> Vec> { + let mut edges = Vec::new(); + if !visited.insert(r) { + return edges; } - is_borrow - } - fn non_empty_edges(&self, n: NodeId, start: NodeId, reasons: FxHashSet>) -> Vec> { - if !(self.graph.skip_empty_nodes && self.is_empty_node(n)) { - return vec![Edge::new(start, n, reasons)]; + if !self.skip_empty_nodes || self.has_associated_place(r) { + return vec![Edge::new(start, r, reasons)]; } - let mut edges = Vec::new(); - let node = self.graph.get_node(n); - for (&b, edge) in &node.blocks { + for (&b, edge) in &self.nodes[r].blocks { let mut reasons = reasons.clone(); reasons.extend(edge); - edges.extend(self.non_empty_edges(b, start, reasons)); + edges.extend(self.non_empty_edges(b, start, reasons, visited)); } + visited.remove(&r); edges } } -impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { - fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.graph.get_id()).unwrap() } +impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.get_id()).unwrap() } - fn node_id(&'a self, n: &NodeId) -> dot::Id<'a> { - dot::Id::new(format!("N_{n:?}_{}", self.graph.get_id())).unwrap() + fn node_id(&'a self, r: &RegionVid) -> dot::Id<'a> { + let r = format!("{r:?}").replace("'?", "N"); + let id = format!("{r}_{}", self.get_id()); + dot::Id::new(id).unwrap() } - fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { - if self.is_borrow_only(e.from).is_some() { - dot::Arrow::from_arrow(dot::ArrowShape::Dot(dot::Fill::Filled)) - } else { - dot::Arrow::default() - } - } + // fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { + // if self.borrow_kind(e.from).is_some() { + // dot::Arrow::from_arrow(dot::ArrowShape::Dot(dot::Fill::Filled)) + // } else { + // dot::Arrow::default() + // } + // } fn edge_style(&'a self, e: &Edge<'tcx>) -> dot::Style { - if self.is_borrow_only(e.from).is_some() { - dot::Style::Dashed + if self.borrow_kind(e.from).is_some() { + dot::Style::Dotted } else { dot::Style::Solid } } fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { - if e.to == usize::MAX { - return dot::LabelText::LabelStr(Cow::Borrowed("static")); - } - let mut label = e.reasons.iter().map(|r| { - let reason = match r.reason { - ConstraintCategory::Return(_) => "return", - ConstraintCategory::Yield => "yield", - ConstraintCategory::UseAsConst => "const", - ConstraintCategory::UseAsStatic => "static", - ConstraintCategory::TypeAnnotation => "type", - ConstraintCategory::Cast => "cast", - ConstraintCategory::ClosureBounds => "closure", - ConstraintCategory::CallArgument(_) => "arg", - ConstraintCategory::CopyBound => "copy", - ConstraintCategory::SizedBound => "sized", - ConstraintCategory::Assignment => "assign", - ConstraintCategory::Usage => "use", - ConstraintCategory::OpaqueType => "opaque", - ConstraintCategory::ClosureUpvar(_) => "upvar", - ConstraintCategory::Predicate(_) => "pred", - ConstraintCategory::Boring => "boring", - ConstraintCategory::BoringNoLocation => "boring_nl", - ConstraintCategory::Internal => "internal", - }; - format!("{:?} ({reason})", r.creation) - }).map(|s| format!("{s}\n")).collect::(); + let mut label = e.reasons.iter().map(|s| format!("{s}\n")).collect::(); label = label[..label.len() - 1].to_string(); dot::LabelText::LabelStr(Cow::Owned(label)) } - fn node_shape(&'a self, n: &NodeId) -> Option> { - if *n == usize::MAX { + fn node_shape(&'a self, r: &RegionVid) -> Option> { + if self.static_regions.contains(&r) { return Some(dot::LabelText::LabelStr(Cow::Borrowed("house"))); } - self.is_borrow_only(*n).map(|kind| match kind { + self.borrow_kind(*r).map(|kind| match kind { BorrowKind::Shared => dot::LabelText::LabelStr(Cow::Borrowed("box")), BorrowKind::Shallow => @@ -143,56 +115,54 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { dot::LabelText::LabelStr(Cow::Borrowed("ellipse")), }) } - fn node_label(&'a self, n: &NodeId) -> dot::LabelText<'a> { - if *n == usize::MAX { - let regions = &self.graph.static_regions; - let places: Vec<_> = regions.iter().flat_map(|r| self.graph.cgx.region_map.get(r)).collect(); - return dot::LabelText::LabelStr(Cow::Owned(format!("'static\n{regions:?}\n{places:?}"))); + fn node_label(&'a self, r: &RegionVid) -> dot::LabelText<'a> { + if *r == RegionVid::MAX { + // return dot::LabelText::LabelStr(Cow::Owned()); + unimplemented!(); + } + let mut label = format!("{r:?}"); + if let Some(place) = self.get_associated_place(*r) { + label += &format!("\n{place:?}"); + } else { + label += "\n???"; + } + if let Some(region_info) = self.facts2.region_inference_context.var_infos.get(*r) { + label += &format!("\n{:?}, {:?}", region_info.origin, region_info.universe); } - let contained_by = self.get_corresponding_places(*n); - let contained_by = contained_by.map(|c| format!("{c:?}")).unwrap_or_else(|| "???".to_string()); - // let process_place = |p: Place<'tcx>| p; - // let contained_by = contained_by.collect::>(); - let node = self.graph.get_node(*n); - // assert!(node.regions.len() == 1); - let label = format!("{:?}\n{contained_by}", node.region); dot::LabelText::LabelStr(Cow::Owned(label)) } } -impl<'a, 'b, 'tcx> dot::GraphWalk<'a, NodeId, Edge<'tcx>> for Regions<'b, 'tcx> { - fn nodes(&self) -> dot::Nodes<'a, NodeId> { - let mut nodes: Vec<_> = self.graph.nodes - .iter() - .enumerate() - .filter_map(|(idx, n)| n.as_ref().map(|_| idx)) - .filter(|&idx| !self.graph.skip_empty_nodes || !self.is_empty_node(idx)) +impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> { + fn nodes(&self) -> dot::Nodes<'a, RegionVid> { + let mut nodes: Vec<_> = self.all_nodes() + .filter(|(r, _)| !self.skip_empty_nodes || self.has_associated_place(*r)) + .map(|(r, _)| r) .collect(); - if self.graph.static_regions.len() > 1 { - nodes.push(usize::MAX); - } + // if self.static_regions.len() > 1 { + // nodes.push(usize::MAX); + // } Cow::Owned(nodes) } fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { let mut edges = Vec::new(); - for (c, n) in self.graph.nodes.iter().enumerate() { - if let Some(n) = n { - if self.graph.skip_empty_nodes && self.is_empty_node(c) { - continue; - } - if n.borrows_from_static { - edges.push(Edge::new(c, usize::MAX, FxHashSet::default())); - } - for (&b, edge) in &n.blocks { - edges.extend(self.non_empty_edges(b, c, edge.clone())); - } + for (r, n) in self.all_nodes() { + if self.skip_empty_nodes && !self.has_associated_place(r) { + continue; + } + // if n.borrows_from_static { + // edges.push(Edge::new(c, usize::MAX, FxHashSet::default())); + // } + let visited = &mut FxHashSet::from_iter([r]); + for (&b, edge) in &n.blocks { + edges.extend(self.non_empty_edges(b, r, edge.clone(), visited)); } } Cow::Owned(edges) } - fn source(&self, e: &Edge<'tcx>) -> NodeId { e.from } + fn source(&self, e: &Edge<'tcx>) -> RegionVid { e.from } - fn target(&self, e: &Edge<'tcx>) -> NodeId { e.to } + fn target(&self, e: &Edge<'tcx>) -> RegionVid { e.to } } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index 8293ae0b28d..a4623fca5e3 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -11,7 +11,7 @@ use prusti_rustc_interface::{ data_structures::fx::{FxIndexMap, FxHashSet}, borrowck::{ borrow_set::{BorrowData, TwoPhaseActivation}, - consumers::{Borrows, BorrowIndex, RichLocation, PlaceConflictBias, places_conflict, calculate_borrows_out_of_scope_at_location}, + consumers::{Borrows, BorrowIndex, RichLocation, OutlivesConstraint, PlaceConflictBias, places_conflict, calculate_borrows_out_of_scope_at_location}, }, dataflow::{Analysis, AnalysisDomain, ResultsCursor}, index::{bit_set::{BitSet, HybridBitSet}, Idx}, @@ -29,8 +29,9 @@ use crate::{ utils::PlaceRepacker, coupling_graph::cg::{Graph, Node}, }; -use super::{cg::{Cg, Regions, PathCondition, EdgeInfo}, shared_borrow_set::SharedBorrowSet, CgContext}; +use super::{cg::{EdgeInfo}, shared_borrow_set::SharedBorrowSet, CgContext}; +#[tracing::instrument(name = "draw_dots", level = "debug", skip(c))] pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { let mut graph = Vec::new(); let body = c.body(); @@ -39,13 +40,18 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a continue; } c.seek_to_block_start(block); - let mut g = c.get().regions.clone(); - g.graph.id = Some(format!("pre_{block:?}")); + let mut g = c.get().clone(); + g.id = Some(format!("{block:?}_pre")); dot::render(&g, &mut graph).unwrap(); for statement_index in 0..body.terminator_loc(block).statement_index+1 { c.seek_after_primary_effect(Location { block, statement_index }); - let mut g = c.get().regions.clone(); - g.graph.id = Some(format!("{block:?}_{statement_index}")); + g = c.get().clone(); + g.id = Some(format!("{block:?}_{statement_index}")); + dot::render(&g, &mut graph).unwrap(); + } + if let TerminatorKind::Return = data.terminator().kind { + g.skip_empty_nodes = true; + g.id = Some(format!("{block:?}_ret")); dot::render(&g, &mut graph).unwrap(); } } @@ -54,22 +60,6 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a let regex = regex::Regex::new(r"digraph (([^ ])+) \{").unwrap(); let combined = regex.replace_all(&combined, "subgraph cluster_$1 {\n label = \"$1\""); - // let mut paths: Vec<_> = std::fs::read_dir("log/coupling/individual").unwrap().into_iter().map(|p| p.unwrap().path()).collect(); - // paths.sort_by(|a, b| { - // let a = a.file_stem().unwrap().to_string_lossy(); - // let mut a = a.split("_"); - // let a0 = a.next().unwrap().strip_prefix("bb").unwrap().parse::().unwrap(); - // let a1 = a.next().unwrap().parse::().unwrap(); - // let b = b.file_stem().unwrap().to_string_lossy(); - // let mut b = b.split("_"); - // let b0 = b.next().unwrap().strip_prefix("bb").unwrap().parse::().unwrap(); - // let b1 = b.next().unwrap().parse::().unwrap(); - // (a0, a1).cmp(&(b0, b1)) - // }); - // let combined = paths.into_iter().fold(String::new(), |acc, p| { - // let data = std::fs::read_to_string(p).expect("Unable to read file"); - // acc + ®ex.replace_all(&data, "subgraph cluster_$1 {\n label = \"$1\"") - // }); std::fs::write("log/coupling/all.dot", format!("digraph root {{\n{combined}}}")).expect("Unable to write file"); } @@ -102,56 +92,8 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope, printed_dot, cgx } } - // fn handle_diff(&self, state: &mut Regions<'_, 'tcx>, delta: BorrowDelta, location: Location) { - // // let input_facts = self.facts.input_facts.borrow(); - // // let input_facts = input_facts.as_ref().unwrap(); - // // let location_table = self.facts.location_table.borrow(); - // // let location_table = location_table.as_ref().unwrap(); - // // for idx in delta.set.iter() { - // // let loan_issued_at = &input_facts.loan_issued_at; - // // let (r, _, l) = loan_issued_at.iter().find(|(_, b, _)| idx == *b).copied().unwrap(); - // // let l = location_table.to_location(l); - // // let RichLocation::Mid(l) = l else { unreachable!() }; - // // assert_eq!(l, location); - // // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r == *ro).map(|(l, _)| (*l, r)).collect::>(); - // // state.borrows.insert(idx, (vec![r], locals)); - // // } - // // state.subset.extend(input_facts.subset_base.iter().filter( - // // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location - // // ).map(|(r1, r2, _)| (*r1, *r2))); - // // // TODO: do a proper fixpoint here - // // for _ in 0..10 { - // // for &(r1, r2) in &state.subset { - // // let locals = input_facts.use_of_var_derefs_origin.iter().filter(|(_, ro)| r2 == *ro).map(|(l, _)| (*l, r2)).collect::>(); - // // let mut did_push = false; - // // for (_, s) in state.borrows.iter_mut() { - // // if s.0.contains(&r1) { - // // did_push = true; - // // if !s.0.contains(&r2) { - // // s.0.push(r2); - // // s.1.extend(locals.iter().copied()); - // // } - // // } - // // } - // // // assert!(did_push, "r1: {:?}, r2: {:?}, location: {:?}, state: {:?}", r1, r2, location, state); - // // } - // // } - // // for r in delta.cleared.iter() { - // // // TODO: why does unwrap fail? - // // let removed = state.borrows.remove(&r);//.unwrap(); - // // // for (_, s) in state.borrows.iter_mut() { - // // // s.0.retain(|r| !removed.0.contains(r)); - // // // s.1.retain(|l| !removed.1.contains(l)); - // // // } - // // } - // // print!(" {:?}", state.borrows); - // self.handle_graph(state, delta, location); - // } - - #[tracing::instrument(name = "handle_graph", level = "debug", skip(self))] - fn handle_graph(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { - // println!("location: {:?}", l); - + #[tracing::instrument(name = "handle_kills", level = "debug", skip(self))] + fn handle_kills(&self, state: &mut Graph<'_, 'tcx>, delta: &BorrowDelta, location: Location) { let input_facts = self.facts.input_facts.borrow(); let input_facts = input_facts.as_ref().unwrap(); let location_table = self.facts.location_table.borrow(); @@ -160,73 +102,79 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // let input_facts = self.facts2.region_inference_context.borrow(); let oos = self.out_of_scope.get(&location); + + for bi in delta.cleared.iter() { + let data = &self.facts2.borrow_set[bi]; + // TODO: remove if the asserts below pass: + let (r, _, l) = input_facts.loan_issued_at.iter().find( + |(_, b, _)| bi == *b + ).copied().unwrap(); + let l = rich_to_loc(location_table.to_location(l)); + assert_eq!(r, data.region); + assert_eq!(l, data.reserve_location); + + // println!("killed: {r:?} {killed:?} {l:?}"); + if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { + state.kill_borrow(data); + } else { + state.remove(data.region, location); + } + + // // TODO: is this necessary? + // let local = data.borrowed_place.local; + // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // // println!("killed region: {region:?}"); + // state.output_to_dot("log/coupling/kill.dot"); + // let removed = state.remove(region, location, true, false); + // // assert!(!removed, "killed region: {region:?} at {location:?} (local: {local:?})"); + // } + } + if let Some(oos) = oos { - for bi in oos { + for &bi in oos { + // What is the difference between the two (oos)? + assert!(delta.cleared.contains(bi), "Cleared borrow not in out of scope: {:?} vs {:?} (@ {location:?})", delta.cleared, oos); + if delta.cleared.contains(bi) { + continue; + } + + let data = &self.facts2.borrow_set[bi]; + // TODO: remove if the asserts below pass: let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| bi == b + |(_, b, _)| bi == *b ).copied().unwrap(); - // println!("UGHBJS region: {r:?} location: {l:?}"); - state.graph.kill(r); let l = rich_to_loc(location_table.to_location(l)); - let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); - let local = borrow_data.assigned_place.local; - for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // println!("IHUBJ local: {local:?} region: {region:?}"); - state.graph.remove(region, true, false); - } - } - } + assert_eq!(r, data.region); + assert_eq!(l, data.reserve_location); - // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_a.dot")).unwrap(); - // dot::render(&state.graph, &mut f).unwrap(); + state.kill_borrow(data); - for killed in delta.cleared.iter() { - if oos.map(|oos| oos.contains(&killed)).unwrap_or_default() { - continue; - } - let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| killed == *b - ).copied().unwrap(); - // println!("killed: {r:?} {killed:?} {l:?}"); - state.graph.remove(r, false, false); - let l = rich_to_loc(location_table.to_location(l)); - let borrow_data = self.facts2.borrow_set.location_map.get(&l).unwrap(); - let local = borrow_data.borrowed_place.local; - for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // println!("killed region: {region:?}"); - state.graph.remove(region, true, false); + // // TODO: is this necessary? + // let local = data.assigned_place.local; + // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // // println!("IHUBJ local: {local:?} region: {region:?}"); + // let removed = state.remove(region, location, true, false); + // assert!(!removed); + // } } } + } - // let mut f = std::fs::File::create(format!("log/coupling/individual/{l}_b.dot")).unwrap(); - // dot::render(&state.graph, &mut f).unwrap(); - - // let new_subsets: Vec<_> = input_facts.subset_base.iter().filter( - // |(_, _, l)| rich_to_loc(location_table.to_location(*l)) == location - // ).map(|(r1, r2, _)| (*r1, *r2)).collect(); - // for (r1, r2) in new_subsets { - // state.graph.outlives(r1, r2); - // } + fn get_constraints_for_loc(&self, location: Option) -> impl Iterator> + '_ { + self.facts2.region_inference_context.outlives_constraints().filter( + move |c| c.locations.from_location() == location + ) } #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] - fn handle_outlives(&self, state: &mut Regions<'_, 'tcx>, delta: &BorrowDelta, location: Location) { - let constraints = self.facts2.region_inference_context.outlives_constraints(); - for c in constraints { - if let Some(from) = c.locations.from_location() { - if from == location { - let reason = EdgeInfo { - reason: c.category, - creation: location.block, - }; - state.graph.outlives(c.sup, c.sub, reason); - } - } + fn handle_outlives(&self, state: &mut Graph<'_, 'tcx>, delta: &BorrowDelta, location: Location) { + for c in self.get_constraints_for_loc(Some(location)) { + state.outlives(c); } } #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] - fn kill_shared_borrows_on_place(&self, state: &mut Regions<'_, 'tcx>, place: Place<'tcx>) { + fn kill_shared_borrows_on_place(&self, location: Location, state: &mut Graph<'_, 'tcx>, place: Place<'tcx>) { // let other_borrows_of_local = state // .shared_borrows // .local_map @@ -245,7 +193,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // ) // }); // for data in definitely_conflicting_borrows { - // state.graph.remove(data.region, true); + // state.remove(data.region, true); // } let Some(local) = place.as_local() else { // Only remove nodes if assigned to the entire local (this is what rustc allows too) @@ -262,21 +210,49 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { // println!("killing"); // Also remove any overwritten borrows locals - for (®ion, _) in state.graph.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { + for (®ion, _) in state.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { // println!("Killing local: {local:?}: {region:?}"); - // TODO: could set `remove_dangling_children` to false here - state.graph.remove(region, true, true); + // TODO: could set `remove_dangling_children` to true here + state.remove(region, location); + } + } + + /// This is the hack we use to make a `fn foo<'a>(x: &'a _, y: &'a _, ...) -> &'a _` look like + /// `fn foo<'a: 'c, 'b: 'c, 'c>(x: &'a _, y: &'b _, ...) -> &'c _` to the analysis. + #[tracing::instrument(name = "ignore_outlives", level = "debug", skip(self), ret)] + fn ignore_outlives(&self, c: OutlivesConstraint<'tcx>) -> bool { + let arg_count = self.repacker.body().arg_count; + let sup = self.cgx.region_map.get(&c.sup); + let sub = self.cgx.region_map.get(&c.sub); + match (sup, sub) { + // If `projects_exactly_one_deref` then it must be the `'a` region of a `x: &'a ...`, rather than being nested deeper withing the local + (_, Some(sub)) => { + sub.place.projects_exactly_one_deref() + && sub.place.local.index() <= arg_count + && sub.place.local != RETURN_PLACE + } + // (Some(sup), Some(sub)) => { + // if !(sup.place.projects_exactly_one_deref() + // && sub.place.projects_exactly_one_deref() + // && sup.place.local.index() <= arg_count + // && sub.place.local.index() <= arg_count) { + // return false; + // } + // debug_assert_ne!(sup.place.local, sub.place.local); + // sub.place.local != RETURN_PLACE + // } + _ => false, } } } impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { - type Domain = Cg<'a, 'tcx>; + type Domain = Graph<'a, 'tcx>; const NAME: &'static str = "coupling_graph"; fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - Cg::new(self.repacker, self.facts, self.facts2, self.cgx) + Graph::new(self.repacker, self.facts, self.facts2, self.cgx) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { @@ -290,21 +266,21 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { // } // } - // println!("body: {body:#?}"); - // println!("\ninput_facts: {:?}", self.facts.input_facts); - // println!("output_facts: {:#?}\n", self.facts.output_facts); - // println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); - // println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); - // println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); - // println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context.outlives_constraints().collect::>()); - // // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); - // let lt = self.facts.location_table.borrow(); - // let lt = lt.as_ref().unwrap(); - // for pt in lt.all_points() { - // println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); - // } - // println!("out_of_scope: {:?}", self.out_of_scope); - // println!("region_map: {:#?}\n", self.cgx.region_map); + println!("body: {body:#?}"); + println!("\ninput_facts: {:?}", self.facts.input_facts); + println!("output_facts: {:#?}\n", self.facts.output_facts); + println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); + println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); + println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); + println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context.outlives_constraints().collect::>()); + // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); + let lt = self.facts.location_table.borrow(); + let lt = lt.as_ref().unwrap(); + for pt in lt.all_points() { + println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); + } + println!("out_of_scope: {:?}", self.out_of_scope); + println!("region_map: {:#?}\n", self.cgx.region_map); } } @@ -318,15 +294,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { ) { let l = format!("{:?}", location).replace('[', "_").replace(']', ""); // println!("Location: {l}"); - state.regions.graph.id = Some(l.clone()); + state.id = Some(l.clone()); if location.statement_index == 0 { - state.regions.version += 1; - assert!(state.regions.version < 10); + state.version += 1; + assert!(state.version < 10); // println!("\nblock: {:?}", location.block); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); + state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); @@ -336,48 +312,46 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { // print!("{statement:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); - self.handle_graph(&mut state.regions, &delta, location); + self.handle_kills(state, &delta, location); + match statement.kind { StatementKind::Assign(box (assigned_place, ref lhs)) => { - self.kill_shared_borrows_on_place(&mut state.regions, assigned_place); + self.kill_shared_borrows_on_place(location, state, assigned_place); if let Rvalue::Use(Operand::Constant(c)) = lhs { // println!("Checking {:?} vs {curr_loc:?}", self.facts2.region_inference_context.outlives_constraints().map(|c| format!("{:?}", c.locations)).collect::>()); - for c in self.facts2.region_inference_context.outlives_constraints().filter( - |c| c.locations.from_location() == Some(location) - ) { + for c in self.get_constraints_for_loc(Some(location)) { // println!("Got one: {c:?}"); - state.regions.graph.make_static(c.sup); - state.regions.graph.make_static(c.sub); + state.outlives_static(c.sub, location); } } } // If a local was only moved out of and not reborrowed, it's region is never killed officially StatementKind::StorageDead(local) => { - self.kill_shared_borrows_on_place(&mut state.regions, Place::from(local)); + self.kill_shared_borrows_on_place(location, state, Place::from(local)); // let input_facts = self.facts.input_facts.borrow(); // let input_facts = input_facts.as_ref().unwrap(); // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { // // println!("killed region manually: {region:?}"); - // state.regions.graph.remove(region, true); + // state.remove(region, true); // } - // let to_rem: Vec<_> = state.regions.graph.shared_borrows.iter().filter(|(_, data)| data.borrowed_place.local == local).map(|(_, data)| data.region).collect(); + // let to_rem: Vec<_> = state.shared_borrows.iter().filter(|(_, data)| data.borrowed_place.local == local).map(|(_, data)| data.region).collect(); // for region in to_rem { // // println!("killed region manually: {region:?}"); - // state.regions.graph.remove(region, true); + // state.remove(region, true); // } } _ => (), } // if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - // state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_mid.dot", state.regions.version)); + // state.output_to_dot(format!("log/coupling/individual/{l}_v{}_mid.dot", state.version)); // } - self.handle_outlives(&mut state.regions, &delta, location); + self.handle_outlives(state, &delta, location); state.live = other; // println!(); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); + state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); } } @@ -390,15 +364,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { ) -> TerminatorEdges<'mir, 'tcx> { let l = format!("{:?}", location).replace('[', "_").replace(']', ""); // println!("Location: {l}"); - state.regions.graph.id = Some(l.clone()); + state.id = Some(l.clone()); if location.statement_index == 0 { - state.regions.version += 1; - assert!(state.regions.version < 10); + state.version += 1; + assert!(state.version < 10); // println!("\nblock: {:?}", location.block); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.regions.version)); + state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); @@ -425,39 +399,32 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { // } // print!("{terminator:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); - self.handle_graph(&mut state.regions, &delta, location); - self.handle_outlives(&mut state.regions, &delta, location); + self.handle_kills(state, &delta, location); + self.handle_outlives(state, &delta, location); state.live = other; if let TerminatorKind::Return = &terminator.kind { if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", state.regions.version)); + state.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", state.version)); } // Pretend we have a storage dead for all `always_live_locals` other than the args/return for l in self.repacker.always_live_locals_non_args().iter() { - self.kill_shared_borrows_on_place(&mut state.regions, l.into()); + self.kill_shared_borrows_on_place(location, state, l.into()); } // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` - for r in self.facts2.borrow_set.location_map.values() { - state.regions.graph.remove(r.region, true, false); + for r in self.facts2.borrow_set.location_map.values().chain(self.cgx.sbs.location_map.values()) { + state.remove(r.region, location); } - for r in self.cgx.sbs.location_map.values() { - state.regions.graph.remove(r.region, true, false); + + for c in self.get_constraints_for_loc(None).filter(|c| !self.ignore_outlives(*c)) { + state.outlives(c); } - state.regions.merge_for_return(); + state.merge_for_return(); } // println!(); if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.regions.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.regions.version)); - } - match terminator.edges() { - TerminatorEdges::SwitchInt { targets, discr } => { - let _ = PathCondition::new(location.block, targets.all_targets().len()); - // let map = targets.iter().map(|(v, bb)| (bb, Some(v))).chain(std::iter::once((targets.otherwise(), None))).collect(); - // state.regions.path_condition.push() - } - _ => () + state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); } terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 2422d576dd5..364bbc1c86a 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,107 +15,21 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::cg::{Cg, Graph, SharedBorrows, PathCondition}; - -impl JoinSemiLattice for Cg<'_, '_> { - #[tracing::instrument(name = "Cg::join", level = "debug", skip_all)] - fn join(&mut self, other: &Self) -> bool { - // if self.done == 20 { - // panic!(); - // // return false; - // } - // self.done += 1; - let actually_changed = self.regions.graph.join(&other.regions.graph); - return actually_changed; - let mut changed = self.live.union(&other.live); - for (idx, data) in other.regions.borrows.iter() { - match self.regions.borrows.entry(*idx) { - Entry::Occupied(mut o) => { - let (a, b) = o.get_mut(); - for r in &data.0 { - if !a.contains(r) { - changed = true; - a.push(*r); - } - } - for r in &data.1 { - if !b.contains(r) { - changed = true; - b.push(*r); - } - } - } - Entry::Vacant(v) => { - changed = true; - v.insert(data.clone()); - } - } - } - for s in &other.regions.subset { - if !self.regions.subset.contains(s) { - changed = true; - self.regions.subset.push(*s); - } - } - // if self.regions.graph != other.regions.graph { - // changed = true; - // self.regions.graph = other.regions.graph.clone(); - // } - actually_changed - } -} +use super::cg::{Graph}; impl JoinSemiLattice for Graph<'_, '_> { #[tracing::instrument(name = "Graph::join", level = "debug", ret)] fn join(&mut self, other: &Self) -> bool { // println!("Joining graphs:\n{:?}: {:?}\n{:?}: {:?}", self.id, self.nodes, other.id, other.nodes); let mut changed = false; - for node in other.nodes.iter().flat_map(|n| n) { - // let rep = node.regions.iter().next().unwrap(); - let rep = node.region; - for (to, reasons) in node.blocks.iter() { - let (from, to) = other.edge_to_regions(node.id, *to); - let was_new = self.outlives_many(to, from, reasons); - changed = changed || was_new; - // if was_new { - // println!("New edge: {:?} -> {:?} ({:?})", from, to, reasons); - // } - } - if node.borrows_from_static { - changed = changed || self.set_borrows_from_static(rep); + for (from, node) in other.all_nodes() { + for (&to, reasons) in node.blocks.iter() { + for &reason in reasons { + let was_new = self.outlives_inner(to, from, reason); + changed = changed || was_new; + } } } - let old_len = self.static_regions.len(); - for &r in &other.static_regions { - self.make_static(r); - } - changed = changed || self.static_regions.len() != old_len; - changed - } -} - -impl SharedBorrows<'_> { - fn join(&mut self, other: &Self) -> bool { - println!("Joining shared borrows: {:?} and {:?}", self.location_map, other.location_map); - let old_len = self.location_map.len(); - for (k, v) in other.location_map.iter() { - self.insert(*k, v.clone()); - } - self.location_map.len() != old_len - } -} - -impl JoinSemiLattice for PathCondition { - fn join(&mut self, other: &Self) -> bool { - assert_eq!(self.bb, other.bb); - let old_values = self.values; - self.values |= other.values; - let mut changed = old_values != self.values; - for (i, v) in self.other_values.iter_mut().enumerate() { - let old_value = *v; - *v |= other.other_values[i]; - changed = changed || old_value != *v; - } changed } } diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 03784be3f19..63c8cdccc4f 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -227,6 +227,10 @@ impl<'tcx> Place<'tcx> { .last_projection() .map(|(place, proj)| (place.into(), proj)) } + + pub fn projects_exactly_one_deref(self) -> bool { + self.projection.len() == 1 && matches!(self.projection[0], ProjectionElem::Deref) + } } impl Debug for Place<'_> { From a0e1026d7604b2f1bc55e2f4ca6de36fd0741f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 29 Sep 2023 20:49:05 +0200 Subject: [PATCH 35/58] Rearrange things --- .../src/coupling_graph/context/mod.rs | 101 +++++ .../{impl => context}/region_place.rs | 0 .../{impl => context}/shared_borrow_set.rs | 0 .../src/coupling_graph/impl/dot.rs | 29 +- .../src/coupling_graph/impl/engine.rs | 328 +++------------ .../coupling_graph/impl/{cg.rs => graph.rs} | 182 +-------- .../coupling_graph/impl/join_semi_lattice.rs | 13 +- .../src/coupling_graph/impl/mod.rs | 55 +-- .../src/coupling_graph/impl/triple.rs | 380 ++++++++++++++++++ mir-state-analysis/src/coupling_graph/mod.rs | 2 + mir-state-analysis/src/lib.rs | 2 +- mir-state-analysis/src/loop/mod.rs | 6 - 12 files changed, 564 insertions(+), 534 deletions(-) create mode 100644 mir-state-analysis/src/coupling_graph/context/mod.rs rename mir-state-analysis/src/coupling_graph/{impl => context}/region_place.rs (100%) rename mir-state-analysis/src/coupling_graph/{impl => context}/shared_borrow_set.rs (100%) rename mir-state-analysis/src/coupling_graph/impl/{cg.rs => graph.rs} (63%) create mode 100644 mir-state-analysis/src/coupling_graph/impl/triple.rs diff --git a/mir-state-analysis/src/coupling_graph/context/mod.rs b/mir-state-analysis/src/coupling_graph/context/mod.rs new file mode 100644 index 00000000000..4a913c21385 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/context/mod.rs @@ -0,0 +1,101 @@ +// © 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, cell::RefCell}; + +use crate::{utils::{PlaceRepacker, Place}, r#loop::LoopAnalysis}; +use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + borrowck::consumers::{Borrows, BorrowIndex, OutlivesConstraint}, + dataflow::{Analysis, ResultsCursor}, + data_structures::fx::{FxIndexMap, FxHashMap}, + middle::{mir::{Body, Location, RETURN_PLACE}, ty::{RegionVid, TyCtxt}}, +}; + +pub(crate) mod shared_borrow_set; +pub(crate) mod region_place; + +pub struct CgContext<'a, 'tcx> { + pub rp: PlaceRepacker<'a, 'tcx>, + pub facts: &'a BorrowckFacts, + pub facts2: &'a BorrowckFacts2<'tcx>, + + pub sbs: SharedBorrowSet<'tcx>, + pub region_map: FxHashMap>, + pub loops: LoopAnalysis, +} + +impl fmt::Debug for CgContext<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CgContext").field("sbs", &self.sbs).field("region_map", &self.region_map).finish() + } +} + +impl<'a, 'tcx> CgContext<'a, 'tcx> { + #[tracing::instrument(name = "CgContext::new", level = "debug", skip_all, ret)] + pub fn new( + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + facts: &'a BorrowckFacts, + facts2: &'a BorrowckFacts2<'tcx> + ) -> Self { + let borrow_set = &facts2.borrow_set; + let sbs = SharedBorrowSet::build(tcx, body, borrow_set); + let rp = PlaceRepacker::new(body, tcx); + let input_facts = facts.input_facts.borrow(); + let region_map = Perms::region_place_map( + &input_facts.as_ref().unwrap().use_of_var_derefs_origin, + borrow_set, + &sbs, + rp, + ); + let loops = LoopAnalysis::find_loops(body); + + Self { + rp, + facts, + facts2, + sbs, + region_map, + loops, + } + } + + pub fn get_constraints_for_loc(&self, location: Option) -> impl Iterator> + '_ { + self.facts2.region_inference_context.outlives_constraints().filter( + move |c| c.locations.from_location() == location + ) + } + + /// This is the hack we use to make a `fn foo<'a>(x: &'a _, y: &'a _, ...) -> &'a _` look like + /// `fn foo<'a: 'c, 'b: 'c, 'c>(x: &'a _, y: &'b _, ...) -> &'c _` to the analysis. + #[tracing::instrument(name = "ignore_outlives", level = "debug", skip(self), ret)] + pub fn ignore_outlives(&self, c: OutlivesConstraint<'tcx>) -> bool { + let arg_count = self.rp.body().arg_count; + let sup = self.region_map.get(&c.sup); + let sub = self.region_map.get(&c.sub); + match (sup, sub) { + // If `projects_exactly_one_deref` then it must be the `'a` region of a `x: &'a ...`, rather than being nested deeper withing the local + (_, Some(sub)) => { + sub.place.projects_exactly_one_deref() + && sub.place.local.index() <= arg_count + && sub.place.local != RETURN_PLACE + } + // (Some(sup), Some(sub)) => { + // if !(sup.place.projects_exactly_one_deref() + // && sub.place.projects_exactly_one_deref() + // && sup.place.local.index() <= arg_count + // && sub.place.local.index() <= arg_count) { + // return false; + // } + // debug_assert_ne!(sup.place.local, sub.place.local); + // sub.place.local != RETURN_PLACE + // } + _ => false, + } + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/region_place.rs b/mir-state-analysis/src/coupling_graph/context/region_place.rs similarity index 100% rename from mir-state-analysis/src/coupling_graph/impl/region_place.rs rename to mir-state-analysis/src/coupling_graph/context/region_place.rs diff --git a/mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs b/mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs similarity index 100% rename from mir-state-analysis/src/coupling_graph/impl/shared_borrow_set.rs rename to mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index 09b05a52cc6..f7d8a4da166 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -14,9 +14,7 @@ use prusti_rustc_interface::{ middle::{mir::{BorrowKind, ConstraintCategory}, ty::{RegionVid, TyKind}}, }; -use crate::utils::Place; - -use super::{cg::{Graph, EdgeInfo}, region_place::Perms}; +use super::{graph::EdgeInfo, triple::Cg}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Edge<'tcx> { @@ -31,7 +29,7 @@ impl<'tcx> Edge<'tcx> { } } -impl<'a, 'tcx> Graph<'a, 'tcx> { +impl<'a, 'tcx> Cg<'a, 'tcx> { fn get_id(&self) -> String { if let Some(id) = &self.id { id.replace('[', "_").replace(']', "") @@ -40,7 +38,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } } } -impl<'a, 'tcx> Graph<'a, 'tcx> { +impl<'a, 'tcx> Cg<'a, 'tcx> { // pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { // self.get_corresponding_places(n).is_none() // } @@ -51,7 +49,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { /// For regions created by `... = &'r ...`, find the kind of borrow. pub(crate) fn borrow_kind(&self, r: RegionVid) -> Option { // TODO: we could put this into a `FxHashMap` in `cgx`. - self.facts2.borrow_set.location_map.iter() + self.cgx.facts2.borrow_set.location_map.iter() .chain(&self.cgx.sbs.location_map) .find(|(_, data)| data.region == r) .map(|(_, data)| data.kind) @@ -64,7 +62,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { if !self.skip_empty_nodes || self.has_associated_place(r) { return vec![Edge::new(start, r, reasons)]; } - for (&b, edge) in &self.nodes[r].blocks { + for (&b, edge) in &self.graph.nodes[r].blocks { let mut reasons = reasons.clone(); reasons.extend(edge); edges.extend(self.non_empty_edges(b, start, reasons, visited)); @@ -74,7 +72,7 @@ impl<'a, 'tcx> Graph<'a, 'tcx> { } } -impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> { +impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.get_id()).unwrap() } fn node_id(&'a self, r: &RegionVid) -> dot::Id<'a> { @@ -103,7 +101,7 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> dot::LabelText::LabelStr(Cow::Owned(label)) } fn node_shape(&'a self, r: &RegionVid) -> Option> { - if self.static_regions.contains(&r) { + if self.graph.static_regions.contains(&r) { return Some(dot::LabelText::LabelStr(Cow::Borrowed("house"))); } self.borrow_kind(*r).map(|kind| match kind { @@ -126,16 +124,17 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> } else { label += "\n???"; } - if let Some(region_info) = self.facts2.region_inference_context.var_infos.get(*r) { - label += &format!("\n{:?}, {:?}", region_info.origin, region_info.universe); - } + // Not super useful: the `origin` is always NLL. + // if let Some(region_info) = self.facts2.region_inference_context.var_infos.get(*r) { + // label += &format!("\n{:?}, {:?}", region_info.origin, region_info.universe); + // } dot::LabelText::LabelStr(Cow::Owned(label)) } } -impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> { +impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { fn nodes(&self) -> dot::Nodes<'a, RegionVid> { - let mut nodes: Vec<_> = self.all_nodes() + let mut nodes: Vec<_> = self.graph.all_nodes() .filter(|(r, _)| !self.skip_empty_nodes || self.has_associated_place(*r)) .map(|(r, _)| r) .collect(); @@ -147,7 +146,7 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Graph<'b, 'tcx> fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { let mut edges = Vec::new(); - for (r, n) in self.all_nodes() { + for (r, n) in self.graph.all_nodes() { if self.skip_empty_nodes && !self.has_associated_place(r) { continue; } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index a4623fca5e3..ac91b13fa97 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -26,10 +26,10 @@ use prusti_rustc_interface::{ use crate::{ free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, - utils::PlaceRepacker, coupling_graph::cg::{Graph, Node}, + utils::PlaceRepacker, coupling_graph::{graph::{Graph, Node}, CgContext}, }; -use super::{cg::{EdgeInfo}, shared_borrow_set::SharedBorrowSet, CgContext}; +use super::triple::Cg; #[tracing::instrument(name = "draw_dots", level = "debug", skip(c))] pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { @@ -64,195 +64,41 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a } pub(crate) struct CoupligGraph<'a, 'tcx> { - pub(crate) repacker: PlaceRepacker<'a, 'tcx>, - pub(crate) facts: &'a BorrowckFacts, - pub(crate) facts2: &'a BorrowckFacts2<'tcx>, - pub(crate) flow_borrows: RefCell>>, - pub(crate) out_of_scope: FxIndexMap>, - pub(crate) printed_dot: FxHashSet, - pub(crate) cgx: &'a CgContext<'a, 'tcx>, + cgx: &'a CgContext<'a, 'tcx>, + out_of_scope: FxIndexMap>, + flow_borrows: RefCell>>, } + impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { - pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>) -> Self { + pub(crate) fn new(cgx: &'a CgContext<'a, 'tcx>) -> Self { if cfg!(debug_assertions) { std::fs::remove_dir_all("log/coupling").ok(); std::fs::create_dir_all("log/coupling/individual").unwrap(); } - let repacker = PlaceRepacker::new(body, tcx); - let regioncx = &*facts2.region_inference_context; - let borrow_set = &*facts2.borrow_set; - let out_of_scope = calculate_borrows_out_of_scope_at_location(body, regioncx, borrow_set); - let flow_borrows = Borrows::new(tcx, body, regioncx, borrow_set) - .into_engine(tcx, body) + let borrow_set = &cgx.facts2.borrow_set; + let regioncx = &*cgx.facts2.region_inference_context; + let out_of_scope = calculate_borrows_out_of_scope_at_location(cgx.rp.body(), regioncx, borrow_set); + let flow_borrows = Borrows::new(cgx.rp.tcx(), cgx.rp.body(), regioncx, borrow_set) + .into_engine(cgx.rp.tcx(), cgx.rp.body()) .pass_name("borrowck") .iterate_to_fixpoint() - .into_results_cursor(body); - let printed_dot = FxHashSet::default(); - CoupligGraph { repacker, facts, facts2, flow_borrows: RefCell::new(flow_borrows), out_of_scope, printed_dot, cgx } - } - - #[tracing::instrument(name = "handle_kills", level = "debug", skip(self))] - fn handle_kills(&self, state: &mut Graph<'_, 'tcx>, delta: &BorrowDelta, location: Location) { - let input_facts = self.facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - let location_table = self.facts.location_table.borrow(); - let location_table = location_table.as_ref().unwrap(); - - // let input_facts = self.facts2.region_inference_context.borrow(); - - let oos = self.out_of_scope.get(&location); - - for bi in delta.cleared.iter() { - let data = &self.facts2.borrow_set[bi]; - // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| bi == *b - ).copied().unwrap(); - let l = rich_to_loc(location_table.to_location(l)); - assert_eq!(r, data.region); - assert_eq!(l, data.reserve_location); - - // println!("killed: {r:?} {killed:?} {l:?}"); - if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { - state.kill_borrow(data); - } else { - state.remove(data.region, location); - } - - // // TODO: is this necessary? - // let local = data.borrowed_place.local; - // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // // println!("killed region: {region:?}"); - // state.output_to_dot("log/coupling/kill.dot"); - // let removed = state.remove(region, location, true, false); - // // assert!(!removed, "killed region: {region:?} at {location:?} (local: {local:?})"); - // } - } - - if let Some(oos) = oos { - for &bi in oos { - // What is the difference between the two (oos)? - assert!(delta.cleared.contains(bi), "Cleared borrow not in out of scope: {:?} vs {:?} (@ {location:?})", delta.cleared, oos); - if delta.cleared.contains(bi) { - continue; - } - - let data = &self.facts2.borrow_set[bi]; - // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| bi == *b - ).copied().unwrap(); - let l = rich_to_loc(location_table.to_location(l)); - assert_eq!(r, data.region); - assert_eq!(l, data.reserve_location); - - state.kill_borrow(data); + .into_results_cursor(cgx.rp.body()); - // // TODO: is this necessary? - // let local = data.assigned_place.local; - // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // // println!("IHUBJ local: {local:?} region: {region:?}"); - // let removed = state.remove(region, location, true, false); - // assert!(!removed); - // } - } - } - } - - fn get_constraints_for_loc(&self, location: Option) -> impl Iterator> + '_ { - self.facts2.region_inference_context.outlives_constraints().filter( - move |c| c.locations.from_location() == location - ) - } - - #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] - fn handle_outlives(&self, state: &mut Graph<'_, 'tcx>, delta: &BorrowDelta, location: Location) { - for c in self.get_constraints_for_loc(Some(location)) { - state.outlives(c); - } - } - - #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] - fn kill_shared_borrows_on_place(&self, location: Location, state: &mut Graph<'_, 'tcx>, place: Place<'tcx>) { - // let other_borrows_of_local = state - // .shared_borrows - // .local_map - // .get(&place.local) - // .into_iter() - // .flat_map(|bs| bs.iter()) - // .copied() - // .map(|idx| &state.shared_borrows.location_map[idx.as_usize()]); - // let definitely_conflicting_borrows = other_borrows_of_local.filter(|d| { - // places_conflict( - // self.repacker.tcx(), - // self.repacker.body(), - // d.borrowed_place, - // place, - // PlaceConflictBias::NoOverlap, - // ) - // }); - // for data in definitely_conflicting_borrows { - // state.remove(data.region, true); - // } - let Some(local) = place.as_local() else { - // Only remove nodes if assigned to the entire local (this is what rustc allows too) - return - }; - // println!("Killing: {local:?}"); - - // let was_shared_borrow_from = self.cgx.sbs.local_map.contains_key(&local); - // let was_shared_borrow_to = self.cgx.sbs.location_map.values().find(|bd| bd.assigned_place.local == local); - // if !was_shared_borrow_from && was_shared_borrow_to.is_none() { - // println!("early ret: {:?}", self.cgx.sbs.local_map); - // return; - // } - // println!("killing"); - - // Also remove any overwritten borrows locals - for (®ion, _) in state.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { - // println!("Killing local: {local:?}: {region:?}"); - - // TODO: could set `remove_dangling_children` to true here - state.remove(region, location); - } - } - - /// This is the hack we use to make a `fn foo<'a>(x: &'a _, y: &'a _, ...) -> &'a _` look like - /// `fn foo<'a: 'c, 'b: 'c, 'c>(x: &'a _, y: &'b _, ...) -> &'c _` to the analysis. - #[tracing::instrument(name = "ignore_outlives", level = "debug", skip(self), ret)] - fn ignore_outlives(&self, c: OutlivesConstraint<'tcx>) -> bool { - let arg_count = self.repacker.body().arg_count; - let sup = self.cgx.region_map.get(&c.sup); - let sub = self.cgx.region_map.get(&c.sub); - match (sup, sub) { - // If `projects_exactly_one_deref` then it must be the `'a` region of a `x: &'a ...`, rather than being nested deeper withing the local - (_, Some(sub)) => { - sub.place.projects_exactly_one_deref() - && sub.place.local.index() <= arg_count - && sub.place.local != RETURN_PLACE - } - // (Some(sup), Some(sub)) => { - // if !(sup.place.projects_exactly_one_deref() - // && sub.place.projects_exactly_one_deref() - // && sup.place.local.index() <= arg_count - // && sub.place.local.index() <= arg_count) { - // return false; - // } - // debug_assert_ne!(sup.place.local, sub.place.local); - // sub.place.local != RETURN_PLACE - // } - _ => false, + Self { + cgx, + out_of_scope, + flow_borrows: RefCell::new(flow_borrows), } } } impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { - type Domain = Graph<'a, 'tcx>; + type Domain = Cg<'a, 'tcx>; const NAME: &'static str = "coupling_graph"; fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - Graph::new(self.repacker, self.facts, self.facts2, self.cgx) + Cg::new(self.cgx) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { @@ -266,21 +112,23 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { // } // } - println!("body: {body:#?}"); - println!("\ninput_facts: {:?}", self.facts.input_facts); - println!("output_facts: {:#?}\n", self.facts.output_facts); - println!("location_map: {:#?}\n", self.facts2.borrow_set.location_map); - println!("activation_map: {:#?}\n", self.facts2.borrow_set.activation_map); - println!("local_map: {:#?}\n", self.facts2.borrow_set.local_map); - println!("region_inference_context: {:#?}\n", self.facts2.region_inference_context.outlives_constraints().collect::>()); - // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); - let lt = self.facts.location_table.borrow(); - let lt = lt.as_ref().unwrap(); - for pt in lt.all_points() { - println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); + if false { + println!("body: {body:#?}"); + println!("\ninput_facts: {:?}", self.cgx.facts.input_facts); + println!("output_facts: {:#?}\n", self.cgx.facts.output_facts); + println!("location_map: {:#?}\n", self.cgx.facts2.borrow_set.location_map); + println!("activation_map: {:#?}\n", self.cgx.facts2.borrow_set.activation_map); + println!("local_map: {:#?}\n", self.cgx.facts2.borrow_set.local_map); + println!("region_inference_context: {:#?}\n", self.cgx.facts2.region_inference_context.outlives_constraints().collect::>()); + // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); + let lt = self.cgx.facts.location_table.borrow(); + let lt = lt.as_ref().unwrap(); + for pt in lt.all_points() { + println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); + } + println!("out_of_scope: {:?}", self.out_of_scope); + println!("region_map: {:#?}\n", self.cgx.region_map); } - println!("out_of_scope: {:?}", self.out_of_scope); - println!("region_map: {:#?}\n", self.cgx.region_map); } } @@ -301,7 +149,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); @@ -309,48 +157,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } self.flow_borrows.borrow_mut().seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); - // print!("{statement:?} ({other:?}):"); let delta = calculate_diff(&other, &state.live); - self.handle_kills(state, &delta, location); - - match statement.kind { - StatementKind::Assign(box (assigned_place, ref lhs)) => { - self.kill_shared_borrows_on_place(location, state, assigned_place); - if let Rvalue::Use(Operand::Constant(c)) = lhs { - // println!("Checking {:?} vs {curr_loc:?}", self.facts2.region_inference_context.outlives_constraints().map(|c| format!("{:?}", c.locations)).collect::>()); - for c in self.get_constraints_for_loc(Some(location)) { - // println!("Got one: {c:?}"); - state.outlives_static(c.sub, location); - } - } - } - // If a local was only moved out of and not reborrowed, it's region is never killed officially - StatementKind::StorageDead(local) => { - self.kill_shared_borrows_on_place(location, state, Place::from(local)); - - // let input_facts = self.facts.input_facts.borrow(); - // let input_facts = input_facts.as_ref().unwrap(); - // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { - // // println!("killed region manually: {region:?}"); - // state.remove(region, true); - // } - // let to_rem: Vec<_> = state.shared_borrows.iter().filter(|(_, data)| data.borrowed_place.local == local).map(|(_, data)| data.region).collect(); - // for region in to_rem { - // // println!("killed region manually: {region:?}"); - // state.remove(region, true); - // } - } - _ => (), - } - // if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - // state.output_to_dot(format!("log/coupling/individual/{l}_v{}_mid.dot", state.version)); - // } - self.handle_outlives(state, &delta, location); + let oos = self.out_of_scope.get(&location); + state.handle_kills(&delta, oos, location); + state.visit_statement(statement, location); + state.handle_outlives(&delta, location); state.live = other; - // println!(); - if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); } } @@ -371,7 +186,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); } self.flow_borrows.borrow_mut().seek_to_block_start(location.block); @@ -379,51 +194,15 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } self.flow_borrows.borrow_mut().seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); - // if let TerminatorKind::Call { func, args, destination, target, fn_span, .. } = &terminator.kind { - // if let Operand::Constant(c) = func { - // println!("user_ty: {:?}", c.user_ty); - // println!("call: {:?}", c.literal); - // if let ConstantKind::Val(cv, ty) = c.literal { - // println!("val: {:?}", cv); - // println!("ty: {:?}", ty); - // } - // println!("\n\n\ncall: {:?}", func); - // } - // for arg in args { - // match arg { - // Operand::Copy(a) => println!("copy ({arg:?}): {:?}", a), - // Operand::Move(b) => println!("move ({arg:?}): {:?}", b), - // Operand::Constant(c) => println!("const ({arg:?}): {:?}", c.literal), - // } - // } - // } - // print!("{terminator:?} ({other:?}):"); + let delta = calculate_diff(&other, &state.live); - self.handle_kills(state, &delta, location); - self.handle_outlives(state, &delta, location); + let oos = self.out_of_scope.get(&location); + state.handle_kills(&delta, oos, location); + state.visit_terminator(terminator, location); + state.handle_outlives(&delta, location); state.live = other; - if let TerminatorKind::Return = &terminator.kind { - if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", state.version)); - } - - // Pretend we have a storage dead for all `always_live_locals` other than the args/return - for l in self.repacker.always_live_locals_non_args().iter() { - self.kill_shared_borrows_on_place(location, state, l.into()); - } - // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` - for r in self.facts2.borrow_set.location_map.values().chain(self.cgx.sbs.location_map.values()) { - state.remove(r.region, location); - } - for c in self.get_constraints_for_loc(None).filter(|c| !self.ignore_outlives(*c)) { - state.outlives(c); - } - state.merge_for_return(); - } - // println!(); - - if cfg!(debug_assertions) && !self.repacker.body().basic_blocks[location.block].is_cleanup { + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); } terminator.edges() @@ -440,9 +219,9 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { } #[derive(Debug)] -struct BorrowDelta { +pub struct BorrowDelta { set: HybridBitSet, - cleared: HybridBitSet, + pub cleared: HybridBitSet, } fn calculate_diff(curr: &BitSet, old: &BitSet) -> BorrowDelta { @@ -464,10 +243,3 @@ fn calculate_diff(curr: &BitSet, old: &BitSet) -> Borr cleared: cleared_in_curr, } } - -fn rich_to_loc(l: RichLocation) -> Location { - match l { - RichLocation::Start(l) => l, - RichLocation::Mid(l) => l, - } -} diff --git a/mir-state-analysis/src/coupling_graph/impl/cg.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs similarity index 63% rename from mir-state-analysis/src/coupling_graph/impl/cg.rs rename to mir-state-analysis/src/coupling_graph/impl/graph.rs index f4ee9e9e1ce..6539ea70439 100644 --- a/mir-state-analysis/src/coupling_graph/impl/cg.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -20,99 +20,24 @@ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::{PlaceRepacker, Place}, + utils::{PlaceRepacker, Place}, coupling_graph::{CgContext, region_place::Perms}, }; -use super::{engine::CoupligGraph, shared_borrow_set::SharedBorrowSet, CgContext, region_place::Perms}; - -#[derive(Clone)] -pub struct Graph<'a, 'tcx> { - pub id: Option, - pub rp: PlaceRepacker<'a, 'tcx>, - pub facts: &'a BorrowckFacts, - pub facts2: &'a BorrowckFacts2<'tcx>, - pub cgx: &'a CgContext<'a, 'tcx>, - - pub(crate) live: BitSet, - pub version: usize, - pub skip_empty_nodes: bool, +use super::engine::CoupligGraph; +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Graph<'tcx> { pub nodes: IndexVec>, // Regions equal to 'static pub static_regions: FxHashSet, } -impl Debug for Graph<'_, '_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - f.debug_struct("Graph") - .field("id", &self.id) - .field("nodes", &self.nodes) - .field("skip_empty_nodes", &self.skip_empty_nodes) - // .field("static_regions", &self.static_regions) - .finish() - } -} - -impl PartialEq for Graph<'_, '_> { - fn eq(&self, other: &Self) -> bool { - self.nodes == other.nodes //&& self.static_regions == other.static_regions - } -} -impl Eq for Graph<'_, '_> {} - -impl<'a, 'tcx: 'a> Graph<'a, 'tcx> { +impl<'tcx> Graph<'tcx> { // TODO: get it from `UniversalRegions` instead pub fn static_region() -> RegionVid { RegionVid::from_u32(0) } - pub fn new(rp: PlaceRepacker<'a, 'tcx>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, cgx: &'a CgContext<'a, 'tcx>) -> Self { - let live = BitSet::new_empty(facts2.borrow_set.location_map.len()); - let mut result = Self { - id: None, - rp, - facts, - facts2, - cgx, - live, - version: 0, - skip_empty_nodes: false, - nodes: IndexVec::from_elem_n(Node::new(), facts2.region_inference_context.var_infos.len()), - static_regions: FxHashSet::from_iter([Self::static_region()]), - }; - // let input_facts = facts.input_facts.borrow(); - // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { - // result.outlives(r1, r2); - // } - - ////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) - let input_facts = facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - let constraints: Vec<_> = facts2.region_inference_context.outlives_constraints().collect(); - let constraints_no_loc: Vec<_> = constraints.iter().filter(|c| c.locations.from_location().is_none()).copied().collect(); - - // Make one single `'static` node - // let n = result.region_to_node(Self::static_region()); - // let node = result.get_node_mut(n); - // let mut to_make_static = vec![Self::static_region()]; - // while let Some(r) = to_make_static.pop() { - // for c in constraints.iter().filter(|c| c.sub == r) { - // if node.regions.insert(c.sup) { - // to_make_static.push(c.sup); - // } - // } - // } - // println!("Static node: {node:?}"); - // let mut to_make_static = vec![Self::static_region()]; - // while let Some(r) = to_make_static.pop() { - // for &c in constraints_no_loc.iter().filter(|c| c.sub == r) { - // if result.outlives(c) { - // to_make_static.push(c.sup); - // } - // } - // } - result - } #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> bool { let edge = EdgeInfo { @@ -149,15 +74,6 @@ impl<'a, 'tcx: 'a> Graph<'a, 'tcx> { #[tracing::instrument(name = "Graph::kill_borrow", level = "debug", skip(self))] /// Remove borrow from graph and all nodes that block it and the node it blocks pub fn kill_borrow(&mut self, data: &BorrowData<'tcx>) { - if cfg!(debug_assertions) { - let blocks = &self.nodes[data.region].blocks; - assert_eq!(blocks.len(), 1); - // A borrow should have places associated with it (i.e. be in the `region_map`)! - assert_eq!( - self.cgx.region_map[&data.region].place.local, - self.cgx.region_map[blocks.keys().next().unwrap()].place.local - ); - } self.kill(data.region); } @@ -353,13 +269,6 @@ impl<'a, 'tcx: 'a> Graph<'a, 'tcx> { } (blocks, blocked_by) } - - pub(crate) fn get_associated_place(&self, r: RegionVid) -> Option<&Perms<'tcx>> { - self.cgx.region_map.get(&r) - } - pub(crate) fn has_associated_place(&self, r: RegionVid) -> bool { - self.cgx.region_map.contains_key(&r) - } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -415,84 +324,3 @@ impl<'tcx> Node<'tcx> { } } } - -impl<'a, 'tcx> DebugWithContext> for Graph<'a, 'tcx> { - fn fmt_diff_with( - &self, - old: &Self, - _ctxt: &CoupligGraph<'a, 'tcx>, - f: &mut Formatter<'_>, - ) -> Result { - Ok(()) - } -} - -impl<'tcx> Graph<'_, 'tcx> { - #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] - pub fn merge_for_return(&self) { - let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); - let in_facts = self.facts.input_facts.borrow(); - let universal_region = &in_facts.as_ref().unwrap().universal_region; - - for (r, node) in self.all_nodes() { - // Skip unknown empty nodes, we may want to figure out how to deal with them in the future - if !self.has_associated_place(r) { - continue; - } - - if self.borrow_kind(r).is_some() { - self.output_to_dot("log/coupling/error.dot"); - panic!("{node:?}"); - } else { - // let r = *node.regions.iter().next().unwrap(); - if universal_region.contains(&r) { - continue; - } - - let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == r - // let r = c.sub.as_u32(); // The thing that lives shorter - // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region - }); - // If None then we have something left which doesn't outlive the function region! - // if proof.is_none() { - // let in_facts = self.facts.input_facts.borrow(); - // let r = &in_facts.as_ref().unwrap().universal_region; - // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); - // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); - // // std::fs::remove_dir_all("log/coupling").ok(); - // // std::fs::create_dir_all("log/coupling/individual").unwrap(); - // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); - // dot::render(self, &mut f).unwrap(); - // } - // println!("Found proof: {proof:?}"); - if proof.is_none() { - self.output_to_dot("log/coupling/error.dot"); - panic!("Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); - } - } - } - for &r in &self.static_regions { - if universal_region.contains(&r) { - continue; - } - // It's possible that we get some random unnamed regions in the static set - if !self.has_associated_place(r) { - continue; - } - let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == r - }); - if proof.is_none() { - self.output_to_dot("log/coupling/error.dot"); - panic!("Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); - } - } - } - pub fn output_to_dot>(&self, path: P) { - // TODO: - // return; - let mut f = std::fs::File::create(path).unwrap(); - dot::render(self, &mut f).unwrap(); - } -} diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 364bbc1c86a..4ccea5fd6c9 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,12 +15,17 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::cg::{Graph}; +use super::{triple::Cg, graph::Graph}; -impl JoinSemiLattice for Graph<'_, '_> { - #[tracing::instrument(name = "Graph::join", level = "debug", ret)] +impl JoinSemiLattice for Cg<'_, '_> { + #[tracing::instrument(name = "Cg::join", level = "debug", ret)] + fn join(&mut self, other: &Self) -> bool { + self.graph.join(&other.graph) + } +} + +impl JoinSemiLattice for Graph<'_> { fn join(&mut self, other: &Self) -> bool { - // println!("Joining graphs:\n{:?}: {:?}\n{:?}: {:?}", self.id, self.nodes, other.id, other.nodes); let mut changed = false; for (from, node) in other.all_nodes() { for (&to, reasons) in node.blocks.iter() { diff --git a/mir-state-analysis/src/coupling_graph/impl/mod.rs b/mir-state-analysis/src/coupling_graph/impl/mod.rs index 1cb9af3f65e..0b18bc46fac 100644 --- a/mir-state-analysis/src/coupling_graph/impl/mod.rs +++ b/mir-state-analysis/src/coupling_graph/impl/mod.rs @@ -4,59 +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; - -use crate::{utils::{PlaceRepacker, Place}, r#loop::LoopAnalysis}; -use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; -use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; -use prusti_rustc_interface::{ - data_structures::fx::FxHashMap, - middle::{mir::Body, ty::{RegionVid, TyCtxt}}, -}; - pub(crate) mod engine; -pub(crate) mod cg; +pub(crate) mod graph; pub(crate) mod join_semi_lattice; -pub(crate) mod shared_borrow_set; -pub(crate) mod region_place; +pub(crate) mod triple; mod dot; - -pub struct CgContext<'a, 'tcx> { - pub rp: PlaceRepacker<'a, 'tcx>, - pub sbs: SharedBorrowSet<'tcx>, - pub region_map: FxHashMap>, - pub loops: LoopAnalysis, -} - -impl fmt::Debug for CgContext<'_, '_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CgContext").field("sbs", &self.sbs).field("region_map", &self.region_map).finish() - } -} - -impl<'a, 'tcx> CgContext<'a, 'tcx> { - #[tracing::instrument(name = "CgContext::new", level = "debug", skip_all, ret)] - pub fn new( - tcx: TyCtxt<'tcx>, - body: &'a Body<'tcx>, - facts: &'a BorrowckFacts, - facts2: &'a BorrowckFacts2<'tcx> - ) -> Self { - let sbs = SharedBorrowSet::build(tcx, body, &facts2.borrow_set); - let rp = PlaceRepacker::new(body, tcx); - let input_facts = facts.input_facts.borrow(); - let region_map = Perms::region_place_map( - &input_facts.as_ref().unwrap().use_of_var_derefs_origin, - &facts2.borrow_set, - &sbs, - rp, - ); - let loops = LoopAnalysis::find_loops(body); - Self { - rp, - sbs, - region_map, - loops, - } - } -} diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs new file mode 100644 index 00000000000..8982baa6ac3 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -0,0 +1,380 @@ +// © 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, Display}, borrow::Cow}; + +use derive_more::{Deref, DerefMut}; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, + index::bit_set::BitSet, + dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, + borrowck::{borrow_set::BorrowData, consumers::{BorrowIndex, RichLocation, OutlivesConstraint}}, + middle::{mir::{BasicBlock, ConstraintCategory, Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, RETURN_PLACE, Location, Operand, visit::Visitor}, ty::{RegionVid, TyKind}}, +}; + +use crate::{ + free_pcs::{ + engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, + }, + utils::{PlaceRepacker, Place}, coupling_graph::{CgContext, region_place::Perms}, +}; + +use super::{engine::{CoupligGraph, BorrowDelta}, graph::{Graph, Node}}; + + +#[derive(Clone)] +pub struct Cg<'a, 'tcx> { + pub id: Option, + pub cgx: &'a CgContext<'a, 'tcx>, + + pub(crate) live: BitSet, + pub version: usize, + pub skip_empty_nodes: bool, + + pub graph: Graph<'tcx>, +} + +impl Debug for Cg<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.debug_struct("Graph") + .field("id", &self.id) + .field("nodes", &self.graph) + .finish() + } +} + +impl PartialEq for Cg<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.graph == other.graph + } +} +impl Eq for Cg<'_, '_> {} + +impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { + fn fmt_diff_with( + &self, + old: &Self, + _ctxt: &CoupligGraph<'a, 'tcx>, + f: &mut Formatter<'_>, + ) -> Result { + Ok(()) + } +} + +impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { + pub fn new(cgx: &'a CgContext<'a, 'tcx>) -> Self { + let graph = Graph { + nodes: IndexVec::from_elem_n(Node::new(), cgx.facts2.region_inference_context.var_infos.len()), + static_regions: FxHashSet::from_iter([Graph::static_region()]), + }; + let live = BitSet::new_empty(cgx.facts2.borrow_set.location_map.len()); + let mut result = Self { + id: None, + cgx, + live, + version: 0, + skip_empty_nodes: false, + graph, + }; + // let input_facts = facts.input_facts.borrow(); + // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { + // result.outlives(r1, r2); + // } + + ////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) + let input_facts = cgx.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + let constraints: Vec<_> = cgx.facts2.region_inference_context.outlives_constraints().collect(); + let constraints_no_loc: Vec<_> = constraints.iter().filter(|c| c.locations.from_location().is_none()).copied().collect(); + + // Make one single `'static` node + // let n = result.region_to_node(Self::static_region()); + // let node = result.get_node_mut(n); + // let mut to_make_static = vec![Self::static_region()]; + // while let Some(r) = to_make_static.pop() { + // for c in constraints.iter().filter(|c| c.sub == r) { + // if node.regions.insert(c.sup) { + // to_make_static.push(c.sup); + // } + // } + // } + // println!("Static node: {node:?}"); + // let mut to_make_static = vec![Self::static_region()]; + // while let Some(r) = to_make_static.pop() { + // for &c in constraints_no_loc.iter().filter(|c| c.sub == r) { + // if result.outlives(c) { + // to_make_static.push(c.sup); + // } + // } + // } + + result + } + pub(crate) fn get_associated_place(&self, r: RegionVid) -> Option<&Perms<'tcx>> { + self.cgx.region_map.get(&r) + } + pub(crate) fn has_associated_place(&self, r: RegionVid) -> bool { + self.cgx.region_map.contains_key(&r) + } + + + #[tracing::instrument(name = "handle_kills", level = "debug", skip(self))] + pub fn handle_kills(&mut self, delta: &BorrowDelta, oos: Option<&Vec>, location: Location) { + let input_facts = self.cgx.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + let location_table = self.cgx.facts.location_table.borrow(); + let location_table = location_table.as_ref().unwrap(); + + // let input_facts = self.facts2.region_inference_context.borrow(); + + for bi in delta.cleared.iter() { + let data = &self.cgx.facts2.borrow_set[bi]; + // TODO: remove if the asserts below pass: + let (r, _, l) = input_facts.loan_issued_at.iter().find( + |(_, b, _)| bi == *b + ).copied().unwrap(); + let l = rich_to_loc(location_table.to_location(l)); + assert_eq!(r, data.region); + assert_eq!(l, data.reserve_location); + + // println!("killed: {r:?} {killed:?} {l:?}"); + if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { + self.output_to_dot("log/coupling/kill.dot"); + self.graph.kill_borrow(data); + } else { + self.graph.remove(data.region, location); + } + + // // TODO: is this necessary? + // let local = data.borrowed_place.local; + // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // // println!("killed region: {region:?}"); + // state.output_to_dot("log/coupling/kill.dot"); + // let removed = state.remove(region, location, true, false); + // // assert!(!removed, "killed region: {region:?} at {location:?} (local: {local:?})"); + // } + } + + if let Some(oos) = oos { + for &bi in oos { + // What is the difference between the two (oos)? + assert!(delta.cleared.contains(bi), "Cleared borrow not in out of scope: {:?} vs {:?} (@ {location:?})", delta.cleared, oos); + if delta.cleared.contains(bi) { + continue; + } + + let data = &self.cgx.facts2.borrow_set[bi]; + // TODO: remove if the asserts below pass: + let (r, _, l) = input_facts.loan_issued_at.iter().find( + |(_, b, _)| bi == *b + ).copied().unwrap(); + let l = rich_to_loc(location_table.to_location(l)); + assert_eq!(r, data.region); + assert_eq!(l, data.reserve_location); + + self.graph.kill_borrow(data); + + // // TODO: is this necessary? + // let local = data.assigned_place.local; + // for region in input_facts.use_of_var_derefs_origin.iter().filter(|(l, _)| *l == local).map(|(_, r)| *r) { + // // println!("IHUBJ local: {local:?} region: {region:?}"); + // let removed = state.remove(region, location, true, false); + // assert!(!removed); + // } + } + } + } + + #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] + pub fn handle_outlives(&mut self, delta: &BorrowDelta, location: Location) { + for c in self.cgx.get_constraints_for_loc(Some(location)) { + self.graph.outlives(c); + } + } + + #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] + fn kill_shared_borrows_on_place(&mut self, location: Location, place: MirPlace<'tcx>) { + let Some(local) = place.as_local() else { + // Only remove nodes if assigned to the entire local (this is what rustc allows too) + return + }; + for (®ion, _) in self.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { + self.graph.remove(region, location); + } + } +} + +impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { + fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { + self.super_operand(operand, location); + match *operand { + Operand::Copy(_) => (), + Operand::Move(place) => { + // TODO: check that this is ok (maybe issue if we move out of ref typed arg) + // self.kill_shared_borrows_on_place(location, *place); + } + Operand::Constant(..) => { + // TODO: anything here? + } + } + } + + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + use StatementKind::*; + match &statement.kind { + Assign(box (place, _)) => { + self.kill_shared_borrows_on_place(location, *place); + } + &StorageDead(local) => { + self.kill_shared_borrows_on_place(location, local.into()); + } + _ => (), + }; + self.super_statement(statement, location); + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + self.super_terminator(terminator, location); + use TerminatorKind::*; + match &terminator.kind { + Goto { .. } + | SwitchInt { .. } + | UnwindResume + | UnwindTerminate(_) + | Unreachable + | Assert { .. } + | GeneratorDrop + | FalseEdge { .. } + | FalseUnwind { .. } => (), + Return => { + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { + let l = format!("{location:?}").replace('[', "_").replace(']', ""); + self.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", self.version)); + } + + // Pretend we have a storage dead for all `always_live_locals` other than the args/return + for l in self.cgx.rp.always_live_locals_non_args().iter() { + self.kill_shared_borrows_on_place(location, l.into()); + } + // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` + for r in self.cgx.facts2.borrow_set.location_map.values().chain(self.cgx.sbs.location_map.values()) { + self.graph.remove(r.region, location); + } + + let input_facts = self.cgx.facts.input_facts.borrow(); + let input_facts = input_facts.as_ref().unwrap(); + // TODO: use this + let known_placeholder_subset = &input_facts.known_placeholder_subset; + for c in self.cgx.get_constraints_for_loc(None).filter(|c| !self.cgx.ignore_outlives(*c)) { + self.graph.outlives(c); + } + self.merge_for_return(); + } + &Drop { place, .. } => { + + } + &Call { destination, .. } => { + + } + &Yield { resume_arg, .. } => { + + } + InlineAsm { .. } => todo!("{terminator:?}"), + }; + } + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location:Location) { + match rvalue { + Rvalue::Use(Operand::Constant(_)) => { + for c in self.cgx.get_constraints_for_loc(Some(location)) { + self.graph.outlives_static(c.sub, location); + } + } + _ => (), + } + self.super_rvalue(rvalue, location); + } +} + +impl<'tcx> Cg<'_, 'tcx> { + #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] + pub fn merge_for_return(&self) { + let outlives: Vec<_> = self.cgx.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); + let in_facts = self.cgx.facts.input_facts.borrow(); + let universal_region = &in_facts.as_ref().unwrap().universal_region; + + for (r, node) in self.graph.all_nodes() { + // Skip unknown empty nodes, we may want to figure out how to deal with them in the future + if !self.has_associated_place(r) { + continue; + } + + if self.borrow_kind(r).is_some() { + self.output_to_dot("log/coupling/error.dot"); + panic!("{node:?}"); + } else { + // let r = *node.regions.iter().next().unwrap(); + if universal_region.contains(&r) { + continue; + } + + let proof = outlives.iter().find(|c| { + universal_region.contains(&c.sub) && c.sup == r + // let r = c.sub.as_u32(); // The thing that lives shorter + // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region + }); + // If None then we have something left which doesn't outlive the function region! + // if proof.is_none() { + // let in_facts = self.facts.input_facts.borrow(); + // let r = &in_facts.as_ref().unwrap().universal_region; + // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); + // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); + // // std::fs::remove_dir_all("log/coupling").ok(); + // // std::fs::create_dir_all("log/coupling/individual").unwrap(); + // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); + // dot::render(self, &mut f).unwrap(); + // } + // println!("Found proof: {proof:?}"); + if proof.is_none() { + self.output_to_dot("log/coupling/error.dot"); + panic!("Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); + } + } + } + for &r in &self.graph.static_regions { + if universal_region.contains(&r) { + continue; + } + // It's possible that we get some random unnamed regions in the static set + if !self.has_associated_place(r) { + continue; + } + let proof = outlives.iter().find(|c| { + universal_region.contains(&c.sub) && c.sup == r + }); + if proof.is_none() { + self.output_to_dot("log/coupling/error.dot"); + panic!("Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); + } + } + } + pub fn output_to_dot>(&self, path: P) { + // TODO: + // return; + let mut f = std::fs::File::create(path).unwrap(); + dot::render(self, &mut f).unwrap(); + } +} + + + +fn rich_to_loc(l: RichLocation) -> Location { + match l { + RichLocation::Start(l) => l, + RichLocation::Mid(l) => l, + } +} + diff --git a/mir-state-analysis/src/coupling_graph/mod.rs b/mir-state-analysis/src/coupling_graph/mod.rs index 9456a304f3f..25f0559813c 100644 --- a/mir-state-analysis/src/coupling_graph/mod.rs +++ b/mir-state-analysis/src/coupling_graph/mod.rs @@ -5,5 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. mod r#impl; +mod context; pub use r#impl::*; +pub use context::*; diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 66b4d5c27dd..e6dcf9649e4 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -50,7 +50,7 @@ pub fn run_coupling_graph<'mir, 'tcx>( // return; // } let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); - let fpcs = coupling_graph::engine::CoupligGraph::new(tcx, mir, facts, facts2, &cgx); + let fpcs = coupling_graph::engine::CoupligGraph::new(&cgx); let analysis = fpcs .into_engine(tcx, mir) .pass_name("coupling_graph") diff --git a/mir-state-analysis/src/loop/mod.rs b/mir-state-analysis/src/loop/mod.rs index 5ece5a60734..1464308441c 100644 --- a/mir-state-analysis/src/loop/mod.rs +++ b/mir-state-analysis/src/loop/mod.rs @@ -4,12 +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/. -// © 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::{Idx, IndexVec}, middle::mir::{BasicBlock, Body, START_BLOCK} From 6b9d4694b8387d4391c8612aa1a560c2d1a3b831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 29 Sep 2023 20:55:09 +0200 Subject: [PATCH 36/58] Disable printing and dumping when running under top crates --- mir-state-analysis/src/coupling_graph/impl/engine.rs | 8 +++++--- mir-state-analysis/src/coupling_graph/impl/triple.rs | 12 +++++++----- mir-state-analysis/src/lib.rs | 9 +++++---- mir-state-analysis/tests/top_crates.rs | 1 + prusti-utils/src/config.rs | 5 +++++ prusti/src/callbacks.rs | 2 +- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index ac91b13fa97..eaf4e45e58c 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -67,10 +67,11 @@ pub(crate) struct CoupligGraph<'a, 'tcx> { cgx: &'a CgContext<'a, 'tcx>, out_of_scope: FxIndexMap>, flow_borrows: RefCell>>, + top_crates: bool, } impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { - pub(crate) fn new(cgx: &'a CgContext<'a, 'tcx>) -> Self { + pub(crate) fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { if cfg!(debug_assertions) { std::fs::remove_dir_all("log/coupling").ok(); std::fs::create_dir_all("log/coupling/individual").unwrap(); @@ -89,6 +90,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { cgx, out_of_scope, flow_borrows: RefCell::new(flow_borrows), + top_crates } } } @@ -98,7 +100,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { const NAME: &'static str = "coupling_graph"; fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - Cg::new(self.cgx) + Cg::new(self.cgx, self.top_crates) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { @@ -112,7 +114,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { // } // } - if false { + if !self.top_crates { println!("body: {body:#?}"); println!("\ninput_facts: {:?}", self.cgx.facts.input_facts); println!("output_facts: {:#?}\n", self.cgx.facts.output_facts); diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 8982baa6ac3..4126ef8b522 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -34,6 +34,7 @@ pub struct Cg<'a, 'tcx> { pub(crate) live: BitSet, pub version: usize, pub skip_empty_nodes: bool, + pub top_crates: bool, pub graph: Graph<'tcx>, } @@ -66,7 +67,7 @@ impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { } impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { - pub fn new(cgx: &'a CgContext<'a, 'tcx>) -> Self { + pub fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { let graph = Graph { nodes: IndexVec::from_elem_n(Node::new(), cgx.facts2.region_inference_context.var_infos.len()), static_regions: FxHashSet::from_iter([Graph::static_region()]), @@ -78,6 +79,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { live, version: 0, skip_empty_nodes: false, + top_crates, graph, }; // let input_facts = facts.input_facts.borrow(); @@ -362,10 +364,10 @@ impl<'tcx> Cg<'_, 'tcx> { } } pub fn output_to_dot>(&self, path: P) { - // TODO: - // return; - let mut f = std::fs::File::create(path).unwrap(); - dot::render(self, &mut f).unwrap(); + if !self.top_crates { + let mut f = std::fs::File::create(path).unwrap(); + dot::render(self, &mut f).unwrap(); + } } } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index e6dcf9649e4..6f2be73d1a1 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -42,6 +42,7 @@ pub fn run_coupling_graph<'mir, 'tcx>( facts: &'mir BorrowckFacts, facts2: &'mir BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, + top_crates: bool, ) { // if tcx.item_name(mir.source.def_id()).as_str().starts_with("main") { // return; @@ -50,18 +51,18 @@ pub fn run_coupling_graph<'mir, 'tcx>( // return; // } let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); - let fpcs = coupling_graph::engine::CoupligGraph::new(&cgx); + let fpcs = coupling_graph::engine::CoupligGraph::new(&cgx, top_crates); let analysis = fpcs .into_engine(tcx, mir) .pass_name("coupling_graph") .iterate_to_fixpoint(); let c = analysis.into_results_cursor(mir); - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && !top_crates { coupling_graph::engine::draw_dots(c); } } -pub fn test_coupling_graph<'tcx>(mir: &Body<'tcx>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>) { - let analysis = run_coupling_graph(mir, facts, facts2, tcx); +pub fn test_coupling_graph<'tcx>(mir: &Body<'tcx>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, top_crates: bool) { + let analysis = run_coupling_graph(mir, facts, facts2, tcx, top_crates); // free_pcs::check(analysis); } diff --git a/mir-state-analysis/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs index 3945b1f1a37..0c68817ec86 100644 --- a/mir-state-analysis/tests/top_crates.rs +++ b/mir-state-analysis/tests/top_crates.rs @@ -69,6 +69,7 @@ fn run_on_crate(name: &str, version: &str) { .env("PRUSTI_SKIP_UNSUPPORTED_FEATURES", "true") // .env("PRUSTI_LOG", "debug") .env("PRUSTI_NO_VERIFY_DEPS", "true") + .env("PRUSTI_TOP_CRATES", "true") .current_dir(&dirname) .status() .unwrap(); diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 5941025b1ef..98ceccb7ec4 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -133,6 +133,7 @@ lazy_static::lazy_static! { // TODO: remove this option settings.set_default("test_free_pcs", false).unwrap(); settings.set_default("test_coupling_graph", false).unwrap(); + settings.set_default("top_crates", false).unwrap(); settings.set_default("print_desugared_specs", false).unwrap(); settings.set_default("print_typeckd_specs", false).unwrap(); @@ -1042,3 +1043,7 @@ pub fn test_free_pcs() -> bool { pub fn test_coupling_graph() -> bool { read_setting("test_coupling_graph") } + +pub fn top_crates() -> bool { + read_setting("top_crates") +} diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index f4e5ce92312..d7323730b05 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -186,7 +186,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { let name = env.name.get_unique_item_name(*proc_id); println!("Calculating CG for: {name} ({:?})", mir.span); - test_coupling_graph(&*mir, &*facts, &*facts2, tcx); + test_coupling_graph(&*mir, &*facts, &*facts2, tcx, config::top_crates()); } } else { verify(env, def_spec); From d7e85d227076cdb2a180a2b7e0a744ad426ee2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Fri, 29 Sep 2023 21:10:44 +0200 Subject: [PATCH 37/58] Small bugfixes --- mir-state-analysis/src/coupling_graph/impl/graph.rs | 3 +++ mir-state-analysis/src/coupling_graph/impl/triple.rs | 9 +++++---- mir-state-analysis/src/lib.rs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index 6539ea70439..53c6e888a48 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -47,6 +47,9 @@ impl<'tcx> Graph<'tcx> { self.outlives_inner(c.sup, c.sub, edge) } pub fn outlives_static(&mut self, r: RegionVid, l: Location) { + if r == Self::static_region() { + return; + } let edge = EdgeInfo { creation: Some(l.block), reason: ConstraintCategory::Internal }; self.outlives_inner(r, Self::static_region(), edge); } diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 4126ef8b522..d05d7c7c571 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -145,7 +145,6 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { // println!("killed: {r:?} {killed:?} {l:?}"); if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { - self.output_to_dot("log/coupling/kill.dot"); self.graph.kill_borrow(data); } else { self.graph.remove(data.region, location); @@ -163,8 +162,9 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { if let Some(oos) = oos { for &bi in oos { - // What is the difference between the two (oos)? - assert!(delta.cleared.contains(bi), "Cleared borrow not in out of scope: {:?} vs {:?} (@ {location:?})", delta.cleared, oos); + // What is the difference between the two (oos)? It's that `delta.cleared` may kill it earlier than `oos` + // imo we want to completely disregard `oos`. (TODO) + // assert!(delta.cleared.contains(bi), "Cleared borrow not in out of scope: {:?} vs {:?} (@ {location:?})", delta.cleared, oos); if delta.cleared.contains(bi) { continue; } @@ -291,6 +291,7 @@ impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location:Location) { match rvalue { Rvalue::Use(Operand::Constant(_)) => { + // TODO: this is a hack, find a better way to do things for c in self.cgx.get_constraints_for_loc(Some(location)) { self.graph.outlives_static(c.sub, location); } @@ -342,7 +343,7 @@ impl<'tcx> Cg<'_, 'tcx> { // println!("Found proof: {proof:?}"); if proof.is_none() { self.output_to_dot("log/coupling/error.dot"); - panic!("Found a region which does not outlive the function region: {node:?} ({universal_region:?})"); + panic!("Found a region which does not outlive the function region: {r:?} {node:?} ({universal_region:?})"); } } } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 6f2be73d1a1..2fabc1e0c9d 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -47,7 +47,7 @@ pub fn run_coupling_graph<'mir, 'tcx>( // if tcx.item_name(mir.source.def_id()).as_str().starts_with("main") { // return; // } - // if tcx.item_name(mir.source.def_id()).as_str() != "debug" { + // if !format!("{:?}", mir.source.def_id()).contains("fmt") { // return; // } let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); From b554699e616b725b5f871fe43c1aede764cedc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 3 Oct 2023 19:56:58 +0200 Subject: [PATCH 38/58] Analysis done --- .../src/coupling_graph/context/mod.rs | 84 ++--- .../context/outlives_info/mod.rs | 162 ++++++++ .../coupling_graph/context/region_info/map.rs | 302 +++++++++++++++ .../coupling_graph/context/region_info/mod.rs | 160 ++++++++ .../coupling_graph/context/region_place.rs | 59 ++- .../context/shared_borrow_set.rs | 28 +- .../src/coupling_graph/impl/dot.rs | 117 +++--- .../src/coupling_graph/impl/engine.rs | 185 ++++++--- .../src/coupling_graph/impl/graph.rs | 281 +++++--------- .../coupling_graph/impl/join_semi_lattice.rs | 2 +- .../src/coupling_graph/impl/triple.rs | 352 ++++++++---------- mir-state-analysis/src/coupling_graph/mod.rs | 2 +- mir-state-analysis/src/lib.rs | 12 +- mir-state-analysis/src/loop/mod.rs | 31 +- mir-state-analysis/src/utils/display.rs | 27 +- mir-state-analysis/src/utils/repacker.rs | 42 ++- mir-state-analysis/src/utils/ty/mod.rs | 21 +- mir-state-analysis/src/utils/ty/ty_rec.rs | 7 +- 18 files changed, 1252 insertions(+), 622 deletions(-) create mode 100644 mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs create mode 100644 mir-state-analysis/src/coupling_graph/context/region_info/map.rs create mode 100644 mir-state-analysis/src/coupling_graph/context/region_info/mod.rs diff --git a/mir-state-analysis/src/coupling_graph/context/mod.rs b/mir-state-analysis/src/coupling_graph/context/mod.rs index 4a913c21385..5f6df015fe7 100644 --- a/mir-state-analysis/src/coupling_graph/context/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/mod.rs @@ -4,20 +4,32 @@ // 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, cell::RefCell}; +use std::{cell::RefCell, fmt}; -use crate::{utils::{PlaceRepacker, Place}, r#loop::LoopAnalysis}; -use self::{shared_borrow_set::SharedBorrowSet, region_place::Perms}; +use self::{ + outlives_info::OutlivesInfo, region_info::RegionInfo, region_place::PlaceRegion, + shared_borrow_set::SharedBorrowSet, +}; +use crate::{ + r#loop::LoopAnalysis, + utils::{Place, PlaceRepacker}, +}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ - borrowck::consumers::{Borrows, BorrowIndex, OutlivesConstraint}, + borrowck::consumers::{BorrowIndex, Borrows, OutlivesConstraint}, + data_structures::fx::FxHashMap, dataflow::{Analysis, ResultsCursor}, - data_structures::fx::{FxIndexMap, FxHashMap}, - middle::{mir::{Body, Location, RETURN_PLACE}, ty::{RegionVid, TyCtxt}}, + index::IndexVec, + middle::{ + mir::{Body, Location, RETURN_PLACE}, + ty::{RegionVid, TyCtxt}, + }, }; pub(crate) mod shared_borrow_set; pub(crate) mod region_place; +pub(crate) mod region_info; +pub(crate) mod outlives_info; pub struct CgContext<'a, 'tcx> { pub rp: PlaceRepacker<'a, 'tcx>, @@ -25,13 +37,20 @@ pub struct CgContext<'a, 'tcx> { pub facts2: &'a BorrowckFacts2<'tcx>, pub sbs: SharedBorrowSet<'tcx>, - pub region_map: FxHashMap>, + // pub region_map: FxHashMap>, pub loops: LoopAnalysis, + + pub region_info: RegionInfo<'tcx>, + pub outlives_info: OutlivesInfo<'tcx>, } impl fmt::Debug for CgContext<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("CgContext").field("sbs", &self.sbs).field("region_map", &self.region_map).finish() + f.debug_struct("CgContext") + .field("sbs", &self.sbs) + .field("region_info", &self.region_info) + .field("outlives_info", &self.outlives_info) + .finish() } } @@ -41,61 +60,26 @@ impl<'a, 'tcx> CgContext<'a, 'tcx> { tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, facts: &'a BorrowckFacts, - facts2: &'a BorrowckFacts2<'tcx> + facts2: &'a BorrowckFacts2<'tcx>, ) -> Self { let borrow_set = &facts2.borrow_set; let sbs = SharedBorrowSet::build(tcx, body, borrow_set); let rp = PlaceRepacker::new(body, tcx); let input_facts = facts.input_facts.borrow(); - let region_map = Perms::region_place_map( - &input_facts.as_ref().unwrap().use_of_var_derefs_origin, - borrow_set, - &sbs, - rp, - ); + let input_facts = input_facts.as_ref().unwrap(); let loops = LoopAnalysis::find_loops(body); + let region_info = RegionInfo::new(rp, input_facts, facts2, &sbs); + let outlives_info = OutlivesInfo::new(input_facts, facts2, ®ion_info); + Self { rp, facts, facts2, sbs, - region_map, loops, - } - } - - pub fn get_constraints_for_loc(&self, location: Option) -> impl Iterator> + '_ { - self.facts2.region_inference_context.outlives_constraints().filter( - move |c| c.locations.from_location() == location - ) - } - - /// This is the hack we use to make a `fn foo<'a>(x: &'a _, y: &'a _, ...) -> &'a _` look like - /// `fn foo<'a: 'c, 'b: 'c, 'c>(x: &'a _, y: &'b _, ...) -> &'c _` to the analysis. - #[tracing::instrument(name = "ignore_outlives", level = "debug", skip(self), ret)] - pub fn ignore_outlives(&self, c: OutlivesConstraint<'tcx>) -> bool { - let arg_count = self.rp.body().arg_count; - let sup = self.region_map.get(&c.sup); - let sub = self.region_map.get(&c.sub); - match (sup, sub) { - // If `projects_exactly_one_deref` then it must be the `'a` region of a `x: &'a ...`, rather than being nested deeper withing the local - (_, Some(sub)) => { - sub.place.projects_exactly_one_deref() - && sub.place.local.index() <= arg_count - && sub.place.local != RETURN_PLACE - } - // (Some(sup), Some(sub)) => { - // if !(sup.place.projects_exactly_one_deref() - // && sub.place.projects_exactly_one_deref() - // && sup.place.local.index() <= arg_count - // && sub.place.local.index() <= arg_count) { - // return false; - // } - // debug_assert_ne!(sup.place.local, sub.place.local); - // sub.place.local != RETURN_PLACE - // } - _ => false, + region_info, + outlives_info, } } } diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs new file mode 100644 index 00000000000..a07133b408e --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs @@ -0,0 +1,162 @@ +// © 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_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, Borrows, OutlivesConstraint, PoloniusInput, RustcFacts}, + }, + data_structures::fx::FxHashMap, + dataflow::{Analysis, ResultsCursor}, + index::IndexVec, + middle::{ + mir::{ + Body, ConstraintCategory, Local, Location, Operand, Place, StatementKind, + TerminatorKind, RETURN_PLACE, + }, + ty::{RegionVid, Ty, TyCtxt}, + }, +}; + +use super::region_info::RegionInfo; + +#[derive(Debug)] +pub struct OutlivesInfo<'tcx> { + pub universal_local_constraints: Vec>, + pub local_constraints: Vec>, // but with no location info + pub type_ascription_constraints: Vec>, + pub location_constraints: FxHashMap>>, + + pub universal_constraints: Vec<(RegionVid, RegionVid)>, +} + +impl<'tcx> OutlivesInfo<'tcx> { + pub fn new( + input_facts: &PoloniusInput, + facts2: &BorrowckFacts2<'tcx>, + ri: &RegionInfo<'tcx>, + ) -> Self { + let universal_constraints = input_facts.known_placeholder_subset.clone(); + + let mut universal_local_constraints = Vec::new(); + let mut local_constraints = Vec::new(); + let mut type_ascription_constraints = Vec::new(); + let mut location_constraints: FxHashMap>> = + FxHashMap::default(); + for constraint in facts2.region_inference_context.outlives_constraints() { + if let Some(loc) = constraint.locations.from_location() { + location_constraints + .entry(loc) + .or_default() + .push(constraint); + } else if ri.map.is_universal(constraint.sup) || ri.map.is_universal(constraint.sub) { + if ri.map.is_universal(constraint.sup) && ri.map.is_universal(constraint.sub) { + // Not sure why the `region_inference_context` can rarely contain inter-universal constraints, + // but we should already have all of these in `universal_constraints`. + assert!(universal_constraints.contains(&(constraint.sup, constraint.sub))); + } else { + universal_local_constraints.push(constraint); + } + } else if matches!(constraint.category, ConstraintCategory::TypeAnnotation) { + type_ascription_constraints.push(constraint); + } else { + local_constraints.push(constraint); + } + } + Self { + universal_local_constraints, + local_constraints, + type_ascription_constraints, + location_constraints, + universal_constraints, + } + } + + fn location(&self, location: Location) -> &[OutlivesConstraint<'tcx>] { + self.location_constraints + .get(&location) + .map_or(&[], |v| v.as_slice()) + } + // #[tracing::instrument(name = "OutlivesInfo::pre_constraints", level = "debug", skip(self, ri))] + pub fn pre_constraints<'a>( + &'a self, + location: Location, + local: Option, + ri: &'a RegionInfo<'tcx>, + ) -> impl Iterator> + 'a { + self.location(location).iter().filter(move |c| { + if let Some(local) = local { + ri.map + .get(c.sub) + .get_place() + .map(|l| l != local) + .unwrap_or(true) + && ri + .map + .get(c.sup) + .get_place() + .map(|l| l != local) + .unwrap_or(true) + } else { + true + } + }) + } + // #[tracing::instrument(name = "OutlivesInfo::pre_constraints", level = "debug", skip(self, ri))] + pub fn post_constraints<'a>( + &'a self, + location: Location, + local: Option, + ri: &'a RegionInfo<'tcx>, + ) -> impl Iterator> + 'a { + self.location(location).iter().filter(move |c| { + !(if let Some(local) = local { + ri.map + .get(c.sub) + .get_place() + .map(|l| l != local) + .unwrap_or(true) + && ri + .map + .get(c.sup) + .get_place() + .map(|l| l != local) + .unwrap_or(true) + } else { + true + }) + }) + } +} + +pub trait AssignsToPlace<'tcx> { + fn assigns_to(&self) -> Option>; +} + +impl<'tcx> AssignsToPlace<'tcx> for StatementKind<'tcx> { + fn assigns_to(&self) -> Option> { + match self { + StatementKind::Assign(box (place, _)) => Some(*place), + &StatementKind::StorageDead(local) => Some(local.into()), + _ => None, + } + } +} +impl<'tcx> AssignsToPlace<'tcx> for TerminatorKind<'tcx> { + fn assigns_to(&self) -> Option> { + match self { + TerminatorKind::Drop { place, .. } + | TerminatorKind::Call { + destination: place, .. + } + | TerminatorKind::Yield { + resume_arg: place, .. + } => Some(*place), + _ => None, + } + } +} diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs new file mode 100644 index 00000000000..42aea67d395 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs @@ -0,0 +1,302 @@ +// © 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_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, Borrows, OutlivesConstraint}, + }, + data_structures::fx::FxHashMap, + dataflow::{Analysis, ResultsCursor}, + index::IndexVec, + infer::infer::{ + region_constraints::RegionVariableInfo, LateBoundRegionConversionTime, + NllRegionVariableOrigin, RegionVariableOrigin, + }, + middle::{ + mir::{Body, BorrowKind, Local, Location, Operand, RETURN_PLACE}, + ty::{BoundRegionKind, PlaceholderRegion, RegionVid, Ty, TyCtxt, TyKind}, + }, + span::{Span, Symbol}, +}; + +use crate::{ + coupling_graph::CgContext, + utils::{Place, PlaceRepacker}, +}; + +#[derive(Debug)] +pub struct RegionInfoMap<'tcx> { + region_info: IndexVec>, + universal: usize, +} + +#[derive(Debug, Clone)] +pub enum RegionKind<'tcx> { + // Universal regions (placeholders) + Static, + Param(ParamRegion), + Function, + UnknownUniversal, + // Local regions + Place { + region: RegionVid, + local: Local, + // ty: Ty<'tcx>, + }, + Borrow(BorrowData<'tcx>), + EarlyBound(Symbol), + LateBound { + // span: Span, + kind: BoundRegionKind, + ctime: LateBoundRegionConversionTime, + }, + Placeholder(Option), + MiscLocal, + UnknownLocal, +} + +impl<'tcx> RegionKind<'tcx> { + pub fn is_static(&self) -> bool { + matches!(self, Self::Static) + } + pub fn is_param(&self) -> bool { + matches!(self, Self::Param(..)) + } + pub fn is_function(&self) -> bool { + matches!(self, Self::Function) + } + pub fn is_unknown_universal(&self) -> bool { + matches!(self, Self::UnknownUniversal) + } + pub fn is_place(&self) -> bool { + matches!(self, Self::Place { .. }) + } + pub fn is_borrow(&self) -> bool { + matches!(self, Self::Borrow(..)) + } + pub fn is_early_bound(&self) -> bool { + matches!(self, Self::EarlyBound(..)) + } + pub fn is_late_bound(&self) -> bool { + matches!(self, Self::LateBound { .. }) + } + pub fn is_placeholder(&self) -> bool { + matches!(self, Self::Placeholder(..)) + } + pub fn is_misc_local(&self) -> bool { + matches!(self, Self::MiscLocal) + } + pub fn is_unknown_local(&self) -> bool { + matches!(self, Self::UnknownLocal) + } + + pub fn is_unknown(&self) -> bool { + matches!(self, Self::UnknownUniversal | Self::UnknownLocal) + } + + pub fn is_universal(&self) -> bool { + matches!( + self, + Self::Static | Self::Param(..) | Self::Function | Self::UnknownUniversal + ) + } + pub fn is_local(&self) -> bool { + !self.is_universal() + } + + // #[tracing::instrument(name = "RegionKind::get_place", level = "trace", ret)] + pub fn get_place(&self) -> Option { + match self { + Self::Place { local, .. } => Some(*local), + _ => None, + } + } + pub fn get_borrow(&self) -> Option<&BorrowData<'tcx>> { + match self { + Self::Borrow(data) => Some(data), + _ => None, + } + } + + pub fn to_string(&self, cgx: &CgContext<'_, 'tcx>) -> String { + match self { + Self::Static => "'static".to_string(), + Self::Param(ParamRegion { regions }) => { + let mut result = "old(".to_string(); + for (idx, r) in regions + .iter() + .map(|&r| cgx.region_info.map.get(r).to_string(cgx)) + .enumerate() + { + if idx != 0 { + result += " + "; + } + result += &r; + } + result += ")"; + result + } + Self::Function => "'fn".to_string(), + Self::UnknownUniversal => "'unknown".to_string(), + Self::Place { region, local } => { + let place = Place::from(*local); + let exact = place.deref_to_region(*region, cgx.rp); + let display = exact.unwrap_or(place).to_string(cgx.rp); + if exact.is_some() { + format!("{display:?}") + } else { + format!("AllIn({region:?}, {display:?})") + } + } + Self::Borrow(b) => match b.kind { + BorrowKind::Shared => { + format!("& {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } + BorrowKind::Mut { .. } => { + format!("&mut {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } + BorrowKind::Shallow => { + format!("&sh {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } + }, + Self::EarlyBound(name) => name.as_str().to_string(), + Self::LateBound { kind, ctime } => { + let kind = match kind { + BoundRegionKind::BrAnon(..) => "'_", + BoundRegionKind::BrNamed(_, name) => name.as_str(), + BoundRegionKind::BrEnv => "'env", + }; + let ctime = match ctime { + LateBoundRegionConversionTime::FnCall => "fn", + LateBoundRegionConversionTime::HigherRankedType => "hrt", + LateBoundRegionConversionTime::AssocTypeProjection(_) => "atp", + }; + format!("{kind} ({ctime})") + } + Self::Placeholder(None) => "'for".to_string(), + Self::Placeholder(Some(p)) => { + let kind = match &p.bound.kind { + BoundRegionKind::BrAnon(..) => "'_", + BoundRegionKind::BrNamed(_, name) => name.as_str(), + BoundRegionKind::BrEnv => "'env", + }; + format!("{kind}@{:?}", p.universe) + } + Self::MiscLocal => "?misc?".to_string(), + Self::UnknownLocal => "???".to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct ParamRegion { + pub regions: Vec, +} + +impl<'tcx> RegionInfoMap<'tcx> { + pub fn new(universal: usize, total: usize) -> Self { + let region_info = IndexVec::from_fn_n( + |r: RegionVid| { + if r.index() < universal { + RegionKind::UnknownUniversal + } else { + RegionKind::UnknownLocal + } + }, + total, + ); + Self { + region_info, + universal, + } + } + + pub(super) fn set(&mut self, r: RegionVid, kind: RegionKind<'tcx>) { + match self.get(r) { + RegionKind::UnknownUniversal => assert!(kind.is_static() || kind.is_function()), + RegionKind::UnknownLocal => assert!( + kind.is_place() + || kind.is_borrow() + || kind.is_early_bound() + || kind.is_late_bound() + || kind.is_placeholder() + || kind.is_misc_local(), + "{kind:?}" + ), + other => panic!("{other:?}"), + } + self.region_info[r] = kind; + } + pub(super) fn set_param(&mut self, r: RegionVid, local: RegionVid) { + let info = &mut self.region_info[r]; + match info { + RegionKind::Param(ParamRegion { regions }) => regions.push(local), + RegionKind::UnknownUniversal => { + *info = RegionKind::Param(ParamRegion { + regions: vec![local], + }) + } + // Ignore equivalences between static and a local + RegionKind::Static => (), + _ => panic!("{info:?}"), + } + } + pub(super) fn set_region_info(&mut self, r: RegionVid, info: RegionVariableInfo) { + // TODO: figure out the universes stuff + // assert_eq!(info.universe.index(), 0); + match info.origin { + RegionVariableOrigin::MiscVariable(_) => self.set(r, RegionKind::MiscLocal), + RegionVariableOrigin::PatternRegion(_) => todo!(), + RegionVariableOrigin::AddrOfRegion(_) => todo!(), + RegionVariableOrigin::Autoref(_) => todo!(), + RegionVariableOrigin::Coercion(_) => todo!(), + RegionVariableOrigin::EarlyBoundRegion(_, name) => { + self.set(r, RegionKind::EarlyBound(name)) + } + RegionVariableOrigin::LateBoundRegion(_, kind, ctime) => { + self.set(r, RegionKind::LateBound { kind, ctime }) + } + RegionVariableOrigin::UpvarRegion(_, _) => todo!(), + RegionVariableOrigin::Nll(k) => match k { + NllRegionVariableOrigin::FreeRegion => { + assert!(self.get(r).is_universal()); + return; + } + NllRegionVariableOrigin::Placeholder(p) => { + self.set(r, RegionKind::Placeholder(Some(p))) + } + NllRegionVariableOrigin::Existential { from_forall: true } => { + self.set(r, RegionKind::Placeholder(None)) + } + NllRegionVariableOrigin::Existential { from_forall: false } => (), + }, + } + assert!(!self.get(r).is_universal()); + } + + // #[tracing::instrument(name = "RegionInfoMap::get", level = "trace", skip(self), ret)] + pub fn get(&self, r: RegionVid) -> &RegionKind<'tcx> { + &self.region_info[r] + } + pub fn is_universal(&self, r: RegionVid) -> bool { + r.index() < self.universal + } + pub(super) fn universal(&self) -> usize { + self.universal + } + + pub fn region_len(&self) -> usize { + self.region_info.len() + } + pub fn all_regions(&self) -> impl Iterator { + (0..self.region_info.len()).map(RegionVid::from) + } + pub fn for_local(&self, r: RegionVid, l: Local) -> bool { + self.get(r).get_place() == Some(l) + } +} diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs new file mode 100644 index 00000000000..84daf1f0873 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -0,0 +1,160 @@ +// © 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_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, Borrows, OutlivesConstraint, PoloniusInput, RustcFacts}, + }, + data_structures::fx::FxHashMap, + dataflow::{Analysis, ResultsCursor}, + index::IndexVec, + middle::{ + mir::{Body, Local, Location, Operand, RETURN_PLACE}, + ty::{RegionVid, Ty, TyCtxt}, + }, +}; + +use crate::{ + coupling_graph::region_info::map::ParamRegion, + utils::{Place, PlaceRepacker}, +}; + +use self::map::{RegionInfoMap, RegionKind}; + +use super::{region_place::PlaceRegion, shared_borrow_set::SharedBorrowSet}; + +pub mod map; + +#[derive(Debug)] +pub struct RegionInfo<'tcx> { + pub map: RegionInfoMap<'tcx>, + pub static_region: RegionVid, + pub function_region: RegionVid, +} + +impl<'tcx> RegionInfo<'tcx> { + pub fn new( + rp: PlaceRepacker<'_, 'tcx>, + input_facts: &PoloniusInput, + facts2: &BorrowckFacts2<'tcx>, + sbs: &SharedBorrowSet<'tcx>, + ) -> Self { + let mut map = RegionInfoMap::new( + input_facts.universal_region.len(), + facts2.region_inference_context.var_infos.len(), + ); + // Assumption: universal regions are the first regions + debug_assert!(input_facts + .universal_region + .iter() + .all(|&r| map.is_universal(r))); + + // Init universals + let (static_region, function_region) = + Self::initialize_universals(&mut map, rp, input_facts, facts2); + + // Init locals + Self::initialize_locals(&mut map, rp, input_facts, facts2, sbs); + + // Init from `var_infos` + for r in map.all_regions() { + let info = facts2.region_inference_context.var_infos[r]; + map.set_region_info(r, info); + } + + Self { + map, + static_region, + function_region, + } + } + + pub fn initialize_universals( + map: &mut RegionInfoMap<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + input_facts: &PoloniusInput, + facts2: &BorrowckFacts2, + ) -> (RegionVid, RegionVid) { + let static_region = *input_facts.universal_region.first().unwrap(); + // Check that static is actually first + if cfg!(debug_symbols) { + // Static should outlive all other placeholders + let outlives = input_facts + .known_placeholder_subset + .iter() + .filter(|&&(sup, sub)| { + assert_ne!(static_region, sub); + static_region == sup + }); + assert_eq!(outlives.count(), map.universal() - 1); + } + let function_region = *input_facts.universal_region.last().unwrap(); + // Check that the function region is actually last + if cfg!(debug_symbols) { + // All other placeholders should outlive the function region + let outlives = input_facts + .known_placeholder_subset + .iter() + .filter(|&&(sup, sub)| { + assert_ne!(function_region, sup); + function_region == sub + }); + assert_eq!(outlives.count(), map.universal() - 1); + } + + // Collect equivalences between universal and local + let mut equivalence_map: FxHashMap<(RegionVid, RegionVid), u8> = FxHashMap::default(); + for c in facts2 + .region_inference_context + .outlives_constraints() + .filter(|o| { + o.locations.from_location().is_none() + && (map.is_universal(o.sup) || map.is_universal(o.sub)) + && !(map.is_universal(o.sup) && map.is_universal(o.sub)) + }) + { + let (universal, other, incr) = if map.is_universal(c.sup) { + (c.sup, c.sub, 1) + } else { + (c.sub, c.sup, 3) + }; + let entry = equivalence_map.entry((universal, other)).or_default(); + *entry += incr; + assert!(*entry == 1 || *entry == 3 || *entry == 4); + } + + // Set the entries in the map + map.set(static_region, RegionKind::Static); + for ((universal, local), sum) in equivalence_map { + if sum == 4 { + map.set_param(universal, local); + } + } + map.set(function_region, RegionKind::Function); + (static_region, function_region) + } + + pub fn initialize_locals( + map: &mut RegionInfoMap<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + input_facts: &PoloniusInput, + facts2: &BorrowckFacts2<'tcx>, + sbs: &SharedBorrowSet<'tcx>, + ) { + for &(local, region) in &input_facts.use_of_var_derefs_origin { + map.set(region, RegionKind::Place { region, local }); + } + for data in sbs + .location_map + .values() + .chain(facts2.borrow_set.location_map.values()) + { + map.set(data.region, RegionKind::Borrow(data.clone())); + } + } +} diff --git a/mir-state-analysis/src/coupling_graph/context/region_place.rs b/mir-state-analysis/src/coupling_graph/context/region_place.rs index a181d2cfeed..6e35c70d820 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_place.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_place.rs @@ -7,25 +7,34 @@ use std::fmt::Debug; use prusti_rustc_interface::{ + borrowck::{ + borrow_set::{BorrowData, BorrowSet, LocalsStateAtExit, TwoPhaseActivation}, + consumers::{BorrowIndex, PlaceExt}, + }, data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, - index::bit_set::BitSet, - dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, - borrowck::{borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation, LocalsStateAtExit}, consumers::{BorrowIndex, PlaceExt}}, - middle::{mir::{ConstraintCategory, RETURN_PLACE, Location, Rvalue, Body, traversal, visit::Visitor}, ty::{RegionVid, TyKind}}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{ + traversal, visit::Visitor, Body, ConstraintCategory, Local, Location, Rvalue, + RETURN_PLACE, + }, + ty::{RegionVid, TyKind}, + }, }; -use crate::utils::{Place, PlaceRepacker, display::PlaceDisplay}; +use crate::utils::{display::PlaceDisplay, Place, PlaceRepacker}; use super::shared_borrow_set::SharedBorrowSet; #[derive(Clone)] -pub struct Perms<'tcx> { +pub struct PlaceRegion<'tcx> { pub place: Place<'tcx>, pub region: Option, pub pretty: PlaceDisplay<'tcx>, } -impl Debug for Perms<'_> { +impl Debug for PlaceRegion<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hint = if self.pretty.is_user() { format!(" <<{:?}>>", self.pretty) @@ -39,38 +48,58 @@ impl Debug for Perms<'_> { } } -impl<'tcx> Perms<'tcx> { +impl<'tcx> PlaceRegion<'tcx> { pub fn region_place_map( use_of_var_derefs_origin: &Vec<(Local, RegionVid)>, borrows: &BorrowSet<'tcx>, shared_borrows: &SharedBorrowSet<'tcx>, rp: PlaceRepacker<'_, 'tcx>, - ) -> FxHashMap> { + ) -> FxHashMap> { let mut map = FxHashMap::default(); for &(l, r) in use_of_var_derefs_origin { let place = l.into(); let perm = if let Some(place) = Self::try_make_precise(place, r, rp) { - Perms { place, region: None, pretty: place.to_string(rp) } + PlaceRegion { + place, + region: None, + pretty: place.to_string(rp), + } } else { - Perms { place, region: Some(r), pretty: place.to_string(rp) } + PlaceRegion { + place, + region: Some(r), + pretty: place.to_string(rp), + } }; let existing = map.insert(r, perm); assert!(existing.is_none(), "{existing:?} vs {:?}", map[&r]); } - for data in shared_borrows.location_map.values().chain(borrows.location_map.values()) { + for data in shared_borrows + .location_map + .values() + .chain(borrows.location_map.values()) + { let place = data.borrowed_place.into(); - let perm = Perms { + let perm = PlaceRegion { place, region: None, pretty: place.to_string(rp), }; let existing = map.insert(data.region, perm); - assert!(existing.is_none(), "{existing:?} vs {:?}", map[&data.region]); + assert!( + existing.is_none(), + "{existing:?} vs {:?}", + map[&data.region] + ); } map } - fn try_make_precise(mut p: Place<'tcx>, r: RegionVid, rp: PlaceRepacker<'_, 'tcx>) -> Option> { + fn try_make_precise( + mut p: Place<'tcx>, + r: RegionVid, + rp: PlaceRepacker<'_, 'tcx>, + ) -> Option> { let mut ty = p.ty(rp).ty; while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { ty = inner_ty; diff --git a/mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs b/mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs index d4613062873..45ddfd43d3a 100644 --- a/mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs +++ b/mir-state-analysis/src/coupling_graph/context/shared_borrow_set.rs @@ -5,11 +5,20 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. use prusti_rustc_interface::{ + borrowck::{ + borrow_set::{BorrowData, BorrowSet, LocalsStateAtExit, TwoPhaseActivation}, + consumers::{BorrowIndex, PlaceExt}, + }, data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, - index::bit_set::BitSet, - dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, - borrowck::{borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation, LocalsStateAtExit}, consumers::{BorrowIndex, PlaceExt}}, - middle::{mir::{ConstraintCategory, RETURN_PLACE, Location, Place, Rvalue, Body, traversal, visit::Visitor}, ty::{TyCtxt}}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{ + traversal, visit::Visitor, Body, ConstraintCategory, Local, Location, Place, Rvalue, + RETURN_PLACE, + }, + ty::TyCtxt, + }, }; // Identical to `rustc_borrowck/src/borrow_set.rs` but for shared borrows @@ -20,11 +29,7 @@ pub struct SharedBorrowSet<'tcx> { } impl<'tcx> SharedBorrowSet<'tcx> { - pub(crate) fn build( - tcx: TyCtxt<'tcx>, - body: &Body<'tcx>, - borrows: &BorrowSet<'tcx>, - ) -> Self { + pub(crate) fn build(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, borrows: &BorrowSet<'tcx>) -> Self { let mut visitor = GatherBorrows { tcx, body: &body, @@ -78,7 +83,10 @@ impl<'a, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'tcx> { let (idx, _) = self.location_map.insert_full(location, borrow); let idx = BorrowIndex::from(idx); - self.local_map.entry(borrowed_place.local).or_default().insert(idx); + self.local_map + .entry(borrowed_place.local) + .or_default() + .insert(idx); } self.super_assign(assigned_place, rvalue, location) diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index f7d8a4da166..623d506e83d 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -7,11 +7,14 @@ use std::borrow::Cow; use prusti_rustc_interface::{ - data_structures::fx::{FxHashMap, FxHashSet}, - index::bit_set::BitSet, - dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, borrowck::consumers::BorrowIndex, - middle::{mir::{BorrowKind, ConstraintCategory}, ty::{RegionVid, TyKind}}, + data_structures::fx::{FxHashMap, FxHashSet}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{BorrowKind, ConstraintCategory, Local}, + ty::{RegionVid, TyKind}, + }, }; use super::{graph::EdgeInfo, triple::Cg}; @@ -39,27 +42,23 @@ impl<'a, 'tcx> Cg<'a, 'tcx> { } } impl<'a, 'tcx> Cg<'a, 'tcx> { - // pub(crate) fn is_empty_node(&self, n: NodeId) -> bool { - // self.get_corresponding_places(n).is_none() - // } - // fn get_corresponding_places(&self, n: NodeId) -> Option<&Perms<'tcx>> { - // let node = self.get_node(n); - // self.cgx.region_map.get(&node.region) - // } - /// For regions created by `... = &'r ...`, find the kind of borrow. - pub(crate) fn borrow_kind(&self, r: RegionVid) -> Option { - // TODO: we could put this into a `FxHashMap` in `cgx`. - self.cgx.facts2.borrow_set.location_map.iter() - .chain(&self.cgx.sbs.location_map) - .find(|(_, data)| data.region == r) - .map(|(_, data)| data.kind) - } - fn non_empty_edges(&self, r: RegionVid, start: RegionVid, reasons: FxHashSet>, visited: &mut FxHashSet) -> Vec> { + fn non_empty_edges( + &self, + r: RegionVid, + start: RegionVid, + mut reasons: FxHashSet>, + visited: &mut FxHashSet, + ) -> Vec> { let mut edges = Vec::new(); if !visited.insert(r) { return edges; } - if !self.skip_empty_nodes || self.has_associated_place(r) { + if (self.dot_filter)(self.cgx.region_info.map.get(r)) { + // Remove empty reason + reasons.remove(&EdgeInfo { + creation: None, + reason: None, + }); return vec![Edge::new(start, r, reasons)]; } for (&b, edge) in &self.graph.nodes[r].blocks { @@ -73,7 +72,9 @@ impl<'a, 'tcx> Cg<'a, 'tcx> { } impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { - fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(self.get_id()).unwrap() } + fn graph_id(&'a self) -> dot::Id<'a> { + dot::Id::new(self.get_id()).unwrap() + } fn node_id(&'a self, r: &RegionVid) -> dot::Id<'a> { let r = format!("{r:?}").replace("'?", "N"); @@ -81,49 +82,54 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { dot::Id::new(id).unwrap() } - // fn edge_end_arrow(&'a self, e: &Edge) -> dot::Arrow { - // if self.borrow_kind(e.from).is_some() { - // dot::Arrow::from_arrow(dot::ArrowShape::Dot(dot::Fill::Filled)) - // } else { - // dot::Arrow::default() - // } - // } fn edge_style(&'a self, e: &Edge<'tcx>) -> dot::Style { - if self.borrow_kind(e.from).is_some() { + if self.cgx.region_info.map.get(e.from).is_borrow() { dot::Style::Dotted } else { dot::Style::Solid } } fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { - let mut label = e.reasons.iter().map(|s| format!("{s}\n")).collect::(); - label = label[..label.len() - 1].to_string(); + let mut label = e + .reasons + .iter() + .map(|s| format!("{s}\n")) + .collect::(); + if label.len() > 0 { + label = label[..label.len() - 1].to_string(); + } dot::LabelText::LabelStr(Cow::Owned(label)) } + fn node_color(&'a self, r: &RegionVid) -> Option> { + let kind = self.get_kind(*r); + if kind.is_universal() { + Some(dot::LabelText::LabelStr(Cow::Borrowed("red"))) + } else { + None + } + } fn node_shape(&'a self, r: &RegionVid) -> Option> { if self.graph.static_regions.contains(&r) { return Some(dot::LabelText::LabelStr(Cow::Borrowed("house"))); } - self.borrow_kind(*r).map(|kind| match kind { - BorrowKind::Shared => - dot::LabelText::LabelStr(Cow::Borrowed("box")), - BorrowKind::Shallow => - dot::LabelText::LabelStr(Cow::Borrowed("triangle")), - BorrowKind::Mut { kind } => - dot::LabelText::LabelStr(Cow::Borrowed("ellipse")), - }) + // For regions created by `... = &'r ...`, find the kind of borrow. + self.cgx + .region_info + .map + .get(*r) + .get_borrow() + .map(|data| match data.kind { + BorrowKind::Shared => dot::LabelText::LabelStr(Cow::Borrowed("box")), + BorrowKind::Shallow => dot::LabelText::LabelStr(Cow::Borrowed("triangle")), + BorrowKind::Mut { kind } => dot::LabelText::LabelStr(Cow::Borrowed("polygon")), + }) } fn node_label(&'a self, r: &RegionVid) -> dot::LabelText<'a> { if *r == RegionVid::MAX { // return dot::LabelText::LabelStr(Cow::Owned()); unimplemented!(); } - let mut label = format!("{r:?}"); - if let Some(place) = self.get_associated_place(*r) { - label += &format!("\n{place:?}"); - } else { - label += "\n???"; - } + let mut label = format!("{r:?}\n{}", self.get_kind(*r).to_string(&self.cgx)); // Not super useful: the `origin` is always NLL. // if let Some(region_info) = self.facts2.region_inference_context.var_infos.get(*r) { // label += &format!("\n{:?}, {:?}", region_info.origin, region_info.universe); @@ -134,8 +140,10 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { fn nodes(&self) -> dot::Nodes<'a, RegionVid> { - let mut nodes: Vec<_> = self.graph.all_nodes() - .filter(|(r, _)| !self.skip_empty_nodes || self.has_associated_place(*r)) + let mut nodes: Vec<_> = self + .graph + .all_nodes() + .filter(|(r, _)| (self.dot_filter)(self.cgx.region_info.map.get(*r))) .map(|(r, _)| r) .collect(); // if self.static_regions.len() > 1 { @@ -147,12 +155,9 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { let mut edges = Vec::new(); for (r, n) in self.graph.all_nodes() { - if self.skip_empty_nodes && !self.has_associated_place(r) { + if !(self.dot_filter)(self.cgx.region_info.map.get(r)) { continue; } - // if n.borrows_from_static { - // edges.push(Edge::new(c, usize::MAX, FxHashSet::default())); - // } let visited = &mut FxHashSet::from_iter([r]); for (&b, edge) in &n.blocks { edges.extend(self.non_empty_edges(b, r, edge.clone(), visited)); @@ -161,7 +166,11 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { Cow::Owned(edges) } - fn source(&self, e: &Edge<'tcx>) -> RegionVid { e.from } + fn source(&self, e: &Edge<'tcx>) -> RegionVid { + e.from + } - fn target(&self, e: &Edge<'tcx>) -> RegionVid { e.to } + fn target(&self, e: &Edge<'tcx>) -> RegionVid { + e.to + } } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index eaf4e45e58c..68a639142d3 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -8,25 +8,37 @@ use std::cell::RefCell; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ - data_structures::fx::{FxIndexMap, FxHashSet}, borrowck::{ borrow_set::{BorrowData, TwoPhaseActivation}, - consumers::{Borrows, BorrowIndex, RichLocation, OutlivesConstraint, PlaceConflictBias, places_conflict, calculate_borrows_out_of_scope_at_location}, + consumers::{ + calculate_borrows_out_of_scope_at_location, places_conflict, BorrowIndex, Borrows, + OutlivesConstraint, PlaceConflictBias, RichLocation, + }, }, + data_structures::fx::{FxHashSet, FxIndexMap}, dataflow::{Analysis, AnalysisDomain, ResultsCursor}, - index::{bit_set::{BitSet, HybridBitSet}, Idx}, + index::{ + bit_set::{BitSet, HybridBitSet}, + Idx, + }, middle::{ mir::{ - TerminatorKind, Operand, ConstantKind, StatementKind, Rvalue, - visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Place, Location, Statement, Terminator, TerminatorEdges, RETURN_PLACE, + visit::Visitor, BasicBlock, Body, CallReturnPlaces, ConstantKind, Local, Location, + Operand, Place, Rvalue, Statement, StatementKind, Terminator, TerminatorEdges, + TerminatorKind, RETURN_PLACE, START_BLOCK, }, ty::{RegionVid, TyCtxt}, }, }; use crate::{ + coupling_graph::{ + graph::{Graph, Node}, + outlives_info::AssignsToPlace, + CgContext, + }, free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, - utils::PlaceRepacker, coupling_graph::{graph::{Graph, Node}, CgContext}, + utils::PlaceRepacker, }; use super::triple::Cg; @@ -35,22 +47,32 @@ use super::triple::Cg; pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { let mut graph = Vec::new(); let body = c.body(); + c.seek_to_block_start(START_BLOCK); + let mut g = c.get().clone(); + g.id = Some(format!("start")); + dot::render(&g, &mut graph).unwrap(); + for (block, data) in body.basic_blocks.iter_enumerated() { if data.is_cleanup { continue; } c.seek_to_block_start(block); let mut g = c.get().clone(); + g.dot_filter = |k| k.is_local(); g.id = Some(format!("{block:?}_pre")); dot::render(&g, &mut graph).unwrap(); - for statement_index in 0..body.terminator_loc(block).statement_index+1 { - c.seek_after_primary_effect(Location { block, statement_index }); + for statement_index in 0..body.terminator_loc(block).statement_index + 1 { + c.seek_after_primary_effect(Location { + block, + statement_index, + }); g = c.get().clone(); + g.dot_filter = |k| k.is_local(); g.id = Some(format!("{block:?}_{statement_index}")); dot::render(&g, &mut graph).unwrap(); } if let TerminatorKind::Return = data.terminator().kind { - g.skip_empty_nodes = true; + g.dot_filter = |k| !k.is_unknown_local(); g.id = Some(format!("{block:?}_ret")); dot::render(&g, &mut graph).unwrap(); } @@ -60,7 +82,11 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a let regex = regex::Regex::new(r"digraph (([^ ])+) \{").unwrap(); let combined = regex.replace_all(&combined, "subgraph cluster_$1 {\n label = \"$1\""); - std::fs::write("log/coupling/all.dot", format!("digraph root {{\n{combined}}}")).expect("Unable to write file"); + std::fs::write( + "log/coupling/all.dot", + format!("digraph root {{\n{combined}}}"), + ) + .expect("Unable to write file"); } pub(crate) struct CoupligGraph<'a, 'tcx> { @@ -79,18 +105,45 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { let borrow_set = &cgx.facts2.borrow_set; let regioncx = &*cgx.facts2.region_inference_context; - let out_of_scope = calculate_borrows_out_of_scope_at_location(cgx.rp.body(), regioncx, borrow_set); + let out_of_scope = + calculate_borrows_out_of_scope_at_location(cgx.rp.body(), regioncx, borrow_set); let flow_borrows = Borrows::new(cgx.rp.tcx(), cgx.rp.body(), regioncx, borrow_set) .into_engine(cgx.rp.tcx(), cgx.rp.body()) .pass_name("borrowck") .iterate_to_fixpoint() .into_results_cursor(cgx.rp.body()); + if !top_crates { + println!("body: {:#?}", cgx.rp.body()); + println!("\ninput_facts: {:?}", cgx.facts.input_facts); + println!("output_facts: {:#?}\n", cgx.facts.output_facts); + println!("location_map: {:#?}\n", cgx.facts2.borrow_set.location_map); + println!( + "activation_map: {:#?}\n", + cgx.facts2.borrow_set.activation_map + ); + println!("local_map: {:#?}\n", cgx.facts2.borrow_set.local_map); + // println!("locals_state_at_exit: {:#?}\n", facts2.borrow_set.locals_state_at_exit); + let lt = cgx.facts.location_table.borrow(); + let lt = lt.as_ref().unwrap(); + for pt in lt.all_points() { + println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, facts.output_facts.origins_live_at(pt)); + } + println!("out_of_scope: {:?}", out_of_scope); + println!("cgx: {:#?}\n", cgx); + for r in cgx.region_info.map.all_regions() { + println!( + "R: {r:?}: {:?}", + cgx.facts2.region_inference_context.var_infos.get(r) + ); + } + } + Self { cgx, out_of_scope, flow_borrows: RefCell::new(flow_borrows), - top_crates + top_crates, } } } @@ -104,33 +157,7 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { - // // Sanity check (already done when creating region to place map) - // if cfg!(debug_assertions) { - // let input_facts = self.facts.input_facts.borrow(); - // let use_of_var_derefs_origin = &input_facts.as_ref().unwrap().use_of_var_derefs_origin; - // // Each region should only have a single associated local - // for (_, r) in use_of_var_derefs_origin { - // assert!(use_of_var_derefs_origin.iter().filter(|(_, ro)| r == ro).count() <= 1, "{use_of_var_derefs_origin:?}"); - // } - // } - - if !self.top_crates { - println!("body: {body:#?}"); - println!("\ninput_facts: {:?}", self.cgx.facts.input_facts); - println!("output_facts: {:#?}\n", self.cgx.facts.output_facts); - println!("location_map: {:#?}\n", self.cgx.facts2.borrow_set.location_map); - println!("activation_map: {:#?}\n", self.cgx.facts2.borrow_set.activation_map); - println!("local_map: {:#?}\n", self.cgx.facts2.borrow_set.local_map); - println!("region_inference_context: {:#?}\n", self.cgx.facts2.region_inference_context.outlives_constraints().collect::>()); - // println!("locals_state_at_exit: {:#?}\n", self.facts2.borrow_set.locals_state_at_exit); - let lt = self.cgx.facts.location_table.borrow(); - let lt = lt.as_ref().unwrap(); - for pt in lt.all_points() { - println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, self.facts.output_facts.origins_live_at(pt)); - } - println!("out_of_scope: {:?}", self.out_of_scope); - println!("region_map: {:#?}\n", self.cgx.region_map); - } + state.initialize_start_block() } } @@ -151,24 +178,36 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup + { + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + false, + ); } - self.flow_borrows.borrow_mut().seek_to_block_start(location.block); + self.flow_borrows + .borrow_mut() + .seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } - self.flow_borrows.borrow_mut().seek_after_primary_effect(location); + self.flow_borrows + .borrow_mut() + .seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); let delta = calculate_diff(&other, &state.live); let oos = self.out_of_scope.get(&location); state.handle_kills(&delta, oos, location); + state.handle_outlives(location, statement.kind.assigns_to()); state.visit_statement(statement, location); - state.handle_outlives(&delta, location); + state.live = other; if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}.dot", state.version), + false, + ); } } @@ -188,24 +227,68 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot(format!("log/coupling/individual/{l}_v{}_start.dot", state.version)); + if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup + { + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + false, + ); } - self.flow_borrows.borrow_mut().seek_to_block_start(location.block); + self.flow_borrows + .borrow_mut() + .seek_to_block_start(location.block); state.live = self.flow_borrows.borrow().get().clone(); } - self.flow_borrows.borrow_mut().seek_after_primary_effect(location); + self.flow_borrows + .borrow_mut() + .seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); let delta = calculate_diff(&other, &state.live); let oos = self.out_of_scope.get(&location); state.handle_kills(&delta, oos, location); + state.handle_outlives(location, terminator.kind.assigns_to()); state.visit_terminator(terminator, location); - state.handle_outlives(&delta, location); + + match &terminator.kind { + TerminatorKind::Return => { + if cfg!(debug_assertions) + && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup + { + let l = format!("{location:?}").replace('[', "_").replace(']', ""); + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_pre.dot", state.version), + false, + ); + } + // Pretend we have a storage dead for all `always_live_locals` other than the args/return + for l in self.cgx.rp.always_live_locals_non_args().iter() { + state.kill_shared_borrows_on_place(location, l.into()); + } + // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` + for r in self + .cgx + .facts2 + .borrow_set + .location_map + .values() + .chain(self.cgx.sbs.location_map.values()) + { + state.graph.remove(r.region, Some(location)); + } + + state.merge_for_return(location); + } + _ => (), + }; + state.live = other; if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot(format!("log/coupling/individual/{l}_v{}.dot", state.version)); + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}.dot", state.version), + false, + ); } terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index 53c6e888a48..bf4df80aa67 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -4,23 +4,33 @@ // 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, Display}, borrow::Cow}; +use std::{ + borrow::Cow, + fmt::{Debug, Display, Formatter, Result}, +}; use derive_more::{Deref, DerefMut}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, OutlivesConstraint}, + }, data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, - index::bit_set::BitSet, - dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, - borrowck::{borrow_set::BorrowData, consumers::{BorrowIndex, OutlivesConstraint}}, - middle::{mir::{BasicBlock, ConstraintCategory, RETURN_PLACE, Location, Operand}, ty::{RegionVid, TyKind}}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{BasicBlock, ConstraintCategory, Local, Location, Operand, RETURN_PLACE}, + ty::{RegionVid, TyKind}, + }, }; use crate::{ + coupling_graph::{region_place::PlaceRegion, CgContext}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::{PlaceRepacker, Place}, coupling_graph::{CgContext, region_place::Perms}, + utils::{Place, PlaceRepacker}, }; use super::engine::CoupligGraph; @@ -33,29 +43,40 @@ pub struct Graph<'tcx> { } impl<'tcx> Graph<'tcx> { - // TODO: get it from `UniversalRegions` instead - pub fn static_region() -> RegionVid { - RegionVid::from_u32(0) - } - #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> bool { let edge = EdgeInfo { - creation: c.locations.from_location().map(|l| l.block), - reason: c.category, + creation: c.locations.from_location(), + reason: Some(c.category), }; self.outlives_inner(c.sup, c.sub, edge) } - pub fn outlives_static(&mut self, r: RegionVid, l: Location) { - if r == Self::static_region() { + pub fn outlives_static(&mut self, r: RegionVid, static_region: RegionVid, l: Location) { + if r == static_region { return; } - let edge = EdgeInfo { creation: Some(l.block), reason: ConstraintCategory::Internal }; - self.outlives_inner(r, Self::static_region(), edge); + let edge = EdgeInfo { + creation: Some(l), + reason: None, + }; + self.outlives_inner(r, static_region, edge); } + pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid) -> bool { + let edge = EdgeInfo { + creation: None, + reason: None, + }; + self.outlives_inner(sup, sub, edge) + } + // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] - pub(crate) fn outlives_inner(&mut self, sup: RegionVid, sub: RegionVid, edge: EdgeInfo<'tcx>) -> bool { + pub(crate) fn outlives_inner( + &mut self, + sup: RegionVid, + sub: RegionVid, + edge: EdgeInfo<'tcx>, + ) -> bool { if sup == sub { panic!(); return false; @@ -63,10 +84,18 @@ impl<'tcx> Graph<'tcx> { if self.static_regions.contains(&sub) { Self::set_static_region(&self.nodes, &mut self.static_regions, sup); } - self.nodes[sup].blocked_by.entry(sub).or_default().insert(edge); + self.nodes[sup] + .blocked_by + .entry(sub) + .or_default() + .insert(edge); self.nodes[sub].blocks.entry(sup).or_default().insert(edge) } - fn set_static_region(nodes: &IndexVec>, static_regions: &mut FxHashSet, r: RegionVid) { + fn set_static_region( + nodes: &IndexVec>, + static_regions: &mut FxHashSet, + r: RegionVid, + ) { if static_regions.insert(r) { for &sup in nodes[r].blocks.keys() { Self::set_static_region(nodes, static_regions, sup); @@ -94,14 +123,17 @@ impl<'tcx> Graph<'tcx> { // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). - pub fn remove(&mut self, r: RegionVid, l: Location) -> bool { + pub fn remove(&mut self, r: RegionVid, l: Option) -> bool { let (blocks, blocked_by) = self.remove_all_edges(r); let changed = !(blocks.is_empty() && blocked_by.is_empty()); for &block in blocks.keys() { for &blocked_by in blocked_by.keys() { // Do not rejoin nodes in a loop to themselves if blocked_by != block { - let edge = EdgeInfo { creation: Some(l.block), reason: ConstraintCategory::Internal }; + let edge = EdgeInfo { + creation: l, + reason: None, + }; self.outlives_inner(block, blocked_by, edge); } } @@ -117,151 +149,19 @@ impl<'tcx> Graph<'tcx> { changed } - // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - // pub fn set_borrows_from_static(&mut self, r: RegionVid) -> bool { - // if let Some(n) = self.region_to_node(r) { - // self.set_borrows_from_static_node(n) - // } else { - // false - // } - // } - // fn set_borrows_from_static_node(&mut self, n: NodeId) -> bool { - // let node = self.get_node_mut(n); - // let already_set = node.borrows_from_static; - // node.borrows_from_static = true; - // already_set - // } - // #[tracing::instrument(name = "Graph::equate_regions", level = "trace", skip(self), ret)] - // pub fn make_static(&mut self, r: RegionVid) -> bool { - // // TODO: instead of using `region_to_node`, do not add a node if already present? - // if let Some(n) = self.region_to_node(r) { - // self.make_static_node(n); - // true - // } else { - // false - // } - // } - // fn make_static_node(&mut self, n: NodeId) { - // // If there is a cycle we could have already removed and made static - // if let Some(mut node) = self.remove_node(n) { - // // println!("Making static node: {node:?}"); - // // self.static_regions.extend(node.regions.drain()); - // self.static_regions.insert(node.region); - // for (block_by, _) in node.blocked_by.drain() { - // // assert!(node.blocked_by.is_empty()); - // self.set_borrows_from_static_node(block_by); - // } - // for (block, _) in node.blocks.drain() { - // self.make_static_node(block); - // } - // } - // } - - // fn reachable(&self, from: NodeId, to: NodeId) -> Option> { - // // println!("Checking reachability from {} to {}", from, to); - // let mut nodes = FxHashSet::default(); - // if from == to { - // return Some(nodes); - // } - // for (&next, _) in &self.get_node(from).blocks { - // if let Some(others) = self.reachable(next, to) { - // nodes.insert(from); - // nodes.extend(others); - // } - // } - // if nodes.is_empty() { - // None - // } else { - // Some(nodes) - // } - // } - // fn region_to_node(&mut self, r: RegionVid) -> Option { - // if self.static_regions.contains(&r) { - // return None; - // } - // let mut last_none = self.nodes.len(); - // for (i, n) in self.nodes.iter().enumerate() { - // if let Some(n) = n { - // // if n.regions.contains(&r) { - // if n.region == r { - // return Some(i); - // } - // } else { - // last_none = i; - // } - // } - // if last_none == self.nodes.len() { - // self.nodes.push(Some(Node::new(last_none, r))); - // } else { - // self.nodes[last_none] = Some(Node::new(last_none, r)); - // } - // Some(last_none) - // } - // fn kill_node(&mut self, n: NodeId) { - // let removed = self.remove_node(n).unwrap(); - // for (blocked_by, _) in removed.blocked_by { - // // May have a diamond shape, so may - // if self.nodes[blocked_by].is_some() { - // self.kill_node(blocked_by); - // } - // } - // } - - // fn remove_node_rejoin(&mut self, id: NodeId) -> Node<'tcx> { - // let n = self.remove_node(id).unwrap(); - // for (&blocked_by, edge) in &n.blocked_by { - // for (&block, _) in &n.blocks { - // // Do not rejoin nodes in a loop to themselves - // if blocked_by != block { - // self.blocks(blocked_by, block, edge); - // } - // } - // if n.borrows_from_static { - // self.set_borrows_from_static_node(blocked_by); - // } - // } - // n - // } - // // Remove node without rejoining the graph - // fn remove_node(&mut self, n: NodeId) -> Option> { - // let to_remove = self.nodes[n].take()?; - // for &block in to_remove.blocks.keys() { - // let rem = self.get_node_mut(block).blocked_by.remove(&n); - // assert!(rem.is_some()); - // } - // for &block_by in to_remove.blocked_by.keys() { - // let rem = self.get_node_mut(block_by).blocks.remove(&n); - // assert!(rem.is_some()); - // } - // Some(to_remove) - // } - // pub(crate) fn get_node(&self, n: NodeId) -> &Node<'tcx> { - // self.nodes[n].as_ref().unwrap() - // } - // fn get_node_mut(&mut self, n: NodeId) -> &mut Node<'tcx> { - // self.nodes[n].as_mut().unwrap() - // } - // fn blocks(&mut self, n1: NodeId, n2: NodeId, reason: &FxHashSet>) -> bool { - // assert_ne!(n1, n2); - // let mut changed = false; - // let reasons = self.get_node_mut(n1).blocks.entry(n2).or_default(); - // let old_size = reasons.len(); - // reasons.extend(reason); - // changed = changed || reasons.len() != old_size; - // let reasons = self.get_node_mut(n2).blocked_by.entry(n1).or_default(); - // let old_size = reasons.len(); - // reasons.extend(reason); - // changed = changed || reasons.len() != old_size; - // changed - // } pub(crate) fn all_nodes(&self) -> impl Iterator)> { - self.nodes.iter_enumerated().filter(|(_, n)| !n.blocked_by.is_empty() || !n.blocks.is_empty()) + self.nodes + .iter_enumerated() + .filter(|(_, n)| !n.blocked_by.is_empty() || !n.blocks.is_empty()) } - fn remove_all_edges(&mut self, r: RegionVid) -> ( + fn remove_all_edges( + &mut self, + r: RegionVid, + ) -> ( FxHashMap>>, FxHashMap>>, - ) { + ) { let blocks = std::mem::replace(&mut self.nodes[r].blocks, FxHashMap::default()); for block in blocks.keys() { self.nodes[*block].blocked_by.remove(&r); @@ -276,44 +176,46 @@ impl<'tcx> Graph<'tcx> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { - // pub id: NodeId, - // pub regions: FxHashSet, - // pub region: RegionVid, pub blocks: FxHashMap>>, pub blocked_by: FxHashMap>>, - // pub borrows_from_static: bool, - // pub contained_by: Vec, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EdgeInfo<'tcx> { - pub creation: Option, - pub reason: ConstraintCategory<'tcx>, + pub creation: Option, + pub reason: Option>, } impl Display for EdgeInfo<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let reason = match self.reason { - ConstraintCategory::Return(_) => "return", - ConstraintCategory::Yield => "yield", - ConstraintCategory::UseAsConst => "const", - ConstraintCategory::UseAsStatic => "static", - ConstraintCategory::TypeAnnotation => "type", - ConstraintCategory::Cast => "cast", - ConstraintCategory::ClosureBounds => "closure", - ConstraintCategory::CallArgument(_) => "arg", - ConstraintCategory::CopyBound => "copy", - ConstraintCategory::SizedBound => "sized", - ConstraintCategory::Assignment => "assign", - ConstraintCategory::Usage => "use", - ConstraintCategory::OpaqueType => "opaque", - ConstraintCategory::ClosureUpvar(_) => "upvar", - ConstraintCategory::Predicate(_) => "pred", - ConstraintCategory::Boring => "boring", - ConstraintCategory::BoringNoLocation => "boring_nl", - ConstraintCategory::Internal => "internal", + let reason = if let Some(reason) = self.reason { + match reason { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + ConstraintCategory::UseAsConst => "const", + ConstraintCategory::UseAsStatic => "static", + ConstraintCategory::TypeAnnotation => "type", + ConstraintCategory::Cast => "cast", + ConstraintCategory::ClosureBounds => "closure", + ConstraintCategory::CallArgument(_) => "arg", + ConstraintCategory::CopyBound => "copy", + ConstraintCategory::SizedBound => "sized", + ConstraintCategory::Assignment => "assign", + ConstraintCategory::Usage => "use", + ConstraintCategory::OpaqueType => "opaque", + ConstraintCategory::ClosureUpvar(_) => "upvar", + ConstraintCategory::Predicate(_) => "pred", + ConstraintCategory::Boring => "?", + ConstraintCategory::BoringNoLocation => "? no_loc", + ConstraintCategory::Internal => "internal", + } + } else { + "other" }; - let creation = self.creation.map(|c| format!("{c:?}")).unwrap_or_else(|| "sig".to_string()); + let creation = self + .creation + .map(|c| format!("{c:?}")) + .unwrap_or_else(|| "sig".to_string()); write!(f, "{creation} ({reason})") } } @@ -323,7 +225,6 @@ impl<'tcx> Node<'tcx> { Self { blocks: FxHashMap::default(), blocked_by: FxHashMap::default(), - // contained_by: Vec::new(), } } } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 4ccea5fd6c9..ff7584e7755 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,7 +15,7 @@ use crate::{ utils::{PlaceOrdering, PlaceRepacker}, }; -use super::{triple::Cg, graph::Graph}; +use super::{graph::Graph, triple::Cg}; impl JoinSemiLattice for Cg<'_, '_> { #[tracing::instrument(name = "Cg::join", level = "debug", ret)] diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index d05d7c7c571..1516d8d6f46 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -4,27 +4,43 @@ // 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, Display}, borrow::Cow}; +use std::{ + borrow::Cow, + fmt::{Debug, Display, Formatter, Result}, +}; use derive_more::{Deref, DerefMut}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, OutlivesConstraint, RichLocation}, + }, data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, - index::bit_set::BitSet, - dataflow::fmt::DebugWithContext, index::IndexVec, middle::mir::Local, - borrowck::{borrow_set::BorrowData, consumers::{BorrowIndex, RichLocation, OutlivesConstraint}}, - middle::{mir::{BasicBlock, ConstraintCategory, Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, RETURN_PLACE, Location, Operand, visit::Visitor}, ty::{RegionVid, TyKind}}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{ + visit::Visitor, BasicBlock, ConstraintCategory, Local, Location, Operand, + Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + RETURN_PLACE, + }, + ty::{RegionVid, TyKind}, + }, }; use crate::{ + coupling_graph::{region_info::map::RegionKind, region_place::PlaceRegion, CgContext}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::{PlaceRepacker, Place}, coupling_graph::{CgContext, region_place::Perms}, + utils::{Place, PlaceRepacker}, }; -use super::{engine::{CoupligGraph, BorrowDelta}, graph::{Graph, Node}}; - +use super::{ + engine::{BorrowDelta, CoupligGraph}, + graph::{Graph, Node}, +}; #[derive(Clone)] pub struct Cg<'a, 'tcx> { @@ -33,7 +49,7 @@ pub struct Cg<'a, 'tcx> { pub(crate) live: BitSet, pub version: usize, - pub skip_empty_nodes: bool, + pub dot_filter: fn(&RegionKind<'_>) -> bool, pub top_crates: bool, pub graph: Graph<'tcx>, @@ -67,78 +83,72 @@ impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { } impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { + pub fn static_region(&self) -> RegionVid { + self.cgx.region_info.static_region + } + pub fn function_region(&self) -> RegionVid { + self.cgx.region_info.function_region + } + pub fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { let graph = Graph { - nodes: IndexVec::from_elem_n(Node::new(), cgx.facts2.region_inference_context.var_infos.len()), - static_regions: FxHashSet::from_iter([Graph::static_region()]), + nodes: IndexVec::from_elem_n(Node::new(), cgx.region_info.map.region_len()), + static_regions: FxHashSet::from_iter([cgx.region_info.static_region]), }; + let live = BitSet::new_empty(cgx.facts2.borrow_set.location_map.len()); - let mut result = Self { + Self { id: None, cgx, live, version: 0, - skip_empty_nodes: false, + dot_filter: |_| true, top_crates, graph, - }; - // let input_facts = facts.input_facts.borrow(); - // for &(r1, r2) in &input_facts.as_ref().unwrap().known_placeholder_subset { - // result.outlives(r1, r2); - // } - - ////// Ignore all global outlives constraints for now to have a nice graph (i.e. result is not in the same node as args) - let input_facts = cgx.facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - let constraints: Vec<_> = cgx.facts2.region_inference_context.outlives_constraints().collect(); - let constraints_no_loc: Vec<_> = constraints.iter().filter(|c| c.locations.from_location().is_none()).copied().collect(); - - // Make one single `'static` node - // let n = result.region_to_node(Self::static_region()); - // let node = result.get_node_mut(n); - // let mut to_make_static = vec![Self::static_region()]; - // while let Some(r) = to_make_static.pop() { - // for c in constraints.iter().filter(|c| c.sub == r) { - // if node.regions.insert(c.sup) { - // to_make_static.push(c.sup); - // } - // } - // } - // println!("Static node: {node:?}"); - // let mut to_make_static = vec![Self::static_region()]; - // while let Some(r) = to_make_static.pop() { - // for &c in constraints_no_loc.iter().filter(|c| c.sub == r) { - // if result.outlives(c) { - // to_make_static.push(c.sup); - // } - // } - // } - - result - } - pub(crate) fn get_associated_place(&self, r: RegionVid) -> Option<&Perms<'tcx>> { - self.cgx.region_map.get(&r) + } } - pub(crate) fn has_associated_place(&self, r: RegionVid) -> bool { - self.cgx.region_map.contains_key(&r) + pub fn initialize_start_block(&mut self) { + for c in &self.cgx.outlives_info.local_constraints { + self.graph.outlives(*c); + } + for c in &self.cgx.outlives_info.universal_local_constraints { + self.graph.outlives(*c); + } + for &(sup, sub) in &self.cgx.outlives_info.universal_constraints { + self.graph.outlives_placeholder(sup, sub); + } } + #[tracing::instrument(name = "get_associated_place", level = "trace", skip(self), ret)] + pub(crate) fn get_kind(&self, r: RegionVid) -> &RegionKind<'tcx> { + self.cgx.region_info.map.get(r) + } + #[tracing::instrument(name = "is_unknown_local", level = "trace", skip(self), ret)] + pub(crate) fn is_unknown_local(&self, r: RegionVid) -> bool { + self.get_kind(r).is_unknown_local() + } #[tracing::instrument(name = "handle_kills", level = "debug", skip(self))] - pub fn handle_kills(&mut self, delta: &BorrowDelta, oos: Option<&Vec>, location: Location) { + pub fn handle_kills( + &mut self, + delta: &BorrowDelta, + oos: Option<&Vec>, + location: Location, + ) { let input_facts = self.cgx.facts.input_facts.borrow(); let input_facts = input_facts.as_ref().unwrap(); let location_table = self.cgx.facts.location_table.borrow(); let location_table = location_table.as_ref().unwrap(); - // let input_facts = self.facts2.region_inference_context.borrow(); - for bi in delta.cleared.iter() { let data = &self.cgx.facts2.borrow_set[bi]; // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| bi == *b - ).copied().unwrap(); + let (r, _, l) = input_facts + .loan_issued_at + .iter() + .find(|(_, b, _)| bi == *b) + .copied() + .unwrap(); let l = rich_to_loc(location_table.to_location(l)); assert_eq!(r, data.region); assert_eq!(l, data.reserve_location); @@ -147,7 +157,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { self.graph.kill_borrow(data); } else { - self.graph.remove(data.region, location); + self.graph.remove(data.region, Some(location)); } // // TODO: is this necessary? @@ -171,9 +181,12 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { let data = &self.cgx.facts2.borrow_set[bi]; // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts.loan_issued_at.iter().find( - |(_, b, _)| bi == *b - ).copied().unwrap(); + let (r, _, l) = input_facts + .loan_issued_at + .iter() + .find(|(_, b, _)| bi == *b) + .copied() + .unwrap(); let l = rich_to_loc(location_table.to_location(l)); assert_eq!(r, data.region); assert_eq!(l, data.reserve_location); @@ -192,20 +205,37 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] - pub fn handle_outlives(&mut self, delta: &BorrowDelta, location: Location) { - for c in self.cgx.get_constraints_for_loc(Some(location)) { + pub fn handle_outlives(&mut self, location: Location, assigns_to: Option>) { + let local = assigns_to.map(|a| a.local); + for &c in self + .cgx + .outlives_info + .pre_constraints(location, local, &self.cgx.region_info) + { + self.graph.outlives(c); + } + if let Some(place) = assigns_to { + self.kill_shared_borrows_on_place(location, place); + } + for &c in self + .cgx + .outlives_info + .post_constraints(location, local, &self.cgx.region_info) + { self.graph.outlives(c); } } #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] - fn kill_shared_borrows_on_place(&mut self, location: Location, place: MirPlace<'tcx>) { + pub fn kill_shared_borrows_on_place(&mut self, location: Location, place: MirPlace<'tcx>) { let Some(local) = place.as_local() else { // Only remove nodes if assigned to the entire local (this is what rustc allows too) - return + return; }; - for (®ion, _) in self.cgx.region_map.iter().filter(|(_, p)| p.place.local == local) { - self.graph.remove(region, location); + for region in self.cgx.region_info.map.all_regions() { + if self.cgx.region_info.map.for_local(region, local) { + self.graph.remove(region, Some(location)); + } } } } @@ -216,168 +246,96 @@ impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { match *operand { Operand::Copy(_) => (), Operand::Move(place) => { - // TODO: check that this is ok (maybe issue if we move out of ref typed arg) - // self.kill_shared_borrows_on_place(location, *place); + self.kill_shared_borrows_on_place(location, place); } Operand::Constant(..) => { // TODO: anything here? } } } - - fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { - use StatementKind::*; - match &statement.kind { - Assign(box (place, _)) => { - self.kill_shared_borrows_on_place(location, *place); - } - &StorageDead(local) => { - self.kill_shared_borrows_on_place(location, local.into()); - } - _ => (), - }; - self.super_statement(statement, location); - } - - fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { - self.super_terminator(terminator, location); - use TerminatorKind::*; - match &terminator.kind { - Goto { .. } - | SwitchInt { .. } - | UnwindResume - | UnwindTerminate(_) - | Unreachable - | Assert { .. } - | GeneratorDrop - | FalseEdge { .. } - | FalseUnwind { .. } => (), - Return => { - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - let l = format!("{location:?}").replace('[', "_").replace(']', ""); - self.output_to_dot(format!("log/coupling/individual/{l}_v{}_pre.dot", self.version)); - } - - // Pretend we have a storage dead for all `always_live_locals` other than the args/return - for l in self.cgx.rp.always_live_locals_non_args().iter() { - self.kill_shared_borrows_on_place(location, l.into()); - } - // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` - for r in self.cgx.facts2.borrow_set.location_map.values().chain(self.cgx.sbs.location_map.values()) { - self.graph.remove(r.region, location); - } - - let input_facts = self.cgx.facts.input_facts.borrow(); - let input_facts = input_facts.as_ref().unwrap(); - // TODO: use this - let known_placeholder_subset = &input_facts.known_placeholder_subset; - for c in self.cgx.get_constraints_for_loc(None).filter(|c| !self.cgx.ignore_outlives(*c)) { - self.graph.outlives(c); - } - self.merge_for_return(); - } - &Drop { place, .. } => { - - } - &Call { destination, .. } => { - - } - &Yield { resume_arg, .. } => { - - } - InlineAsm { .. } => todo!("{terminator:?}"), - }; - } - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location:Location) { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { match rvalue { Rvalue::Use(Operand::Constant(_)) => { // TODO: this is a hack, find a better way to do things - for c in self.cgx.get_constraints_for_loc(Some(location)) { - self.graph.outlives_static(c.sub, location); + for c in + self.cgx + .outlives_info + .pre_constraints(location, None, &self.cgx.region_info) + { + self.graph + .outlives_static(c.sub, self.static_region(), location); } } _ => (), } self.super_rvalue(rvalue, location); } + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + self.super_statement(statement, location); + match &statement.kind { + StatementKind::AscribeUserType(box (p, _), _) => { + for &c in self + .cgx + .outlives_info + .type_ascription_constraints + .iter() + .filter(|c| { + self.cgx.region_info.map.for_local(c.sup, p.local) + || self.cgx.region_info.map.for_local(c.sub, p.local) + }) + { + self.graph.outlives(c); + } + } + _ => (), + } + } } impl<'tcx> Cg<'_, 'tcx> { #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] - pub fn merge_for_return(&self) { - let outlives: Vec<_> = self.cgx.facts2.region_inference_context.outlives_constraints().filter(|c| c.locations.from_location().is_none()).collect(); - let in_facts = self.cgx.facts.input_facts.borrow(); - let universal_region = &in_facts.as_ref().unwrap().universal_region; - - for (r, node) in self.graph.all_nodes() { - // Skip unknown empty nodes, we may want to figure out how to deal with them in the future - if !self.has_associated_place(r) { - continue; - } - - if self.borrow_kind(r).is_some() { - self.output_to_dot("log/coupling/error.dot"); - panic!("{node:?}"); - } else { - // let r = *node.regions.iter().next().unwrap(); - if universal_region.contains(&r) { - continue; + pub fn merge_for_return(&mut self, location: Location) { + let regions: Vec<_> = self.graph.all_nodes().map(|(r, _)| r).collect(); + for r in regions { + let kind = self.cgx.region_info.map.get(r); + match kind { + RegionKind::Static + | RegionKind::Param(_) + | RegionKind::UnknownUniversal + | RegionKind::Function => continue, + RegionKind::Place { local, .. } => { + if local.index() > self.cgx.rp.body().arg_count { + self.output_to_dot("log/coupling/error.dot", true); + panic!("{r:?} ({location:?}) {:?}", self.graph.nodes[r]); + } } - - let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == r - // let r = c.sub.as_u32(); // The thing that lives shorter - // r == 0 || r == 1 // `0` means that it's static, `1` means that it's the function region - }); - // If None then we have something left which doesn't outlive the function region! - // if proof.is_none() { - // let in_facts = self.facts.input_facts.borrow(); - // let r = &in_facts.as_ref().unwrap().universal_region; - // let outlives: Vec<_> = self.facts2.region_inference_context.outlives_constraints().collect(); - // println!("Dumping graph to `log/coupling/error.dot`. Error: {outlives:?} (ur: {r:?})"); - // // std::fs::remove_dir_all("log/coupling").ok(); - // // std::fs::create_dir_all("log/coupling/individual").unwrap(); - // let mut f = std::fs::File::create("log/coupling/error.dot").unwrap(); - // dot::render(self, &mut f).unwrap(); - // } - // println!("Found proof: {proof:?}"); - if proof.is_none() { - self.output_to_dot("log/coupling/error.dot"); - panic!("Found a region which does not outlive the function region: {r:?} {node:?} ({universal_region:?})"); + RegionKind::Borrow(_) => { + // Should not have borrows left + self.output_to_dot("log/coupling/error.dot", true); + panic!("{r:?} {:?}", self.graph.nodes[r]); } + // Ignore (and thus delete) early/late bound (mostly fn call) regions + RegionKind::EarlyBound(..) => (), + RegionKind::LateBound { .. } => (), + RegionKind::Placeholder(..) => (), + RegionKind::MiscLocal => (), + // Skip unknown empty nodes, we may want to figure out how to deal with them in the future + RegionKind::UnknownLocal => (), } - } - for &r in &self.graph.static_regions { - if universal_region.contains(&r) { - continue; - } - // It's possible that we get some random unnamed regions in the static set - if !self.has_associated_place(r) { - continue; - } - let proof = outlives.iter().find(|c| { - universal_region.contains(&c.sub) && c.sup == r - }); - if proof.is_none() { - self.output_to_dot("log/coupling/error.dot"); - panic!("Found a region which does not outlive the function region: {r:?} ({universal_region:?})"); - } + self.graph.remove(r, None); } } - pub fn output_to_dot>(&self, path: P) { - if !self.top_crates { + pub fn output_to_dot>(&self, path: P, error: bool) { + if !self.top_crates || error { let mut f = std::fs::File::create(path).unwrap(); dot::render(self, &mut f).unwrap(); } } } - - fn rich_to_loc(l: RichLocation) -> Location { match l { RichLocation::Start(l) => l, RichLocation::Mid(l) => l, } } - diff --git a/mir-state-analysis/src/coupling_graph/mod.rs b/mir-state-analysis/src/coupling_graph/mod.rs index 25f0559813c..462c87f0984 100644 --- a/mir-state-analysis/src/coupling_graph/mod.rs +++ b/mir-state-analysis/src/coupling_graph/mod.rs @@ -7,5 +7,5 @@ mod r#impl; mod context; -pub use r#impl::*; pub use context::*; +pub use r#impl::*; diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 2fabc1e0c9d..36f8fea2520 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -47,7 +47,8 @@ pub fn run_coupling_graph<'mir, 'tcx>( // if tcx.item_name(mir.source.def_id()).as_str().starts_with("main") { // return; // } - // if !format!("{:?}", mir.source.def_id()).contains("fmt") { + // if !format!("{:?}", mir.source.def_id()).ends_with("serialize_adjacently_tagged_variant)") { + // println!("{:?}", mir.source.def_id()); // return; // } let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); @@ -60,9 +61,16 @@ pub fn run_coupling_graph<'mir, 'tcx>( if cfg!(debug_assertions) && !top_crates { coupling_graph::engine::draw_dots(c); } + // panic!() } -pub fn test_coupling_graph<'tcx>(mir: &Body<'tcx>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, top_crates: bool) { +pub fn test_coupling_graph<'tcx>( + mir: &Body<'tcx>, + facts: &BorrowckFacts, + facts2: &BorrowckFacts2<'tcx>, + tcx: TyCtxt<'tcx>, + top_crates: bool, +) { let analysis = run_coupling_graph(mir, facts, facts2, tcx, top_crates); // free_pcs::check(analysis); } diff --git a/mir-state-analysis/src/loop/mod.rs b/mir-state-analysis/src/loop/mod.rs index 1464308441c..449ec150d45 100644 --- a/mir-state-analysis/src/loop/mod.rs +++ b/mir-state-analysis/src/loop/mod.rs @@ -6,7 +6,7 @@ use prusti_rustc_interface::{ index::{Idx, IndexVec}, - middle::mir::{BasicBlock, Body, START_BLOCK} + middle::mir::{BasicBlock, Body, START_BLOCK}, }; #[derive(Clone, Debug)] @@ -45,14 +45,11 @@ impl LoopSet { self.data[idx] &= !(1 << (loop_idx % Self::OFFSET)); } fn iter(&self) -> impl DoubleEndedIterator + '_ { - self.data - .iter() - .enumerate() - .flat_map(|(idx, &val)| { - let to = if val == 0 { 0 } else { Self::OFFSET }; - (0..to) - .filter(move |&i| val & (1 << i) != 0) - .map(move |i| LoopId::new(idx * Self::OFFSET + i)) + self.data.iter().enumerate().flat_map(|(idx, &val)| { + let to = if val == 0 { 0 } else { Self::OFFSET }; + (0..to) + .filter(move |&i| val & (1 << i) != 0) + .map(move |i| LoopId::new(idx * Self::OFFSET + i)) }) } } @@ -71,9 +68,11 @@ impl LoopAnalysis { }; let raw_bb_data: *const _ = &analysis.bb_data; - let mut visited_bbs: IndexVec = IndexVec::from_elem_n(false, body.basic_blocks.len()); + let mut visited_bbs: IndexVec = + IndexVec::from_elem_n(false, body.basic_blocks.len()); - let mut loop_head_bb_index: IndexVec = IndexVec::from_elem_n(NO_LOOP, body.basic_blocks.len()); + let mut loop_head_bb_index: IndexVec = + IndexVec::from_elem_n(NO_LOOP, body.basic_blocks.len()); for bb in body.basic_blocks.reverse_postorder().iter().copied().rev() { let data = &mut analysis.bb_data[bb]; for succ in body.basic_blocks[bb].terminator().successors() { @@ -117,11 +116,11 @@ impl LoopAnalysis { } /// Returns the loop which contains `bb` as well as all other loops of `bb`. pub fn outermost_loop(&self, bb: BasicBlock) -> Option { - self.loops(bb).next() + self.loops(bb).min_by_key(|l| self.loop_nest_depth(*l)) } /// Returns the loop which contains `bb` but no other loops of `bb`. pub fn innermost_loop(&self, bb: BasicBlock) -> Option { - self.loops(bb).next_back() + self.loops(bb).max_by_key(|l| self.loop_nest_depth(*l)) } fn consistency_check(&self) { @@ -131,12 +130,6 @@ impl LoopAnalysis { assert_eq!(self[l], START_BLOCK); } assert!(start_loops.is_empty()); - // Check that `innermost_loop` and `outermost_loop` are correct (TODO: remove this check) - for bb in self.bb_data.indices() { - let innermost_depth = self.innermost_loop(bb).map(|l| self.loop_nest_depth(l)).unwrap_or_default(); - let outermost_depth = self.outermost_loop(bb).map(|l| self.loop_nest_depth(l)).unwrap_or_default(); - assert!(self.loops(bb).map(|l| self.loop_nest_depth(l)).all(|d| outermost_depth <= d && d <= innermost_depth)); - } } } diff --git a/mir-state-analysis/src/utils/display.rs b/mir-state-analysis/src/utils/display.rs index f6a55137782..b61dee29fc7 100644 --- a/mir-state-analysis/src/utils/display.rs +++ b/mir-state-analysis/src/utils/display.rs @@ -4,7 +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 std::{borrow::Cow, collections::VecDeque, fmt::{Debug, Formatter, Result}}; +use std::{ + borrow::Cow, + collections::VecDeque, + fmt::{Debug, Formatter, Result}, +}; use prusti_rustc_interface::{ middle::{ @@ -21,21 +25,21 @@ use super::{Place, PlaceRepacker}; #[derive(Clone)] pub enum PlaceDisplay<'tcx> { Temporary(Place<'tcx>), - User(String), + User(Place<'tcx>, String), } impl<'tcx> Debug for PlaceDisplay<'tcx> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { PlaceDisplay::Temporary(place) => write!(f, "{place:?}"), - PlaceDisplay::User(s) => write!(f, "{s}"), + PlaceDisplay::User(place, s) => write!(f, "{place:?} <<{s}>>"), } } } impl<'tcx> PlaceDisplay<'tcx> { pub fn is_user(&self) -> bool { - matches!(self, PlaceDisplay::User(_)) + matches!(self, PlaceDisplay::User(..)) } } @@ -68,11 +72,8 @@ impl<'tcx> Place<'tcx> { } _ => None, }; - let Some(local_name) = repacker - .mir - .var_debug_info - .iter() - .find_map(get_local_name) else { + let Some(local_name) = repacker.mir.var_debug_info.iter().find_map(get_local_name) + else { return PlaceDisplay::Temporary(*self); }; Cow::Owned(local_name) @@ -102,7 +103,11 @@ impl<'tcx> Place<'tcx> { let fields = match def.adt_kind() { AdtKind::Struct => &def.non_enum_variant().fields, AdtKind::Enum => { - let Some(PlaceElem::Downcast(_, variant_idx)) = self.projection.get(index - 1) else { unimplemented!() } ; + let Some(PlaceElem::Downcast(_, variant_idx)) = + self.projection.get(index - 1) + else { + unimplemented!() + }; &def.variant(*variant_idx).fields } kind => unimplemented!("{kind:?}"), @@ -158,6 +163,6 @@ impl<'tcx> Place<'tcx> { } let full = parts.make_contiguous().join(""); - PlaceDisplay::User(full) + PlaceDisplay::User(*self, full) } } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 2717c0a10a3..43a6d423e38 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -7,18 +7,18 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashSet, dataflow::storage, - index::{Idx, bit_set::BitSet}, + index::{bit_set::BitSet, Idx}, middle::{ mir::{ - tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, - ProjectionElem, PlaceElem, + tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, PlaceElem, + ProjectionElem, }, - ty::{Ty, TyCtxt, TyKind, RegionVid}, + ty::{RegionVid, Ty, TyCtxt, TyKind}, }, target::abi::FieldIdx, }; -use crate::utils::ty::{DeepTypeVisitor, Stack, DeepTypeVisitable}; +use crate::utils::ty::{DeepTypeVisitable, DeepTypeVisitor, Stack}; use super::Place; @@ -68,7 +68,8 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { } pub fn always_live_locals_non_args(self) -> BitSet { let mut all = self.always_live_locals(); - for arg in 0..self.mir.arg_count+1 { // Includes `RETURN_PLACE` + for arg in 0..self.mir.arg_count + 1 { + // Includes `RETURN_PLACE` all.remove(Local::new(arg)); } all @@ -407,8 +408,35 @@ impl<'tcx> Place<'tcx> { pub fn mk_deref(self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { let elems = repacker.tcx.mk_place_elems_from_iter( - self.projection.iter().copied().chain(std::iter::once(PlaceElem::Deref)) + self.projection + .iter() + .copied() + .chain(std::iter::once(PlaceElem::Deref)), ); Self::new(self.local, elems) } + + pub fn deref_to_region( + mut self, + r: RegionVid, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> Option { + let mut ty = self.ty(repacker).ty; + while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { + ty = inner_ty; + self = self.mk_deref(repacker); + if rr.is_var() && rr.as_var() == r { + return Some(self); + } + } + None + } + + pub fn param_kind(self, repacker: PlaceRepacker<'_, 'tcx>) -> Option { + if self.local.as_usize() <= repacker.mir.arg_count { + Some(self.local) + } else { + None + } + } } diff --git a/mir-state-analysis/src/utils/ty/mod.rs b/mir-state-analysis/src/utils/ty/mod.rs index 3f9a7927203..b1a5d1b036b 100644 --- a/mir-state-analysis/src/utils/ty/mod.rs +++ b/mir-state-analysis/src/utils/ty/mod.rs @@ -14,10 +14,9 @@ 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::{Ty, TyKind, TyCtxt}, + ty::{Ty, TyCtxt, TyKind}, }, }; @@ -44,15 +43,17 @@ pub trait DeepTypeVisitable<'tcx> { impl<'tcx> DeepTypeVisitable<'tcx> for Ty<'tcx> { fn visit_with(&self, visitor: &mut impl DeepTypeVisitor<'tcx>, stack: &mut Stack<'tcx>) { - if stack.0.contains(self) { return; } + if stack.0.contains(self) { + return; + } stack.0.push(*self); match self.kind() { - TyKind::Bool | - TyKind::Char | - TyKind::Int(_) | - TyKind::Uint(_) | - TyKind::Float(_) | - TyKind::Str => (), + TyKind::Bool + | TyKind::Char + | TyKind::Int(_) + | TyKind::Uint(_) + | TyKind::Float(_) + | TyKind::Str => (), TyKind::Adt(def_id, substs) => { for field in def_id.all_fields() { let ty = field.ty(visitor.tcx(), substs); diff --git a/mir-state-analysis/src/utils/ty/ty_rec.rs b/mir-state-analysis/src/utils/ty/ty_rec.rs index bcd49bdb2ef..ece53c89a8c 100644 --- a/mir-state-analysis/src/utils/ty/ty_rec.rs +++ b/mir-state-analysis/src/utils/ty/ty_rec.rs @@ -10,14 +10,13 @@ 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::{Ty, TyKind, TyCtxt}, + ty::{Ty, TyCtxt, TyKind}, }, }; -use crate::utils::ty::{DeepTypeVisitor, DeepTypeVisitable, Stack}; +use crate::utils::ty::{DeepTypeVisitable, DeepTypeVisitor, Stack}; pub fn is_ty_rec<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> bool { struct RecTyWalk<'tcx>(TyCtxt<'tcx>, bool); From cda10d4b643ddb2fa502e764858a0ac0bb41e96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 3 Oct 2023 20:05:49 +0200 Subject: [PATCH 39/58] Remove outlives constraints uniqueness assumption --- .../src/coupling_graph/context/region_info/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index 84daf1f0873..151cc1e6569 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -121,17 +121,18 @@ impl<'tcx> RegionInfo<'tcx> { let (universal, other, incr) = if map.is_universal(c.sup) { (c.sup, c.sub, 1) } else { - (c.sub, c.sup, 3) + (c.sub, c.sup, 2) }; let entry = equivalence_map.entry((universal, other)).or_default(); - *entry += incr; - assert!(*entry == 1 || *entry == 3 || *entry == 4); + *entry |= incr; + // Note: Outlives constraints may be duplicated!! + // e.g. in `hashbrown-0.14.1` in `hashbrown::raw::RawTable::::clear::{closure#0}` at `src/raw/mod.rs:1021:37: 1021:66 (#0)` } // Set the entries in the map map.set(static_region, RegionKind::Static); for ((universal, local), sum) in equivalence_map { - if sum == 4 { + if sum == 3 { map.set_param(universal, local); } } From 2e47a2705a88ba523dc3ad2e0359799943f26e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 3 Oct 2023 20:06:40 +0200 Subject: [PATCH 40/58] fmt --- prusti-interface/src/environment/body.rs | 5 ++++- prusti-interface/src/environment/borrowck/facts.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 326c0d2d5bd..b307f55d4d8 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -10,7 +10,10 @@ use rustc_hash::FxHashMap; use rustc_middle::ty::GenericArgsRef; use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc}; -use crate::environment::{borrowck::facts::{BorrowckFacts, BorrowckFacts2}, mir_storage}; +use crate::environment::{ + borrowck::facts::{BorrowckFacts, BorrowckFacts2}, + mir_storage, +}; /// Stores any possible MIR body (from the compiler) that /// Prusti might want to work with. Cheap to clone diff --git a/prusti-interface/src/environment/borrowck/facts.rs b/prusti-interface/src/environment/borrowck/facts.rs index 314b3477da0..a3b5005f75a 100644 --- a/prusti-interface/src/environment/borrowck/facts.rs +++ b/prusti-interface/src/environment/borrowck/facts.rs @@ -7,7 +7,7 @@ use prusti_rustc_interface::{ borrowck::{ borrow_set::BorrowSet, - consumers::{LocationTable, RichLocation, RustcFacts, RegionInferenceContext}, + consumers::{LocationTable, RegionInferenceContext, RichLocation, RustcFacts}, }, middle::mir, polonius_engine::FactTypes, From 7e19cc716e8beb7f0d3c0646512dcf3b4a1ce010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 9 Oct 2023 16:27:56 +0200 Subject: [PATCH 41/58] Improve constant analysis --- .../coupling_graph/context/region_info/map.rs | 17 +- .../coupling_graph/context/region_info/mod.rs | 38 +- .../src/coupling_graph/impl/engine.rs | 58 ++- .../src/coupling_graph/impl/graph.rs | 4 +- .../src/coupling_graph/impl/triple.rs | 52 ++- mir-state-analysis/src/utils/const/mod.rs | 359 ++++++++++++++++++ mir-state-analysis/src/utils/mod.rs | 1 + mir-state-analysis/tests/top_crates.rs | 1 + prusti-utils/src/config.rs | 5 + prusti/src/driver.rs | 11 +- 10 files changed, 474 insertions(+), 72 deletions(-) create mode 100644 mir-state-analysis/src/utils/const/mod.rs diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs index 42aea67d395..9cdb12a039c 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs @@ -33,6 +33,7 @@ use crate::{ pub struct RegionInfoMap<'tcx> { region_info: IndexVec>, universal: usize, + constant_regions: Vec, } #[derive(Debug, Clone)] @@ -43,6 +44,7 @@ pub enum RegionKind<'tcx> { Function, UnknownUniversal, // Local regions + ConstRef(bool), Place { region: RegionVid, local: Local, @@ -73,6 +75,9 @@ impl<'tcx> RegionKind<'tcx> { pub fn is_unknown_universal(&self) -> bool { matches!(self, Self::UnknownUniversal) } + pub fn is_const_ref(&self) -> bool { + matches!(self, Self::ConstRef(..)) + } pub fn is_place(&self) -> bool { matches!(self, Self::Place { .. }) } @@ -143,6 +148,8 @@ impl<'tcx> RegionKind<'tcx> { } Self::Function => "'fn".to_string(), Self::UnknownUniversal => "'unknown".to_string(), + Self::ConstRef(true) => "ext_const".to_string(), + Self::ConstRef(false) => "const".to_string(), Self::Place { region, local } => { let place = Place::from(*local); let exact = place.deref_to_region(*region, cgx.rp); @@ -213,6 +220,7 @@ impl<'tcx> RegionInfoMap<'tcx> { Self { region_info, universal, + constant_regions: Vec::new(), } } @@ -220,7 +228,8 @@ impl<'tcx> RegionInfoMap<'tcx> { match self.get(r) { RegionKind::UnknownUniversal => assert!(kind.is_static() || kind.is_function()), RegionKind::UnknownLocal => assert!( - kind.is_place() + kind.is_const_ref() + || kind.is_place() || kind.is_borrow() || kind.is_early_bound() || kind.is_late_bound() @@ -230,6 +239,9 @@ impl<'tcx> RegionInfoMap<'tcx> { ), other => panic!("{other:?}"), } + if kind.is_const_ref() { + self.constant_regions.push(r); + } self.region_info[r] = kind; } pub(super) fn set_param(&mut self, r: RegionVid, local: RegionVid) { @@ -299,4 +311,7 @@ impl<'tcx> RegionInfoMap<'tcx> { pub fn for_local(&self, r: RegionVid, l: Local) -> bool { self.get(r).get_place() == Some(l) } + pub fn const_regions(&self) -> &[RegionVid] { + &self.constant_regions + } } diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index 151cc1e6569..4cb4d78bee9 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -14,14 +14,14 @@ use prusti_rustc_interface::{ dataflow::{Analysis, ResultsCursor}, index::IndexVec, middle::{ - mir::{Body, Local, Location, Operand, RETURN_PLACE}, - ty::{RegionVid, Ty, TyCtxt}, + mir::{visit::Visitor, Body, Constant, Local, Location, Operand, RETURN_PLACE, Rvalue, ConstantKind}, + ty::{GenericArg, RegionVid, Ty, TyCtxt}, }, }; use crate::{ coupling_graph::region_info::map::ParamRegion, - utils::{Place, PlaceRepacker}, + utils::{Place, PlaceRepacker, r#const::ConstEval}, }; use self::map::{RegionInfoMap, RegionKind}; @@ -58,6 +58,9 @@ impl<'tcx> RegionInfo<'tcx> { let (static_region, function_region) = Self::initialize_universals(&mut map, rp, input_facts, facts2); + // Init consts + Self::initialize_consts(&mut map, rp); + // Init locals Self::initialize_locals(&mut map, rp, input_facts, facts2, sbs); @@ -140,6 +143,17 @@ impl<'tcx> RegionInfo<'tcx> { (static_region, function_region) } + pub fn initialize_consts(map: &mut RegionInfoMap<'tcx>, rp: PlaceRepacker<'_, 'tcx>) { + let mut collector = ConstantRegionCollector { map }; + collector.visit_body(rp.body()); + for constant in &rp.body().required_consts { + for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { + assert!(r.is_var(), "{r:?} in {constant:?}"); + map.set(r.as_var(), RegionKind::ConstRef(true)); + } + } + } + pub fn initialize_locals( map: &mut RegionInfoMap<'tcx>, rp: PlaceRepacker<'_, 'tcx>, @@ -159,3 +173,21 @@ impl<'tcx> RegionInfo<'tcx> { } } } + +struct ConstantRegionCollector<'a, 'tcx> { + map: &'a mut RegionInfoMap<'tcx>, +} +impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, 'tcx> { + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + // We might have a non-var `r` as the function operand (const) to a function call. + // This function call may be e.g. `ParseBuffer::<'_>::step` and we want to ignore the `'_`. + // So instead of matching on arbitrary constants, we only match on Rvalue creation of them. + self.super_rvalue(rvalue, location); + if let Rvalue::Use(Operand::Constant(constant)) = rvalue { + for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { + assert!(r.is_var(), "{r:?} in {constant:?}"); + self.map.set(r.as_var(), RegionKind::ConstRef(false)); + } + } + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index 68a639142d3..438a836a913 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -98,7 +98,7 @@ pub(crate) struct CoupligGraph<'a, 'tcx> { impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { pub(crate) fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { - if cfg!(debug_assertions) { + if cfg!(debug_assertions) && !top_crates { std::fs::remove_dir_all("log/coupling").ok(); std::fs::create_dir_all("log/coupling/individual").unwrap(); } @@ -178,13 +178,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup - { - state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.version), - false, - ); - } + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + false, + ); self.flow_borrows .borrow_mut() .seek_to_block_start(location.block); @@ -203,12 +200,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { state.live = other; - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.version), - false, - ); - } + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}.dot", state.version), + false, + ); } #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] @@ -227,13 +222,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { assert!(state.version < 10); // println!("\nblock: {:?}", location.block); - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup - { - state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.version), - false, - ); - } + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + false, + ); self.flow_borrows .borrow_mut() .seek_to_block_start(location.block); @@ -252,15 +244,11 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { match &terminator.kind { TerminatorKind::Return => { - if cfg!(debug_assertions) - && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup - { - let l = format!("{location:?}").replace('[', "_").replace(']', ""); - state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_pre.dot", state.version), - false, - ); - } + let l = format!("{location:?}").replace('[', "_").replace(']', ""); + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}_pre.dot", state.version), + false, + ); // Pretend we have a storage dead for all `always_live_locals` other than the args/return for l in self.cgx.rp.always_live_locals_non_args().iter() { state.kill_shared_borrows_on_place(location, l.into()); @@ -284,12 +272,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { state.live = other; - if cfg!(debug_assertions) && !self.cgx.rp.body().basic_blocks[location.block].is_cleanup { - state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.version), - false, - ); - } + state.output_to_dot( + format!("log/coupling/individual/{l}_v{}.dot", state.version), + false, + ); terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index bf4df80aa67..4469bd772c5 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -51,12 +51,12 @@ impl<'tcx> Graph<'tcx> { }; self.outlives_inner(c.sup, c.sub, edge) } - pub fn outlives_static(&mut self, r: RegionVid, static_region: RegionVid, l: Location) { + pub fn outlives_static(&mut self, r: RegionVid, static_region: RegionVid) { if r == static_region { return; } let edge = EdgeInfo { - creation: Some(l), + creation: None, reason: None, }; self.outlives_inner(r, static_region, edge); diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 1516d8d6f46..96b57bf5151 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -21,11 +21,12 @@ use prusti_rustc_interface::{ index::{bit_set::BitSet, IndexVec}, middle::{ mir::{ - visit::Visitor, BasicBlock, ConstraintCategory, Local, Location, Operand, - Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, - RETURN_PLACE, + interpret::{ConstValue, GlobalAlloc, Scalar}, + visit::Visitor, + BasicBlock, ConstraintCategory, Local, Location, Operand, Place as MirPlace, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, RETURN_PLACE, ConstantKind, }, - ty::{RegionVid, TyKind}, + ty::{GenericArgKind, RegionVid, TyKind, ParamEnv}, }, }; @@ -34,7 +35,7 @@ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::{Place, PlaceRepacker}, + utils::{r#const::ConstEval, Place, PlaceRepacker}, }; use super::{ @@ -117,6 +118,10 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { for &(sup, sub) in &self.cgx.outlives_info.universal_constraints { self.graph.outlives_placeholder(sup, sub); } + for &const_region in self.cgx.region_info.map.const_regions() { + self.graph + .outlives_static(const_region, self.static_region()); + } } #[tracing::instrument(name = "get_associated_place", level = "trace", skip(self), ret)] @@ -155,6 +160,14 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { // println!("killed: {r:?} {killed:?} {l:?}"); if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { + if self.graph.static_regions.contains(&data.region) { + self.output_to_dot("log/coupling/kill.dot", true); + } + assert!( + !self.graph.static_regions.contains(&data.region), + "{data:?} {location:?} {:?}", + self.graph.static_regions + ); self.graph.kill_borrow(data); } else { self.graph.remove(data.region, Some(location)); @@ -243,32 +256,13 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { self.super_operand(operand, location); - match *operand { + match operand { Operand::Copy(_) => (), - Operand::Move(place) => { + &Operand::Move(place) => { self.kill_shared_borrows_on_place(location, place); } - Operand::Constant(..) => { - // TODO: anything here? - } - } - } - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { - match rvalue { - Rvalue::Use(Operand::Constant(_)) => { - // TODO: this is a hack, find a better way to do things - for c in - self.cgx - .outlives_info - .pre_constraints(location, None, &self.cgx.region_info) - { - self.graph - .outlives_static(c.sub, self.static_region(), location); - } - } - _ => (), + Operand::Constant(_) => (), } - self.super_rvalue(rvalue, location); } fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { self.super_statement(statement, location); @@ -315,6 +309,7 @@ impl<'tcx> Cg<'_, 'tcx> { panic!("{r:?} {:?}", self.graph.nodes[r]); } // Ignore (and thus delete) early/late bound (mostly fn call) regions + RegionKind::ConstRef(..) => (), RegionKind::EarlyBound(..) => (), RegionKind::LateBound { .. } => (), RegionKind::Placeholder(..) => (), @@ -326,7 +321,8 @@ impl<'tcx> Cg<'_, 'tcx> { } } pub fn output_to_dot>(&self, path: P, error: bool) { - if !self.top_crates || error { + if cfg!(debug_assertions) && (!self.top_crates || error) { + std::fs::create_dir_all("log/coupling/individual").unwrap(); let mut f = std::fs::File::create(path).unwrap(); dot::render(self, &mut f).unwrap(); } diff --git a/mir-state-analysis/src/utils/const/mod.rs b/mir-state-analysis/src/utils/const/mod.rs new file mode 100644 index 00000000000..0dc58907d12 --- /dev/null +++ b/mir-state-analysis/src/utils/const/mod.rs @@ -0,0 +1,359 @@ +// © 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/. + +// This module is not used, and instead we use https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/struct.Promoted.html. +// I've kept this around as a skeleton for working with `ConstValue` and `ConstAlloc` if we ever need that in the future. + +use prusti_rustc_interface::{ + abi::{HasDataLayout, Size, Variants, TargetDataLayout, TagEncoding, VariantIdx}, + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, OutlivesConstraint, RichLocation}, + }, + data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{ + interpret::{ + alloc_range, AllocId, AllocRange, Allocation, ConstAllocation, ConstValue, + GlobalAlloc, Scalar, + }, + visit::Visitor, + BasicBlock, Constant, ConstantKind, ConstraintCategory, Local, Location, Operand, + Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + RETURN_PLACE, + }, + ty::{ + FloatTy, GenericArgKind, Instance, ParamEnv, ParamEnvAnd, RegionVid, ScalarInt, Ty, + TyKind, layout::{TyAndLayout, LayoutError, HasTyCtxt, HasParamEnv}, TyCtxt, FieldDef, + }, + }, +}; + +use super::PlaceRepacker; + +/// Do not use, see note at the top of this module +pub struct EvaluatedConst<'tcx> { + ty: Ty<'tcx>, + kind: EcKind<'tcx>, +} + +impl<'tcx> std::fmt::Debug for EvaluatedConst<'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.kind.fmt(f) + } +} + +#[derive(Debug)] +pub enum EcKind<'tcx> { + // Primitive + Bool(bool), + Int(i128), + Uint(u128), // Note: Also includes `char` + Float(f64), // Note: it is lossless to store a `f32` in a `f64` + Str(&'tcx str), + // Other + ZeroSized, // Get info from `ty` + Function(Instance<'tcx>), + // Compound + Array(Vec>), + Adt(VariantIdx, Vec<(&'tcx FieldDef, EvaluatedConst<'tcx>)>), + // Reference/Pointer + Pointer(Option>>), +} + +impl<'tcx> EcKind<'tcx> { + pub fn to_bits(&self) -> Option { + match *self { + EcKind::Int(value) => Some(value as u128), + EcKind::Uint(value) => Some(value), + _ => None, + } + } +} + +pub trait ConstEval<'tcx> { + fn eval(self, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx>; +} + +impl<'tcx> ConstEval<'tcx> for Constant<'tcx> { + fn eval(self, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx> { + let param_env = rp.tcx().param_env(rp.body().source.def_id()); + let eval = self.literal.eval(rp.tcx(), param_env, None); + assert!(!(self.literal.try_to_scalar().is_some() && eval.is_err())); + // let eval = eval.ok()?; + // TODO: find a good way to resolve errors here + let eval = eval.unwrap(); + eval.eval(self.ty(), rp) + } +} + +pub trait ConstEvalTy<'tcx> { + fn eval(self, ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx>; +} + +impl<'tcx> ConstEvalTy<'tcx> for ConstValue<'tcx> { + fn eval(self, ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx> { + println!("const {self:?}, ty: {ty:?}"); + match self { + ConstValue::Scalar(scalar) => scalar.eval(ty, rp), + ConstValue::ZeroSized => EvaluatedConst { + ty, + kind: EcKind::ZeroSized, + }, + ConstValue::Slice { data, start, end } => { + let inner_ty = ty.builtin_deref(true).unwrap().ty; + let range = alloc_range(Size::from_bytes(start), Size::from_bytes(end - start)); + eval_range(data, range, inner_ty, rp) + } + ConstValue::Indirect { alloc_id, offset } => { + let alloc = rp.tcx().global_alloc(alloc_id).unwrap_memory(); + let size = alloc.inner().size() - offset; + eval_range(alloc, alloc_range(offset, size), ty, rp) + } + } + } +} + +impl<'tcx> ConstEvalTy<'tcx> for Scalar { + fn eval(self, ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx> { + println!("scalar {self:?}, ty: {ty:?}"); + match self { + Scalar::Int(bytes) => bytes.eval(ty, rp), + Scalar::Ptr(ptr, _) => { + match rp.tcx().global_alloc(ptr.provenance) { + GlobalAlloc::Function(instance) => EvaluatedConst { + ty, + kind: EcKind::Function(instance), + }, + GlobalAlloc::VTable(_, _) => todo!(), + GlobalAlloc::Static(_) => todo!(), + GlobalAlloc::Memory(mem) => { + // If the `unwrap` ever panics we need a different way to get the inner type + // let inner_ty = ty.builtin_deref(true).map(|t| t.ty).unwrap_or(ty); + let inner_ty = ty.builtin_deref(true).unwrap().ty; + let inner = Box::new(mem.eval(inner_ty, rp)); + EvaluatedConst { + ty, + kind: EcKind::Pointer(Some(inner)), + } + } + } + } + } + } +} + +impl<'tcx> ConstEvalTy<'tcx> for ScalarInt { + fn eval(self, ty: Ty<'tcx>, _rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx> { + let kind = match ty.kind() { + TyKind::Bool => EcKind::Bool(self.try_to_bool().unwrap()), + TyKind::Int(_) => EcKind::Int(self.try_to_int(self.size()).unwrap()), + TyKind::Char | TyKind::Uint(_) => EcKind::Uint(self.try_to_uint(self.size()).unwrap()), + TyKind::Float(FloatTy::F32) => { + let float = f32::from_bits(self.try_to_u32().unwrap()); + EcKind::Float(f64::from(float)) // Lossless conversion, see https://doc.rust-lang.org/std/primitive.f32.html#method.from-3 + } + TyKind::Float(FloatTy::F64) => { + EcKind::Float(f64::from_bits(self.try_to_u64().unwrap())) + } + _ => unreachable!("ScalarInt::eval: {self:?}, ty: {ty:?}"), + }; + EvaluatedConst { ty, kind } + } +} + +impl<'tcx> ConstEvalTy<'tcx> for ConstAllocation<'tcx> { + fn eval(self, ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> EvaluatedConst<'tcx> { + let range = alloc_range(Size::ZERO, self.inner().size()); + eval_range(self, range, ty, rp) + } +} + +fn eval_range<'tcx>( + ca: ConstAllocation<'tcx>, + range: AllocRange, + ty: Ty<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, +) -> EvaluatedConst<'tcx> { + println!("ca {ca:?}, ty: {ty:?}, range: {range:?}"); + match ty.kind() { + TyKind::Str => { + let peat = ParamEnvAnd { + param_env: ParamEnv::reveal_all(), + value: ty, + }; + let layout = rp.tcx().layout_of(peat).unwrap(); + println!("layout: {layout:?}"); + + // Cannot use `read_scalar` here: it panics if the allocation size is larger + // than can fit in u128 as it cannot create a `Scalar::Int` for the data. + let value = ca + .inner() + .get_bytes_strip_provenance(&rp.tcx(), range) + .unwrap(); + EvaluatedConst { + ty, + kind: EcKind::Str(std::str::from_utf8(value).unwrap()), + } + } + &TyKind::Array(elem_ty, _) | &TyKind::Slice(elem_ty) => { + let layout = layout_of(elem_ty, rp).unwrap(); + // println!("layout: {layout:?}"); + assert_eq!(range.size.bits() % layout.size.bits(), 0); + let elements = range.size.bits() / layout.size.bits(); + let values = (0..elements) + .map(|idx| layout.size.checked_mul(idx, &rp.tcx()).unwrap()) + .map(|offset| range.subrange(alloc_range(offset, layout.size))) + .map(|subrange| eval_range(ca, subrange, elem_ty, rp)) + .collect(); + EvaluatedConst { + ty, + kind: EcKind::Array(values), + } + } + // Always zero-sized, closures with captured state aren't const? + TyKind::FnDef(..) | TyKind::Closure(..) => { + assert_eq!(range.size.bits(), 0); + EvaluatedConst { + ty, + kind: EcKind::ZeroSized, + } + } + TyKind::Adt(adt, sub) if adt.variants().is_empty() => { + todo!() + } + TyKind::Adt(adt, sub) => { + if range.size.bits() == 0 { + return EvaluatedConst { + ty, + kind: EcKind::ZeroSized, + }; + } + let cx = &RevealAllEnv::from(rp); + let adt_layout = layout_of(ty, rp).unwrap(); + let index = match adt_layout.variants { + Variants::Single { index } => index, + Variants::Multiple { tag, ref tag_encoding, tag_field, ref variants } => { + let discr_type = ty.discriminant_ty(rp.tcx()); + // TODO: compare with `tag.primitive().to_int_ty(rp.tcx())` + + let discr_offset = adt_layout.fields.offset(tag_field); + let discr_layout = adt_layout.field(cx, tag_field); + // TODO: compare with `layout_of(discr_type, rp).unwrap()` + let discr_range = range.subrange(alloc_range(discr_offset, discr_layout.size)); + let discr_bits = eval_range(ca, discr_range, discr_type, rp); + let discr_bits = discr_bits.kind.to_bits().unwrap(); + match *tag_encoding { + TagEncoding::Direct => { + adt.discriminants(rp.tcx()).find(|(_, var)| var.val == discr_bits).unwrap().0 + } + TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => { + let variants_start = niche_variants.start().as_u32(); + let variants_end = niche_variants.end().as_u32(); + let variant_index_relative = discr_bits - niche_start; + if variant_index_relative <= u128::from(variants_end - variants_start) { + // We should now be within the `u32` range. + let variant_index_relative = u32::try_from(variant_index_relative).unwrap(); + VariantIdx::from_u32(variants_start.checked_add(variant_index_relative).unwrap()) + } else { + untagged_variant + } + } + } + } + }; + let layout = adt_layout.for_variant(cx, index); + let variant = adt.variant(index); + let fields = variant.fields.iter_enumerated().map(|(idx, val)| { + let field = layout.field(cx, idx.as_usize()); + let range = range.subrange(alloc_range(layout.fields.offset(idx.as_usize()), field.size)); + (val, eval_range(ca, range, val.ty(rp.tcx(), sub), rp)) + }).collect(); + EvaluatedConst { + ty, + kind: EcKind::Adt(index, fields), + } + } + TyKind::Tuple(_) => todo!(), + // Note: `TyKind::FnPtr` will lead to `GlobalAlloc::Function` + _ => { + assert!( + range.size.bits() <= u128::BITS.into(), + "{ty:?}, {range:?}: {:?} / {:?}", + ca.inner().init_mask(), + ca.inner().provenance() + ); + let mut range = range; + let is_ptr = ty.is_any_ptr();// !ca.inner().provenance().range_empty(range, &rp.tcx()); + if is_ptr { + let ptr_size = rp.tcx().data_layout().pointer_size; + if ptr_size != range.size { + // For some reason all pointer allocations get reported as + // `16 bytes` even though they are actually `8 bytes`. + assert_eq!(range.size.bits(), ptr_size.bits() * 2); + range.size = ptr_size; + } + if ca.inner().provenance().range_empty(range, &rp.tcx()) { + assert!(ty.is_unsafe_ptr()); + return EvaluatedConst { + ty, + kind: EcKind::Pointer(None), + }; + } + } + assert_eq!(is_ptr, ty.is_any_ptr(), "({range:?}, {is_ptr}, {ty:?}): {:?} / {:?}", ca.inner().init_mask(), ca.inner().provenance()); + match ca.inner().read_scalar(&rp.tcx(), range, is_ptr) { + Ok(scalar) => scalar.eval(ty, rp), + Err(err) => panic!( + "{err:?} ({range:?}, {is_ptr}, {ty:?}): {:?} / {:?}", + ca.inner().init_mask(), + ca.inner().provenance() + ), + } + } + } + + // println!( + // "Range: {range:?}, is_ptr: {is_ptr}, pointer_size: {:?}", + // rp.tcx().data_layout().pointer_size + // ); + // let bytes = self.inspect_with_uninit_and_ptr_outside_interpreter(0..self.len()); + // let provenance = self.provenance(); + // println!("inner: {:?}, extra: {:?}, provenance {:?}", bytes, self.extra, provenance); + // for (_, ptr) in provenance.ptrs().iter() {} +} + +fn layout_of<'tcx>(ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> Result, &'tcx LayoutError<'tcx>> { + let peat = ParamEnvAnd { + param_env: ParamEnv::reveal_all(), + value: ty, + }; + rp.tcx().layout_of(peat) +} + +struct RevealAllEnv<'a, 'tcx>(PlaceRepacker<'a, 'tcx>); +impl HasDataLayout for RevealAllEnv<'_, '_> { + fn data_layout(&self) -> &TargetDataLayout { + self.0.tcx.data_layout() + } +} +impl<'tcx> HasTyCtxt<'tcx> for RevealAllEnv<'_, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.0.tcx() + } +} +impl<'tcx> HasParamEnv<'tcx> for RevealAllEnv<'_, 'tcx> { + fn param_env(&self) -> ParamEnv<'tcx> { + ParamEnv::reveal_all() + } +} +impl<'a, 'tcx> From> for RevealAllEnv<'a, 'tcx> { + fn from(rp: PlaceRepacker<'a, 'tcx>) -> Self { + Self(rp) + } +} diff --git a/mir-state-analysis/src/utils/mod.rs b/mir-state-analysis/src/utils/mod.rs index d983612fba0..a271c0109d8 100644 --- a/mir-state-analysis/src/utils/mod.rs +++ b/mir-state-analysis/src/utils/mod.rs @@ -10,6 +10,7 @@ pub mod display; mod mutable; mod root_place; pub mod ty; +pub mod r#const; pub use mutable::*; pub use place::*; diff --git a/mir-state-analysis/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs index 0c68817ec86..c20053d2382 100644 --- a/mir-state-analysis/tests/top_crates.rs +++ b/mir-state-analysis/tests/top_crates.rs @@ -69,6 +69,7 @@ fn run_on_crate(name: &str, version: &str) { .env("PRUSTI_SKIP_UNSUPPORTED_FEATURES", "true") // .env("PRUSTI_LOG", "debug") .env("PRUSTI_NO_VERIFY_DEPS", "true") + .env("PRUSTI_CAP_LINTS", "allow") .env("PRUSTI_TOP_CRATES", "true") .current_dir(&dirname) .status() diff --git a/prusti-utils/src/config.rs b/prusti-utils/src/config.rs index 98ceccb7ec4..cc3f4522bfc 100644 --- a/prusti-utils/src/config.rs +++ b/prusti-utils/src/config.rs @@ -108,6 +108,7 @@ lazy_static::lazy_static! { settings.set_default("internal_errors_as_warnings", false).unwrap(); settings.set_default("allow_unreachable_unsupported_code", false).unwrap(); settings.set_default("no_verify", false).unwrap(); + settings.set_default::>("cap_lints", None).unwrap(); settings.set_default("no_verify_deps", false).unwrap(); settings.set_default("opt_in_verification", false).unwrap(); settings.set_default("full_compilation", false).unwrap(); @@ -989,6 +990,10 @@ pub fn set_no_verify(value: bool) { write_setting("no_verify", value); } +pub fn cap_lints() -> Option { + read_setting("cap_lints") +} + /// When enabled, verification is skipped for dependencies. pub fn no_verify_deps() -> bool { read_setting("no_verify_deps") diff --git a/prusti/src/driver.rs b/prusti/src/driver.rs index eb6744a4604..fe4aefcd6a4 100644 --- a/prusti/src/driver.rs +++ b/prusti/src/driver.rs @@ -104,8 +104,9 @@ fn main() { // Would `cargo check` not report errors for this crate? That is, are lints disabled // (i.e. is this a non-local crate) - let are_lints_disabled = - arg_value(&original_rustc_args, "--cap-lints", |val| val == "allow").is_some(); + let cap_lints = arg_value(&original_rustc_args, "--cap-lints", |_| true); + let cap_lints_any = cap_lints.is_some(); + let are_lints_disabled = cap_lints.map(|val| val == "allow").unwrap_or_default(); // Remote dependencies (e.g. from git/crates.io), or any dependencies if `no_verify_deps`, // are not verified. However, we still run Prusti on them to export potential specs. @@ -130,6 +131,12 @@ fn main() { rustc_args.push(arg); } } + if !cap_lints_any { + if let Some(new_cap) = config::cap_lints() { + rustc_args.push("--cap-lints".to_owned()); + rustc_args.push(new_cap); + } + } let exit_code = driver::catch_with_exit_code(move || { user::message(format!( From 9e79e8776692fceebaebaa58014e14f9b77a4240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 10 Oct 2023 14:21:41 +0200 Subject: [PATCH 42/58] Change constant lifetime analysis --- .../src/coupling_graph/context/region_info/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index 4cb4d78bee9..d6255e13ad8 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -185,8 +185,12 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, 'tcx> { self.super_rvalue(rvalue, location); if let Rvalue::Use(Operand::Constant(constant)) = rvalue { for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { - assert!(r.is_var(), "{r:?} in {constant:?}"); - self.map.set(r.as_var(), RegionKind::ConstRef(false)); + // We may run into non-var regions here, e.g. assigning a const closure to a local where + // the closure captures state and thus that has e.g. `BrNamed` regions. + if r.is_var() { + // assert!(r.is_var(), "{r:?} in {constant:?} ({:?} at {location:?})", constant.ty()); + self.map.set(r.as_var(), RegionKind::ConstRef(false)); + } } } } From 6bf38d0a84147717f8424340db47b9174a547d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 11 Oct 2023 14:22:38 +0200 Subject: [PATCH 43/58] Bugfix --- .../src/coupling_graph/context/outlives_info/mod.rs | 11 ++++++----- mir-state-analysis/tests/top_crates.rs | 4 ++-- prusti/src/callbacks.rs | 8 +++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs index a07133b408e..2906e77c377 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs @@ -10,7 +10,7 @@ use prusti_rustc_interface::{ borrow_set::BorrowData, consumers::{BorrowIndex, Borrows, OutlivesConstraint, PoloniusInput, RustcFacts}, }, - data_structures::fx::FxHashMap, + data_structures::fx::{FxHashMap, FxHashSet}, dataflow::{Analysis, ResultsCursor}, index::IndexVec, middle::{ @@ -30,7 +30,6 @@ pub struct OutlivesInfo<'tcx> { pub local_constraints: Vec>, // but with no location info pub type_ascription_constraints: Vec>, pub location_constraints: FxHashMap>>, - pub universal_constraints: Vec<(RegionVid, RegionVid)>, } @@ -40,7 +39,8 @@ impl<'tcx> OutlivesInfo<'tcx> { facts2: &BorrowckFacts2<'tcx>, ri: &RegionInfo<'tcx>, ) -> Self { - let universal_constraints = input_facts.known_placeholder_subset.clone(); + let mut universal_constraints = + FxHashSet::from_iter(input_facts.known_placeholder_subset.iter().copied()); let mut universal_local_constraints = Vec::new(); let mut local_constraints = Vec::new(); @@ -57,7 +57,8 @@ impl<'tcx> OutlivesInfo<'tcx> { if ri.map.is_universal(constraint.sup) && ri.map.is_universal(constraint.sub) { // Not sure why the `region_inference_context` can rarely contain inter-universal constraints, // but we should already have all of these in `universal_constraints`. - assert!(universal_constraints.contains(&(constraint.sup, constraint.sub))); + // Except for even more rare situations... + universal_constraints.insert((constraint.sup, constraint.sub)); } else { universal_local_constraints.push(constraint); } @@ -72,7 +73,7 @@ impl<'tcx> OutlivesInfo<'tcx> { local_constraints, type_ascription_constraints, location_constraints, - universal_constraints, + universal_constraints: universal_constraints.into_iter().collect(), } } diff --git a/mir-state-analysis/tests/top_crates.rs b/mir-state-analysis/tests/top_crates.rs index c20053d2382..be0e664f38a 100644 --- a/mir-state-analysis/tests/top_crates.rs +++ b/mir-state-analysis/tests/top_crates.rs @@ -63,8 +63,8 @@ fn run_on_crate(name: &str, version: &str) { .collect::(), ); println!("Running: {prusti:?}"); - let exit = std::process::Command::new(prusti) - // .env("PRUSTI_TEST_FREE_PCS", "true") + let exit = std::process::Command::new(&prusti) + .env("PRUSTI_TEST_FREE_PCS", "true") .env("PRUSTI_TEST_COUPLING_GRAPH", "true") .env("PRUSTI_SKIP_UNSUPPORTED_FEATURES", "true") // .env("PRUSTI_LOG", "debug") diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index d7323730b05..363c3795fd7 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -37,7 +37,7 @@ fn mir_borrowck<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> &BorrowCheckResu if !is_anon_const { let consumer_opts = if is_spec_fn(tcx, def_id.to_def_id()) || config::no_verify() - || config::test_free_pcs() + || (config::test_free_pcs() && !config::test_coupling_graph()) { consumers::ConsumerOptions::RegionInferenceContext } else if config::test_coupling_graph() { @@ -172,7 +172,8 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { println!("Calculating FPCS for: {name} ({:?})", mir.span); test_free_pcs(&mir, tcx); } - } else if config::test_coupling_graph() { + } + if config::test_coupling_graph() { for proc_id in env.get_annotated_procedures_and_types().0.iter() { let mir = env.body.get_impure_fn_body_identity(proc_id.expect_local()); let facts = env @@ -188,7 +189,8 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { println!("Calculating CG for: {name} ({:?})", mir.span); test_coupling_graph(&*mir, &*facts, &*facts2, tcx, config::top_crates()); } - } else { + } + if !config::test_free_pcs() && !config::test_coupling_graph() { verify(env, def_spec); } } From 598ec91607efa00d43e692b892fb5a0f81949b0c 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 44/58] Fix bug in `ConstantIndex` unpacking --- .../src/free_pcs/results/repacks.rs | 2 +- mir-state-analysis/src/utils/place.rs | 65 ++++--------------- mir-state-analysis/src/utils/repacker.rs | 16 ++--- 3 files changed, 22 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 80b38815eb9..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,7 @@ 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`]. diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 63c8cdccc4f..81401146722 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 } @@ -321,6 +293,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 @@ -340,29 +321,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 6630aff5a19..306f0f8f023 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -160,7 +160,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, @@ -185,14 +187,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 0ac0bd4c9b577478289b7dc0c452a6a7288777a1 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 45/58] 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 03649f3b9a00e1919872eb9b0d6e04962f715129 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 46/58] 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 81401146722..cee53586ccc 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 306f0f8f023..63eae6ce5dd 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -187,7 +187,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 ec9439090608ef61c3420b6dbdbc5d95f7f2e182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 17 Oct 2023 16:08:30 +0200 Subject: [PATCH 47/58] Start combining the analyses --- .../src/coupling_graph/check/checker.rs | 338 ++++++++++++++++++ .../src/coupling_graph/check/consistency.rs | 28 ++ .../src/coupling_graph/check/mod.rs | 10 + .../src/coupling_graph/context/mod.rs | 8 +- .../context/outlives_info/edge.rs | 199 +++++++++++ .../context/outlives_info/mod.rs | 2 + .../coupling_graph/context/region_info/map.rs | 242 +++++++++++-- .../coupling_graph/context/region_info/mod.rs | 284 +++++++++++++-- .../coupling_graph/context/region_place.rs | 113 ------ .../src/coupling_graph/impl/dot.rs | 74 ++-- .../src/coupling_graph/impl/engine.rs | 139 ++++--- .../src/coupling_graph/impl/graph.rs | 158 +++----- .../coupling_graph/impl/join_semi_lattice.rs | 30 +- .../src/coupling_graph/impl/triple.rs | 168 ++++++--- mir-state-analysis/src/coupling_graph/mod.rs | 4 + .../src/coupling_graph/results/coupling.rs | 62 ++++ .../src/coupling_graph/results/cursor.rs | 165 +++++++++ .../src/coupling_graph/results/mod.rs | 8 + .../src/free_pcs/check/checker.rs | 10 +- .../src/free_pcs/check/consistency.rs | 9 +- mir-state-analysis/src/free_pcs/check/mod.rs | 2 +- .../src/free_pcs/impl/engine.rs | 8 +- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 30 +- .../src/free_pcs/impl/join_semi_lattice.rs | 2 + mir-state-analysis/src/free_pcs/impl/place.rs | 31 +- .../src/free_pcs/impl/triple.rs | 7 +- .../src/free_pcs/impl/update.rs | 16 +- .../src/free_pcs/results/cursor.rs | 13 +- mir-state-analysis/src/lib.rs | 49 ++- mir-state-analysis/src/utils/display.rs | 2 +- mir-state-analysis/src/utils/place.rs | 13 +- mir-state-analysis/src/utils/repacker.rs | 71 +++- prusti-interface/src/environment/body.rs | 15 +- .../src/environment/mir_storage.rs | 11 +- prusti-interface/src/environment/procedure.rs | 7 +- prusti/src/callbacks.rs | 9 +- 36 files changed, 1823 insertions(+), 514 deletions(-) create mode 100644 mir-state-analysis/src/coupling_graph/check/checker.rs create mode 100644 mir-state-analysis/src/coupling_graph/check/consistency.rs create mode 100644 mir-state-analysis/src/coupling_graph/check/mod.rs create mode 100644 mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs delete mode 100644 mir-state-analysis/src/coupling_graph/context/region_place.rs create mode 100644 mir-state-analysis/src/coupling_graph/results/coupling.rs create mode 100644 mir-state-analysis/src/coupling_graph/results/cursor.rs create mode 100644 mir-state-analysis/src/coupling_graph/results/mod.rs diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs new file mode 100644 index 00000000000..0f833d75180 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -0,0 +1,338 @@ +// © 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::{ + ast::Mutability, + index::IndexVec, + data_structures::fx::{FxHashMap, FxHashSet}, + middle::{ + mir::{visit::Visitor, Location, ProjectionElem, BorrowKind}, + ty::RegionVid, + }, + borrowck::borrow_set::BorrowData, +}; + +use crate::{ + free_pcs::{ + CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, FpcsBound, + }, + utils::{PlaceRepacker, Place}, coupling_graph::{coupling::{CouplingOp, Block}, cursor::CgAnalysis, CgContext, graph::Graph, region_info::map::RegionKind}, +}; +use crate::free_pcs::consistency::CapabilityConsistency; + +#[derive(Clone)] +struct CouplingState<'a, 'tcx> { + blocks: IndexVec>, + blocked_by: IndexVec>, + cgx: &'a CgContext<'a, 'tcx>, +} + +impl<'a, 'tcx> std::fmt::Debug for CouplingState<'a, 'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_map().entries(self.blocks.iter_enumerated()).finish() + } +} + +#[tracing::instrument(name = "cg::check", level = "debug", skip(cg, fpcs_cursor))] +pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: FreePcsAnalysis<'_, 'tcx>) { + let cgx = cg.cgx(); + let rp: PlaceRepacker<'_, '_> = cgx.rp; + let body = rp.body(); + for (block, data) in body.basic_blocks.iter_enumerated() { + cg.analysis_for_bb(block); + fpcs_cursor.analysis_for_bb(block); + + let mut cg_state = CouplingState { + blocks: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), + blocked_by: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), + cgx, + }; + cg_state.initialize(&cg.initial_state()); + assert!(cg_state.compare(&cg.initial_state())); // TODO: remove + + fpcs_cursor.set_bound_non_empty(); + let mut fpcs = Fpcs { + summary: fpcs_cursor.initial_state().clone(), + bottom: false, + repackings: Vec::new(), + repacker: rp, + bound: FpcsBound::empty(true), + }; + // Consistency + fpcs.summary.consistency_check(rp); + for (statement_index, stmt) in data.statements.iter().enumerate() { + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); + let loc = Location { + block, + statement_index, + }; + let fpcs_after = fpcs_cursor.next(loc); + assert_eq!(fpcs_after.location, loc); + fpcs_cursor.unset_bound(); + + let cg_before = cg.before_next(loc); + // Couplings + for c in cg_before.couplings { + c.update_free(&mut cg_state, false); + } + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Couplings bound set + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Extend lifetimes (safe since we unset it later) + // 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); + // Couplings bound unset + fpcs.bound.borrow_mut().0 = None; + + // Only apply coupling ops after + let cg_after = cg.next(loc); + // Couplings + for c in cg_after.couplings { + c.update_free(&mut cg_state, false); + } + assert!(cg_state.compare(&cg_after.state), "{loc:?}"); + } + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); + let loc = Location { + block, + statement_index: data.statements.len(), + }; + let fpcs_after = fpcs_cursor.next(loc); + assert_eq!(fpcs_after.location, loc); + fpcs_cursor.unset_bound(); + + let cg_before = cg.before_next(loc); + // Couplings + for c in cg_before.couplings { + c.update_free(&mut cg_state, false); + } + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Couplings bound set + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); + // 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); + // Couplings bound unset + fpcs.bound.borrow_mut().0 = None; + + assert_eq!(fpcs.summary, fpcs_after.state); + + let cg_after = cg.next(loc); + // Couplings + for c in cg_after.couplings { + c.update_free(&mut cg_state, false); + } + assert!(cg_state.compare(&cg_after.state), "{loc:?}"); + + let fpcs_end = fpcs_cursor.terminator(); + let cg_end = cg.terminator(); + + for (fpcs_succ, cg_succ) in fpcs_end.succs.into_iter().zip(cg_end.succs) { + assert_eq!(fpcs_succ.location, cg_succ.location); + // Repacks + let mut fpcs_from = fpcs.clone(); + for op in fpcs_succ.repacks { + op.update_free( + &mut fpcs_from.summary, + body.basic_blocks[fpcs_succ.location.block].is_cleanup, + rp, + ); + } + assert_eq!(fpcs_from.summary, fpcs_succ.state); + + // Couplings + let mut cg_from = cg_state.clone(); + for op in cg_succ.couplings { + op.update_free(&mut cg_from, false); + } + assert!(cg_from.compare(&cg_succ.state), "{loc:?} -> {:?}", cg_succ.location); + } + } +} + +impl<'a, 'tcx> CouplingState<'a, 'tcx> { + fn initialize(&mut self, graph: &Graph<'tcx>) { + for (sub, v) in graph.nodes.iter_enumerated() { + let sub_info = self.cgx.region_info.map.get(sub); + if sub_info.is_borrow() { + continue; + } + for sup in v.blocks.keys() { + let sup_info = self.cgx.region_info.map.get(*sup); + if sub_info.universal() && sup_info.local() { + continue; + } + self.blocks[sub].insert(*sup); + self.blocked_by[*sup].insert(sub); + } + } + } + + #[tracing::instrument(name = "compare", level = "trace")] + fn compare(&self, other: &Graph) -> bool { + for (sub, v) in self.blocks.iter_enumerated() { + let sub_info = self.cgx.region_info.map.get(sub); + if let Some(brrw) = sub_info.get_borrow() { + if !v.is_empty() { + println!("{sub:?} ({brrw:?}) blocks: {v:?}"); + return false; + } + } else { + let blocks: FxHashSet<_> = other.nodes[sub].blocks.keys().copied().filter(|sup| { + let sup_info = self.cgx.region_info.map.get(*sup); + !(sub_info.universal() && sup_info.local()) + }).collect(); + if v != &blocks { + println!("{sub:?} blocks: {v:?} != {blocks:?}"); + return false; + } + } + } + true + } + + #[tracing::instrument(name = "mk_capability_upper_bound", level = "trace")] + fn mk_capability_upper_bound(&self) -> impl Fn(Place<'tcx>) -> CapabilityKind + '_ { + move |place| self.capability_upper_bound(place) + } + #[tracing::instrument(name = "capability_upper_bound", level = "debug")] + fn capability_upper_bound(&self, place: Place<'tcx>) -> CapabilityKind { + let mut upper_bound = CapabilityKind::Exclusive; + for proj in place.projection_refs(self.cgx.rp) { + match proj { + None => upper_bound = CapabilityKind::Exclusive, + Some((_, _, Mutability::Not)) => upper_bound = CapabilityKind::Read, + Some((region, _, Mutability::Mut)) => { + let r = region.as_var(); // Could this not be a var? + if self.has_real_blockers(r) { + return CapabilityKind::None; + } + } + } + } + tracing::debug!("upper_bound: {:?}", upper_bound); + for borrow in self.active_borrows() { + assert!(self.has_real_blockers(borrow.region)); + // Places related? + if let Some(bound) = upper_bound_borrow(place, borrow, self.cgx.rp) { + upper_bound = upper_bound.minimum(bound).unwrap(); + // Early return + if upper_bound.is_none() { + return upper_bound; + } + } + } + upper_bound + } + fn active_borrows(&self) -> impl Iterator> + '_ { + self.blocked_by + .iter_enumerated() + .filter(|(_, blockers)| !blockers.is_empty()) + .flat_map(move |(region, _)| self.cgx.region_info.map.get(region).get_borrow()) + } + fn has_real_blockers(&self, region: RegionVid) -> bool { + let scc = self.calculate_scc(region); + let fn_region = self.cgx.region_info.function_region; + scc.iter().any(|r| self.blocked_by[*r].iter().any(|blocker| !scc.contains(blocker) && *blocker != fn_region)) + // self.blocked_by[region].iter().copied().any(|r| { + // let r = self.cgx.region_info.map.get(r); + // !r.universal() && !r.is_borrow() + // }) + } + fn calculate_scc(&self, region: RegionVid) -> FxHashSet { + let mut visited_out: FxHashSet<_> = [region, self.cgx.region_info.static_region].into_iter().collect(); + let mut stack = vec![region, self.cgx.region_info.static_region]; + while let Some(next) = stack.pop() { + let blocks = self.blocks[next].iter().copied().filter(|r| visited_out.insert(*r)); + stack.extend(blocks); + } + let mut visited_in: FxHashSet<_> = [region].into_iter().collect(); + let mut stack = vec![region]; + while let Some(next) = stack.pop() { + let blocked_by = self.blocked_by[next].iter().copied().filter(|r| visited_in.insert(*r)); + stack.extend(blocked_by); + } + visited_out.intersection(&visited_in).copied().collect() + } +} + +#[tracing::instrument(name = "upper_bound_borrow", level = "trace", skip(rp), ret)] +fn upper_bound_borrow<'tcx>(place: Place<'tcx>, borrow: &BorrowData<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> Option { + let borrowed = borrow.borrowed_place.into(); + place.partial_cmp(borrowed).map(|cmp| { + let lower_bound = if cmp.is_prefix() && borrowed.projection_tys(rp).skip(place.projection.len()).any(|(ty, _)| ty.ty.is_any_ptr()) { + CapabilityKind::Write + } else { + CapabilityKind::None + }; + let kind = match borrow.kind { + BorrowKind::Shared => CapabilityKind::Read, + BorrowKind::Shallow if cmp.is_suffix() => CapabilityKind::Exclusive, + BorrowKind::Shallow => CapabilityKind::Read, + BorrowKind::Mut { .. } => CapabilityKind::None, + }; + lower_bound.sum(kind).unwrap() + }) +} + +impl CouplingOp { + #[tracing::instrument(name = "CouplingOp::update_free", level = "trace")] + fn update_free<'tcx>( + &self, + cg_state: &mut CouplingState, + is_cleanup: bool, + ) { + match self { + CouplingOp::Add(block) => block.update_free(cg_state, is_cleanup), + CouplingOp::Remove(remove, new_blocks) => { + let blocks = std::mem::replace(&mut cg_state.blocks[*remove], FxHashSet::default()); + for block in blocks { + cg_state.blocked_by[block].remove(&remove); + } + let blocked_by = std::mem::replace(&mut cg_state.blocked_by[*remove], FxHashSet::default()); + for block_by in blocked_by { + cg_state.blocks[block_by].remove(&remove); + } + for block in new_blocks { + block.update_free(cg_state, is_cleanup); + } + } + } + } +} + +impl Block { + fn update_free<'tcx>( + self, + cg_state: &mut CouplingState, + is_cleanup: bool, + ) { + let Block { sup, sub } = self; + assert!(!cg_state.cgx.region_info.map.get(sub).is_borrow()); + cg_state.blocks[sub].insert(sup); + cg_state.blocked_by[sup].insert(sub); + } +} diff --git a/mir-state-analysis/src/coupling_graph/check/consistency.rs b/mir-state-analysis/src/coupling_graph/check/consistency.rs new file mode 100644 index 00000000000..919e06f6f06 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/check/consistency.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/. + +use crate::coupling_graph::{CgContext, graph::Graph, triple::Cg}; + +pub trait CouplingConsistency<'tcx> { + fn consistency_check(&self, cgx: &CgContext<'_, 'tcx>) -> Option; +} + +impl<'tcx> CouplingConsistency<'tcx> for Cg<'_, 'tcx> { + #[tracing::instrument(name = "Graph::consistency_check", level = "trace", skip(self, cgx))] + fn consistency_check(&self, cgx: &CgContext<'_, 'tcx>) -> Option { + for (sub, node) in self.graph.all_nodes() { + for (sup, edge) in node.blocks.iter() { + let sup_info = cgx.region_info.map.get(*sup); + let sub_info = cgx.region_info.map.get(sub); + + if sup_info.is_borrow() && self.graph.static_regions.contains(&sup) { + return Some(format!("Borrow is static: {sup:?}")); + } + } + } + None + } +} diff --git a/mir-state-analysis/src/coupling_graph/check/mod.rs b/mir-state-analysis/src/coupling_graph/check/mod.rs new file mode 100644 index 00000000000..0c96a032e19 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/check/mod.rs @@ -0,0 +1,10 @@ +// © 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 checker; +pub mod consistency; + +pub(crate) use checker::check; diff --git a/mir-state-analysis/src/coupling_graph/context/mod.rs b/mir-state-analysis/src/coupling_graph/context/mod.rs index 5f6df015fe7..32be0f0ab05 100644 --- a/mir-state-analysis/src/coupling_graph/context/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/mod.rs @@ -7,7 +7,7 @@ use std::{cell::RefCell, fmt}; use self::{ - outlives_info::OutlivesInfo, region_info::RegionInfo, region_place::PlaceRegion, + outlives_info::OutlivesInfo, region_info::RegionInfo, shared_borrow_set::SharedBorrowSet, }; use crate::{ @@ -21,13 +21,12 @@ use prusti_rustc_interface::{ dataflow::{Analysis, ResultsCursor}, index::IndexVec, middle::{ - mir::{Body, Location, RETURN_PLACE}, + mir::{Body, Location, RETURN_PLACE, Promoted}, ty::{RegionVid, TyCtxt}, }, }; pub(crate) mod shared_borrow_set; -pub(crate) mod region_place; pub(crate) mod region_info; pub(crate) mod outlives_info; @@ -59,12 +58,13 @@ impl<'a, 'tcx> CgContext<'a, 'tcx> { pub fn new( tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, + promoted: &'a IndexVec>, facts: &'a BorrowckFacts, facts2: &'a BorrowckFacts2<'tcx>, ) -> Self { let borrow_set = &facts2.borrow_set; let sbs = SharedBorrowSet::build(tcx, body, borrow_set); - let rp = PlaceRepacker::new(body, tcx); + let rp = PlaceRepacker::new(body, promoted, tcx); let input_facts = facts.input_facts.borrow(); let input_facts = input_facts.as_ref().unwrap(); let loops = LoopAnalysis::find_loops(body); diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs new file mode 100644 index 00000000000..650dffb1010 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs @@ -0,0 +1,199 @@ +// © 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::{ + borrow::Cow, + fmt::{Debug, Display, Formatter, Result}, +}; + +use derive_more::{Deref, DerefMut}; +use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; +use prusti_rustc_interface::{ + borrowck::{ + borrow_set::BorrowData, + consumers::{BorrowIndex, OutlivesConstraint}, + }, + data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, + dataflow::fmt::DebugWithContext, + index::{bit_set::BitSet, IndexVec}, + middle::{ + mir::{BasicBlock, ConstraintCategory, Local, Location, Operand, RETURN_PLACE, TerminatorKind, StatementKind, Rvalue}, + ty::{RegionVid, TyKind}, + }, +}; + +use crate::coupling_graph::CgContext; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct EdgeInfo<'tcx> { + /// The region which outlives (usually means blocked) + sup: RegionVid, + /// The region which is outlived (usually means blocking) + sub: RegionVid, + pub creation: Option, + pub reason: Option>, +} + +impl<'tcx> EdgeInfo<'tcx> { + pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option) -> Self { + assert_ne!(sup, sub); + Self { + sup, + sub, + creation, + reason: None, + } + } + pub fn sup(self) -> RegionVid { + self.sup + } + pub fn sub(self) -> RegionVid { + self.sub + } + pub fn kind(self, cgx: &CgContext<'_, 'tcx>) -> EdgeKind<'tcx> { + let (sup_info, sub_info) = (cgx.region_info.map.get(self.sup), cgx.region_info.map.get(self.sub)); + let stmt = self.creation.map(|location| cgx.rp.body().stmt_at(location)); + let term = stmt.and_then(|stmt| stmt.right()).map(|t| &t.kind); + let stmt = stmt.and_then(|stmt| stmt.left()).map(|s| &s.kind); + match (self.reason, stmt, term) { + (Some(ConstraintCategory::BoringNoLocation), _, Some(TerminatorKind::Call { .. })) if sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0 => + EdgeKind::FnCallImplied, + (Some(ConstraintCategory::Predicate(_)), _, _) => { + assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); + assert!(sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0); + EdgeKind::FnCallPredicate + } + (Some(ConstraintCategory::CallArgument(_)), _, _) => { + assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); + // Can get a `Self::Static` outlives requirement from a function call + let static_eq = sup_info.is_static() ^ sub_info.is_static(); + let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); + let sup_depth = sub_info.from_function_depth(); + let sub_depth = sup_info.from_function_depth(); + assert!(static_eq || placeholders || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1), + "{sup_info:?} ({})\nand\n{sub_info:?} ({})\n({self:?})", sup_info.from_function_depth(), sub_info.from_function_depth()); + EdgeKind::FnCallArgument + } + (Some(ConstraintCategory::Assignment), _, Some(TerminatorKind::Call { .. })) => { + let static_eq = sup_info.is_static() ^ sub_info.is_static(); + // let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); + let sup_depth = sub_info.from_function_depth(); + let sub_depth = sup_info.from_function_depth(); + assert!(static_eq || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1)); + EdgeKind::FnCallReturn + } + _ if sub_info.is_borrow() || sub_info.is_projection_annotation() => { + if sub_info.is_borrow() { + assert!(matches!(stmt.unwrap(), StatementKind::Assign(box (_, Rvalue::Ref(..))))); + } + // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); + EdgeKind::ContainedIn + } + _ => EdgeKind::Unknown(self.creation, self.reason), + } + } + pub fn many_kind(edge: &Vec, cgx: &CgContext<'_, 'tcx>) -> Vec> { + edge.iter().map(|e| e.kind(cgx)).collect() + } + + /// Is the old edge contained in the new edge? + pub fn is_new_edge(new: &Vec, old: &Vec) -> bool { + assert_ne!(old.len(), 0); + if new.len() < old.len() { + return true; + } + // TODO: optimize? + let mut looking_for = 0; + for (idx, elem) in new.iter().enumerate() { + if &old[looking_for] == elem { + looking_for += 1; + } + let left_to_find = old.len() - looking_for; + if left_to_find == 0 { + return false; + } + if new.len() - idx - 1 < left_to_find { + return true; + } + } + unreachable!() + } +} + +impl Display for EdgeInfo<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let reason = if let Some(reason) = self.reason { + match reason { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + ConstraintCategory::UseAsConst => "const", + ConstraintCategory::UseAsStatic => "static", + ConstraintCategory::TypeAnnotation => "type", + ConstraintCategory::Cast => "cast", + ConstraintCategory::ClosureBounds => "closure", + ConstraintCategory::CallArgument(_) => "arg", + ConstraintCategory::CopyBound => "copy", + ConstraintCategory::SizedBound => "sized", + ConstraintCategory::Assignment => "assign", + ConstraintCategory::Usage => "use", + ConstraintCategory::OpaqueType => "opaque", + ConstraintCategory::ClosureUpvar(_) => "upvar", + ConstraintCategory::Predicate(_) => "pred", + ConstraintCategory::Boring => "?", + ConstraintCategory::BoringNoLocation => "? no_loc", + ConstraintCategory::Internal => "internal", + } + } else { + "other" + }; + let creation = self + .creation + .map(|c| format!("{c:?}")) + .unwrap_or_else(|| "sig".to_string()); + write!(f, "{creation} ({reason})") + } +} + +impl<'tcx> From> for EdgeInfo<'tcx> { + fn from(c: OutlivesConstraint<'tcx>) -> Self { + Self { + sup: c.sup, + sub: c.sub, + creation: c.locations.from_location(), + reason: Some(c.category), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum EdgeKind<'tcx> { + /// An edge from `'a` to `'b` created when + /// re-borrowing `_ = &'a mut (*x).0` with `x: &'b mut (_, _)`. + ContainedIn, + /// An edge from `'a` to `'b` created when + /// calling `fn foo<'a, 'b>(x: &'a mut &'b mut _)`. + FnCallImplied, + /// An edge from `'a` to `'b` created when + /// calling `fn foo<'a, 'b: 'a>(_: &'b mut _) -> &'a mut _`. + FnCallPredicate, + /// An edge from `'a` to `'b` created when + /// calling `fn foo<'a>(_: &'a mut _)` with `x: &'b mut _`. + FnCallArgument, + /// An edge from `'a` to `'b` created when + /// calling `fn foo<'b>(_) -> &'b mut _` to `r: &'a mut = foo()`. + FnCallReturn, + Unknown(Option, Option>), +} + +impl<'tcx> EdgeKind<'tcx> { + pub fn is_blocking(self) -> bool { + !matches!(self, EdgeKind::ContainedIn | EdgeKind::FnCallImplied) + } + + pub fn many_blocking(kinds: Vec) -> bool { + kinds.iter().any(|kind| kind.is_blocking()) + } +} diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs index 2906e77c377..9ef7e4e140e 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/mod.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/. +pub mod edge; + use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ borrowck::{ diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs index 9cdb12a039c..af8622b9ef4 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/map.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, marker::PhantomData}; + use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ borrowck::{ @@ -18,10 +20,10 @@ use prusti_rustc_interface::{ NllRegionVariableOrigin, RegionVariableOrigin, }, middle::{ - mir::{Body, BorrowKind, Local, Location, Operand, RETURN_PLACE}, + mir::{Body, BorrowKind, Local, Location, Operand, RETURN_PLACE, PlaceRef, PlaceElem, Promoted}, ty::{BoundRegionKind, PlaceholderRegion, RegionVid, Ty, TyCtxt, TyKind}, }, - span::{Span, Symbol}, + span::{Span, Symbol, def_id::DefId}, }; use crate::{ @@ -36,6 +38,77 @@ pub struct RegionInfoMap<'tcx> { constant_regions: Vec, } +#[derive(Debug, Clone, Copy)] +pub struct GenericArgRegion<'tcx> { + pub did: DefId, + pub gen_idx: usize, + pub full_ty: Option>, +} + +impl<'tcx> GenericArgRegion<'tcx> { + fn to_string(gars: &Vec, cgx: &CgContext<'_, 'tcx>) -> String { + gars.iter().map(|gar| { + let tcx = cgx.rp.tcx(); + let generic = tcx.generics_of(gar.did).param_at(gar.gen_idx, tcx); + assert_eq!(generic.kind.is_ty_or_const(), gar.full_ty.is_some()); + let ty = gar.full_ty.map(|ty| format!(" = {ty}")).unwrap_or_default(); + if tcx.is_closure(gar.did) { + format!(" closure::<{}{ty}>", generic.name.as_str()) + } else { + format!(" {}::<{}{ty}>", tcx.item_name(gar.did).as_str(), generic.name.as_str()) + } + }).collect() + } +} + +#[derive(Debug, Clone, Copy)] +pub enum ConstRegionKind { + Const(Promote), + TyConst, + /// Crated constructing an Aggregate with internal regions, + /// for example a struct with reference-typed fields. + Aggregate(Promote), +} +impl Display for ConstRegionKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConstRegionKind::Const(p) => write!(f, "const{p}"), + ConstRegionKind::TyConst => write!(f, "tconst"), + ConstRegionKind::Aggregate(p) => write!(f, "aggregate{p}"), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum OtherAnnotationKind { + UserTy, + YieldTy, + RvalueTy(Promote), +} +impl Display for OtherAnnotationKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OtherAnnotationKind::UserTy => write!(f, "user"), + OtherAnnotationKind::YieldTy => write!(f, "yield"), + OtherAnnotationKind::RvalueTy(p) => write!(f, "rvalue{p}"), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Promote { + NotPromoted, + Promoted(Promoted), +} +impl Display for Promote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Promote::NotPromoted => Ok(()), + Promote::Promoted(p) => write!(f, " @promoted[{}]", p.index()), + } + } +} + #[derive(Debug, Clone)] pub enum RegionKind<'tcx> { // Universal regions (placeholders) @@ -43,14 +116,22 @@ pub enum RegionKind<'tcx> { Param(ParamRegion), Function, UnknownUniversal, + // - update `fn universal` if a new one is added! + // Local regions - ConstRef(bool), + ConstRef(ConstRegionKind, Vec>), Place { - region: RegionVid, local: Local, - // ty: Ty<'tcx>, + ty: Ty<'tcx>, + promoted: Promote, + fn_generic: Vec>, }, - Borrow(BorrowData<'tcx>), + Borrow(BorrowData<'tcx>, Promote), + UnusedReturnBug(Promote), // TODO: remove once `https://github.com/rust-lang/rust/pull/116792` lands + + // AggregateGeneric(DefId, usize, Option>), + // FnGeneric(DefId, usize, Option>), + EarlyBound(Symbol), LateBound { // span: Span, @@ -58,6 +139,8 @@ pub enum RegionKind<'tcx> { ctime: LateBoundRegionConversionTime, }, Placeholder(Option), + ProjectionAnnotation(Place<'tcx>, Ty<'tcx>, Vec>), + OtherAnnotation(OtherAnnotationKind, Ty<'tcx>, Vec>), MiscLocal, UnknownLocal, } @@ -84,6 +167,9 @@ impl<'tcx> RegionKind<'tcx> { pub fn is_borrow(&self) -> bool { matches!(self, Self::Borrow(..)) } + pub fn is_unused_return_bug(&self) -> bool { + matches!(self, Self::UnusedReturnBug(..)) + } pub fn is_early_bound(&self) -> bool { matches!(self, Self::EarlyBound(..)) } @@ -93,6 +179,12 @@ impl<'tcx> RegionKind<'tcx> { pub fn is_placeholder(&self) -> bool { matches!(self, Self::Placeholder(..)) } + pub fn is_projection_annotation(&self) -> bool { + matches!(self, Self::ProjectionAnnotation(..)) + } + pub fn is_other_annotation(&self) -> bool { + matches!(self, Self::OtherAnnotation(..)) + } pub fn is_misc_local(&self) -> bool { matches!(self, Self::MiscLocal) } @@ -100,30 +192,76 @@ impl<'tcx> RegionKind<'tcx> { matches!(self, Self::UnknownLocal) } - pub fn is_unknown(&self) -> bool { + pub fn promoted(&self) -> bool { + matches!(self, + Self::Place { promoted: Promote::Promoted(..), .. } + | Self::Borrow(_, Promote::Promoted(..)) + | Self::UnusedReturnBug(Promote::Promoted(..)) + | Self::OtherAnnotation(OtherAnnotationKind::RvalueTy(Promote::Promoted(..)), _, _) + | Self::ConstRef(ConstRegionKind::Const(Promote::Promoted(..)), _) + | Self::ConstRef(ConstRegionKind::Aggregate(Promote::Promoted(..)), _)) + } + pub fn unknown(&self) -> bool { matches!(self, Self::UnknownUniversal | Self::UnknownLocal) } - pub fn is_universal(&self) -> bool { + pub fn universal(&self) -> bool { matches!( self, Self::Static | Self::Param(..) | Self::Function | Self::UnknownUniversal ) } - pub fn is_local(&self) -> bool { - !self.is_universal() + pub fn local(&self) -> bool { + !self.universal() + } + + pub fn from_function_depth(&self) -> usize { + match self { + Self::LateBound { ctime: LateBoundRegionConversionTime::FnCall, .. } => 1, + Self::ConstRef(_, fn_generic) | + Self::Place { fn_generic, .. } | + Self::ProjectionAnnotation(_, _, fn_generic) | + Self::OtherAnnotation(_, _, fn_generic) => fn_generic.len(), + _ => 0, + } + } + + pub fn set_fn_generic(&mut self, generic: GenericArgRegion<'tcx>) { + match self { + Self::ConstRef(_, fn_generic) | + Self::Place { fn_generic, .. } | + Self::ProjectionAnnotation(_, _, fn_generic) | + Self::OtherAnnotation(_, _, fn_generic) => fn_generic.push(generic), + _ => panic!("{self:?} ({generic:?})"), + } + } + pub fn unset_fn_generic(&mut self) { + match self { + Self::ConstRef(_, fn_generic) | + Self::Place { fn_generic, .. } | + Self::ProjectionAnnotation(_, _, fn_generic) | + Self::OtherAnnotation(_, _, fn_generic) => assert!(fn_generic.pop().is_some()), + _ => panic!(), + } } // #[tracing::instrument(name = "RegionKind::get_place", level = "trace", ret)] pub fn get_place(&self) -> Option { match self { - Self::Place { local, .. } => Some(*local), + Self::Place { local, promoted: Promote::NotPromoted, .. } => Some(*local), _ => None, } } pub fn get_borrow(&self) -> Option<&BorrowData<'tcx>> { match self { - Self::Borrow(data) => Some(data), + Self::Borrow(data, Promote::NotPromoted) => Some(data), + _ => None, + } + } + pub fn get_borrow_or_projection_local(&self) -> Option { + match self { + Self::Borrow(data, Promote::NotPromoted) => Some(data.borrowed_place.local), + Self::ProjectionAnnotation(place, _, _) => Some(place.local), _ => None, } } @@ -148,29 +286,32 @@ impl<'tcx> RegionKind<'tcx> { } Self::Function => "'fn".to_string(), Self::UnknownUniversal => "'unknown".to_string(), - Self::ConstRef(true) => "ext_const".to_string(), - Self::ConstRef(false) => "const".to_string(), - Self::Place { region, local } => { + Self::ConstRef(kind, fn_generic) => { + format!("{kind}{}", GenericArgRegion::to_string(fn_generic, cgx)) + } + Self::Place { local, ty, promoted, fn_generic } => { let place = Place::from(*local); - let exact = place.deref_to_region(*region, cgx.rp); - let display = exact.unwrap_or(place).to_string(cgx.rp); - if exact.is_some() { - format!("{display:?}") - } else { - format!("AllIn({region:?}, {display:?})") - } + // let exact = place.deref_to_region(*region, cgx.rp); + // let display = exact.unwrap_or(place).to_string(cgx.rp); + // if exact.is_some() { + // format!("{display:?}{promoted}") + // } else { + format!("AllIn({:?} = {ty}){promoted}", place.to_string(cgx.rp)) + // } } - Self::Borrow(b) => match b.kind { - BorrowKind::Shared => { - format!("& {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) - } - BorrowKind::Mut { .. } => { - format!("&mut {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) - } - BorrowKind::Shallow => { - format!("&sh {:?}", Place::from(b.borrowed_place).to_string(cgx.rp)) + Self::Borrow(b, promoted) => { + match b.kind { + BorrowKind::Shared => { + format!("& {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } + BorrowKind::Mut { .. } => { + format!("&mut {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } + BorrowKind::Shallow => { + format!("&sh {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) + } } - }, + } Self::EarlyBound(name) => name.as_str().to_string(), Self::LateBound { kind, ctime } => { let kind = match kind { @@ -194,13 +335,18 @@ impl<'tcx> RegionKind<'tcx> { }; format!("{kind}@{:?}", p.universe) } + Self::ProjectionAnnotation(place, ty, fn_generic) => + format!("{:?}: {ty}{}", place.to_string(cgx.rp), GenericArgRegion::to_string(fn_generic, cgx)), + &Self::OtherAnnotation(kind, ty, ref fn_generic) => + format!("{kind} {ty}{}", GenericArgRegion::to_string(fn_generic, cgx)), Self::MiscLocal => "?misc?".to_string(), + Self::UnusedReturnBug(..) => unreachable!(), Self::UnknownLocal => "???".to_string(), } } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ParamRegion { pub regions: Vec, } @@ -224,6 +370,19 @@ impl<'tcx> RegionInfoMap<'tcx> { } } + pub(super) fn check_already_local(&mut self, r: RegionVid, l: Local) { + let kind = self.get(r); + if kind.get_place().is_none() { + panic!("{r:?}: {:?} != {:?}", kind, l); + } + assert_eq!(kind.get_place().unwrap(), l); + } + pub(super) fn check_already_borrow(&mut self, r: RegionVid, b: &BorrowData<'tcx>) { + let data = self.get(r).get_borrow().unwrap(); + assert_eq!(data.to_string(), b.to_string()); + assert_eq!(data.reserve_location, b.reserve_location); + assert_eq!(data.activation_location, b.activation_location); + } pub(super) fn set(&mut self, r: RegionVid, kind: RegionKind<'tcx>) { match self.get(r) { RegionKind::UnknownUniversal => assert!(kind.is_static() || kind.is_function()), @@ -231,15 +390,18 @@ impl<'tcx> RegionInfoMap<'tcx> { kind.is_const_ref() || kind.is_place() || kind.is_borrow() + || kind.is_unused_return_bug() || kind.is_early_bound() || kind.is_late_bound() || kind.is_placeholder() + || kind.is_projection_annotation() + || kind.is_other_annotation() || kind.is_misc_local(), "{kind:?}" ), - other => panic!("{other:?}"), + other => panic!("{other:?} ({kind:?})"), } - if kind.is_const_ref() { + if kind.is_const_ref() && kind.from_function_depth() == 0 { self.constant_regions.push(r); } self.region_info[r] = kind; @@ -268,7 +430,8 @@ impl<'tcx> RegionInfoMap<'tcx> { RegionVariableOrigin::Autoref(_) => todo!(), RegionVariableOrigin::Coercion(_) => todo!(), RegionVariableOrigin::EarlyBoundRegion(_, name) => { - self.set(r, RegionKind::EarlyBound(name)) + self.set(r, RegionKind::EarlyBound(name)); + todo!(); // Figure out how this compares to `FnGeneric` } RegionVariableOrigin::LateBoundRegion(_, kind, ctime) => { self.set(r, RegionKind::LateBound { kind, ctime }) @@ -276,7 +439,7 @@ impl<'tcx> RegionInfoMap<'tcx> { RegionVariableOrigin::UpvarRegion(_, _) => todo!(), RegionVariableOrigin::Nll(k) => match k { NllRegionVariableOrigin::FreeRegion => { - assert!(self.get(r).is_universal()); + assert!(self.get(r).universal()); return; } NllRegionVariableOrigin::Placeholder(p) => { @@ -288,7 +451,7 @@ impl<'tcx> RegionInfoMap<'tcx> { NllRegionVariableOrigin::Existential { from_forall: false } => (), }, } - assert!(!self.get(r).is_universal()); + assert!(!self.get(r).universal()); } // #[tracing::instrument(name = "RegionInfoMap::get", level = "trace", skip(self), ret)] @@ -309,7 +472,8 @@ impl<'tcx> RegionInfoMap<'tcx> { (0..self.region_info.len()).map(RegionVid::from) } pub fn for_local(&self, r: RegionVid, l: Local) -> bool { - self.get(r).get_place() == Some(l) + self.get(r).get_place() == Some(l) || + self.get(r).get_borrow_or_projection_local() == Some(l) } pub fn const_regions(&self) -> &[RegionVid] { &self.constant_regions diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index d6255e13ad8..64ea6f66e89 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -4,19 +4,22 @@ // 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::{ops::ControlFlow, marker::PhantomData}; + use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ borrowck::{ - borrow_set::BorrowData, + borrow_set::{BorrowData, TwoPhaseActivation}, consumers::{BorrowIndex, Borrows, OutlivesConstraint, PoloniusInput, RustcFacts}, }, data_structures::fx::FxHashMap, dataflow::{Analysis, ResultsCursor}, - index::IndexVec, + index::Idx, middle::{ - mir::{visit::Visitor, Body, Constant, Local, Location, Operand, RETURN_PLACE, Rvalue, ConstantKind}, - ty::{GenericArg, RegionVid, Ty, TyCtxt}, + mir::{visit::{Visitor, TyContext, PlaceContext}, Body, Constant, Local, Location, Operand, RETURN_PLACE, Rvalue, ConstantKind, Terminator, TerminatorKind, PlaceRef, PlaceElem, ProjectionElem, AggregateKind, Promoted, Place as MirPlace}, + ty::{TypeVisitor, TypeSuperVisitable, TypeVisitable, GenericArg, RegionVid, Ty, TyCtxt, TyKind, Region, GenericArgsRef, BoundVariableKind, Const, GenericArgKind}, }, + span::{Span, Symbol, def_id::DefId}, }; use crate::{ @@ -24,9 +27,9 @@ use crate::{ utils::{Place, PlaceRepacker, r#const::ConstEval}, }; -use self::map::{RegionInfoMap, RegionKind}; +use self::map::{RegionInfoMap, RegionKind, GenericArgRegion, ConstRegionKind, OtherAnnotationKind, Promote}; -use super::{region_place::PlaceRegion, shared_borrow_set::SharedBorrowSet}; +use super::{shared_borrow_set::SharedBorrowSet, CgContext}; pub mod map; @@ -59,7 +62,7 @@ impl<'tcx> RegionInfo<'tcx> { Self::initialize_universals(&mut map, rp, input_facts, facts2); // Init consts - Self::initialize_consts(&mut map, rp); + Self::initialize_consts(&mut map, rp, facts2); // Init locals Self::initialize_locals(&mut map, rp, input_facts, facts2, sbs); @@ -70,6 +73,13 @@ impl<'tcx> RegionInfo<'tcx> { map.set_region_info(r, info); } + // We should have figured out all local regions + for r in map.all_regions() { + // TODO: resolve all unknowns + assert!(!map.get(r).is_unknown_local(), "{r:?} is unknown: {map:?}"); + // assert!(!map.get(r).unknown(), "{r:?} is unknown: {map:?}"); + } + Self { map, static_region, @@ -143,15 +153,22 @@ impl<'tcx> RegionInfo<'tcx> { (static_region, function_region) } - pub fn initialize_consts(map: &mut RegionInfoMap<'tcx>, rp: PlaceRepacker<'_, 'tcx>) { - let mut collector = ConstantRegionCollector { map }; - collector.visit_body(rp.body()); - for constant in &rp.body().required_consts { - for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { - assert!(r.is_var(), "{r:?} in {constant:?}"); - map.set(r.as_var(), RegionKind::ConstRef(true)); - } + pub fn initialize_consts(map: &mut RegionInfoMap<'tcx>, rp: PlaceRepacker<'_, 'tcx>, facts2: &BorrowckFacts2<'tcx>) { + let mut collector = ConstantRegionCollector { + map, inner_kind: None, fn_ptr: false, rp, facts2, return_ty: None, promoted_idx: Promote::NotPromoted, regions_set: None, max_region: None, + }; + for (idx, promoted) in rp.promoted().iter_enumerated() { + collector.promoted_idx = Promote::Promoted(idx); + collector.visit_body(promoted); } + collector.promoted_idx = Promote::NotPromoted; + collector.visit_body(rp.body()); + // for constant in &rp.body().required_consts { + // for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { + // assert!(r.is_var(), "{r:?} in {constant:?}"); + // map.set(r.as_var(), RegionKind::ConstRef(ConstRegionKind::ExternalConst, Vec::new())); + // } + // } } pub fn initialize_locals( @@ -162,36 +179,241 @@ impl<'tcx> RegionInfo<'tcx> { sbs: &SharedBorrowSet<'tcx>, ) { for &(local, region) in &input_facts.use_of_var_derefs_origin { - map.set(region, RegionKind::Place { region, local }); + // TODO: remove + map.check_already_local(region, local); } for data in sbs .location_map .values() .chain(facts2.borrow_set.location_map.values()) { - map.set(data.region, RegionKind::Borrow(data.clone())); + map.check_already_borrow(data.region, data); } } } -struct ConstantRegionCollector<'a, 'tcx> { +struct ConstantRegionCollector<'a, 'b, 'tcx> { map: &'a mut RegionInfoMap<'tcx>, + inner_kind: Option>, + promoted_idx: Promote, + fn_ptr: bool, + rp: PlaceRepacker<'b, 'tcx>, + facts2: &'b BorrowckFacts2<'tcx>, + return_ty: Option>, + // Set this to start counting regions + regions_set: Option, + max_region: Option, } -impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, 'tcx> { - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { - // We might have a non-var `r` as the function operand (const) to a function call. - // This function call may be e.g. `ParseBuffer::<'_>::step` and we want to ignore the `'_`. - // So instead of matching on arbitrary constants, we only match on Rvalue creation of them. - self.super_rvalue(rvalue, location); - if let Rvalue::Use(Operand::Constant(constant)) = rvalue { - for r in constant.ty().walk().flat_map(|ga| ga.as_region()) { - // We may run into non-var regions here, e.g. assigning a const closure to a local where - // the closure captures state and thus that has e.g. `BrNamed` regions. - if r.is_var() { - // assert!(r.is_var(), "{r:?} in {constant:?} ({:?} at {location:?})", constant.ty()); - self.map.set(r.as_var(), RegionKind::ConstRef(false)); +impl<'tcx> ConstantRegionCollector<'_, '_, 'tcx> { + fn with_kind(&mut self, kind: RegionKind<'tcx>, f: impl FnOnce(&mut Self) -> T) -> T { + let disc = std::mem::discriminant(&kind); + let old_kind = self.inner_kind.replace(kind); + assert!(old_kind.is_none()); + let t = f(self); + let kind = self.inner_kind.take(); + assert_eq!(std::mem::discriminant(&kind.unwrap()), disc); + self.inner_kind = old_kind; + t + } + + fn visit_generics_args(&mut self, did: DefId, generics: GenericArgsRef<'tcx>) { + for (gen_idx, arg) in generics.iter().enumerate() { + let inner_kind = self.inner_kind.as_mut().unwrap(); + inner_kind.set_fn_generic(GenericArgRegion { did, gen_idx, full_ty: arg.as_type() }); + arg.visit_with(self); + self.inner_kind.as_mut().unwrap().unset_fn_generic(); + } + } + + fn set_region(&mut self, r: RegionVid, kind: RegionKind<'tcx>) { + self.map.set(r, kind); + if let Some(regions_set) = &mut self.regions_set { + *regions_set += 1; + if let Some(max_region) = self.max_region { + assert_eq!(max_region.index() + 1, r.index()); + } + self.max_region = Some(r); + } + } +} + +impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { + #[tracing::instrument(name = "ConstantRegionCollector::visit_ty", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_ty(&mut self, ty: Ty<'tcx>, ctx: TyContext) { + // println!("Ty ({ctx:?}): {ty:?}"); + match ctx { + TyContext::LocalDecl { local, .. } => { + if local == RETURN_PLACE { + assert!(self.regions_set.is_none() && self.max_region.is_none()); + self.regions_set = Some(0); + } + self.with_kind(RegionKind::Place { local, ty, promoted: self.promoted_idx, fn_generic: Vec::new() }, + |this| ty.visit_with(this) + ); + if local == RETURN_PLACE { + // TODO: remove this once `https://github.com/rust-lang/rust/pull/116792` lands + let return_regions = self.regions_set.take().unwrap(); + if let Some(new_max) = self.max_region.take() { + for r in (0..return_regions).rev().map(|sub| RegionVid::from_usize(new_max.index() - return_regions - sub)) { + self.map.set(r, RegionKind::UnusedReturnBug(self.promoted_idx)); + } + } + } + } + TyContext::ReturnTy(_) => { + assert_eq!(ty, self.return_ty.unwrap()) + } + TyContext::UserTy(_) => { + self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::UserTy, ty, Vec::new()), + |this| ty.visit_with(this) + ); + } + TyContext::YieldTy(_) => { + self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::YieldTy, ty, Vec::new()), + |this| ty.visit_with(this) + ); + } + TyContext::Location(_location) => { + assert!(self.inner_kind.is_some()); + ty.visit_with(self); + } + } + } + + #[tracing::instrument(name = "ConstantRegionCollector::visit_projection_elem", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_projection_elem(&mut self, place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, ctx: PlaceContext, location: Location) { + // println!("Projection elem ({ctx:?}): {place_ref:?} ({elem:?}) [{location:?}]"); + let place = Place::from(place_ref).mk_place_elem(elem, self.rp); + if let Some(ty) = place.last_projection_ty() { + assert!(matches!(elem, ProjectionElem::Field(..) | ProjectionElem::OpaqueCast(..))); + self.with_kind(RegionKind::ProjectionAnnotation(place, ty, Vec::new()), + |this| this.super_projection_elem(place_ref, elem, ctx, location) + ) + } else { + self.super_projection_elem(place_ref, elem, ctx, location) + } + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_ty_const", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_ty_const(&mut self, ct: Const<'tcx>, _location: Location) { + // e.g. from `Rvalue::Repeat` + self.with_kind(RegionKind::ConstRef(ConstRegionKind::TyConst, Vec::new()), |this| { + ct.visit_with(this); + }); + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_constant", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_constant(&mut self, constant: &Constant<'tcx>, _location: Location) { + // println!("Constant: {:?}", constant.ty()); + assert!(self.inner_kind.is_none()); + self.with_kind(RegionKind::ConstRef(ConstRegionKind::Const(self.promoted_idx), Vec::new()), |this| { + constant.visit_with(this); + }); + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_args", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_args(&mut self, args: &GenericArgsRef<'tcx>, location: Location) { + // Do nothing since already handled by `Rvalue::Aggregate`. + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_region", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_region(&mut self, region: Region<'tcx>, location: Location) { + // Do nothing since already handled by `Rvalue::Ref`. + } + + #[tracing::instrument(name = "ConstantRegionCollector::visit_operand", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { + // Temporarily remove `OtherAnnotationKind::RvalueTy` + let from_rvalue = matches!(self.inner_kind, Some(RegionKind::OtherAnnotation(OtherAnnotationKind::RvalueTy(_), _, _))); + if from_rvalue { + let kind = self.inner_kind.take(); + self.super_operand(operand, location); + self.inner_kind = kind; + } else { + self.super_operand(operand, location) + } + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_assign", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_assign(&mut self, place: &MirPlace<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { + match rvalue { + &Rvalue::Aggregate(box AggregateKind::Adt(did, _, generics, _, _), _) | + &Rvalue::Aggregate(box AggregateKind::Closure(did, generics), _) | + &Rvalue::Aggregate(box AggregateKind::Generator(did, generics, _), _) => { + self.with_kind(RegionKind::ConstRef(ConstRegionKind::Aggregate(self.promoted_idx), Vec::new()), + |this| this.visit_generics_args(did, generics)); + self.super_assign(place, rvalue, location); // For the operand + } + &Rvalue::Ref(region, kind, borrowed_place) => { + let is_non_promoted = matches!(self.promoted_idx, Promote::NotPromoted); + let location_map = &self.facts2.borrow_set.location_map; + if is_non_promoted && location_map.contains_key(&location) { + let borrow = location_map.get(&location).unwrap(); + assert_eq!(borrow.region, region.as_var()); + self.set_region(borrow.region, RegionKind::Borrow(borrow.clone(), Promote::NotPromoted)) + } else { + let region = region.as_var(); + let borrow = BorrowData { + reserve_location: location, + activation_location: TwoPhaseActivation::NotTwoPhase, + kind, + region, + borrowed_place, + assigned_place: *place, + }; + self.set_region(borrow.region, RegionKind::Borrow(borrow, self.promoted_idx)) } + self.super_assign(place, rvalue, location) + } + Rvalue::Aggregate(box AggregateKind::Array(ty), _) | + Rvalue::Cast(_, _, ty) | + Rvalue::NullaryOp(_, ty) | + Rvalue::ShallowInitBox(_, ty) => { + self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::RvalueTy(self.promoted_idx), *ty, Vec::new()), + |this| this.super_assign(place, rvalue, location) + ); + } + _ => self.super_assign(place, rvalue, location), + } + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_body", level = "trace", skip_all, fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_body(&mut self, body: &Body<'tcx>) { + // println!("body: {body:#?}"); + self.return_ty = Some(body.local_decls[RETURN_PLACE].ty); + self.super_body(body); + self.return_ty = None; + } +} + +impl<'tcx> TypeVisitor> for ConstantRegionCollector<'_, '_, 'tcx> { + #[tracing::instrument(name = "ConstantRegionCollector::visit_region", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow { + // println!("Region: {r:?}"); + if self.fn_ptr && !r.is_var() { + return ControlFlow::Continue(()); + } + let kind = self.inner_kind.clone().unwrap(); + assert!(self.promoted_idx == Promote::NotPromoted || kind.promoted(), "{kind:?} {r:?}"); + self.set_region(r.as_var(), kind); + ControlFlow::Continue(()) + } + #[tracing::instrument(name = "ConstantRegionCollector::visit_ty", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] + fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { + // println!("Ty inner: {ty:?}"); + match ty.kind() { + &TyKind::FnDef(did, generics) | + &TyKind::Closure(did, generics) => { + self.visit_generics_args(did, generics); + ControlFlow::Continue(()) + } + TyKind::FnPtr(_) => { + self.fn_ptr = true; + let r = ty.super_visit_with(self); + self.fn_ptr = false; + r } + // Maybe we want to handle the `region` here differently? + // TyKind::Dynamic(other, region, _) => todo!(), + TyKind::Generator(_, _, _) => todo!(), + TyKind::GeneratorWitness(_) => todo!(), + TyKind::GeneratorWitnessMIR(_, _) => todo!(), + TyKind::Bound(_, _) => todo!(), + _ => ty.super_visit_with(self), } } } diff --git a/mir-state-analysis/src/coupling_graph/context/region_place.rs b/mir-state-analysis/src/coupling_graph/context/region_place.rs deleted file mode 100644 index 6e35c70d820..00000000000 --- a/mir-state-analysis/src/coupling_graph/context/region_place.rs +++ /dev/null @@ -1,113 +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; - -use prusti_rustc_interface::{ - borrowck::{ - borrow_set::{BorrowData, BorrowSet, LocalsStateAtExit, TwoPhaseActivation}, - consumers::{BorrowIndex, PlaceExt}, - }, - data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, - dataflow::fmt::DebugWithContext, - index::{bit_set::BitSet, IndexVec}, - middle::{ - mir::{ - traversal, visit::Visitor, Body, ConstraintCategory, Local, Location, Rvalue, - RETURN_PLACE, - }, - ty::{RegionVid, TyKind}, - }, -}; - -use crate::utils::{display::PlaceDisplay, Place, PlaceRepacker}; - -use super::shared_borrow_set::SharedBorrowSet; - -#[derive(Clone)] -pub struct PlaceRegion<'tcx> { - pub place: Place<'tcx>, - pub region: Option, - pub pretty: PlaceDisplay<'tcx>, -} - -impl Debug for PlaceRegion<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let hint = if self.pretty.is_user() { - format!(" <<{:?}>>", self.pretty) - } else { - String::new() - }; - match self.region { - Some(r) => write!(f, "AllIn({r:?}, {:?}{hint})", self.place), - None => write!(f, "{:?}{hint}", self.place), - } - } -} - -impl<'tcx> PlaceRegion<'tcx> { - pub fn region_place_map( - use_of_var_derefs_origin: &Vec<(Local, RegionVid)>, - borrows: &BorrowSet<'tcx>, - shared_borrows: &SharedBorrowSet<'tcx>, - rp: PlaceRepacker<'_, 'tcx>, - ) -> FxHashMap> { - let mut map = FxHashMap::default(); - for &(l, r) in use_of_var_derefs_origin { - let place = l.into(); - let perm = if let Some(place) = Self::try_make_precise(place, r, rp) { - PlaceRegion { - place, - region: None, - pretty: place.to_string(rp), - } - } else { - PlaceRegion { - place, - region: Some(r), - pretty: place.to_string(rp), - } - }; - let existing = map.insert(r, perm); - assert!(existing.is_none(), "{existing:?} vs {:?}", map[&r]); - } - for data in shared_borrows - .location_map - .values() - .chain(borrows.location_map.values()) - { - let place = data.borrowed_place.into(); - let perm = PlaceRegion { - place, - region: None, - pretty: place.to_string(rp), - }; - let existing = map.insert(data.region, perm); - assert!( - existing.is_none(), - "{existing:?} vs {:?}", - map[&data.region] - ); - } - map - } - - fn try_make_precise( - mut p: Place<'tcx>, - r: RegionVid, - rp: PlaceRepacker<'_, 'tcx>, - ) -> Option> { - let mut ty = p.ty(rp).ty; - while let TyKind::Ref(rr, inner_ty, _) = *ty.kind() { - ty = inner_ty; - p = p.mk_deref(rp); - if rr.is_var() && rr.as_var() == r { - return Some(p); - } - } - None - } -} diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index 623d506e83d..f2c485b4189 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -17,17 +17,19 @@ use prusti_rustc_interface::{ }, }; -use super::{graph::EdgeInfo, triple::Cg}; +use crate::coupling_graph::outlives_info::edge::{EdgeInfo, EdgeKind}; + +use super::{triple::Cg}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Edge<'tcx> { pub from: RegionVid, pub to: RegionVid, - pub reasons: FxHashSet>, + pub reasons: FxHashSet>>, } impl<'tcx> Edge<'tcx> { - pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>) -> Self { + pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>>) -> Self { Self { from, to, reasons } } } @@ -35,38 +37,41 @@ impl<'tcx> Edge<'tcx> { impl<'a, 'tcx> Cg<'a, 'tcx> { fn get_id(&self) -> String { if let Some(id) = &self.id { - id.replace('[', "_").replace(']', "") + let pre = if self.is_pre { "_pre" } else { "" }; + format!("{id:?}{pre}").replace('[', "_").replace(']', "") } else { - "unnamed".to_string() + "start".to_string() } } } impl<'a, 'tcx> Cg<'a, 'tcx> { fn non_empty_edges( &self, - r: RegionVid, + sub: RegionVid, start: RegionVid, - mut reasons: FxHashSet>, + mut reasons: FxHashSet>>, visited: &mut FxHashSet, ) -> Vec> { let mut edges = Vec::new(); - if !visited.insert(r) { + if !visited.insert(sub) { return edges; } - if (self.dot_filter)(self.cgx.region_info.map.get(r)) { - // Remove empty reason - reasons.remove(&EdgeInfo { - creation: None, - reason: None, - }); - return vec![Edge::new(start, r, reasons)]; + let sub_info = self.cgx.region_info.map.get(sub); + if (self.dot_node_filter)(sub_info) { + // Remove empty reasons + // reasons.retain(|reason| reason.iter().any(|r| r.creation.is_some()) || reason.reason.is_some()); + return vec![Edge::new(start, sub, reasons)]; } - for (&b, edge) in &self.graph.nodes[r].blocks { + for (&sup, edge) in &self.graph.nodes[sub].blocks { + let sup_info = self.cgx.region_info.map.get(sup); + if !(self.dot_edge_filter)(sup_info, sub_info) { + continue; + } let mut reasons = reasons.clone(); - reasons.extend(edge); - edges.extend(self.non_empty_edges(b, start, reasons, visited)); + reasons.extend(edge.clone()); + edges.extend(self.non_empty_edges(sup, start, reasons, visited)); } - visited.remove(&r); + visited.remove(&sub); edges } } @@ -83,17 +88,21 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { } fn edge_style(&'a self, e: &Edge<'tcx>) -> dot::Style { - if self.cgx.region_info.map.get(e.from).is_borrow() { - dot::Style::Dotted - } else { + let is_blocking = e.reasons.iter().all(|e| EdgeKind::many_blocking(EdgeInfo::many_kind(e, self.cgx))); + if is_blocking { dot::Style::Solid + } else { + dot::Style::Dotted } } fn edge_label(&'a self, e: &Edge<'tcx>) -> dot::LabelText<'a> { let mut label = e .reasons .iter() - .map(|s| format!("{s}\n")) + .map(|s| { + let line = s.into_iter().map(|s| s.to_string() + ", ").collect::(); + format!("{}\n", &line[..line.len() - 2]) // `s.len() > 0` + }) .collect::(); if label.len() > 0 { label = label[..label.len() - 1].to_string(); @@ -102,7 +111,7 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { } fn node_color(&'a self, r: &RegionVid) -> Option> { let kind = self.get_kind(*r); - if kind.is_universal() { + if kind.universal() { Some(dot::LabelText::LabelStr(Cow::Borrowed("red"))) } else { None @@ -143,7 +152,7 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { let mut nodes: Vec<_> = self .graph .all_nodes() - .filter(|(r, _)| (self.dot_filter)(self.cgx.region_info.map.get(*r))) + .filter(|(r, _)| (self.dot_node_filter)(self.cgx.region_info.map.get(*r))) .map(|(r, _)| r) .collect(); // if self.static_regions.len() > 1 { @@ -154,13 +163,18 @@ impl<'a, 'b, 'tcx> dot::GraphWalk<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { fn edges(&'a self) -> dot::Edges<'a, Edge<'tcx>> { let mut edges = Vec::new(); - for (r, n) in self.graph.all_nodes() { - if !(self.dot_filter)(self.cgx.region_info.map.get(r)) { + for (sub, n) in self.graph.all_nodes() { + let sub_info = self.cgx.region_info.map.get(sub); + if !(self.dot_node_filter)(sub_info) { continue; } - let visited = &mut FxHashSet::from_iter([r]); - for (&b, edge) in &n.blocks { - edges.extend(self.non_empty_edges(b, r, edge.clone(), visited)); + let visited = &mut FxHashSet::from_iter([sub]); + for (&sup, edge) in &n.blocks { + let sup_info = self.cgx.region_info.map.get(sup); + if !(self.dot_edge_filter)(sup_info, sub_info) { + continue; + } + edges.extend(self.non_empty_edges(sup, sub, edge.clone(), visited)); } } Cow::Owned(edges) diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index 438a836a913..b8d24455484 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -35,7 +35,7 @@ use crate::{ coupling_graph::{ graph::{Graph, Node}, outlives_info::AssignsToPlace, - CgContext, + CgContext, consistency::CouplingConsistency, }, free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, utils::PlaceRepacker, @@ -44,12 +44,11 @@ use crate::{ use super::triple::Cg; #[tracing::instrument(name = "draw_dots", level = "debug", skip(c))] -pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a, 'tcx>>) { +pub(crate) fn draw_dots<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph<'a, 'tcx>>) { let mut graph = Vec::new(); let body = c.body(); c.seek_to_block_start(START_BLOCK); - let mut g = c.get().clone(); - g.id = Some(format!("start")); + let g = c.get().clone(); dot::render(&g, &mut graph).unwrap(); for (block, data) in body.basic_blocks.iter_enumerated() { @@ -58,23 +57,30 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a } c.seek_to_block_start(block); let mut g = c.get().clone(); - g.dot_filter = |k| k.is_local(); - g.id = Some(format!("{block:?}_pre")); + g.dot_node_filter = |k| k.local(); + g.dot_edge_filter = |sup, sub| !(sup.local() && sub.universal()); + g.id = Some(Location { block, statement_index: 0 }); dot::render(&g, &mut graph).unwrap(); - for statement_index in 0..body.terminator_loc(block).statement_index + 1 { - c.seek_after_primary_effect(Location { + drop(g); + for statement_index in 0..body.terminator_loc(block).statement_index { + let location = Location { block, statement_index, - }); - g = c.get().clone(); - g.dot_filter = |k| k.is_local(); - g.id = Some(format!("{block:?}_{statement_index}")); - dot::render(&g, &mut graph).unwrap(); + }; + print_after_loc(c, location, &mut graph); } + // Terminator + let location = Location { + block, + statement_index: body.terminator_loc(block).statement_index, + }; if let TerminatorKind::Return = data.terminator().kind { - g.dot_filter = |k| !k.is_unknown_local(); - g.id = Some(format!("{block:?}_ret")); + c.seek_before_primary_effect(location); + let mut g = c.get().clone(); + g.dot_node_filter = |k| !k.is_unknown_local(); dot::render(&g, &mut graph).unwrap(); + } else { + print_after_loc(c, location, &mut graph); } } let combined = std::str::from_utf8(graph.as_slice()).unwrap().to_string(); @@ -89,14 +95,22 @@ pub(crate) fn draw_dots<'tcx, 'a>(mut c: ResultsCursor<'_, 'tcx, CoupligGraph<'a .expect("Unable to write file"); } -pub(crate) struct CoupligGraph<'a, 'tcx> { - cgx: &'a CgContext<'a, 'tcx>, +fn print_after_loc<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph<'a, 'tcx>>, location: Location, graph: &mut Vec) { + c.seek_after_primary_effect(location); + let mut g = c.get().clone(); + g.dot_node_filter = |k| k.local(); + g.dot_edge_filter = |sup, sub| !(sup.local() && sub.universal()); + dot::render(&g, graph).unwrap(); +} + +pub(crate) struct CouplingGraph<'a, 'tcx> { + pub(crate) cgx: &'a CgContext<'a, 'tcx>, out_of_scope: FxIndexMap>, flow_borrows: RefCell>>, top_crates: bool, } -impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { +impl<'a, 'tcx> CouplingGraph<'a, 'tcx> { pub(crate) fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { if cfg!(debug_assertions) && !top_crates { std::fs::remove_dir_all("log/coupling").ok(); @@ -113,7 +127,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { .iterate_to_fixpoint() .into_results_cursor(cgx.rp.body()); - if !top_crates { + if false && !top_crates { println!("body: {:#?}", cgx.rp.body()); println!("\ninput_facts: {:?}", cgx.facts.input_facts); println!("output_facts: {:#?}\n", cgx.facts.output_facts); @@ -122,7 +136,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { "activation_map: {:#?}\n", cgx.facts2.borrow_set.activation_map ); - println!("local_map: {:#?}\n", cgx.facts2.borrow_set.local_map); + println!("local_map: {:?}\n", cgx.facts2.borrow_set.local_map); // println!("locals_state_at_exit: {:#?}\n", facts2.borrow_set.locals_state_at_exit); let lt = cgx.facts.location_table.borrow(); let lt = lt.as_ref().unwrap(); @@ -130,13 +144,16 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, facts.output_facts.origins_live_at(pt)); } println!("out_of_scope: {:?}", out_of_scope); + println!("outlives_constraints: {:#?}\n", cgx.facts2.region_inference_context.outlives_constraints().collect::>()); println!("cgx: {:#?}\n", cgx); for r in cgx.region_info.map.all_regions() { println!( - "R: {r:?}: {:?}", - cgx.facts2.region_inference_context.var_infos.get(r) + "R: {r:?}: {:?}, {:?}", + cgx.facts2.region_inference_context.var_infos.get(r), + cgx.region_info.map.get(r), ); } + panic!(); } Self { @@ -148,7 +165,7 @@ impl<'a, 'tcx> CoupligGraph<'a, 'tcx> { } } -impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { +impl<'a, 'tcx> AnalysisDomain<'tcx> for CouplingGraph<'a, 'tcx> { type Domain = Cg<'a, 'tcx>; const NAME: &'static str = "coupling_graph"; @@ -161,25 +178,24 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CoupligGraph<'a, 'tcx> { } } -impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { - #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] - fn apply_statement_effect( +impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { + #[tracing::instrument(name = "CouplingGraph::apply_before_statement_effect", level = "debug", skip(self))] + fn apply_before_statement_effect( &mut self, state: &mut Self::Domain, statement: &Statement<'tcx>, location: Location, ) { - let l = format!("{:?}", location).replace('[', "_").replace(']', ""); // println!("Location: {l}"); - state.id = Some(l.clone()); + state.id = Some(location); + state.reset_ops(); if location.statement_index == 0 { - state.version += 1; - assert!(state.version < 10); - + state.is_pre = false; // println!("\nblock: {:?}", location.block); + let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + format!("log/coupling/individual/{l}_v{}_start.dot", state.max_version()), false, ); self.flow_borrows @@ -192,38 +208,48 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { .seek_after_primary_effect(location); let other = self.flow_borrows.borrow().get().clone(); let delta = calculate_diff(&other, &state.live); + state.live = other; let oos = self.out_of_scope.get(&location); state.handle_kills(&delta, oos, location); + } + + #[tracing::instrument(name = "CouplingGraph::apply_statement_effect", level = "debug", skip(self))] + fn apply_statement_effect( + &mut self, + state: &mut Self::Domain, + statement: &Statement<'tcx>, + location: Location, + ) { + state.reset_ops(); state.handle_outlives(location, statement.kind.assigns_to()); state.visit_statement(statement, location); - state.live = other; - + let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.version), + format!("log/coupling/individual/{l}_v{}.dot", state.max_version()), false, ); } - #[tracing::instrument(name = "apply_statement_effect", level = "debug", skip(self))] - fn apply_terminator_effect<'mir>( + + #[tracing::instrument(name = "CouplingGraph::apply_before_terminator_effect", level = "debug", skip(self))] + fn apply_before_terminator_effect( &mut self, state: &mut Self::Domain, - terminator: &'mir Terminator<'tcx>, + terminator: &Terminator<'tcx>, location: Location, - ) -> TerminatorEdges<'mir, 'tcx> { - let l = format!("{:?}", location).replace('[', "_").replace(']', ""); + ) { // println!("Location: {l}"); - state.id = Some(l.clone()); + state.id = Some(location); + state.reset_ops(); if location.statement_index == 0 { - state.version += 1; - assert!(state.version < 10); - + state.is_pre = false; // println!("\nblock: {:?}", location.block); + let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.version), + format!("log/coupling/individual/{l}_v{}_start.dot", state.max_version()), false, ); self.flow_borrows @@ -237,8 +263,20 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { let other = self.flow_borrows.borrow().get().clone(); let delta = calculate_diff(&other, &state.live); + state.live = other; + let oos = self.out_of_scope.get(&location); state.handle_kills(&delta, oos, location); + } + + #[tracing::instrument(name = "CouplingGraph::apply_terminator_effect", level = "debug", skip(self))] + fn apply_terminator_effect<'mir>( + &mut self, + state: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + state.reset_ops(); state.handle_outlives(location, terminator.kind.assigns_to()); state.visit_terminator(terminator, location); @@ -246,12 +284,12 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { TerminatorKind::Return => { let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_pre.dot", state.version), + format!("log/coupling/individual/{l}_v{}_pre.dot", state.max_version()), false, ); // Pretend we have a storage dead for all `always_live_locals` other than the args/return for l in self.cgx.rp.always_live_locals_non_args().iter() { - state.kill_shared_borrows_on_place(location, l.into()); + state.kill_shared_borrows_on_place(Some(location), l.into()); } // Kill all the intermediate borrows, i.e. turn `return -> x.0 -> x` into `return -> x` for r in self @@ -262,7 +300,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { .values() .chain(self.cgx.sbs.location_map.values()) { - state.graph.remove(r.region, Some(location)); + state.graph.remove(r.region); } state.merge_for_return(location); @@ -270,10 +308,9 @@ impl<'a, 'tcx> Analysis<'tcx> for CoupligGraph<'a, 'tcx> { _ => (), }; - state.live = other; - + let l = format!("{:?}", location).replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.version), + format!("log/coupling/individual/{l}_v{}.dot", state.max_version()), false, ); terminator.edges() diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index 4469bd772c5..feafdba0130 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -26,14 +26,14 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{region_place::PlaceRegion, CgContext}, + coupling_graph::{CgContext, outlives_info::edge::EdgeInfo}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, utils::{Place, PlaceRepacker}, }; -use super::engine::CoupligGraph; +use super::engine::CouplingGraph; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Graph<'tcx> { @@ -44,52 +44,33 @@ pub struct Graph<'tcx> { impl<'tcx> Graph<'tcx> { #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] - pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> bool { - let edge = EdgeInfo { - creation: c.locations.from_location(), - reason: Some(c.category), - }; - self.outlives_inner(c.sup, c.sub, edge) + pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> Option<(RegionVid, RegionVid)> { + self.outlives_inner(vec![c.into()]) } - pub fn outlives_static(&mut self, r: RegionVid, static_region: RegionVid) { - if r == static_region { - return; - } - let edge = EdgeInfo { - creation: None, - reason: None, - }; - self.outlives_inner(r, static_region, edge); - } - pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid) -> bool { - let edge = EdgeInfo { - creation: None, - reason: None, - }; - self.outlives_inner(sup, sub, edge) + #[tracing::instrument(name = "Graph::outlives_placeholder", level = "trace", skip(self), ret)] + pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid) -> Option<(RegionVid, RegionVid)> { + let edge = EdgeInfo::no_reason(sup, sub, None); + self.outlives_inner(vec![edge]) } // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) - #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] + #[tracing::instrument(name = "Graph::outlives_inner", level = "trace", skip(self), ret)] pub(crate) fn outlives_inner( &mut self, - sup: RegionVid, - sub: RegionVid, - edge: EdgeInfo<'tcx>, - ) -> bool { - if sup == sub { - panic!(); - return false; - } + edge: Vec>, + ) -> Option<(RegionVid, RegionVid)> { + let (sup, sub) = (edge.last().unwrap().sup(), edge.first().unwrap().sub()); if self.static_regions.contains(&sub) { Self::set_static_region(&self.nodes, &mut self.static_regions, sup); } - self.nodes[sup] - .blocked_by - .entry(sub) - .or_default() - .insert(edge); - self.nodes[sub].blocks.entry(sup).or_default().insert(edge) + self.nodes[sup].blocked_by.insert(sub); + let old = self.nodes[sub].blocks.entry(sup).or_default(); + if old.iter().all(|old| EdgeInfo::is_new_edge(&edge, old)) { + assert!(old.insert(edge)); + Some((sup, sub)) + } else { + None + } } fn set_static_region( nodes: &IndexVec>, @@ -105,17 +86,15 @@ impl<'tcx> Graph<'tcx> { #[tracing::instrument(name = "Graph::kill_borrow", level = "debug", skip(self))] /// Remove borrow from graph and all nodes that block it and the node it blocks - pub fn kill_borrow(&mut self, data: &BorrowData<'tcx>) { - self.kill(data.region); + pub fn kill_borrow(&mut self, data: &BorrowData<'tcx>) -> Vec { + self.kill(data.region) } #[tracing::instrument(name = "Graph::kill", level = "trace", skip(self))] - fn kill(&mut self, r: RegionVid) { + fn kill(&mut self, r: RegionVid) -> Vec { assert!(!self.static_regions.contains(&r)); let (_, blocked_by) = self.remove_all_edges(r); - for (blocked_by, _) in blocked_by { - self.kill(blocked_by); - } + [r].into_iter().chain(blocked_by.iter().flat_map(|(blocked_by, _)| self.kill(*blocked_by))).collect() } #[tracing::instrument(name = "Graph::remove", level = "trace")] @@ -123,18 +102,27 @@ impl<'tcx> Graph<'tcx> { // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). - pub fn remove(&mut self, r: RegionVid, l: Option) -> bool { + pub fn remove(&mut self, r: RegionVid) -> Option<(RegionVid, Vec<(RegionVid, RegionVid)>)> { let (blocks, blocked_by) = self.remove_all_edges(r); let changed = !(blocks.is_empty() && blocked_by.is_empty()); - for &block in blocks.keys() { - for &blocked_by in blocked_by.keys() { + let mut rejoins = Vec::new(); + for (sup, sup_reasons) in blocks { + for (&sub, sub_reasons) in &blocked_by { // Do not rejoin nodes in a loop to themselves - if blocked_by != block { - let edge = EdgeInfo { - creation: l, - reason: None, - }; - self.outlives_inner(block, blocked_by, edge); + if sub != sup { + let mut rejoined = None; + for sup_reason in &sup_reasons { + for sub_reason in sub_reasons { + let reasons = sub_reason.iter().chain(sup_reason).copied().collect(); + let new = self.outlives_inner(reasons); + if new.is_some() { + rejoined = new; + } + } + } + if let Some(rejoined) = rejoined { + rejoins.push(rejoined); + } } } // if remove_dangling_children { @@ -146,7 +134,11 @@ impl<'tcx> Graph<'tcx> { } let was_static = self.static_regions.remove(&r); debug_assert!(!was_static || changed); // Check that `was_static ==> changed` - changed + if changed { + Some((r, rejoins)) + } else { + None + } } pub(crate) fn all_nodes(&self) -> impl Iterator)> { @@ -159,72 +151,30 @@ impl<'tcx> Graph<'tcx> { &mut self, r: RegionVid, ) -> ( - FxHashMap>>, - FxHashMap>>, + FxHashMap>>>, + FxHashMap>>>, ) { let blocks = std::mem::replace(&mut self.nodes[r].blocks, FxHashMap::default()); for block in blocks.keys() { self.nodes[*block].blocked_by.remove(&r); } - let blocked_by = std::mem::replace(&mut self.nodes[r].blocked_by, FxHashMap::default()); - for block_by in blocked_by.keys() { - self.nodes[*block_by].blocks.remove(&r); - } + let blocked_by = std::mem::replace(&mut self.nodes[r].blocked_by, FxHashSet::default()); + let blocked_by = blocked_by.into_iter().map(|bb| (bb, self.nodes[bb].blocks.remove(&r).unwrap())).collect(); (blocks, blocked_by) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { - pub blocks: FxHashMap>>, - pub blocked_by: FxHashMap>>, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EdgeInfo<'tcx> { - pub creation: Option, - pub reason: Option>, -} - -impl Display for EdgeInfo<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let reason = if let Some(reason) = self.reason { - match reason { - ConstraintCategory::Return(_) => "return", - ConstraintCategory::Yield => "yield", - ConstraintCategory::UseAsConst => "const", - ConstraintCategory::UseAsStatic => "static", - ConstraintCategory::TypeAnnotation => "type", - ConstraintCategory::Cast => "cast", - ConstraintCategory::ClosureBounds => "closure", - ConstraintCategory::CallArgument(_) => "arg", - ConstraintCategory::CopyBound => "copy", - ConstraintCategory::SizedBound => "sized", - ConstraintCategory::Assignment => "assign", - ConstraintCategory::Usage => "use", - ConstraintCategory::OpaqueType => "opaque", - ConstraintCategory::ClosureUpvar(_) => "upvar", - ConstraintCategory::Predicate(_) => "pred", - ConstraintCategory::Boring => "?", - ConstraintCategory::BoringNoLocation => "? no_loc", - ConstraintCategory::Internal => "internal", - } - } else { - "other" - }; - let creation = self - .creation - .map(|c| format!("{c:?}")) - .unwrap_or_else(|| "sig".to_string()); - write!(f, "{creation} ({reason})") - } + pub blocks: FxHashMap>>>, + pub blocked_by: FxHashSet, } impl<'tcx> Node<'tcx> { pub fn new() -> Self { Self { blocks: FxHashMap::default(), - blocked_by: FxHashMap::default(), + blocked_by: FxHashSet::default(), } } } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index ff7584e7755..c3bf5f3cfc2 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -12,7 +12,7 @@ use crate::{ free_pcs::{ CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, }, - utils::{PlaceOrdering, PlaceRepacker}, + utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency}, }; use super::{graph::Graph, triple::Cg}; @@ -20,6 +20,9 @@ use super::{graph::Graph, triple::Cg}; impl JoinSemiLattice for Cg<'_, '_> { #[tracing::instrument(name = "Cg::join", level = "debug", ret)] fn join(&mut self, other: &Self) -> bool { + let version = self.version.entry(other.id.unwrap().block).or_default(); + *version += 1; + assert!(*version < 10); self.graph.join(&other.graph) } } @@ -27,14 +30,29 @@ impl JoinSemiLattice for Cg<'_, '_> { impl JoinSemiLattice for Graph<'_> { fn join(&mut self, other: &Self) -> bool { let mut changed = false; - for (from, node) in other.all_nodes() { - for (&to, reasons) in node.blocks.iter() { - for &reason in reasons { - let was_new = self.outlives_inner(to, from, reason); - changed = changed || was_new; + for (_, node) in other.all_nodes() { + for (_, reasons) in node.blocks.iter() { + for reason in reasons.clone() { + let was_new = self.outlives_inner(reason); + changed = changed || was_new.is_some(); } } } changed } } + + +impl Cg<'_, '_> { + #[tracing::instrument(name = "Cg::bridge", level = "debug", ret)] + pub fn bridge(&self, other: &Self) -> Vec { + other.graph.all_nodes().flat_map(|(sub, node)| + node.blocks + .keys() + .filter(move |sup| !self.graph.nodes[sub].blocks.contains_key(*sup)) + .copied() + .flat_map(move |sup| self.outlives_to_block((sup, sub))) + .map(|block| CouplingOp::Add(block)) + ).collect() + } +} diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 96b57bf5151..15a8fb6eddb 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -22,7 +22,7 @@ use prusti_rustc_interface::{ middle::{ mir::{ interpret::{ConstValue, GlobalAlloc, Scalar}, - visit::Visitor, + visit::Visitor, BorrowKind, BasicBlock, ConstraintCategory, Local, Location, Operand, Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, RETURN_PLACE, ConstantKind, }, @@ -31,36 +31,42 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{region_info::map::RegionKind, region_place::PlaceRegion, CgContext}, + coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}}, free_pcs::{ - engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, + engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, CapabilityKind, }, utils::{r#const::ConstEval, Place, PlaceRepacker}, }; use super::{ - engine::{BorrowDelta, CoupligGraph}, + engine::{BorrowDelta, CouplingGraph}, graph::{Graph, Node}, }; #[derive(Clone)] pub struct Cg<'a, 'tcx> { - pub id: Option, + pub id: Option, + pub is_pre: bool, pub cgx: &'a CgContext<'a, 'tcx>, pub(crate) live: BitSet, - pub version: usize, - pub dot_filter: fn(&RegionKind<'_>) -> bool, + pub version: FxHashMap, + pub dot_node_filter: fn(&RegionKind<'_>) -> bool, + pub dot_edge_filter: fn(&RegionKind<'_>, &RegionKind<'_>) -> bool, pub top_crates: bool, pub graph: Graph<'tcx>, + + pub couplings: Vec, + pub touched: FxHashSet, } impl Debug for Cg<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("Graph") .field("id", &self.id) - .field("nodes", &self.graph) + .field("version", &self.version) + // .field("nodes", &self.graph) .finish() } } @@ -72,13 +78,16 @@ impl PartialEq for Cg<'_, '_> { } impl Eq for Cg<'_, '_> {} -impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { +impl<'a, 'tcx> DebugWithContext> for Cg<'a, 'tcx> { fn fmt_diff_with( &self, old: &Self, - _ctxt: &CoupligGraph<'a, 'tcx>, + _ctxt: &CouplingGraph<'a, 'tcx>, f: &mut Formatter<'_>, ) -> Result { + for op in &self.couplings { + writeln!(f, "{op}")?; + } Ok(()) } } @@ -91,6 +100,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { self.cgx.region_info.function_region } + #[tracing::instrument(name = "Cg::new", level = "trace", skip_all)] pub fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { let graph = Graph { nodes: IndexVec::from_elem_n(Node::new(), cgx.region_info.map.region_len()), @@ -100,27 +110,91 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { let live = BitSet::new_empty(cgx.facts2.borrow_set.location_map.len()); Self { id: None, + is_pre: true, cgx, live, - version: 0, - dot_filter: |_| true, + version: FxHashMap::default(), + dot_node_filter: |_| true, + dot_edge_filter: |_, _| true, top_crates, graph, + couplings: Vec::new(), + touched: FxHashSet::default(), } } pub fn initialize_start_block(&mut self) { for c in &self.cgx.outlives_info.local_constraints { - self.graph.outlives(*c); + self.outlives(*c); } for c in &self.cgx.outlives_info.universal_local_constraints { - self.graph.outlives(*c); + self.outlives(*c); } for &(sup, sub) in &self.cgx.outlives_info.universal_constraints { - self.graph.outlives_placeholder(sup, sub); + self.outlives_placeholder(sup, sub); } for &const_region in self.cgx.region_info.map.const_regions() { - self.graph - .outlives_static(const_region, self.static_region()); + self.outlives_static(const_region); + } + // Remove all locals without capabilities from the initial graph + self.kill_shared_borrows_on_place(None, RETURN_PLACE.into()); + for local in self.cgx.rp.body().vars_and_temps_iter() { + self.kill_shared_borrows_on_place(None, local.into()); + } + } + pub fn max_version(&self) -> usize { + self.version.values().copied().max().unwrap_or_default() + } + + pub(crate) fn outlives(&mut self, c: OutlivesConstraint<'tcx>) { + let new = self.graph.outlives(c); + self.outlives_op(new) + } + pub(crate) fn outlives_static(&mut self, r: RegionVid) { + let static_region = self.static_region(); + if r == static_region { + return; + } + self.outlives_placeholder(r, static_region) + } + pub(crate) fn outlives_placeholder(&mut self, r: RegionVid, placeholder: RegionVid) { + let new = self.graph.outlives_placeholder(r, placeholder); + self.outlives_op(new) + } + #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] + fn outlives_op(&mut self, op: Option<(RegionVid, RegionVid)>) { + if let Some(block) = op.and_then(|c| self.outlives_to_block(c)) { + self.couplings.push(CouplingOp::Add(block)); + } + } + + // TODO: remove + pub(crate) fn outlives_to_block(&self, op: (RegionVid, RegionVid)) -> Option { + let (sup, sub) = op; + let (sup_info, sub_info) = (self.cgx.region_info.map.get(sup), self.cgx.region_info.map.get(sub)); + if sub_info.is_borrow() || (sub_info.universal() && sup_info.local()) { + None + } else { + Some(Block { sup, sub, }) + } + } + #[tracing::instrument(name = "Cg::remove", level = "trace", skip(self))] + pub(crate) fn remove(&mut self, r: RegionVid, l: Option) { + let remove = self.graph.remove(r); + if let Some((removed, rejoins)) = remove { + let rejoins = rejoins.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); + self.couplings.push(CouplingOp::Remove(removed, rejoins)); + } + } + #[tracing::instrument(name = "Cg::kill_borrow", level = "trace", skip(self))] + pub(crate) fn kill_borrow(&mut self, data: &BorrowData<'tcx>) { + let remove = self.graph.kill_borrow(data); + for removed in remove.into_iter().rev() { + self.couplings.push(CouplingOp::Remove(removed, Vec::new())); + } + } + pub (crate) fn reset_ops(&mut self) { + for c in self.couplings.drain(..) { + self.touched.extend(c.regions()); } } @@ -147,17 +221,6 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { for bi in delta.cleared.iter() { let data = &self.cgx.facts2.borrow_set[bi]; - // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts - .loan_issued_at - .iter() - .find(|(_, b, _)| bi == *b) - .copied() - .unwrap(); - let l = rich_to_loc(location_table.to_location(l)); - assert_eq!(r, data.region); - assert_eq!(l, data.reserve_location); - // println!("killed: {r:?} {killed:?} {l:?}"); if oos.map(|oos| oos.contains(&bi)).unwrap_or_default() { if self.graph.static_regions.contains(&data.region) { @@ -168,9 +231,9 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { "{data:?} {location:?} {:?}", self.graph.static_regions ); - self.graph.kill_borrow(data); + self.kill_borrow(data); } else { - self.graph.remove(data.region, Some(location)); + self.remove(data.region, Some(location)); } // // TODO: is this necessary? @@ -193,18 +256,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } let data = &self.cgx.facts2.borrow_set[bi]; - // TODO: remove if the asserts below pass: - let (r, _, l) = input_facts - .loan_issued_at - .iter() - .find(|(_, b, _)| bi == *b) - .copied() - .unwrap(); - let l = rich_to_loc(location_table.to_location(l)); - assert_eq!(r, data.region); - assert_eq!(l, data.reserve_location); - - self.graph.kill_borrow(data); + self.kill_borrow(data); // // TODO: is this necessary? // let local = data.assigned_place.local; @@ -225,29 +277,29 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { .outlives_info .pre_constraints(location, local, &self.cgx.region_info) { - self.graph.outlives(c); + self.outlives(c); } if let Some(place) = assigns_to { - self.kill_shared_borrows_on_place(location, place); + self.kill_shared_borrows_on_place(Some(location), place); } for &c in self .cgx .outlives_info .post_constraints(location, local, &self.cgx.region_info) { - self.graph.outlives(c); + self.outlives(c); } } #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] - pub fn kill_shared_borrows_on_place(&mut self, location: Location, place: MirPlace<'tcx>) { + pub fn kill_shared_borrows_on_place(&mut self, location: Option, place: MirPlace<'tcx>) { let Some(local) = place.as_local() else { // Only remove nodes if assigned to the entire local (this is what rustc allows too) return; }; for region in self.cgx.region_info.map.all_regions() { if self.cgx.region_info.map.for_local(region, local) { - self.graph.remove(region, Some(location)); + self.remove(region, location); } } } @@ -259,7 +311,7 @@ impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { match operand { Operand::Copy(_) => (), &Operand::Move(place) => { - self.kill_shared_borrows_on_place(location, place); + self.kill_shared_borrows_on_place(Some(location), place); } Operand::Constant(_) => (), } @@ -278,7 +330,7 @@ impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { || self.cgx.region_info.map.for_local(c.sub, p.local) }) { - self.graph.outlives(c); + self.outlives(c); } } _ => (), @@ -297,27 +349,32 @@ impl<'tcx> Cg<'_, 'tcx> { | RegionKind::Param(_) | RegionKind::UnknownUniversal | RegionKind::Function => continue, - RegionKind::Place { local, .. } => { + RegionKind::Place { local, promoted: Promote::NotPromoted, .. } => { if local.index() > self.cgx.rp.body().arg_count { self.output_to_dot("log/coupling/error.dot", true); panic!("{r:?} ({location:?}) {:?}", self.graph.nodes[r]); } } - RegionKind::Borrow(_) => { + RegionKind::Borrow(_, Promote::NotPromoted) => { // Should not have borrows left self.output_to_dot("log/coupling/error.dot", true); panic!("{r:?} {:?}", self.graph.nodes[r]); } // Ignore (and thus delete) early/late bound (mostly fn call) regions + RegionKind::UnusedReturnBug(..) => unreachable!(), + RegionKind::Place { promoted: Promote::Promoted(..), .. } => (), + RegionKind::Borrow(_, Promote::Promoted(..)) => (), RegionKind::ConstRef(..) => (), RegionKind::EarlyBound(..) => (), RegionKind::LateBound { .. } => (), RegionKind::Placeholder(..) => (), RegionKind::MiscLocal => (), + RegionKind::ProjectionAnnotation(..) => (), + RegionKind::OtherAnnotation(..) => (), // Skip unknown empty nodes, we may want to figure out how to deal with them in the future RegionKind::UnknownLocal => (), } - self.graph.remove(r, None); + self.remove(r, None); } } pub fn output_to_dot>(&self, path: P, error: bool) { @@ -328,10 +385,3 @@ impl<'tcx> Cg<'_, 'tcx> { } } } - -fn rich_to_loc(l: RichLocation) -> Location { - match l { - RichLocation::Start(l) => l, - RichLocation::Mid(l) => l, - } -} diff --git a/mir-state-analysis/src/coupling_graph/mod.rs b/mir-state-analysis/src/coupling_graph/mod.rs index 462c87f0984..25361aa0139 100644 --- a/mir-state-analysis/src/coupling_graph/mod.rs +++ b/mir-state-analysis/src/coupling_graph/mod.rs @@ -6,6 +6,10 @@ mod r#impl; mod context; +mod results; +mod check; pub use context::*; pub use r#impl::*; +pub use results::*; +pub use check::*; diff --git a/mir-state-analysis/src/coupling_graph/results/coupling.rs b/mir-state-analysis/src/coupling_graph/results/coupling.rs new file mode 100644 index 00000000000..6f1bf36b515 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/results/coupling.rs @@ -0,0 +1,62 @@ +// © 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 prusti_rustc_interface::{ + middle::{mir::Local, ty::RegionVid} +}; + +use crate::{free_pcs::CapabilityKind, utils::Place}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CouplingOp { + Add(Block), + Remove(RegionVid, Vec), +} + +impl CouplingOp { + pub fn regions(&self) -> Box + '_> { + match self { + CouplingOp::Add(block) => Box::new([block.sup, block.sub].into_iter()), + CouplingOp::Remove(remove, block) => + Box::new([*remove].into_iter().chain(block.iter().flat_map(|b| [b.sup, b.sub].into_iter()))), + } + } +} + +impl Display for CouplingOp { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + CouplingOp::Add(block) => block.fmt(f), + CouplingOp::Remove(remove, patch) => { + write!(f, "Remove({remove:?}, {{")?; + for p in patch { + write!(f, "\n {p}")?; + } + if !patch.is_empty() { + write!(f, "\n")?; + } + write!(f, "}})") + } + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Block { + /// The region that outlives (is blocked) + pub sup: RegionVid, + /// The region that must be outlived (is blocking) + pub sub: RegionVid, + // pub kind: CapabilityKind, +} +impl Display for Block { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Block { sup, sub } = self; + write!(f, "Block({sup:?}, {sub:?})") + } +} diff --git a/mir-state-analysis/src/coupling_graph/results/cursor.rs b/mir-state-analysis/src/coupling_graph/results/cursor.rs new file mode 100644 index 00000000000..c7df879585b --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/results/cursor.rs @@ -0,0 +1,165 @@ +// © 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::{ + free_pcs::{ + engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, + CapabilitySummary, RepackOp, + }, + utils::PlaceRepacker, coupling_graph::{engine::CouplingGraph, graph::Graph, CgContext}, +}; + +use super::coupling::CouplingOp; + +type Cursor<'a, 'mir, 'tcx> = ResultsCursor<'mir, 'tcx, CouplingGraph<'a, 'tcx>>; + +pub struct CgAnalysis<'a, 'mir, 'tcx> { + cursor: Cursor<'a, 'mir, 'tcx>, + did_before: bool, + curr_stmt: Option, + end_stmt: Option, +} + +impl<'a, 'mir, 'tcx> CgAnalysis<'a, 'mir, 'tcx> { + pub(crate) fn new(cursor: Cursor<'a, 'mir, 'tcx>) -> Self { + Self { + cursor, + did_before: false, + 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 + .cursor + .analysis() + .cgx + .rp + .body() + .terminator_loc(block) + .successor_within_block(); + self.curr_stmt = Some(Location { + block, + statement_index: 0, + }); + self.end_stmt = Some(end_stmt); + self.did_before = false; + } + + fn body(&self) -> &'a Body<'tcx> { + self.cursor.analysis().cgx.rp.body() + } + pub(crate) fn cgx(&mut self) -> &'a CgContext<'a, 'tcx> { + self.cursor.results().analysis.cgx + } + + pub fn initial_state(&self) -> &Graph<'tcx> { + &self.cursor.get().graph + } + pub fn initial_coupling(&self) -> &Vec { + &self.cursor.get().couplings + } + pub fn before_next(&mut self, exp_loc: Location) -> CgLocation<'tcx> { + let location = self.curr_stmt.unwrap(); + assert_eq!(location, exp_loc); + assert!(location <= self.end_stmt.unwrap()); + assert!(!self.did_before); + self.did_before = true; + self.cursor.seek_before_primary_effect(location); + let state = self.cursor.get(); + CgLocation { + location, + state: state.graph.clone(), + couplings: state.couplings.clone(), + } + } + pub fn next(&mut self, exp_loc: Location) -> CgLocation<'tcx> { + let location = self.curr_stmt.unwrap(); + assert_eq!(location, exp_loc); + assert!(location < self.end_stmt.unwrap()); + assert!(self.did_before); + self.did_before = false; + self.curr_stmt = Some(location.successor_within_block()); + + self.cursor.seek_after_primary_effect(location); + let state = self.cursor.get(); + CgLocation { + location, + state: state.graph.clone(), + couplings: state.couplings.clone(), + } + } + pub fn terminator(&mut self) -> CgTerminator<'tcx> { + let location = self.curr_stmt.unwrap(); + assert!(location == self.end_stmt.unwrap()); + assert!(!self.did_before); + self.curr_stmt = None; + self.end_stmt = None; + + // TODO: cleanup + let state = self.cursor.get().clone(); + 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); + CgLocation { + location: Location { + block: succ, + statement_index: 0, + }, + state: to.graph.clone(), + couplings: state.bridge(&to), + } + }) + .collect(); + CgTerminator { succs } + } + + /// Recommened interface. + /// Does *not* require that one calls `analysis_for_bb` first + pub fn get_all_for_bb(&mut self, block: BasicBlock) -> CgBasicBlock<'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(); + CgBasicBlock { + statements, + terminator, + } + } +} + +pub struct CgBasicBlock<'tcx> { + pub statements: Vec>, + pub terminator: CgTerminator<'tcx>, +} + +#[derive(Debug)] +pub struct CgLocation<'tcx> { + pub location: Location, + /// Couplings before the statement + pub couplings: Vec, + /// State after the statement + pub state: Graph<'tcx>, +} + +#[derive(Debug)] +pub struct CgTerminator<'tcx> { + pub succs: Vec>, +} diff --git a/mir-state-analysis/src/coupling_graph/results/mod.rs b/mir-state-analysis/src/coupling_graph/results/mod.rs new file mode 100644 index 00000000000..b36c4b4b0b7 --- /dev/null +++ b/mir-state-analysis/src/coupling_graph/results/mod.rs @@ -0,0 +1,8 @@ +// © 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 coupling; +pub mod cursor; diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index e65f83ed99b..0883224e7c4 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -11,13 +11,14 @@ use prusti_rustc_interface::{ use crate::{ free_pcs::{ - CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, + CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, FpcsBound, }, utils::PlaceRepacker, }; -use super::consistency::CapabilityConistency; +use super::consistency::CapabilityConsistency; +#[tracing::instrument(name = "check", level = "debug", skip(cursor))] pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { let rp = cursor.repacker(); let body = rp.body(); @@ -28,6 +29,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { bottom: false, repackings: Vec::new(), repacker: rp, + bound: FpcsBound::empty(false), }; // Consistency fpcs.summary.consistency_check(rp); @@ -89,8 +91,8 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { } impl<'tcx> RepackOp<'tcx> { - #[tracing::instrument(level = "debug", skip(rp))] - fn update_free( + #[tracing::instrument(name = "RepackOp::update_free", level = "debug", skip(rp))] + pub(crate) fn update_free( self, state: &mut CapabilitySummary<'tcx>, is_cleanup: bool, diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs index af5f2fa7a84..cc595dcd514 100644 --- a/mir-state-analysis/src/free_pcs/check/consistency.rs +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -9,11 +9,12 @@ use crate::{ utils::{Place, PlaceRepacker}, }; -pub trait CapabilityConistency<'tcx> { +pub trait CapabilityConsistency<'tcx> { fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>); } -impl<'tcx, T: CapabilityConistency<'tcx>> CapabilityConistency<'tcx> for Summary { +impl<'tcx, T: CapabilityConsistency<'tcx>> CapabilityConsistency<'tcx> for Summary { + #[tracing::instrument(name = "Summary::consistency_check", level = "trace", skip(self, repacker))] fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { for p in self.iter() { p.consistency_check(repacker) @@ -21,7 +22,7 @@ impl<'tcx, T: CapabilityConistency<'tcx>> CapabilityConistency<'tcx> for Summary } } -impl<'tcx> CapabilityConistency<'tcx> for CapabilityLocal<'tcx> { +impl<'tcx> CapabilityConsistency<'tcx> for CapabilityLocal<'tcx> { fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { match self { CapabilityLocal::Unallocated => {} @@ -30,7 +31,7 @@ impl<'tcx> CapabilityConistency<'tcx> for CapabilityLocal<'tcx> { } } -impl<'tcx> CapabilityConistency<'tcx> for CapabilityProjections<'tcx> { +impl<'tcx> CapabilityConsistency<'tcx> for CapabilityProjections<'tcx> { fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { // All keys unrelated to each other let keys = self.keys().copied().collect::>(); diff --git a/mir-state-analysis/src/free_pcs/check/mod.rs b/mir-state-analysis/src/free_pcs/check/mod.rs index 6a308a2b833..ab93cd14b65 100644 --- a/mir-state-analysis/src/free_pcs/check/mod.rs +++ b/mir-state-analysis/src/free_pcs/check/mod.rs @@ -5,6 +5,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. mod checker; -mod consistency; +pub(crate) mod consistency; 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 index a5a555cae2c..7e5210c9002 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -6,11 +6,11 @@ use prusti_rustc_interface::{ dataflow::{Analysis, AnalysisDomain}, - index::Idx, + index::{Idx, IndexVec}, middle::{ mir::{ visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Location, Statement, - Terminator, TerminatorEdges, RETURN_PLACE, + Terminator, TerminatorEdges, RETURN_PLACE, Promoted, }, ty::TyCtxt, }, @@ -23,8 +23,8 @@ use crate::{ 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); + pub(crate) fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, promoted: &'a IndexVec>) -> Self { + let repacker = PlaceRepacker::new(body, promoted, tcx); FreePlaceCapabilitySummary(repacker) } } diff --git a/mir-state-analysis/src/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index b5bc981b678..00f5973b8de 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -4,7 +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/. -use std::fmt::{Debug, Formatter, Result}; +use std::{fmt::{Debug, Formatter, Result}, cell::RefCell}; use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ @@ -15,15 +15,24 @@ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::PlaceRepacker, + utils::{PlaceRepacker, Place}, }; -#[derive(Clone)] +use super::CapabilityKind; + +pub struct FpcsBound<'tcx>(pub Option) -> CapabilityKind>>, pub bool); +impl FpcsBound<'_> { + pub fn empty(expect: bool) -> RefCell { + RefCell::new(Self(None, expect)) + } +} + pub struct Fpcs<'a, 'tcx> { pub(crate) repacker: PlaceRepacker<'a, 'tcx>, pub(crate) bottom: bool, pub summary: CapabilitySummary<'tcx>, pub repackings: Vec>, + pub bound: RefCell>, } impl<'a, 'tcx> Fpcs<'a, 'tcx> { pub(crate) fn new(repacker: PlaceRepacker<'a, 'tcx>) -> Self { @@ -33,10 +42,23 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { bottom: true, summary, repackings: Vec::new(), + bound: FpcsBound::empty(false), } } + #[tracing::instrument(name = "Fpcs::bound", level = "trace", skip(self), ret)] + pub(crate) fn bound(&self, place: Place<'tcx>) -> CapabilityKind { + let bound = self.bound.borrow(); + assert_eq!(bound.1, bound.0.is_some()); + bound.0.as_ref().map(|b| b(place)).unwrap_or(CapabilityKind::Exclusive) + } } +impl Clone for Fpcs<'_, '_> { + fn clone(&self) -> Self { + let expect_bound = self.bound.borrow().1; + Self { repacker: self.repacker, bottom: self.bottom, summary: self.summary.clone(), repackings: self.repackings.clone(), bound: FpcsBound::empty(expect_bound) } + } +} impl PartialEq for Fpcs<'_, '_> { fn eq(&self, other: &Self) -> bool { self.bottom == other.bottom @@ -46,7 +68,7 @@ impl PartialEq for Fpcs<'_, '_> { } impl Eq for Fpcs<'_, '_> {} -impl<'a, 'tcx> Debug for Fpcs<'a, 'tcx> { +impl Debug for Fpcs<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.summary.fmt(f) } 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..4924592065f 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 @@ -39,6 +39,8 @@ impl<'tcx> RepackingJoinSemiLattice<'tcx> for CapabilitySummary<'tcx> { } changed } + + #[tracing::instrument(name = "CapabilitySummary::bridge", level = "trace", skip(repacker))] fn bridge(&self, other: &Self, repacker: PlaceRepacker<'_, 'tcx>) -> Vec> { let mut repacks = Vec::new(); for (l, from) in self.iter_enumerated() { diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index 259dd639036..6fb55887ed7 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -44,6 +44,11 @@ pub enum CapabilityKind { /// [`CapabilityKind::Exclusive`] for everything not through a dereference, /// [`CapabilityKind::Write`] for everything through a dereference. ShallowExclusive, + + /// The capability was `Exclusive` but has been blocked by a shared borrow. + Read, + /// The capability was `Exclusive` but has been blocked by a mutable borrow. + None, } impl Debug for CapabilityKind { fn fmt(&self, f: &mut Formatter<'_>) -> Result { @@ -51,6 +56,8 @@ impl Debug for CapabilityKind { CapabilityKind::Write => write!(f, "W"), CapabilityKind::Exclusive => write!(f, "E"), CapabilityKind::ShallowExclusive => write!(f, "e"), + CapabilityKind::Read => write!(f, "R"), + CapabilityKind::None => write!(f, "N"), } } } @@ -63,12 +70,12 @@ impl PartialOrd for CapabilityKind { } match (self, other) { // W < E, W < e - (CapabilityKind::Write, CapabilityKind::Exclusive) - | (CapabilityKind::ShallowExclusive, CapabilityKind::Exclusive) + (_, CapabilityKind::Exclusive) + | (CapabilityKind::None, _) | (CapabilityKind::Write, CapabilityKind::ShallowExclusive) => Some(Ordering::Less), // E > W, e > W - (CapabilityKind::Exclusive, CapabilityKind::Write) - | (CapabilityKind::Exclusive, CapabilityKind::ShallowExclusive) + (CapabilityKind::Exclusive, _) + | (_, CapabilityKind::None) | (CapabilityKind::ShallowExclusive, CapabilityKind::Write) => Some(Ordering::Greater), _ => None, } @@ -85,10 +92,26 @@ impl CapabilityKind { pub fn is_shallow_exclusive(self) -> bool { matches!(self, CapabilityKind::ShallowExclusive) } + pub fn is_read(self) -> bool { + matches!(self, CapabilityKind::Read) + } + pub fn is_none(self) -> bool { + matches!(self, CapabilityKind::None) + } + #[tracing::instrument(name = "CapabilityKind::minimum", level = "trace", ret)] pub fn minimum(self, other: Self) -> Option { match self.partial_cmp(&other)? { Ordering::Greater => Some(other), _ => Some(self), } } + #[tracing::instrument(name = "CapabilityKind::sum", level = "trace", ret)] + pub fn sum(self, other: Self) -> Option { + match (self, other) { + (other, CapabilityKind::None) | + (CapabilityKind::None, other) => Some(other), + (CapabilityKind::Write, CapabilityKind::Read) => Some(CapabilityKind::Exclusive), + _ => None, + } + } } diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 0b7b82e278b..6c400653237 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -15,6 +15,7 @@ use prusti_rustc_interface::{ use crate::free_pcs::{CapabilityKind, Fpcs}; impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { + #[tracing::instrument(name = "Fpcs::visit_operand", level = "debug", skip(self))] fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { self.super_operand(operand, location); match *operand { @@ -29,6 +30,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { } } + #[tracing::instrument(name = "Fpcs::visit_statement", level = "debug", skip(self))] fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { self.super_statement(statement, location); use StatementKind::*; @@ -42,7 +44,8 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { _ => unreachable!(), } } - &FakeRead(box (_, place)) | &PlaceMention(box place) => self.requires_read(place), + &FakeRead(box (_, place)) => self.requires_read(place), + &PlaceMention(box place) => self.requires_alloc(place), &SetDiscriminant { box place, .. } => self.requires_exclusive(place), &Deinit(box place) => { // TODO: Maybe OK to also allow `Write` here? @@ -62,6 +65,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { }; } + #[tracing::instrument(name = "Fpcs::visit_terminator", level = "debug", skip(self))] fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { self.super_terminator(terminator, location); use TerminatorKind::*; @@ -103,6 +107,7 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { }; } + #[tracing::instrument(name = "Fpcs::visit_rvalue", level = "debug", skip(self))] fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { self.super_rvalue(rvalue, location); use Rvalue::*; diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 40c483ba431..ef7b1a55533 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -28,9 +28,16 @@ impl<'tcx> Fpcs<'_, 'tcx> { self.repackings.push(RepackOp::IgnoreStorageDead(local)) } } + // TODO: make this work properly through references (i.e. check that the lifetime is live) + #[tracing::instrument(name = "Fpcs::requires_alloc", level = "trace")] + pub(crate) fn requires_alloc(&mut self, place: impl Into> + Debug) { + let place = place.into(); + self.requires(place, CapabilityKind::None) + } #[tracing::instrument(name = "Fpcs::requires_read", level = "trace")] pub(crate) fn requires_read(&mut self, place: impl Into> + Debug) { - self.requires(place, CapabilityKind::Exclusive) + let place = place.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`! @@ -50,13 +57,13 @@ impl<'tcx> Fpcs<'_, 'tcx> { assert!(!place.projects_shared_ref(self.repacker)); self.requires(place, CapabilityKind::Exclusive) } - fn requires(&mut self, place: impl Into>, cap: CapabilityKind) { - let place = place.into(); + fn requires(&mut self, place: Place<'tcx>, cap: CapabilityKind) { 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); + let bound = self.bound(place); + assert!(kind.minimum(bound).unwrap() >= cap, "{place:?}, have: {kind:?}, bound: {bound:?}, want: {cap:?}"); } pub(crate) fn ensures_unalloc(&mut self, local: Local) { @@ -87,6 +94,7 @@ impl<'tcx> Fpcs<'_, 'tcx> { } impl<'tcx> CapabilityProjections<'tcx> { + #[tracing::instrument(name = "CapabilityProjections::repack", level = "trace", skip(repacker), ret)] pub(super) fn repack( &mut self, to: Place<'tcx>, diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index 06f93fae2a8..0285b3fc7d6 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -12,9 +12,9 @@ use prusti_rustc_interface::{ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, - CapabilitySummary, RepackOp, + CapabilitySummary, RepackOp, CapabilityKind, }, - utils::PlaceRepacker, + utils::{PlaceRepacker, Place}, }; type Cursor<'mir, 'tcx> = ResultsCursor<'mir, 'tcx, FreePlaceCapabilitySummary<'mir, 'tcx>>; @@ -57,6 +57,15 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { self.cursor.results().analysis.0 } + pub fn set_bound_non_empty(&self) { + self.cursor.get().bound.borrow_mut().1 = true; + } + pub fn set_bound(&self, bound: Box) -> CapabilityKind>) { + self.cursor.get().bound.borrow_mut().0 = Some(bound); + } + pub fn unset_bound(&self) { + self.cursor.get().bound.borrow_mut().0 = None; + } pub fn initial_state(&self) -> &CapabilitySummary<'tcx> { &self.cursor.get().summary } diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 36f8fea2520..2654c0d9d8a 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -14,16 +14,18 @@ pub mod r#loop; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ + index::IndexVec, dataflow::Analysis, - middle::{mir::Body, ty::TyCtxt}, + middle::{mir::{Body, START_BLOCK, Promoted}, ty::TyCtxt}, }; #[tracing::instrument(name = "run_free_pcs", level = "debug", skip(tcx))] pub fn run_free_pcs<'mir, 'tcx>( mir: &'mir Body<'tcx>, + promoted: &'mir IndexVec>, tcx: TyCtxt<'tcx>, ) -> free_pcs::FreePcsAnalysis<'mir, 'tcx> { - let fpcs = free_pcs::engine::FreePlaceCapabilitySummary::new(tcx, mir); + let fpcs = free_pcs::engine::FreePlaceCapabilitySummary::new(tcx, mir, promoted); let analysis = fpcs .into_engine(tcx, mir) .pass_name("free_pcs") @@ -31,46 +33,55 @@ pub fn run_free_pcs<'mir, 'tcx>( free_pcs::FreePcsAnalysis::new(analysis.into_results_cursor(mir)) } -pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, tcx: TyCtxt<'tcx>) { - let analysis = run_free_pcs(mir, tcx); +pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, promoted: &IndexVec>, tcx: TyCtxt<'tcx>) { + let analysis = run_free_pcs(mir, promoted, tcx); free_pcs::check(analysis); } -#[tracing::instrument(name = "run_coupling_graph", level = "debug", skip(facts, facts2, tcx))] +#[tracing::instrument(name = "run_coupling_graph", level = "debug", skip(tcx))] pub fn run_coupling_graph<'mir, 'tcx>( mir: &'mir Body<'tcx>, - facts: &'mir BorrowckFacts, - facts2: &'mir BorrowckFacts2<'tcx>, + cgx: &'mir coupling_graph::CgContext<'mir, 'tcx>, tcx: TyCtxt<'tcx>, top_crates: bool, -) { +) -> coupling_graph::cursor::CgAnalysis<'mir, 'mir, 'tcx> { // if tcx.item_name(mir.source.def_id()).as_str().starts_with("main") { // return; // } - // if !format!("{:?}", mir.source.def_id()).ends_with("serialize_adjacently_tagged_variant)") { - // println!("{:?}", mir.source.def_id()); - // return; - // } - let cgx = coupling_graph::CgContext::new(tcx, mir, facts, facts2); - let fpcs = coupling_graph::engine::CoupligGraph::new(&cgx, top_crates); + let fpcs = coupling_graph::engine::CouplingGraph::new(&cgx, top_crates); let analysis = fpcs .into_engine(tcx, mir) .pass_name("coupling_graph") .iterate_to_fixpoint(); - let c = analysis.into_results_cursor(mir); + let mut c = analysis.into_results_cursor(mir); if cfg!(debug_assertions) && !top_crates { - coupling_graph::engine::draw_dots(c); + coupling_graph::engine::draw_dots(&mut c); } - // panic!() + c.seek_to_block_start(START_BLOCK); + coupling_graph::cursor::CgAnalysis::new(c) } pub fn test_coupling_graph<'tcx>( mir: &Body<'tcx>, + promoted: &IndexVec>, facts: &BorrowckFacts, facts2: &BorrowckFacts2<'tcx>, tcx: TyCtxt<'tcx>, top_crates: bool, ) { - let analysis = run_coupling_graph(mir, facts, facts2, tcx, top_crates); - // free_pcs::check(analysis); + // println!("{:?}", mir.source.def_id()); + // if !format!("{:?}", mir.source.def_id()) + // .ends_with("parse_delimited)") + // { + // return; + // } + + + let fpcs_analysis = run_free_pcs(mir, promoted, tcx); + let cgx = coupling_graph::CgContext::new(tcx, mir, promoted, facts, facts2); + let cg_analysis = run_coupling_graph(mir, &cgx, tcx, top_crates); + coupling_graph::check(cg_analysis, fpcs_analysis); + + + // panic!() } diff --git a/mir-state-analysis/src/utils/display.rs b/mir-state-analysis/src/utils/display.rs index b61dee29fc7..e5bb5a3bede 100644 --- a/mir-state-analysis/src/utils/display.rs +++ b/mir-state-analysis/src/utils/display.rs @@ -32,7 +32,7 @@ impl<'tcx> Debug for PlaceDisplay<'tcx> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { match self { PlaceDisplay::Temporary(place) => write!(f, "{place:?}"), - PlaceDisplay::User(place, s) => write!(f, "{place:?} <<{s}>>"), + PlaceDisplay::User(place, s) => write!(f, "{place:?} = {s}"), } } } diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index cee53586ccc..57a371fbc3b 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -13,8 +13,9 @@ use std::{ use derive_more::{Deref, DerefMut}; -use prusti_rustc_interface::middle::mir::{ - Local, Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem, +use prusti_rustc_interface::middle::{ + mir::{Local, Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem}, + ty::Ty, }; // #[derive(Clone, Copy, Deref, DerefMut, Hash, PartialEq, Eq)] @@ -210,6 +211,14 @@ impl<'tcx> Place<'tcx> { pub fn projects_exactly_one_deref(self) -> bool { self.projection.len() == 1 && matches!(self.projection[0], ProjectionElem::Deref) } + + pub fn last_projection_ty(self) -> Option> { + self.last_projection().and_then(|(_, proj)| match proj { + ProjectionElem::Field(_, ty) | + ProjectionElem::OpaqueCast(ty) => Some(ty), + _ => None, + }) + } } impl Debug for Place<'_> { diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 63eae6ce5dd..86057293c12 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -7,13 +7,13 @@ use prusti_rustc_interface::{ data_structures::fx::FxHashSet, dataflow::storage, - index::{bit_set::BitSet, Idx}, + index::{bit_set::BitSet, Idx, IndexVec}, middle::{ mir::{ tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, PlaceElem, - ProjectionElem, + ProjectionElem, Promoted, }, - ty::{RegionVid, Ty, TyCtxt, TyKind}, + ty::{RegionVid, Region, Ty, TyCtxt, TyKind}, }, target::abi::FieldIdx, }; @@ -51,12 +51,13 @@ impl ProjectionRefKind { // TODO: modified version of fns taken from `prusti-interface/src/utils.rs`; deduplicate pub struct PlaceRepacker<'a, 'tcx: 'a> { pub(super) mir: &'a Body<'tcx>, + pub(super) promoted: &'a IndexVec>, pub(super) tcx: TyCtxt<'tcx>, } impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { - pub fn new(mir: &'a Body<'tcx>, tcx: TyCtxt<'tcx>) -> Self { - Self { mir, tcx } + pub fn new(mir: &'a Body<'tcx>, promoted: &'a IndexVec>, tcx: TyCtxt<'tcx>) -> Self { + Self { mir, promoted, tcx } } pub fn local_count(self) -> usize { @@ -79,6 +80,10 @@ impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { self.mir } + pub fn promoted(self) -> &'a IndexVec> { + self.promoted + } + pub fn tcx(self) -> TyCtxt<'tcx> { self.tcx } @@ -353,6 +358,20 @@ impl<'tcx> Place<'tcx> { } } + /// Returns all `TyKind::Ref` and `TyKind::RawPtr` that `self` projects through. + /// The `Option` acts as an either where `TyKind::RawPtr` corresponds to a `None`. + #[tracing::instrument(name = "Place::projection_refs", level = "trace", skip(repacker))] + pub fn projection_refs( + self, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> impl Iterator, Ty<'tcx>, Mutability)>> { + self.projection_tys(repacker).filter_map(|(ty, _)| match ty.ty.kind() { + &TyKind::Ref(r, ty, m) => Some(Some((r, ty, m))), + &TyKind::RawPtr(_) => Some(None), + _ => None, + }) + } + pub fn projects_shared_ref(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { self.projects_ty( |typ| { @@ -397,15 +416,37 @@ impl<'tcx> Place<'tcx> { mut predicate: impl FnMut(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)); + // TODO: remove + let old_result = (|| { + 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 + })(); + let new_result = self.projection_tys(repacker).find(|(typ, _)| predicate(*typ)).map(|(_, proj)| { + let projection = repacker.tcx.mk_place_elems(proj); + Self::new(self.local, projection) + }); + assert_eq!(old_result, new_result); + new_result + } + + #[tracing::instrument(name = "Place::projection_tys", level = "trace", skip(repacker), ret)] + pub fn projection_tys( + self, + repacker: PlaceRepacker<'_, 'tcx>, + ) -> impl Iterator, &'tcx [PlaceElem<'tcx>])> { + let mut typ = PlaceTy::from_ty(repacker.mir.local_decls()[self.local].ty); + self.projection.iter().enumerate().map(move |(idx, elem)| { + let ret = (typ, &self.projection[0..idx]); typ = typ.projection_ty(repacker.tcx, *elem); - } - None + ret + }) } pub fn all_behind_region(self, r: RegionVid, repacker: PlaceRepacker<'_, 'tcx>) -> Vec { @@ -423,11 +464,15 @@ impl<'tcx> Place<'tcx> { } pub fn mk_deref(self, repacker: PlaceRepacker<'_, 'tcx>) -> Self { + self.mk_place_elem(PlaceElem::Deref, repacker) + } + + pub fn mk_place_elem(self, elem: PlaceElem<'tcx>, repacker: PlaceRepacker<'_, 'tcx>) -> Self { let elems = repacker.tcx.mk_place_elems_from_iter( self.projection .iter() .copied() - .chain(std::iter::once(PlaceElem::Deref)), + .chain([elem]), ); Self::new(self.local, elems) } diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index b307f55d4d8..7db5fdac4af 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -1,4 +1,5 @@ use prusti_rustc_interface::{ + index::IndexVec, macros::{TyDecodable, TyEncodable}, middle::{ mir, @@ -18,11 +19,14 @@ use crate::environment::{ /// Stores any possible MIR body (from the compiler) that /// Prusti might want to work with. Cheap to clone #[derive(Clone, TyEncodable, TyDecodable)] -pub struct MirBody<'tcx>(Rc>); +pub struct MirBody<'tcx>(Rc>, Rc>>); impl<'tcx> MirBody<'tcx> { pub fn body(&self) -> Rc> { self.0.clone() } + pub fn promoted(&self) -> Rc>> { + self.1.clone() + } } impl<'tcx> std::ops::Deref for MirBody<'tcx> { type Target = mir::Body<'tcx>; @@ -148,7 +152,7 @@ impl<'tcx> EnvBody<'tcx> { }; BodyWithBorrowckFacts { - body: MirBody(Rc::new(body_with_facts.body)), + body: MirBody(Rc::new(body_with_facts.body), Rc::new(body_with_facts.promoted)), borrowck_facts: Rc::new(facts), borrowck_facts2: Rc::new(facts2), } @@ -159,8 +163,8 @@ impl<'tcx> EnvBody<'tcx> { fn load_local_mir(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> MirBody<'tcx> { // SAFETY: This is safe because we are feeding in the same `tcx` // that was used to store the data. - let body = unsafe { mir_storage::retrieve_promoted_mir_body(tcx, def_id) }; - MirBody(Rc::new(body)) + let (body, promoted) = unsafe { mir_storage::retrieve_promoted_mir_body(tcx, def_id) }; + MirBody(Rc::new(body), Rc::new(promoted)) } fn get_monomorphised( @@ -196,7 +200,8 @@ impl<'tcx> EnvBody<'tcx> { } else { ty::EarlyBinder::bind(body.0).instantiate(self.tcx, substs) }; - v.insert(MirBody(monomorphised)).clone() + // TODO: monomorphise promoted as well + v.insert(MirBody(monomorphised, body.1)).clone() } else { unreachable!() } diff --git a/prusti-interface/src/environment/mir_storage.rs b/prusti-interface/src/environment/mir_storage.rs index 1dd393bd421..6a67958ca56 100644 --- a/prusti-interface/src/environment/mir_storage.rs +++ b/prusti-interface/src/environment/mir_storage.rs @@ -7,6 +7,7 @@ //! `'tcx`. use prusti_rustc_interface::{ + index::IndexVec, borrowck::consumers::BodyWithBorrowckFacts, data_structures::fx::FxHashMap, hir::def_id::LocalDefId, @@ -19,7 +20,7 @@ thread_local! { RefCell>> = RefCell::new(FxHashMap::default()); pub static SHARED_STATE_WITHOUT_FACTS: - RefCell>> = + RefCell, IndexVec>)>> = RefCell::new(FxHashMap::default()); } @@ -66,12 +67,14 @@ pub unsafe fn store_promoted_mir_body<'tcx>( _tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: mir::Body<'tcx>, + promoted: IndexVec>, ) { // SAFETY: See the module level comment. let body: mir::Body<'static> = unsafe { std::mem::transmute(body) }; + let promoted: IndexVec> = unsafe { std::mem::transmute(promoted) }; SHARED_STATE_WITHOUT_FACTS.with(|state| { let mut map = state.borrow_mut(); - assert!(map.insert(def_id, body).is_none()); + assert!(map.insert(def_id, (body, promoted)).is_none()); }); } @@ -79,8 +82,8 @@ pub unsafe fn store_promoted_mir_body<'tcx>( pub(super) unsafe fn retrieve_promoted_mir_body<'tcx>( _tcx: TyCtxt<'tcx>, def_id: LocalDefId, -) -> mir::Body<'tcx> { - let body_without_facts: mir::Body<'static> = SHARED_STATE_WITHOUT_FACTS.with(|state| { +) -> (mir::Body<'tcx>, IndexVec>) { + let body_without_facts: (mir::Body<'static>, IndexVec>) = SHARED_STATE_WITHOUT_FACTS.with(|state| { let mut map = state.borrow_mut(); map.remove(&def_id).unwrap() }); diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index 8dc9a90cab7..be4cc4fbd29 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -11,10 +11,11 @@ use crate::{ }; use log::{debug, trace}; use prusti_rustc_interface::{ + index::IndexVec, data_structures::fx::{FxHashMap, FxHashSet}, hir::def_id, middle::{ - mir::{self, AggregateKind, BasicBlock, BasicBlockData, Body, Rvalue, StatementKind}, + mir::{self, AggregateKind, BasicBlock, BasicBlockData, Body, Rvalue, StatementKind, Promoted}, ty::{Ty, TyCtxt}, }, span::Span, @@ -137,6 +138,10 @@ impl<'tcx> Procedure<'tcx> { self.mir.body() } + pub fn get_promoted_rc(&self) -> std::rc::Rc>> { + self.mir.promoted() + } + /// Get the typing context. pub fn get_tcx(&self) -> TyCtxt<'tcx> { self.tcx diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index 363c3795fd7..f564b2c7bc2 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -73,7 +73,7 @@ fn mir_promoted<'tcx>( // SAFETY: This is safe because we are feeding in the same `tcx` that is // going to be used as a witness when pulling out the data. unsafe { - mir_storage::store_promoted_mir_body(tcx, def_id, result.0.borrow().clone()); + mir_storage::store_promoted_mir_body(tcx, def_id, result.0.borrow().clone(), result.1.borrow().clone()); } result } @@ -163,14 +163,15 @@ 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() { + if config::test_free_pcs() && !config::test_coupling_graph() { for proc_id in env.get_annotated_procedures_and_types().0.iter() { let current_procedure = env.get_procedure(*proc_id); let mir = current_procedure.get_mir_rc(); + let promoted = current_procedure.get_promoted_rc(); let name = env.name.get_unique_item_name(*proc_id); println!("Calculating FPCS for: {name} ({:?})", mir.span); - test_free_pcs(&mir, tcx); + test_free_pcs(&mir, &promoted, tcx); } } if config::test_coupling_graph() { @@ -187,7 +188,7 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { let name = env.name.get_unique_item_name(*proc_id); println!("Calculating CG for: {name} ({:?})", mir.span); - test_coupling_graph(&*mir, &*facts, &*facts2, tcx, config::top_crates()); + test_coupling_graph(&*mir.body(), &*mir.promoted(), &*facts, &*facts2, tcx, config::top_crates()); } } if !config::test_free_pcs() && !config::test_coupling_graph() { From 29d695e08b37ff64a334d879fec22f6b89c1d01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Tue, 17 Oct 2023 16:47:31 +0200 Subject: [PATCH 48/58] Bugfixes --- .../src/coupling_graph/check/checker.rs | 30 ++++++++++--------- .../context/outlives_info/edge.rs | 2 +- .../coupling_graph/context/region_info/mod.rs | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index 0f833d75180..cdeae6c8dcc 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -64,21 +64,22 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre // Consistency fpcs.summary.consistency_check(rp); for (statement_index, stmt) in data.statements.iter().enumerate() { - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); - fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); let loc = Location { block, statement_index, }; - let fpcs_after = fpcs_cursor.next(loc); - assert_eq!(fpcs_after.location, loc); - fpcs_cursor.unset_bound(); - let cg_before = cg.before_next(loc); // Couplings for c in cg_before.couplings { c.update_free(&mut cg_state, false); } + + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); + let fpcs_after = fpcs_cursor.next(loc); + assert_eq!(fpcs_after.location, loc); + fpcs_cursor.unset_bound(); + // Repacks for op in fpcs_after.repacks { op.update_free(&mut fpcs.summary, false, rp); @@ -105,21 +106,22 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre } assert!(cg_state.compare(&cg_after.state), "{loc:?}"); } - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); - fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); let loc = Location { block, statement_index: data.statements.len(), }; - let fpcs_after = fpcs_cursor.next(loc); - assert_eq!(fpcs_after.location, loc); - fpcs_cursor.unset_bound(); - let cg_before = cg.before_next(loc); // Couplings for c in cg_before.couplings { c.update_free(&mut cg_state, false); } + + let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); + let fpcs_after = fpcs_cursor.next(loc); + assert_eq!(fpcs_after.location, loc); + fpcs_cursor.unset_bound(); + // Repacks for op in fpcs_after.repacks { op.update_free(&mut fpcs.summary, false, rp); @@ -195,9 +197,9 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { fn compare(&self, other: &Graph) -> bool { for (sub, v) in self.blocks.iter_enumerated() { let sub_info = self.cgx.region_info.map.get(sub); - if let Some(brrw) = sub_info.get_borrow() { + if sub_info.is_borrow() { if !v.is_empty() { - println!("{sub:?} ({brrw:?}) blocks: {v:?}"); + println!("{sub:?} ({:?}) blocks: {v:?}", sub_info.get_borrow()); return false; } } else { diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs index 650dffb1010..8f8e989dad3 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs @@ -86,7 +86,7 @@ impl<'tcx> EdgeInfo<'tcx> { EdgeKind::FnCallReturn } _ if sub_info.is_borrow() || sub_info.is_projection_annotation() => { - if sub_info.is_borrow() { + if let Some(_) = sub_info.get_borrow() { assert!(matches!(stmt.unwrap(), StatementKind::Assign(box (_, Rvalue::Ref(..))))); } // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index 64ea6f66e89..110ba0718f7 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -226,6 +226,7 @@ impl<'tcx> ConstantRegionCollector<'_, '_, 'tcx> { } fn set_region(&mut self, r: RegionVid, kind: RegionKind<'tcx>) { + assert!(self.promoted_idx == Promote::NotPromoted || kind.promoted(), "{kind:?} {r:?}"); self.map.set(r, kind); if let Some(regions_set) = &mut self.regions_set { *regions_set += 1; @@ -388,7 +389,6 @@ impl<'tcx> TypeVisitor> for ConstantRegionCollector<'_, '_, 'tcx> { return ControlFlow::Continue(()); } let kind = self.inner_kind.clone().unwrap(); - assert!(self.promoted_idx == Promote::NotPromoted || kind.promoted(), "{kind:?} {r:?}"); self.set_region(r.as_var(), kind); ControlFlow::Continue(()) } From 89ece293b6f1597c3b52f88b9a3ae2632983095e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 18 Oct 2023 19:00:50 +0200 Subject: [PATCH 49/58] Loop generalization, two-phase borrows and kill_many --- .../src/coupling_graph/check/checker.rs | 116 ++++++++++-------- .../context/outlives_info/edge.rs | 87 +++++++++---- .../src/coupling_graph/impl/dot.rs | 12 +- .../src/coupling_graph/impl/engine.rs | 30 +++-- .../src/coupling_graph/impl/graph.rs | 81 ++++++++++-- .../coupling_graph/impl/join_semi_lattice.rs | 34 ++--- .../src/coupling_graph/impl/triple.rs | 78 ++++++++---- .../src/coupling_graph/results/coupling.rs | 8 +- mir-state-analysis/src/loop/mod.rs | 19 +++ 9 files changed, 333 insertions(+), 132 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index cdeae6c8dcc..295958695cd 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -9,7 +9,7 @@ use prusti_rustc_interface::{ index::IndexVec, data_structures::fx::{FxHashMap, FxHashSet}, middle::{ - mir::{visit::Visitor, Location, ProjectionElem, BorrowKind}, + mir::{visit::Visitor, Location, ProjectionElem, BorrowKind, Statement}, ty::RegionVid, }, borrowck::borrow_set::BorrowData, @@ -27,6 +27,7 @@ use crate::free_pcs::consistency::CapabilityConsistency; struct CouplingState<'a, 'tcx> { blocks: IndexVec>, blocked_by: IndexVec>, + waiting_to_activate: FxHashSet, cgx: &'a CgContext<'a, 'tcx>, } @@ -48,6 +49,7 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre let mut cg_state = CouplingState { blocks: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), blocked_by: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), + waiting_to_activate: FxHashSet::default(), cgx, }; cg_state.initialize(&cg.initial_state()); @@ -68,43 +70,7 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre block, statement_index, }; - let cg_before = cg.before_next(loc); - // Couplings - for c in cg_before.couplings { - c.update_free(&mut cg_state, false); - } - - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); - fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); - let fpcs_after = fpcs_cursor.next(loc); - assert_eq!(fpcs_after.location, loc); - fpcs_cursor.unset_bound(); - - // Repacks - for op in fpcs_after.repacks { - op.update_free(&mut fpcs.summary, false, rp); - } - // Couplings bound set - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); - fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Extend lifetimes (safe since we unset it later) - // 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); - // Couplings bound unset - fpcs.bound.borrow_mut().0 = None; - - // Only apply coupling ops after - let cg_after = cg.next(loc); - // Couplings - for c in cg_after.couplings { - c.update_free(&mut cg_state, false); - } - assert!(cg_state.compare(&cg_after.state), "{loc:?}"); + cg_state.check_location(loc, stmt, &mut fpcs, &mut cg, &mut fpcs_cursor); } let loc = Location { block, @@ -191,6 +157,50 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { self.blocked_by[*sup].insert(sub); } } + self.waiting_to_activate = graph.inactive_loans.clone(); + } + + #[tracing::instrument(name = "CouplingState::check_location", level = "trace", skip(self, stmt, cg, fpcs, fpcs_cursor))] + fn check_location(&mut self, loc: Location, stmt: &Statement<'tcx>, fpcs: &mut Fpcs<'_, 'tcx>, cg: &mut CgAnalysis<'_, '_, 'tcx>, fpcs_cursor: &mut FreePcsAnalysis<'_, 'tcx>) { + let rp = self.cgx.rp; + + let cg_before = cg.before_next(loc); + // Couplings + for c in cg_before.couplings { + c.update_free(self, false); + } + + let bound: Box) -> CapabilityKind> = Box::new(self.mk_capability_upper_bound()); + fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); + let fpcs_after = fpcs_cursor.next(loc); + assert_eq!(fpcs_after.location, loc); + fpcs_cursor.unset_bound(); + + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Couplings bound set + let bound: Box) -> CapabilityKind> = Box::new(self.mk_capability_upper_bound()); + fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Extend lifetimes (safe since we unset it later) + // 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); + // Couplings bound unset + fpcs.bound.borrow_mut().0 = None; + + // Only apply coupling ops after + let cg_after = cg.next(loc); + // Couplings + for c in cg_after.couplings { + c.update_free(self, false); + } + assert!(self.compare(&cg_after.state), "{loc:?}"); } #[tracing::instrument(name = "compare", level = "trace")] @@ -252,7 +262,7 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { fn active_borrows(&self) -> impl Iterator> + '_ { self.blocked_by .iter_enumerated() - .filter(|(_, blockers)| !blockers.is_empty()) + .filter(|(region, blockers)| !blockers.is_empty() && !self.waiting_to_activate.contains(region)) .flat_map(move |(region, _)| self.cgx.region_info.map.get(region).get_borrow()) } fn has_real_blockers(&self, region: RegionVid) -> bool { @@ -310,18 +320,25 @@ impl CouplingOp { match self { CouplingOp::Add(block) => block.update_free(cg_state, is_cleanup), CouplingOp::Remove(remove, new_blocks) => { - let blocks = std::mem::replace(&mut cg_state.blocks[*remove], FxHashSet::default()); - for block in blocks { - cg_state.blocked_by[block].remove(&remove); - } - let blocked_by = std::mem::replace(&mut cg_state.blocked_by[*remove], FxHashSet::default()); - for block_by in blocked_by { - cg_state.blocks[block_by].remove(&remove); - } + Self::remove(cg_state, *remove); for block in new_blocks { block.update_free(cg_state, is_cleanup); } } + CouplingOp::Activate(region) => { + let contained = cg_state.waiting_to_activate.remove(region); + assert!(contained); + } + } + } + fn remove(cg_state: &mut CouplingState, remove: RegionVid) { + let blocks = std::mem::replace(&mut cg_state.blocks[remove], FxHashSet::default()); + for block in blocks { + cg_state.blocked_by[block].remove(&remove); + } + let blocked_by = std::mem::replace(&mut cg_state.blocked_by[remove], FxHashSet::default()); + for block_by in blocked_by { + cg_state.blocks[block_by].remove(&remove); } } } @@ -332,8 +349,11 @@ impl Block { cg_state: &mut CouplingState, is_cleanup: bool, ) { - let Block { sup, sub } = self; + let Block { sup, sub, waiting_to_activate } = self; assert!(!cg_state.cgx.region_info.map.get(sub).is_borrow()); + if waiting_to_activate && cg_state.waiting_to_activate.insert(sup) { + assert!(cg_state.blocked_by[sup].is_empty()); + } cg_state.blocks[sub].insert(sup); cg_state.blocked_by[sup].insert(sub); } diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs index 8f8e989dad3..9042f31fdc9 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs @@ -27,6 +27,14 @@ use prusti_rustc_interface::{ use crate::coupling_graph::CgContext; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum EdgeOrigin { + Rustc, + RustcUniversal, + Static, + Opaque, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct EdgeInfo<'tcx> { /// The region which outlives (usually means blocked) @@ -35,16 +43,20 @@ pub struct EdgeInfo<'tcx> { sub: RegionVid, pub creation: Option, pub reason: Option>, + pub origin: EdgeOrigin, } impl<'tcx> EdgeInfo<'tcx> { - pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option) -> Self { - assert_ne!(sup, sub); + pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option, origin: EdgeOrigin) -> Self { + if !matches!(origin, EdgeOrigin::Opaque) { + assert_ne!(sup, sub); + } Self { sup, sub, creation, reason: None, + origin, } } pub fn sup(self) -> RegionVid { @@ -53,6 +65,9 @@ impl<'tcx> EdgeInfo<'tcx> { pub fn sub(self) -> RegionVid { self.sub } + pub fn is_opaque(self) -> bool { + matches!(self.origin, EdgeOrigin::Opaque) + } pub fn kind(self, cgx: &CgContext<'_, 'tcx>) -> EdgeKind<'tcx> { let (sup_info, sub_info) = (cgx.region_info.map.get(self.sup), cgx.region_info.map.get(self.sub)); let stmt = self.creation.map(|location| cgx.rp.body().stmt_at(location)); @@ -86,9 +101,6 @@ impl<'tcx> EdgeInfo<'tcx> { EdgeKind::FnCallReturn } _ if sub_info.is_borrow() || sub_info.is_projection_annotation() => { - if let Some(_) = sub_info.get_borrow() { - assert!(matches!(stmt.unwrap(), StatementKind::Assign(box (_, Rvalue::Ref(..))))); - } // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); EdgeKind::ContainedIn } @@ -99,27 +111,45 @@ impl<'tcx> EdgeInfo<'tcx> { edge.iter().map(|e| e.kind(cgx)).collect() } - /// Is the old edge contained in the new edge? - pub fn is_new_edge(new: &Vec, old: &Vec) -> bool { - assert_ne!(old.len(), 0); - if new.len() < old.len() { - return true; + pub fn widen(edge: &Vec, top: impl Fn(RegionVid, RegionVid) -> Self, needs_widening: impl Fn(Location) -> bool) -> Vec { + let mut new_edge = Vec::new(); + let widen_edge: &mut Option<(RegionVid, RegionVid)> = &mut None; + for &e in edge { + if e.creation.map(|loc| needs_widening(loc)).unwrap_or_default() { + match widen_edge { + Some((_, sup)) => *sup = e.sup, + None => *widen_edge = Some((e.sub, e.sup)), + } + } else { + if let Some((sub, sup)) = widen_edge.take() { + new_edge.push(top(sup, sub)); + } + new_edge.push(e); + } + } + if let Some((sub, sup)) = widen_edge.take() { + new_edge.push(top(sup, sub)); } - // TODO: optimize? + new_edge + } + pub fn generalized_by(target: &Vec, by: &Vec) -> bool { let mut looking_for = 0; - for (idx, elem) in new.iter().enumerate() { - if &old[looking_for] == elem { + for elem in target.iter().copied() { + if looking_for == by.len() { + return false; + } else if by[looking_for].is_opaque() { + if looking_for == by.len() - 1 { + return true; + } else if by[looking_for + 1] == elem { + looking_for += 2; + } + } else if by[looking_for] == elem { looking_for += 1; - } - let left_to_find = old.len() - looking_for; - if left_to_find == 0 { + } else { return false; } - if new.len() - idx - 1 < left_to_find { - return true; - } } - unreachable!() + looking_for == by.len() } } @@ -153,7 +183,21 @@ impl Display for EdgeInfo<'_> { .creation .map(|c| format!("{c:?}")) .unwrap_or_else(|| "sig".to_string()); - write!(f, "{creation} ({reason})") + match self.origin { + EdgeOrigin::Rustc => write!(f, "{creation} ({reason})"), + EdgeOrigin::RustcUniversal => { + assert!(self.reason.is_none() && self.creation.is_none()); + write!(f, "universal") + } + EdgeOrigin::Static => { + assert!(self.reason.is_none() && self.creation.is_none()); + write!(f, "promoted") + } + EdgeOrigin::Opaque => { + assert!(self.reason.is_none() && self.creation.is_some()); + write!(f, "{creation} (loop)") + } + } } } @@ -164,6 +208,7 @@ impl<'tcx> From> for EdgeInfo<'tcx> { sub: c.sub, creation: c.locations.from_location(), reason: Some(c.category), + origin: EdgeOrigin::Rustc, } } } diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index f2c485b4189..b4f1bf76eda 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -12,7 +12,7 @@ use prusti_rustc_interface::{ dataflow::fmt::DebugWithContext, index::{bit_set::BitSet, IndexVec}, middle::{ - mir::{BorrowKind, ConstraintCategory, Local}, + mir::{BorrowKind, ConstraintCategory, Local, BasicBlock}, ty::{RegionVid, TyKind}, }, }; @@ -36,11 +36,11 @@ impl<'tcx> Edge<'tcx> { impl<'a, 'tcx> Cg<'a, 'tcx> { fn get_id(&self) -> String { - if let Some(id) = &self.id { - let pre = if self.is_pre { "_pre" } else { "" }; - format!("{id:?}{pre}").replace('[', "_").replace(']', "") - } else { + if self.location.block == BasicBlock::MAX { "start".to_string() + } else { + let pre = if self.is_pre { "_pre" } else { "" }; + format!("{:?}{pre}", self.location).replace('[', "_").replace(']', "") } } } @@ -113,6 +113,8 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { let kind = self.get_kind(*r); if kind.universal() { Some(dot::LabelText::LabelStr(Cow::Borrowed("red"))) + } else if self.graph.inactive_loans.contains(r) { + Some(dot::LabelText::LabelStr(Cow::Borrowed("blue"))) } else { None } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index b8d24455484..b5595f626fa 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -4,7 +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/. -use std::cell::RefCell; +use std::cell::{RefCell, Cell}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ @@ -48,7 +48,8 @@ pub(crate) fn draw_dots<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph< let mut graph = Vec::new(); let body = c.body(); c.seek_to_block_start(START_BLOCK); - let g = c.get().clone(); + let mut g = c.get().clone(); + g.location.block = BasicBlock::MAX; dot::render(&g, &mut graph).unwrap(); for (block, data) in body.basic_blocks.iter_enumerated() { @@ -59,7 +60,6 @@ pub(crate) fn draw_dots<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph< let mut g = c.get().clone(); g.dot_node_filter = |k| k.local(); g.dot_edge_filter = |sup, sub| !(sup.local() && sub.universal()); - g.id = Some(Location { block, statement_index: 0 }); dot::render(&g, &mut graph).unwrap(); drop(g); for statement_index in 0..body.terminator_loc(block).statement_index { @@ -105,6 +105,7 @@ fn print_after_loc<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph<'a, ' pub(crate) struct CouplingGraph<'a, 'tcx> { pub(crate) cgx: &'a CgContext<'a, 'tcx>, + bb_index: Cell, out_of_scope: FxIndexMap>, flow_borrows: RefCell>>, top_crates: bool, @@ -158,6 +159,7 @@ impl<'a, 'tcx> CouplingGraph<'a, 'tcx> { Self { cgx, + bb_index: Cell::new(START_BLOCK), out_of_scope, flow_borrows: RefCell::new(flow_borrows), top_crates, @@ -170,10 +172,13 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CouplingGraph<'a, 'tcx> { const NAME: &'static str = "coupling_graph"; fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { - Cg::new(self.cgx, self.top_crates) + let block = self.bb_index.get(); + self.bb_index.set(block.plus(1)); + Cg::new(self.cgx, self.top_crates, Location { block, statement_index: 0 }) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { + self.bb_index.set(START_BLOCK); state.initialize_start_block() } } @@ -186,8 +191,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { statement: &Statement<'tcx>, location: Location, ) { - // println!("Location: {l}"); - state.id = Some(location); + assert_eq!(state.location, location); state.reset_ops(); if location.statement_index == 0 { @@ -195,7 +199,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { // println!("\nblock: {:?}", location.block); let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.max_version()), + format!("log/coupling/individual/{l}_v{}_start.dot", state.sum_version()), false, ); self.flow_borrows @@ -227,9 +231,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.max_version()), + format!("log/coupling/individual/{l}_v{}.dot", state.sum_version()), false, ); + state.location.statement_index += 1; } @@ -241,7 +246,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { location: Location, ) { // println!("Location: {l}"); - state.id = Some(location); + assert_eq!(state.location, location); state.reset_ops(); if location.statement_index == 0 { @@ -249,7 +254,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { // println!("\nblock: {:?}", location.block); let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.max_version()), + format!("log/coupling/individual/{l}_v{}_start.dot", state.sum_version()), false, ); self.flow_borrows @@ -284,7 +289,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { TerminatorKind::Return => { let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_pre.dot", state.max_version()), + format!("log/coupling/individual/{l}_v{}_pre.dot", state.sum_version()), false, ); // Pretend we have a storage dead for all `always_live_locals` other than the args/return @@ -310,9 +315,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { let l = format!("{:?}", location).replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}.dot", state.max_version()), + format!("log/coupling/individual/{l}_v{}.dot", state.sum_version()), false, ); + state.location.statement_index += 1; terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index feafdba0130..9900f84caa8 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -26,7 +26,7 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{CgContext, outlives_info::edge::EdgeInfo}, + coupling_graph::{CgContext, outlives_info::edge::{EdgeInfo, EdgeOrigin}}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, @@ -40,6 +40,8 @@ pub struct Graph<'tcx> { pub nodes: IndexVec>, // Regions equal to 'static pub static_regions: FxHashSet, + // Two-phase loans waiting to activate + pub inactive_loans: FxHashSet, } impl<'tcx> Graph<'tcx> { @@ -48,28 +50,53 @@ impl<'tcx> Graph<'tcx> { self.outlives_inner(vec![c.into()]) } #[tracing::instrument(name = "Graph::outlives_placeholder", level = "trace", skip(self), ret)] - pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid) -> Option<(RegionVid, RegionVid)> { - let edge = EdgeInfo::no_reason(sup, sub, None); + pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid, origin: EdgeOrigin) -> Option<(RegionVid, RegionVid)> { + let edge = EdgeInfo::no_reason(sup, sub, None, origin); self.outlives_inner(vec![edge]) } // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) #[tracing::instrument(name = "Graph::outlives_inner", level = "trace", skip(self), ret)] - pub(crate) fn outlives_inner( + fn outlives_inner( &mut self, edge: Vec>, ) -> Option<(RegionVid, RegionVid)> { + let (sup, sub) = self.outlives_unwrap_edge(&edge); + self.nodes[sup].blocked_by.insert(sub); + let blocks = self.nodes[sub].blocks.entry(sup).or_default(); + if blocks.insert(edge) { + Some((sup, sub)) + } else { + None + } + } + fn outlives_unwrap_edge(&mut self, edge: &Vec>) -> (RegionVid, RegionVid) { let (sup, sub) = (edge.last().unwrap().sup(), edge.first().unwrap().sub()); if self.static_regions.contains(&sub) { Self::set_static_region(&self.nodes, &mut self.static_regions, sup); } + (sup, sub) + } + + // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) + #[tracing::instrument(name = "Graph::outlives_join", level = "trace", skip(self), ret)] + pub(super) fn outlives_join( + &mut self, + edge: Vec>, + ) -> Option<(RegionVid, RegionVid)> { + let (sup, sub) = self.outlives_unwrap_edge(&edge); self.nodes[sup].blocked_by.insert(sub); - let old = self.nodes[sub].blocks.entry(sup).or_default(); - if old.iter().all(|old| EdgeInfo::is_new_edge(&edge, old)) { - assert!(old.insert(edge)); - Some((sup, sub)) - } else { + let blocks = self.nodes[sub].blocks.entry(sup).or_default(); + + if blocks.iter().any(|other| EdgeInfo::generalized_by(&edge, other)) { None + } else { + blocks.retain(|other| !EdgeInfo::generalized_by(other, &edge)); + if blocks.insert(edge) { + Some((sup, sub)) + } else { + None + } } } fn set_static_region( @@ -110,6 +137,8 @@ impl<'tcx> Graph<'tcx> { for (&sub, sub_reasons) in &blocked_by { // Do not rejoin nodes in a loop to themselves if sub != sup { + // TODO: change this so that we do not need to make opaque + assert!(sup_reasons.len() * sub_reasons.len() < 100); let mut rejoined = None; for sup_reason in &sup_reasons { for sub_reason in sub_reasons { @@ -141,6 +170,17 @@ impl<'tcx> Graph<'tcx> { } } + #[tracing::instrument(name = "Graph::remove_many", level = "trace")] + pub fn remove_many(&mut self, rs: &FxHashSet) -> Vec<(RegionVid, Vec<(RegionVid, RegionVid)>)> { + for &r in rs { + if self.predecessors(r).iter().all(|pre| rs.contains(pre)) || self.successors(r).iter().all(|suc| rs.contains(suc)) { + self.static_regions.remove(&r); + self.remove_all_edges(r); + } + } + rs.iter().flat_map(|r| self.remove(*r)).collect() + } + pub(crate) fn all_nodes(&self) -> impl Iterator)> { self.nodes .iter_enumerated() @@ -162,6 +202,29 @@ impl<'tcx> Graph<'tcx> { let blocked_by = blocked_by.into_iter().map(|bb| (bb, self.nodes[bb].blocks.remove(&r).unwrap())).collect(); (blocks, blocked_by) } + + fn predecessors(&self, r: RegionVid) -> FxHashSet { + let mut predecessors = FxHashSet::default(); + self.predecessors_helper(r, &mut predecessors); + predecessors + } + fn predecessors_helper(&self, r: RegionVid, visited: &mut FxHashSet) { + let tp: Vec<_> = self.nodes[r].blocked_by.iter().copied().filter(|r| visited.insert(*r)).collect(); + for r in tp { + self.predecessors_helper(r, visited) + } + } + fn successors(&self, r: RegionVid) -> FxHashSet { + let mut successors = FxHashSet::default(); + self.successors_helper(r, &mut successors); + successors + } + fn successors_helper(&self, r: RegionVid, visited: &mut FxHashSet) { + let tp: Vec<_> = self.nodes[r].blocks.iter().map(|(r, _)| *r).filter(|r| visited.insert(*r)).collect(); + for r in tp { + self.successors_helper(r, visited) + } + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index c3bf5f3cfc2..ea48868d11d 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -6,13 +6,16 @@ use std::collections::hash_map::Entry; -use prusti_rustc_interface::dataflow::JoinSemiLattice; +use prusti_rustc_interface::{ + middle::mir::Location, + dataflow::JoinSemiLattice, +}; use crate::{ free_pcs::{ CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, }, - utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency}, + utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency, outlives_info::edge::{EdgeInfo, EdgeOrigin}}, }; use super::{graph::Graph, triple::Cg}; @@ -20,29 +23,32 @@ use super::{graph::Graph, triple::Cg}; impl JoinSemiLattice for Cg<'_, '_> { #[tracing::instrument(name = "Cg::join", level = "debug", ret)] fn join(&mut self, other: &Self) -> bool { - let version = self.version.entry(other.id.unwrap().block).or_default(); + let version = self.version.entry(other.location.block).or_default(); *version += 1; - assert!(*version < 10); - self.graph.join(&other.graph) - } -} + assert!(*version < 20, "{:?} -> {:?}", other.location, self.location); -impl JoinSemiLattice for Graph<'_> { - fn join(&mut self, other: &Self) -> bool { + let loop_head = self.cgx.loops.loop_head_of(self.location.block); + let top = |sup, sub| EdgeInfo::no_reason(sup, sub, Some(self.location), EdgeOrigin::Opaque); + let needs_widening = |loc: Location| loop_head.map(|l| self.cgx.loops.in_loop(loc.block, l)).unwrap_or_default(); + // Are we looping back into the loop head from within the loop? + let loop_into = loop_head.map(|l| self.cgx.loops.in_loop(other.location.block, l)); let mut changed = false; - for (_, node) in other.all_nodes() { - for (_, reasons) in node.blocks.iter() { - for reason in reasons.clone() { - let was_new = self.outlives_inner(reason); + for (_, node) in other.graph.all_nodes() { + for (_, edges) in node.blocks.iter() { + for edge in edges { + let edge = EdgeInfo::widen(edge, top, needs_widening); + let was_new = self.graph.outlives_join(edge); changed = changed || was_new.is_some(); } } } + let old_len = self.graph.inactive_loans.len(); + self.graph.inactive_loans.extend(other.graph.inactive_loans.iter().copied()); + changed = changed || old_len != self.graph.inactive_loans.len(); changed } } - impl Cg<'_, '_> { #[tracing::instrument(name = "Cg::bridge", level = "debug", ret)] pub fn bridge(&self, other: &Self) -> Vec { diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 15a8fb6eddb..b4511168d05 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -13,7 +13,7 @@ use derive_more::{Deref, DerefMut}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ borrowck::{ - borrow_set::BorrowData, + borrow_set::{BorrowData, TwoPhaseActivation}, consumers::{BorrowIndex, OutlivesConstraint, RichLocation}, }, data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}, @@ -31,7 +31,7 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}}, + coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}, outlives_info::edge::EdgeOrigin}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, CapabilityKind, }, @@ -45,7 +45,7 @@ use super::{ #[derive(Clone)] pub struct Cg<'a, 'tcx> { - pub id: Option, + pub location: Location, pub is_pre: bool, pub cgx: &'a CgContext<'a, 'tcx>, @@ -64,7 +64,7 @@ pub struct Cg<'a, 'tcx> { impl Debug for Cg<'_, '_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("Graph") - .field("id", &self.id) + .field("location", &self.location) .field("version", &self.version) // .field("nodes", &self.graph) .finish() @@ -101,15 +101,16 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } #[tracing::instrument(name = "Cg::new", level = "trace", skip_all)] - pub fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool) -> Self { + pub fn new(cgx: &'a CgContext<'a, 'tcx>, top_crates: bool, location: Location) -> Self { let graph = Graph { nodes: IndexVec::from_elem_n(Node::new(), cgx.region_info.map.region_len()), static_regions: FxHashSet::from_iter([cgx.region_info.static_region]), + inactive_loans: FxHashSet::default(), }; let live = BitSet::new_empty(cgx.facts2.borrow_set.location_map.len()); Self { - id: None, + location, is_pre: true, cgx, live, @@ -130,10 +131,10 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { self.outlives(*c); } for &(sup, sub) in &self.cgx.outlives_info.universal_constraints { - self.outlives_placeholder(sup, sub); + self.outlives_placeholder(sup, sub, EdgeOrigin::RustcUniversal); } for &const_region in self.cgx.region_info.map.const_regions() { - self.outlives_static(const_region); + self.outlives_static(const_region, EdgeOrigin::Static); } // Remove all locals without capabilities from the initial graph self.kill_shared_borrows_on_place(None, RETURN_PLACE.into()); @@ -141,23 +142,23 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { self.kill_shared_borrows_on_place(None, local.into()); } } - pub fn max_version(&self) -> usize { - self.version.values().copied().max().unwrap_or_default() + pub fn sum_version(&self) -> usize { + self.version.values().copied().sum::() } pub(crate) fn outlives(&mut self, c: OutlivesConstraint<'tcx>) { let new = self.graph.outlives(c); self.outlives_op(new) } - pub(crate) fn outlives_static(&mut self, r: RegionVid) { + pub(crate) fn outlives_static(&mut self, r: RegionVid, origin: EdgeOrigin) { let static_region = self.static_region(); if r == static_region { return; } - self.outlives_placeholder(r, static_region) + self.outlives_placeholder(r, static_region, origin) } - pub(crate) fn outlives_placeholder(&mut self, r: RegionVid, placeholder: RegionVid) { - let new = self.graph.outlives_placeholder(r, placeholder); + pub(crate) fn outlives_placeholder(&mut self, r: RegionVid, placeholder: RegionVid, origin: EdgeOrigin) { + let new = self.graph.outlives_placeholder(r, placeholder, origin); self.outlives_op(new) } #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] @@ -174,17 +175,35 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { if sub_info.is_borrow() || (sub_info.universal() && sup_info.local()) { None } else { - Some(Block { sup, sub, }) + let waiting_to_activate = self.graph.inactive_loans.contains(&sup); + Some(Block { sup, sub, waiting_to_activate, }) } } - #[tracing::instrument(name = "Cg::remove", level = "trace", skip(self))] + #[tracing::instrument(name = "Cg::remove", level = "debug", skip(self))] pub(crate) fn remove(&mut self, r: RegionVid, l: Option) { let remove = self.graph.remove(r); - if let Some((removed, rejoins)) = remove { - let rejoins = rejoins.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); - self.couplings.push(CouplingOp::Remove(removed, rejoins)); + if let Some(op) = remove { + self.remove_op(op); } } + #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] + fn remove_op(&mut self, op: (RegionVid, Vec<(RegionVid, RegionVid)>)) { + let rejoins = op.1.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); + self.couplings.push(CouplingOp::Remove(op.0, rejoins)); + } + #[tracing::instrument(name = "Cg::remove", level = "debug", skip(self), ret)] + pub(crate) fn remove_many(&mut self, mut r: FxHashSet) { + let ops = self.graph.remove_many(&r); + for op in ops { + r.remove(&op.0); + self.remove_op(op); + } + for removed in r { + self.couplings.push(CouplingOp::Remove(removed, Vec::new())); + } + // let rejoins = rejoins.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); + // self.couplings.push(CouplingOp::RemoveMany(removed, rejoins)); + } #[tracing::instrument(name = "Cg::kill_borrow", level = "trace", skip(self))] pub(crate) fn kill_borrow(&mut self, data: &BorrowData<'tcx>) { let remove = self.graph.kill_borrow(data); @@ -267,6 +286,20 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { // } } } + + if let Some(borrow) = self.cgx.facts2.borrow_set.location_map.get(&location) { + if let TwoPhaseActivation::ActivatedAt(_) = borrow.activation_location { + self.graph.inactive_loans.insert(borrow.region); + } + } + if let Some(activations) = self.cgx.facts2.borrow_set.activation_map.get(&location) { + for activated in activations { + let borrow = &self.cgx.facts2.borrow_set[*activated]; + let contained = self.graph.inactive_loans.remove(&borrow.region); + assert!(contained); + self.couplings.push(CouplingOp::Activate(borrow.region)); + } + } } #[tracing::instrument(name = "handle_outlives", level = "debug", skip(self))] @@ -339,9 +372,10 @@ impl<'tcx> Visitor<'tcx> for Cg<'_, 'tcx> { } impl<'tcx> Cg<'_, 'tcx> { - #[tracing::instrument(name = "Regions::merge_for_return", level = "trace")] + #[tracing::instrument(name = "Cg::merge_for_return", level = "debug")] pub fn merge_for_return(&mut self, location: Location) { let regions: Vec<_> = self.graph.all_nodes().map(|(r, _)| r).collect(); + let mut to_remove = FxHashSet::default(); for r in regions { let kind = self.cgx.region_info.map.get(r); match kind { @@ -374,9 +408,11 @@ impl<'tcx> Cg<'_, 'tcx> { // Skip unknown empty nodes, we may want to figure out how to deal with them in the future RegionKind::UnknownLocal => (), } - self.remove(r, None); + to_remove.insert(r); } + self.remove_many(to_remove); } + #[tracing::instrument(name = "Cg::output_to_dot", level = "debug", skip_all)] pub fn output_to_dot>(&self, path: P, error: bool) { if cfg!(debug_assertions) && (!self.top_crates || error) { std::fs::create_dir_all("log/coupling/individual").unwrap(); diff --git a/mir-state-analysis/src/coupling_graph/results/coupling.rs b/mir-state-analysis/src/coupling_graph/results/coupling.rs index 6f1bf36b515..d7ddea56678 100644 --- a/mir-state-analysis/src/coupling_graph/results/coupling.rs +++ b/mir-state-analysis/src/coupling_graph/results/coupling.rs @@ -16,6 +16,7 @@ use crate::{free_pcs::CapabilityKind, utils::Place}; pub enum CouplingOp { Add(Block), Remove(RegionVid, Vec), + Activate(RegionVid), } impl CouplingOp { @@ -24,6 +25,7 @@ impl CouplingOp { CouplingOp::Add(block) => Box::new([block.sup, block.sub].into_iter()), CouplingOp::Remove(remove, block) => Box::new([*remove].into_iter().chain(block.iter().flat_map(|b| [b.sup, b.sub].into_iter()))), + CouplingOp::Activate(region) => Box::new([*region].into_iter()), } } } @@ -42,6 +44,7 @@ impl Display for CouplingOp { } write!(f, "}})") } + CouplingOp::Activate(region) => write!(f, "Activate({region:?})"), } } } @@ -53,10 +56,11 @@ pub struct Block { /// The region that must be outlived (is blocking) pub sub: RegionVid, // pub kind: CapabilityKind, + pub waiting_to_activate: bool, } impl Display for Block { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let Block { sup, sub } = self; - write!(f, "Block({sup:?}, {sub:?})") + let Block { sup, sub, waiting_to_activate } = *self; + write!(f, "Block({sup:?}, {sub:?}){}", if waiting_to_activate { "?" } else { "" }) } } diff --git a/mir-state-analysis/src/loop/mod.rs b/mir-state-analysis/src/loop/mod.rs index 449ec150d45..6bae3812c4e 100644 --- a/mir-state-analysis/src/loop/mod.rs +++ b/mir-state-analysis/src/loop/mod.rs @@ -44,6 +44,14 @@ impl LoopSet { assert!(idx < self.data.len()); self.data[idx] &= !(1 << (loop_idx % Self::OFFSET)); } + fn contains(&self, loop_idx: LoopId) -> bool { + let loop_idx = loop_idx.index(); + let idx = loop_idx / Self::OFFSET; + if idx >= self.data.len() { + return false; + } + self.data[idx] & (1 << (loop_idx % Self::OFFSET)) != 0 + } fn iter(&self) -> impl DoubleEndedIterator + '_ { self.data.iter().enumerate().flat_map(|(idx, &val)| { let to = if val == 0 { 0 } else { Self::OFFSET }; @@ -105,6 +113,9 @@ impl LoopAnalysis { } analysis } + pub fn in_loop(&self, bb: BasicBlock, l: LoopId) -> bool { + self.bb_data[bb].contains(l) + } pub fn loops(&self, bb: BasicBlock) -> impl DoubleEndedIterator + '_ { self.bb_data[bb].iter() } @@ -122,6 +133,10 @@ impl LoopAnalysis { pub fn innermost_loop(&self, bb: BasicBlock) -> Option { self.loops(bb).max_by_key(|l| self.loop_nest_depth(*l)) } + /// If `bb` is a loop head, return the loop for which it is the head. + pub fn loop_head_of(&self, bb: BasicBlock) -> Option { + self.loops(bb).find(|l| self[*l] == bb) + } fn consistency_check(&self) { // Start block can be in a maximum of one loop, of which it is the head @@ -130,6 +145,10 @@ impl LoopAnalysis { assert_eq!(self[l], START_BLOCK); } assert!(start_loops.is_empty()); + // A bb can only be the loop head of a single loop + for lh in &self.loop_heads { + assert_eq!(self.loop_heads.iter().filter(|other| *other == lh).count(), 1); + } } } From 58d03fb90ed7cfd55aa1ee4063b8708ff6951ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Thu, 19 Oct 2023 17:55:12 +0200 Subject: [PATCH 50/58] Bugfixes --- .../src/coupling_graph/check/checker.rs | 37 +--- .../context/outlives_info/edge.rs | 179 ++++++++++-------- .../src/coupling_graph/impl/dot.rs | 12 +- .../src/coupling_graph/impl/engine.rs | 13 +- .../src/coupling_graph/impl/graph.rs | 46 +++-- .../coupling_graph/impl/join_semi_lattice.rs | 19 +- .../src/coupling_graph/impl/triple.rs | 24 +-- 7 files changed, 174 insertions(+), 156 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index 295958695cd..cbd4eb7291f 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -144,17 +144,9 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre impl<'a, 'tcx> CouplingState<'a, 'tcx> { fn initialize(&mut self, graph: &Graph<'tcx>) { for (sub, v) in graph.nodes.iter_enumerated() { - let sub_info = self.cgx.region_info.map.get(sub); - if sub_info.is_borrow() { - continue; - } - for sup in v.blocks.keys() { - let sup_info = self.cgx.region_info.map.get(*sup); - if sub_info.universal() && sup_info.local() { - continue; - } - self.blocks[sub].insert(*sup); - self.blocked_by[*sup].insert(sub); + for sup in v.true_edges() { + self.blocks[sub].insert(sup); + self.blocked_by[sup].insert(sub); } } self.waiting_to_activate = graph.inactive_loans.clone(); @@ -203,24 +195,15 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { assert!(self.compare(&cg_after.state), "{loc:?}"); } - #[tracing::instrument(name = "compare", level = "trace")] + #[tracing::instrument(name = "CouplingState::compare", level = "trace")] fn compare(&self, other: &Graph) -> bool { + // println!("Compare"); for (sub, v) in self.blocks.iter_enumerated() { - let sub_info = self.cgx.region_info.map.get(sub); - if sub_info.is_borrow() { - if !v.is_empty() { - println!("{sub:?} ({:?}) blocks: {v:?}", sub_info.get_borrow()); - return false; - } - } else { - let blocks: FxHashSet<_> = other.nodes[sub].blocks.keys().copied().filter(|sup| { - let sup_info = self.cgx.region_info.map.get(*sup); - !(sub_info.universal() && sup_info.local()) - }).collect(); - if v != &blocks { - println!("{sub:?} blocks: {v:?} != {blocks:?}"); - return false; - } + let blocks: FxHashSet<_> = other.nodes[sub].true_edges().into_iter().collect(); + // println!("Compare {sub:?} blocks: {v:?} == {blocks:?}"); + if v != &blocks { + println!("{sub:?} blocks: {v:?} != {blocks:?}"); + return false; } } true diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs index 9042f31fdc9..b04b96700a1 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs @@ -36,89 +36,33 @@ pub enum EdgeOrigin { } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EdgeInfo<'tcx> { - /// The region which outlives (usually means blocked) - sup: RegionVid, - /// The region which is outlived (usually means blocking) - sub: RegionVid, - pub creation: Option, - pub reason: Option>, - pub origin: EdgeOrigin, +pub struct Edge<'tcx> { + pub info: EdgeInfo<'tcx>, + pub kind: EdgeKind, } -impl<'tcx> EdgeInfo<'tcx> { - pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option, origin: EdgeOrigin) -> Self { - if !matches!(origin, EdgeOrigin::Opaque) { - assert_ne!(sup, sub); - } - Self { - sup, - sub, - creation, - reason: None, - origin, - } - } +impl<'tcx> Edge<'tcx> { pub fn sup(self) -> RegionVid { - self.sup + self.info.sup } pub fn sub(self) -> RegionVid { - self.sub + self.info.sub } pub fn is_opaque(self) -> bool { - matches!(self.origin, EdgeOrigin::Opaque) + matches!(self.info.origin, EdgeOrigin::Opaque) } - pub fn kind(self, cgx: &CgContext<'_, 'tcx>) -> EdgeKind<'tcx> { - let (sup_info, sub_info) = (cgx.region_info.map.get(self.sup), cgx.region_info.map.get(self.sub)); - let stmt = self.creation.map(|location| cgx.rp.body().stmt_at(location)); - let term = stmt.and_then(|stmt| stmt.right()).map(|t| &t.kind); - let stmt = stmt.and_then(|stmt| stmt.left()).map(|s| &s.kind); - match (self.reason, stmt, term) { - (Some(ConstraintCategory::BoringNoLocation), _, Some(TerminatorKind::Call { .. })) if sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0 => - EdgeKind::FnCallImplied, - (Some(ConstraintCategory::Predicate(_)), _, _) => { - assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); - assert!(sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0); - EdgeKind::FnCallPredicate - } - (Some(ConstraintCategory::CallArgument(_)), _, _) => { - assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); - // Can get a `Self::Static` outlives requirement from a function call - let static_eq = sup_info.is_static() ^ sub_info.is_static(); - let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); - let sup_depth = sub_info.from_function_depth(); - let sub_depth = sup_info.from_function_depth(); - assert!(static_eq || placeholders || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1), - "{sup_info:?} ({})\nand\n{sub_info:?} ({})\n({self:?})", sup_info.from_function_depth(), sub_info.from_function_depth()); - EdgeKind::FnCallArgument - } - (Some(ConstraintCategory::Assignment), _, Some(TerminatorKind::Call { .. })) => { - let static_eq = sup_info.is_static() ^ sub_info.is_static(); - // let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); - let sup_depth = sub_info.from_function_depth(); - let sub_depth = sup_info.from_function_depth(); - assert!(static_eq || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1)); - EdgeKind::FnCallReturn - } - _ if sub_info.is_borrow() || sub_info.is_projection_annotation() => { - // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); - EdgeKind::ContainedIn - } - _ => EdgeKind::Unknown(self.creation, self.reason), - } - } - pub fn many_kind(edge: &Vec, cgx: &CgContext<'_, 'tcx>) -> Vec> { - edge.iter().map(|e| e.kind(cgx)).collect() + pub fn is_blocking(edge: &Vec) -> bool { + edge.iter().any(|e| e.kind.is_blocking()) } pub fn widen(edge: &Vec, top: impl Fn(RegionVid, RegionVid) -> Self, needs_widening: impl Fn(Location) -> bool) -> Vec { let mut new_edge = Vec::new(); let widen_edge: &mut Option<(RegionVid, RegionVid)> = &mut None; for &e in edge { - if e.creation.map(|loc| needs_widening(loc)).unwrap_or_default() { + if e.info.creation.map(|loc| needs_widening(loc)).unwrap_or_default() { match widen_edge { - Some((_, sup)) => *sup = e.sup, - None => *widen_edge = Some((e.sub, e.sup)), + Some((_, sup)) => *sup = e.info.sup, + None => *widen_edge = Some((e.info.sub, e.info.sup)), } } else { if let Some((sub, sup)) = widen_edge.take() { @@ -153,9 +97,9 @@ impl<'tcx> EdgeInfo<'tcx> { } } -impl Display for EdgeInfo<'_> { +impl Display for Edge<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let reason = if let Some(reason) = self.reason { + let reason = if let Some(reason) = self.info.reason { match reason { ConstraintCategory::Return(_) => "return", ConstraintCategory::Yield => "yield", @@ -180,27 +124,103 @@ impl Display for EdgeInfo<'_> { "other" }; let creation = self + .info .creation .map(|c| format!("{c:?}")) .unwrap_or_else(|| "sig".to_string()); - match self.origin { + match self.info.origin { EdgeOrigin::Rustc => write!(f, "{creation} ({reason})"), EdgeOrigin::RustcUniversal => { - assert!(self.reason.is_none() && self.creation.is_none()); + assert!(self.info.reason.is_none() && self.info.creation.is_none()); write!(f, "universal") } EdgeOrigin::Static => { - assert!(self.reason.is_none() && self.creation.is_none()); + assert!(self.info.reason.is_none() && self.info.creation.is_none()); write!(f, "promoted") } EdgeOrigin::Opaque => { - assert!(self.reason.is_none() && self.creation.is_some()); + assert!(self.info.reason.is_none() && self.info.creation.is_some()); write!(f, "{creation} (loop)") } } } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct EdgeInfo<'tcx> { + /// The region which outlives (usually means blocked) + sup: RegionVid, + /// The region which is outlived (usually means blocking) + sub: RegionVid, + pub creation: Option, + pub reason: Option>, + pub origin: EdgeOrigin, +} + +impl<'tcx> EdgeInfo<'tcx> { + pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option, origin: EdgeOrigin) -> Self { + if !matches!(origin, EdgeOrigin::Opaque) { + assert_ne!(sup, sub); + } + Self { + sup, + sub, + creation, + reason: None, + origin, + } + } + #[tracing::instrument(name = "EdgeInfo::to_edge", level = "debug", skip_all, ret)] + pub fn to_edge(self, cgx: &CgContext<'_, 'tcx>) -> Edge<'tcx> { + let (sup_info, sub_info) = (cgx.region_info.map.get(self.sup), cgx.region_info.map.get(self.sub)); + let stmt = self.creation.map(|location| cgx.rp.body().stmt_at(location)); + let term = stmt.and_then(|stmt| stmt.right()).map(|t| &t.kind); + let stmt = stmt.and_then(|stmt| stmt.left()).map(|s| &s.kind); + let kind = match (self.reason, self.origin, stmt, term) { + // (_, EdgeOrigin::Opaque, _, _) => EdgeKind::Opaque, + _ if sup_info.local() && sub_info.universal() && !matches!(self.origin, EdgeOrigin::Opaque) => EdgeKind::UniversalToLocal, + (Some(ConstraintCategory::BoringNoLocation), _, _, Some(TerminatorKind::Call { .. })) if sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0 => + EdgeKind::FnCallImplied, + (Some(ConstraintCategory::Predicate(_)), _, _, _) => { + assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); + assert!(sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0); + EdgeKind::FnCallPredicate + } + (Some(ConstraintCategory::CallArgument(_)), _, _, _) => { + assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); + // Can get a `Self::Static` outlives requirement from a function call + let static_eq = sup_info.is_static() ^ sub_info.is_static(); + let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); + let sup_depth = sub_info.from_function_depth(); + let sub_depth = sup_info.from_function_depth(); + assert!(static_eq || placeholders || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1), + "{sup_info:?} ({})\nand\n{sub_info:?} ({})\n({self:?})", sup_info.from_function_depth(), sub_info.from_function_depth()); + EdgeKind::FnCallArgument + } + (Some(ConstraintCategory::Assignment), _, _, Some(TerminatorKind::Call { .. })) => { + let static_eq = sup_info.is_static() ^ sub_info.is_static(); + // let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); + let sup_depth = sub_info.from_function_depth(); + let sub_depth = sup_info.from_function_depth(); + assert!(static_eq || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1)); + EdgeKind::FnCallReturn + } + _ if sub_info.is_borrow() || sub_info.is_projection_annotation() => { + // assert_ne!(self.origin, EdgeOrigin::Opaque); + // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); + EdgeKind::ContainedIn + } + _ if sub_info.get_place().is_some() && sub_info.get_place() == sup_info.get_borrow_or_projection_local() => { + // assert_ne!(self.origin, EdgeOrigin::Opaque); + // Edge from `_1.1: &mut T` to `AllIn(_1)` + EdgeKind::ContainedIn + } + _ => EdgeKind::Unknown, + }; + Edge { info: self, kind } + } +} + impl<'tcx> From> for EdgeInfo<'tcx> { fn from(c: OutlivesConstraint<'tcx>) -> Self { Self { @@ -214,7 +234,7 @@ impl<'tcx> From> for EdgeInfo<'tcx> { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum EdgeKind<'tcx> { +pub enum EdgeKind { /// An edge from `'a` to `'b` created when /// re-borrowing `_ = &'a mut (*x).0` with `x: &'b mut (_, _)`. ContainedIn, @@ -230,12 +250,15 @@ pub enum EdgeKind<'tcx> { /// An edge from `'a` to `'b` created when /// calling `fn foo<'b>(_) -> &'b mut _` to `r: &'a mut = foo()`. FnCallReturn, - Unknown(Option, Option>), + UniversalToLocal, + /// Created from a loop. + Opaque, + Unknown, } -impl<'tcx> EdgeKind<'tcx> { +impl EdgeKind { pub fn is_blocking(self) -> bool { - !matches!(self, EdgeKind::ContainedIn | EdgeKind::FnCallImplied) + !matches!(self, EdgeKind::ContainedIn | EdgeKind::FnCallImplied | EdgeKind::UniversalToLocal) } pub fn many_blocking(kinds: Vec) -> bool { diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index b4f1bf76eda..1ae4e01b8fe 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -17,7 +17,7 @@ use prusti_rustc_interface::{ }, }; -use crate::coupling_graph::outlives_info::edge::{EdgeInfo, EdgeKind}; +use crate::coupling_graph::outlives_info::edge::{EdgeInfo, Edge as CgEdge}; use super::{triple::Cg}; @@ -25,11 +25,11 @@ use super::{triple::Cg}; pub struct Edge<'tcx> { pub from: RegionVid, pub to: RegionVid, - pub reasons: FxHashSet>>, + pub reasons: FxHashSet>>, } impl<'tcx> Edge<'tcx> { - pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>>) -> Self { + pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>>) -> Self { Self { from, to, reasons } } } @@ -49,7 +49,7 @@ impl<'a, 'tcx> Cg<'a, 'tcx> { &self, sub: RegionVid, start: RegionVid, - mut reasons: FxHashSet>>, + mut reasons: FxHashSet>>, visited: &mut FxHashSet, ) -> Vec> { let mut edges = Vec::new(); @@ -88,7 +88,7 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { } fn edge_style(&'a self, e: &Edge<'tcx>) -> dot::Style { - let is_blocking = e.reasons.iter().all(|e| EdgeKind::many_blocking(EdgeInfo::many_kind(e, self.cgx))); + let is_blocking = e.reasons.iter().any(|e| CgEdge::is_blocking(e)); if is_blocking { dot::Style::Solid } else { @@ -100,7 +100,7 @@ impl<'a, 'b, 'tcx> dot::Labeller<'a, RegionVid, Edge<'tcx>> for Cg<'b, 'tcx> { .reasons .iter() .map(|s| { - let line = s.into_iter().map(|s| s.to_string() + ", ").collect::(); + let line = s.into_iter().map(|s| format!("{s}, ")).collect::(); format!("{}\n", &line[..line.len() - 2]) // `s.len() > 0` }) .collect::(); diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index b5595f626fa..deabc6f45c8 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -79,6 +79,11 @@ pub(crate) fn draw_dots<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph< let mut g = c.get().clone(); g.dot_node_filter = |k| !k.is_unknown_local(); dot::render(&g, &mut graph).unwrap(); + c.seek_after_primary_effect(location); + let mut g = c.get().clone(); + g.location = location.successor_within_block(); + g.dot_node_filter = |k| !k.is_unknown_local(); + dot::render(&g, &mut graph).unwrap(); } else { print_after_loc(c, location, &mut graph); } @@ -191,7 +196,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { statement: &Statement<'tcx>, location: Location, ) { - assert_eq!(state.location, location); + state.location = location; state.reset_ops(); if location.statement_index == 0 { @@ -234,7 +239,6 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { format!("log/coupling/individual/{l}_v{}.dot", state.sum_version()), false, ); - state.location.statement_index += 1; } @@ -246,7 +250,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { location: Location, ) { // println!("Location: {l}"); - assert_eq!(state.location, location); + state.location = location; state.reset_ops(); if location.statement_index == 0 { @@ -305,7 +309,7 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { .values() .chain(self.cgx.sbs.location_map.values()) { - state.graph.remove(r.region); + state.remove(r.region, Some(location)); } state.merge_for_return(location); @@ -318,7 +322,6 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { format!("log/coupling/individual/{l}_v{}.dot", state.sum_version()), false, ); - state.location.statement_index += 1; terminator.edges() } diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index 9900f84caa8..43dc5786d12 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -26,7 +26,7 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{CgContext, outlives_info::edge::{EdgeInfo, EdgeOrigin}}, + coupling_graph::{CgContext, outlives_info::edge::{Edge, EdgeOrigin, EdgeKind}}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, @@ -46,31 +46,32 @@ pub struct Graph<'tcx> { impl<'tcx> Graph<'tcx> { #[tracing::instrument(name = "Graph::outlives", level = "trace", skip(self), ret)] - pub fn outlives(&mut self, c: OutlivesConstraint<'tcx>) -> Option<(RegionVid, RegionVid)> { - self.outlives_inner(vec![c.into()]) - } - #[tracing::instrument(name = "Graph::outlives_placeholder", level = "trace", skip(self), ret)] - pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid, origin: EdgeOrigin) -> Option<(RegionVid, RegionVid)> { - let edge = EdgeInfo::no_reason(sup, sub, None, origin); + pub fn outlives(&mut self, edge: Edge<'tcx>) -> Option<(RegionVid, RegionVid, bool)> { self.outlives_inner(vec![edge]) } + // #[tracing::instrument(name = "Graph::outlives_placeholder", level = "trace", skip(self), ret)] + // pub fn outlives_placeholder(&mut self, sup: RegionVid, sub: RegionVid, origin: EdgeOrigin) -> Option<(RegionVid, RegionVid)> { + // let edge = EdgeInfo::no_reason(sup, sub, None, origin); + // self.outlives_inner(vec![edge]) + // } // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) #[tracing::instrument(name = "Graph::outlives_inner", level = "trace", skip(self), ret)] fn outlives_inner( &mut self, - edge: Vec>, - ) -> Option<(RegionVid, RegionVid)> { + edge: Vec>, + ) -> Option<(RegionVid, RegionVid, bool)> { let (sup, sub) = self.outlives_unwrap_edge(&edge); self.nodes[sup].blocked_by.insert(sub); let blocks = self.nodes[sub].blocks.entry(sup).or_default(); + let is_blocking = edge.iter().any(|edge| edge.kind.is_blocking()); if blocks.insert(edge) { - Some((sup, sub)) + Some((sup, sub, is_blocking)) } else { None } } - fn outlives_unwrap_edge(&mut self, edge: &Vec>) -> (RegionVid, RegionVid) { + fn outlives_unwrap_edge(&mut self, edge: &Vec>) -> (RegionVid, RegionVid) { let (sup, sub) = (edge.last().unwrap().sup(), edge.first().unwrap().sub()); if self.static_regions.contains(&sub) { Self::set_static_region(&self.nodes, &mut self.static_regions, sup); @@ -82,16 +83,16 @@ impl<'tcx> Graph<'tcx> { #[tracing::instrument(name = "Graph::outlives_join", level = "trace", skip(self), ret)] pub(super) fn outlives_join( &mut self, - edge: Vec>, + edge: Vec>, ) -> Option<(RegionVid, RegionVid)> { let (sup, sub) = self.outlives_unwrap_edge(&edge); self.nodes[sup].blocked_by.insert(sub); let blocks = self.nodes[sub].blocks.entry(sup).or_default(); - if blocks.iter().any(|other| EdgeInfo::generalized_by(&edge, other)) { + if blocks.iter().any(|other| Edge::generalized_by(&edge, other)) { None } else { - blocks.retain(|other| !EdgeInfo::generalized_by(other, &edge)); + blocks.retain(|other| !Edge::generalized_by(other, &edge)); if blocks.insert(edge) { Some((sup, sub)) } else { @@ -124,12 +125,12 @@ impl<'tcx> Graph<'tcx> { [r].into_iter().chain(blocked_by.iter().flat_map(|(blocked_by, _)| self.kill(*blocked_by))).collect() } - #[tracing::instrument(name = "Graph::remove", level = "trace")] + #[tracing::instrument(name = "Graph::remove", level = "trace", ret)] /// Remove node from graph and rejoin all blockers and blocked by. // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). - pub fn remove(&mut self, r: RegionVid) -> Option<(RegionVid, Vec<(RegionVid, RegionVid)>)> { + pub fn remove(&mut self, r: RegionVid) -> Option<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { let (blocks, blocked_by) = self.remove_all_edges(r); let changed = !(blocks.is_empty() && blocked_by.is_empty()); let mut rejoins = Vec::new(); @@ -171,7 +172,7 @@ impl<'tcx> Graph<'tcx> { } #[tracing::instrument(name = "Graph::remove_many", level = "trace")] - pub fn remove_many(&mut self, rs: &FxHashSet) -> Vec<(RegionVid, Vec<(RegionVid, RegionVid)>)> { + pub fn remove_many(&mut self, rs: &FxHashSet) -> Vec<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { for &r in rs { if self.predecessors(r).iter().all(|pre| rs.contains(pre)) || self.successors(r).iter().all(|suc| rs.contains(suc)) { self.static_regions.remove(&r); @@ -191,8 +192,8 @@ impl<'tcx> Graph<'tcx> { &mut self, r: RegionVid, ) -> ( - FxHashMap>>>, - FxHashMap>>>, + FxHashMap>>>, + FxHashMap>>>, ) { let blocks = std::mem::replace(&mut self.nodes[r].blocks, FxHashMap::default()); for block in blocks.keys() { @@ -229,7 +230,7 @@ impl<'tcx> Graph<'tcx> { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Node<'tcx> { - pub blocks: FxHashMap>>>, + pub blocks: FxHashMap>>>, pub blocked_by: FxHashSet, } @@ -240,4 +241,9 @@ impl<'tcx> Node<'tcx> { blocked_by: FxHashSet::default(), } } + pub fn true_edges(&self) -> Vec { + self.blocks.iter().filter(|(_, edges)| edges.iter().any( + |edge| Edge::is_blocking(edge) + )).map(|(&r, _)| r).collect() + } } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index ea48868d11d..3fef1a908f3 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -15,7 +15,7 @@ use crate::{ free_pcs::{ CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, }, - utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency, outlives_info::edge::{EdgeInfo, EdgeOrigin}}, + utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency, outlives_info::edge::{EdgeInfo, EdgeOrigin, Edge}}, }; use super::{graph::Graph, triple::Cg}; @@ -28,7 +28,7 @@ impl JoinSemiLattice for Cg<'_, '_> { assert!(*version < 20, "{:?} -> {:?}", other.location, self.location); let loop_head = self.cgx.loops.loop_head_of(self.location.block); - let top = |sup, sub| EdgeInfo::no_reason(sup, sub, Some(self.location), EdgeOrigin::Opaque); + let top = |sup, sub| EdgeInfo::no_reason(sup, sub, Some(self.location), EdgeOrigin::Opaque).to_edge(self.cgx); let needs_widening = |loc: Location| loop_head.map(|l| self.cgx.loops.in_loop(loc.block, l)).unwrap_or_default(); // Are we looping back into the loop head from within the loop? let loop_into = loop_head.map(|l| self.cgx.loops.in_loop(other.location.block, l)); @@ -36,7 +36,7 @@ impl JoinSemiLattice for Cg<'_, '_> { for (_, node) in other.graph.all_nodes() { for (_, edges) in node.blocks.iter() { for edge in edges { - let edge = EdgeInfo::widen(edge, top, needs_widening); + let edge = Edge::widen(edge, top, needs_widening); let was_new = self.graph.outlives_join(edge); changed = changed || was_new.is_some(); } @@ -50,14 +50,15 @@ impl JoinSemiLattice for Cg<'_, '_> { } impl Cg<'_, '_> { - #[tracing::instrument(name = "Cg::bridge", level = "debug", ret)] + #[tracing::instrument(name = "Cg::bridge", level = "debug", fields(self.graph = ?self.graph, other.graph = ?self.graph), ret)] pub fn bridge(&self, other: &Self) -> Vec { other.graph.all_nodes().flat_map(|(sub, node)| - node.blocks - .keys() - .filter(move |sup| !self.graph.nodes[sub].blocks.contains_key(*sup)) - .copied() - .flat_map(move |sup| self.outlives_to_block((sup, sub))) + node.true_edges() + .into_iter() + .filter(move |sup| !self.graph.nodes[sub].blocks.contains_key(sup)) + .map(move |sup| + self.outlives_to_block((sup, sub, true)).unwrap() + ) .map(|block| CouplingOp::Add(block)) ).collect() } diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index b4511168d05..376627cf3f7 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -31,7 +31,7 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}, outlives_info::edge::EdgeOrigin}, + coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}, outlives_info::edge::{EdgeOrigin, EdgeInfo}}, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, CapabilityKind, }, @@ -147,7 +147,8 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } pub(crate) fn outlives(&mut self, c: OutlivesConstraint<'tcx>) { - let new = self.graph.outlives(c); + let edge = EdgeInfo::from(c).to_edge(self.cgx); + let new = self.graph.outlives(edge); self.outlives_op(new) } pub(crate) fn outlives_static(&mut self, r: RegionVid, origin: EdgeOrigin) { @@ -158,25 +159,26 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { self.outlives_placeholder(r, static_region, origin) } pub(crate) fn outlives_placeholder(&mut self, r: RegionVid, placeholder: RegionVid, origin: EdgeOrigin) { - let new = self.graph.outlives_placeholder(r, placeholder, origin); + let edge = EdgeInfo::no_reason(r, placeholder, None, origin).to_edge(self.cgx); + // let new = self.graph.outlives_placeholder(r, placeholder, origin); + let new = self.graph.outlives(edge); self.outlives_op(new) } #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] - fn outlives_op(&mut self, op: Option<(RegionVid, RegionVid)>) { + fn outlives_op(&mut self, op: Option<(RegionVid, RegionVid, bool)>) { if let Some(block) = op.and_then(|c| self.outlives_to_block(c)) { self.couplings.push(CouplingOp::Add(block)); } } // TODO: remove - pub(crate) fn outlives_to_block(&self, op: (RegionVid, RegionVid)) -> Option { - let (sup, sub) = op; - let (sup_info, sub_info) = (self.cgx.region_info.map.get(sup), self.cgx.region_info.map.get(sub)); - if sub_info.is_borrow() || (sub_info.universal() && sup_info.local()) { - None - } else { + pub(crate) fn outlives_to_block(&self, op: (RegionVid, RegionVid, bool)) -> Option { + let (sup, sub, is_blocking) = op; + if is_blocking { let waiting_to_activate = self.graph.inactive_loans.contains(&sup); Some(Block { sup, sub, waiting_to_activate, }) + } else { + None } } #[tracing::instrument(name = "Cg::remove", level = "debug", skip(self))] @@ -187,7 +189,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } } #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] - fn remove_op(&mut self, op: (RegionVid, Vec<(RegionVid, RegionVid)>)) { + fn remove_op(&mut self, op: (RegionVid, Vec<(RegionVid, RegionVid, bool)>)) { let rejoins = op.1.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); self.couplings.push(CouplingOp::Remove(op.0, rejoins)); } From 58fe3c5603c739ea9dedb62df62615802ca51eec 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 51/58] Add weakens before write --- mir-state-analysis/src/free_pcs/impl/triple.rs | 5 +++-- mir-state-analysis/src/free_pcs/impl/update.rs | 9 ++++++--- 2 files changed, 9 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 6c400653237..07e0eda5866 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -83,13 +83,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 ef7b1a55533..f4862dd4363 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -61,9 +61,12 @@ 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 bound = self.bound(place); - assert!(kind.minimum(bound).unwrap() >= cap, "{place:?}, have: {kind:?}, bound: {bound:?}, want: {cap:?}"); + let kind = cp.insert(place, cap).unwrap(); + let bounded = self.bound(place).minimum(kind).unwrap(); + assert!(bounded >= cap); + if bounded != cap && matches!(cap, CapabilityKind::Write) { + self.repackings.push(RepackOp::Weaken(place, kind, cap)); + } } pub(crate) fn ensures_unalloc(&mut self, local: Local) { From c700a96911e3b7dde4e86192c961f4dada72bf8f 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 52/58] 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 f4862dd4363..0d03842ee9a 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -61,12 +61,16 @@ 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)); + } + }; let bounded = self.bound(place).minimum(kind).unwrap(); assert!(bounded >= cap); - if bounded != cap && matches!(cap, CapabilityKind::Write) { - self.repackings.push(RepackOp::Weaken(place, kind, cap)); - } } pub(crate) fn ensures_unalloc(&mut self, local: Local) { From 741284e72d62a49aabed6fc5ab79fe5aeea24f9c 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 53/58] 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 | 4 ++- .../src/free_pcs/impl/triple.rs | 10 +++++-- .../src/free_pcs/results/cursor.rs | 6 ++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index 0883224e7c4..5ed75e01201 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -26,6 +26,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, @@ -41,13 +42,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 @@ -60,13 +71,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 7e5210c9002..83322455713 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 00f5973b8de..156631cf73b 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.rs @@ -30,6 +30,7 @@ impl FpcsBound<'_> { 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>, pub bound: RefCell>, @@ -40,6 +41,7 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { Self { repacker, bottom: true, + apply_pre_effect: true, summary, repackings: Vec::new(), bound: FpcsBound::empty(false), @@ -56,7 +58,7 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { impl Clone for Fpcs<'_, '_> { fn clone(&self) -> Self { let expect_bound = self.bound.borrow().1; - Self { repacker: self.repacker, bottom: self.bottom, summary: self.summary.clone(), repackings: self.repackings.clone(), bound: FpcsBound::empty(expect_bound) } + Self { repacker: self.repacker, bottom: self.bottom, apply_pre_effect: self.apply_pre_effect, summary: self.summary.clone(), repackings: self.repackings.clone(), bound: FpcsBound::empty(expect_bound) } } } impl PartialEq for Fpcs<'_, '_> { diff --git a/mir-state-analysis/src/free_pcs/impl/triple.rs b/mir-state-analysis/src/free_pcs/impl/triple.rs index 07e0eda5866..dcfe3a83e47 100644 --- a/mir-state-analysis/src/free_pcs/impl/triple.rs +++ b/mir-state-analysis/src/free_pcs/impl/triple.rs @@ -32,7 +32,10 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { #[tracing::instrument(name = "Fpcs::visit_statement", level = "debug", skip(self))] 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)) => { @@ -67,7 +70,10 @@ impl<'tcx> Visitor<'tcx> for Fpcs<'_, 'tcx> { #[tracing::instrument(name = "Fpcs::visit_terminator", level = "debug", skip(self))] 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 0285b3fc7d6..e974d0f82e6 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -75,12 +75,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> { @@ -106,6 +109,7 @@ impl<'mir, 'tcx> FreePcsAnalysis<'mir, 'tcx> { }, state: to.summary.clone(), repacks: state.summary.bridge(&to.summary, rp), + repacks_middle: Vec::new(), } }) .collect(); @@ -139,6 +143,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 348623d845bc6698a9da1e177385ebc992fbeae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Wed, 25 Oct 2023 15:08:46 +0100 Subject: [PATCH 54/58] Update checker with new fpcs --- .../src/coupling_graph/check/checker.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index cbd4eb7291f..072a2d9277d 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -58,6 +58,7 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre fpcs_cursor.set_bound_non_empty(); let mut fpcs = Fpcs { summary: fpcs_cursor.initial_state().clone(), + apply_pre_effect: false, bottom: false, repackings: Vec::new(), repacker: rp, @@ -89,7 +90,7 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre fpcs_cursor.unset_bound(); // Repacks - for op in fpcs_after.repacks { + for op in fpcs_after.repacks_middle { op.update_free(&mut fpcs.summary, false, rp); } // Couplings bound set @@ -97,8 +98,17 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // 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); + // Repacks + for op in fpcs_after.repacks { + op.update_free(&mut fpcs.summary, false, rp); + } + // Statement post assert!(fpcs.repackings.is_empty()); + fpcs.apply_pre_effect = false; fpcs.visit_terminator(data.terminator(), loc); assert!(fpcs.repackings.is_empty()); // Consistency @@ -169,7 +179,7 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { fpcs_cursor.unset_bound(); // Repacks - for op in fpcs_after.repacks { + for op in fpcs_after.repacks_middle { op.update_free(&mut fpcs.summary, false, rp); } // Couplings bound set @@ -177,8 +187,18 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Extend lifetimes (safe since we unset it later) // 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 assert!(fpcs.repackings.is_empty()); + fpcs.apply_pre_effect = false; fpcs.visit_statement(stmt, loc); assert!(fpcs.repackings.is_empty()); // Consistency From 38116a18f17027691a61cdedd85468c624fd4e79 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 55/58] fmt and clippy --- analysis/tests/utils.rs | 12 +- .../src/coupling_graph/check/checker.rs | 123 +++++++++---- .../src/coupling_graph/check/consistency.rs | 2 +- .../src/coupling_graph/context/mod.rs | 5 +- .../context/outlives_info/edge.rs | 70 ++++++-- .../coupling_graph/context/region_info/map.rs | 145 ++++++++++------ .../coupling_graph/context/region_info/mod.rs | 162 +++++++++++++----- .../src/coupling_graph/impl/dot.rs | 16 +- .../src/coupling_graph/impl/engine.rs | 68 ++++++-- .../src/coupling_graph/impl/graph.rs | 68 ++++++-- .../coupling_graph/impl/join_semi_lattice.rs | 46 +++-- .../src/coupling_graph/impl/triple.rs | 56 ++++-- mir-state-analysis/src/coupling_graph/mod.rs | 2 +- .../src/coupling_graph/results/coupling.rs | 23 ++- .../src/coupling_graph/results/cursor.rs | 3 +- .../src/free_pcs/check/checker.rs | 3 +- .../src/free_pcs/check/consistency.rs | 6 +- .../src/free_pcs/impl/engine.rs | 41 ++++- mir-state-analysis/src/free_pcs/impl/fpcs.rs | 27 ++- mir-state-analysis/src/free_pcs/impl/place.rs | 3 +- .../src/free_pcs/impl/update.rs | 7 +- .../src/free_pcs/results/cursor.rs | 4 +- mir-state-analysis/src/lib.rs | 15 +- mir-state-analysis/src/loop/mod.rs | 5 +- mir-state-analysis/src/utils/const/mod.rs | 64 +++++-- mir-state-analysis/src/utils/place.rs | 3 +- mir-state-analysis/src/utils/repacker.rs | 39 +++-- prusti-interface/src/environment/body.rs | 10 +- .../src/environment/mir_storage.rs | 10 +- prusti-interface/src/environment/procedure.rs | 6 +- prusti/src/callbacks.rs | 16 +- 31 files changed, 759 insertions(+), 301 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/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index 072a2d9277d..39b7ea73309 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -6,22 +6,29 @@ use prusti_rustc_interface::{ ast::Mutability, - index::IndexVec, + borrowck::borrow_set::BorrowData, data_structures::fx::{FxHashMap, FxHashSet}, + index::IndexVec, middle::{ - mir::{visit::Visitor, Location, ProjectionElem, BorrowKind, Statement}, + mir::{visit::Visitor, BorrowKind, Location, ProjectionElem, Statement}, ty::RegionVid, }, - borrowck::borrow_set::BorrowData, }; use crate::{ + coupling_graph::{ + coupling::{Block, CouplingOp}, + cursor::CgAnalysis, + graph::Graph, + region_info::map::RegionKind, + CgContext, + }, free_pcs::{ - CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, FpcsBound, + consistency::CapabilityConsistency, CapabilityKind, CapabilityLocal, CapabilitySummary, + Fpcs, FpcsBound, FreePcsAnalysis, RepackOp, }, - utils::{PlaceRepacker, Place}, coupling_graph::{coupling::{CouplingOp, Block}, cursor::CgAnalysis, CgContext, graph::Graph, region_info::map::RegionKind}, + utils::{Place, PlaceRepacker}, }; -use crate::free_pcs::consistency::CapabilityConsistency; #[derive(Clone)] struct CouplingState<'a, 'tcx> { @@ -33,12 +40,17 @@ struct CouplingState<'a, 'tcx> { impl<'a, 'tcx> std::fmt::Debug for CouplingState<'a, 'tcx> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_map().entries(self.blocks.iter_enumerated()).finish() + f.debug_map() + .entries(self.blocks.iter_enumerated()) + .finish() } } #[tracing::instrument(name = "cg::check", level = "debug", skip(cg, fpcs_cursor))] -pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: FreePcsAnalysis<'_, 'tcx>) { +pub(crate) fn check<'tcx>( + mut cg: CgAnalysis<'_, '_, 'tcx>, + mut fpcs_cursor: FreePcsAnalysis<'_, 'tcx>, +) { let cgx = cg.cgx(); let rp: PlaceRepacker<'_, '_> = cgx.rp; let body = rp.body(); @@ -48,7 +60,10 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre let mut cg_state = CouplingState { blocks: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), - blocked_by: IndexVec::from_elem_n(FxHashSet::default(), cgx.region_info.map.region_len()), + blocked_by: IndexVec::from_elem_n( + FxHashSet::default(), + cgx.region_info.map.region_len(), + ), waiting_to_activate: FxHashSet::default(), cgx, }; @@ -83,7 +98,8 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre c.update_free(&mut cg_state, false); } - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + let bound: Box) -> CapabilityKind> = + Box::new(cg_state.mk_capability_upper_bound()); fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); let fpcs_after = fpcs_cursor.next(loc); assert_eq!(fpcs_after.location, loc); @@ -94,7 +110,8 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre op.update_free(&mut fpcs.summary, false, rp); } // Couplings bound set - let bound: Box) -> CapabilityKind> = Box::new(cg_state.mk_capability_upper_bound()); + let bound: Box) -> CapabilityKind> = + Box::new(cg_state.mk_capability_upper_bound()); fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Consistency fpcs.summary.consistency_check(rp); @@ -146,7 +163,11 @@ pub(crate) fn check<'tcx>(mut cg: CgAnalysis<'_, '_, 'tcx>, mut fpcs_cursor: Fre for op in cg_succ.couplings { op.update_free(&mut cg_from, false); } - assert!(cg_from.compare(&cg_succ.state), "{loc:?} -> {:?}", cg_succ.location); + assert!( + cg_from.compare(&cg_succ.state), + "{loc:?} -> {:?}", + cg_succ.location + ); } } } @@ -162,8 +183,19 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { self.waiting_to_activate = graph.inactive_loans.clone(); } - #[tracing::instrument(name = "CouplingState::check_location", level = "trace", skip(self, stmt, cg, fpcs, fpcs_cursor))] - fn check_location(&mut self, loc: Location, stmt: &Statement<'tcx>, fpcs: &mut Fpcs<'_, 'tcx>, cg: &mut CgAnalysis<'_, '_, 'tcx>, fpcs_cursor: &mut FreePcsAnalysis<'_, 'tcx>) { + #[tracing::instrument( + name = "CouplingState::check_location", + level = "trace", + skip(self, stmt, cg, fpcs, fpcs_cursor) + )] + fn check_location( + &mut self, + loc: Location, + stmt: &Statement<'tcx>, + fpcs: &mut Fpcs<'_, 'tcx>, + cg: &mut CgAnalysis<'_, '_, 'tcx>, + fpcs_cursor: &mut FreePcsAnalysis<'_, 'tcx>, + ) { let rp = self.cgx.rp; let cg_before = cg.before_next(loc); @@ -172,7 +204,8 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { c.update_free(self, false); } - let bound: Box) -> CapabilityKind> = Box::new(self.mk_capability_upper_bound()); + let bound: Box) -> CapabilityKind> = + Box::new(self.mk_capability_upper_bound()); fpcs_cursor.set_bound(unsafe { std::mem::transmute(bound) }); let fpcs_after = fpcs_cursor.next(loc); assert_eq!(fpcs_after.location, loc); @@ -183,9 +216,10 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { op.update_free(&mut fpcs.summary, false, rp); } // Couplings bound set - let bound: Box) -> CapabilityKind> = Box::new(self.mk_capability_upper_bound()); + let bound: Box) -> CapabilityKind> = + Box::new(self.mk_capability_upper_bound()); fpcs.bound.borrow_mut().0 = Some(unsafe { std::mem::transmute(bound) }); // Extend lifetimes (safe since we unset it later) - // Consistency + // Consistency fpcs.summary.consistency_check(rp); // Statement pre assert!(fpcs.repackings.is_empty()); @@ -265,29 +299,43 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { fn active_borrows(&self) -> impl Iterator> + '_ { self.blocked_by .iter_enumerated() - .filter(|(region, blockers)| !blockers.is_empty() && !self.waiting_to_activate.contains(region)) + .filter(|(region, blockers)| { + !blockers.is_empty() && !self.waiting_to_activate.contains(region) + }) .flat_map(move |(region, _)| self.cgx.region_info.map.get(region).get_borrow()) } fn has_real_blockers(&self, region: RegionVid) -> bool { let scc = self.calculate_scc(region); let fn_region = self.cgx.region_info.function_region; - scc.iter().any(|r| self.blocked_by[*r].iter().any(|blocker| !scc.contains(blocker) && *blocker != fn_region)) + scc.iter().any(|r| { + self.blocked_by[*r] + .iter() + .any(|blocker| !scc.contains(blocker) && *blocker != fn_region) + }) // self.blocked_by[region].iter().copied().any(|r| { // let r = self.cgx.region_info.map.get(r); // !r.universal() && !r.is_borrow() // }) } fn calculate_scc(&self, region: RegionVid) -> FxHashSet { - let mut visited_out: FxHashSet<_> = [region, self.cgx.region_info.static_region].into_iter().collect(); + let mut visited_out: FxHashSet<_> = [region, self.cgx.region_info.static_region] + .into_iter() + .collect(); let mut stack = vec![region, self.cgx.region_info.static_region]; while let Some(next) = stack.pop() { - let blocks = self.blocks[next].iter().copied().filter(|r| visited_out.insert(*r)); + let blocks = self.blocks[next] + .iter() + .copied() + .filter(|r| visited_out.insert(*r)); stack.extend(blocks); } let mut visited_in: FxHashSet<_> = [region].into_iter().collect(); let mut stack = vec![region]; while let Some(next) = stack.pop() { - let blocked_by = self.blocked_by[next].iter().copied().filter(|r| visited_in.insert(*r)); + let blocked_by = self.blocked_by[next] + .iter() + .copied() + .filter(|r| visited_in.insert(*r)); stack.extend(blocked_by); } visited_out.intersection(&visited_in).copied().collect() @@ -295,10 +343,19 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { } #[tracing::instrument(name = "upper_bound_borrow", level = "trace", skip(rp), ret)] -fn upper_bound_borrow<'tcx>(place: Place<'tcx>, borrow: &BorrowData<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> Option { +fn upper_bound_borrow<'tcx>( + place: Place<'tcx>, + borrow: &BorrowData<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, +) -> Option { let borrowed = borrow.borrowed_place.into(); place.partial_cmp(borrowed).map(|cmp| { - let lower_bound = if cmp.is_prefix() && borrowed.projection_tys(rp).skip(place.projection.len()).any(|(ty, _)| ty.ty.is_any_ptr()) { + let lower_bound = if cmp.is_prefix() + && borrowed + .projection_tys(rp) + .skip(place.projection.len()) + .any(|(ty, _)| ty.ty.is_any_ptr()) + { CapabilityKind::Write } else { CapabilityKind::None @@ -315,11 +372,7 @@ fn upper_bound_borrow<'tcx>(place: Place<'tcx>, borrow: &BorrowData<'tcx>, rp: P impl CouplingOp { #[tracing::instrument(name = "CouplingOp::update_free", level = "trace")] - fn update_free<'tcx>( - &self, - cg_state: &mut CouplingState, - is_cleanup: bool, - ) { + fn update_free<'tcx>(&self, cg_state: &mut CouplingState, is_cleanup: bool) { match self { CouplingOp::Add(block) => block.update_free(cg_state, is_cleanup), CouplingOp::Remove(remove, new_blocks) => { @@ -347,12 +400,12 @@ impl CouplingOp { } impl Block { - fn update_free<'tcx>( - self, - cg_state: &mut CouplingState, - is_cleanup: bool, - ) { - let Block { sup, sub, waiting_to_activate } = self; + fn update_free<'tcx>(self, cg_state: &mut CouplingState, is_cleanup: bool) { + let Block { + sup, + sub, + waiting_to_activate, + } = self; assert!(!cg_state.cgx.region_info.map.get(sub).is_borrow()); if waiting_to_activate && cg_state.waiting_to_activate.insert(sup) { assert!(cg_state.blocked_by[sup].is_empty()); diff --git a/mir-state-analysis/src/coupling_graph/check/consistency.rs b/mir-state-analysis/src/coupling_graph/check/consistency.rs index 919e06f6f06..cefaa488203 100644 --- a/mir-state-analysis/src/coupling_graph/check/consistency.rs +++ b/mir-state-analysis/src/coupling_graph/check/consistency.rs @@ -4,7 +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/. -use crate::coupling_graph::{CgContext, graph::Graph, triple::Cg}; +use crate::coupling_graph::{graph::Graph, triple::Cg, CgContext}; pub trait CouplingConsistency<'tcx> { fn consistency_check(&self, cgx: &CgContext<'_, 'tcx>) -> Option; diff --git a/mir-state-analysis/src/coupling_graph/context/mod.rs b/mir-state-analysis/src/coupling_graph/context/mod.rs index 32be0f0ab05..bbc702dfeab 100644 --- a/mir-state-analysis/src/coupling_graph/context/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/mod.rs @@ -7,8 +7,7 @@ use std::{cell::RefCell, fmt}; use self::{ - outlives_info::OutlivesInfo, region_info::RegionInfo, - shared_borrow_set::SharedBorrowSet, + outlives_info::OutlivesInfo, region_info::RegionInfo, shared_borrow_set::SharedBorrowSet, }; use crate::{ r#loop::LoopAnalysis, @@ -21,7 +20,7 @@ use prusti_rustc_interface::{ dataflow::{Analysis, ResultsCursor}, index::IndexVec, middle::{ - mir::{Body, Location, RETURN_PLACE, Promoted}, + mir::{Body, Location, Promoted, RETURN_PLACE}, ty::{RegionVid, TyCtxt}, }, }; diff --git a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs index b04b96700a1..7bf7ec1bedd 100644 --- a/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs +++ b/mir-state-analysis/src/coupling_graph/context/outlives_info/edge.rs @@ -20,7 +20,10 @@ use prusti_rustc_interface::{ dataflow::fmt::DebugWithContext, index::{bit_set::BitSet, IndexVec}, middle::{ - mir::{BasicBlock, ConstraintCategory, Local, Location, Operand, RETURN_PLACE, TerminatorKind, StatementKind, Rvalue}, + mir::{ + BasicBlock, ConstraintCategory, Local, Location, Operand, Rvalue, StatementKind, + TerminatorKind, RETURN_PLACE, + }, ty::{RegionVid, TyKind}, }, }; @@ -55,11 +58,19 @@ impl<'tcx> Edge<'tcx> { edge.iter().any(|e| e.kind.is_blocking()) } - pub fn widen(edge: &Vec, top: impl Fn(RegionVid, RegionVid) -> Self, needs_widening: impl Fn(Location) -> bool) -> Vec { + pub fn widen( + edge: &Vec, + top: impl Fn(RegionVid, RegionVid) -> Self, + needs_widening: impl Fn(Location) -> bool, + ) -> Vec { let mut new_edge = Vec::new(); let widen_edge: &mut Option<(RegionVid, RegionVid)> = &mut None; for &e in edge { - if e.info.creation.map(|loc| needs_widening(loc)).unwrap_or_default() { + if e.info + .creation + .map(|loc| needs_widening(loc)) + .unwrap_or_default() + { match widen_edge { Some((_, sup)) => *sup = e.info.sup, None => *widen_edge = Some((e.info.sub, e.info.sup)), @@ -158,7 +169,12 @@ pub struct EdgeInfo<'tcx> { } impl<'tcx> EdgeInfo<'tcx> { - pub fn no_reason(sup: RegionVid, sub: RegionVid, creation: Option, origin: EdgeOrigin) -> Self { + pub fn no_reason( + sup: RegionVid, + sub: RegionVid, + creation: Option, + origin: EdgeOrigin, + ) -> Self { if !matches!(origin, EdgeOrigin::Opaque) { assert_ne!(sup, sub); } @@ -172,15 +188,31 @@ impl<'tcx> EdgeInfo<'tcx> { } #[tracing::instrument(name = "EdgeInfo::to_edge", level = "debug", skip_all, ret)] pub fn to_edge(self, cgx: &CgContext<'_, 'tcx>) -> Edge<'tcx> { - let (sup_info, sub_info) = (cgx.region_info.map.get(self.sup), cgx.region_info.map.get(self.sub)); - let stmt = self.creation.map(|location| cgx.rp.body().stmt_at(location)); + let (sup_info, sub_info) = ( + cgx.region_info.map.get(self.sup), + cgx.region_info.map.get(self.sub), + ); + let stmt = self + .creation + .map(|location| cgx.rp.body().stmt_at(location)); let term = stmt.and_then(|stmt| stmt.right()).map(|t| &t.kind); let stmt = stmt.and_then(|stmt| stmt.left()).map(|s| &s.kind); let kind = match (self.reason, self.origin, stmt, term) { // (_, EdgeOrigin::Opaque, _, _) => EdgeKind::Opaque, - _ if sup_info.local() && sub_info.universal() && !matches!(self.origin, EdgeOrigin::Opaque) => EdgeKind::UniversalToLocal, - (Some(ConstraintCategory::BoringNoLocation), _, _, Some(TerminatorKind::Call { .. })) if sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0 => - EdgeKind::FnCallImplied, + _ if sup_info.local() + && sub_info.universal() + && !matches!(self.origin, EdgeOrigin::Opaque) => + { + EdgeKind::UniversalToLocal + } + ( + Some(ConstraintCategory::BoringNoLocation), + _, + _, + Some(TerminatorKind::Call { .. }), + ) if sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0 => { + EdgeKind::FnCallImplied + } (Some(ConstraintCategory::Predicate(_)), _, _, _) => { assert!(matches!(term.unwrap(), TerminatorKind::Call { .. })); assert!(sup_info.from_function_depth() > 0 && sub_info.from_function_depth() > 0); @@ -193,8 +225,15 @@ impl<'tcx> EdgeInfo<'tcx> { let placeholders = sup_info.is_placeholder() && sub_info.is_placeholder(); let sup_depth = sub_info.from_function_depth(); let sub_depth = sup_info.from_function_depth(); - assert!(static_eq || placeholders || (sup_depth + 1 == sub_depth) || (sup_depth == sub_depth + 1), - "{sup_info:?} ({})\nand\n{sub_info:?} ({})\n({self:?})", sup_info.from_function_depth(), sub_info.from_function_depth()); + assert!( + static_eq + || placeholders + || (sup_depth + 1 == sub_depth) + || (sup_depth == sub_depth + 1), + "{sup_info:?} ({})\nand\n{sub_info:?} ({})\n({self:?})", + sup_info.from_function_depth(), + sub_info.from_function_depth() + ); EdgeKind::FnCallArgument } (Some(ConstraintCategory::Assignment), _, _, Some(TerminatorKind::Call { .. })) => { @@ -210,7 +249,9 @@ impl<'tcx> EdgeInfo<'tcx> { // assert_eq!(sup_info.get_place().unwrap(), sub_info.get_borrow_or_projection_local().unwrap()); EdgeKind::ContainedIn } - _ if sub_info.get_place().is_some() && sub_info.get_place() == sup_info.get_borrow_or_projection_local() => { + _ if sub_info.get_place().is_some() + && sub_info.get_place() == sup_info.get_borrow_or_projection_local() => + { // assert_ne!(self.origin, EdgeOrigin::Opaque); // Edge from `_1.1: &mut T` to `AllIn(_1)` EdgeKind::ContainedIn @@ -258,7 +299,10 @@ pub enum EdgeKind { impl EdgeKind { pub fn is_blocking(self) -> bool { - !matches!(self, EdgeKind::ContainedIn | EdgeKind::FnCallImplied | EdgeKind::UniversalToLocal) + !matches!( + self, + EdgeKind::ContainedIn | EdgeKind::FnCallImplied | EdgeKind::UniversalToLocal + ) } pub fn many_blocking(kinds: Vec) -> bool { diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs index af8622b9ef4..b4c709f3d06 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/map.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/map.rs @@ -20,10 +20,12 @@ use prusti_rustc_interface::{ NllRegionVariableOrigin, RegionVariableOrigin, }, middle::{ - mir::{Body, BorrowKind, Local, Location, Operand, RETURN_PLACE, PlaceRef, PlaceElem, Promoted}, + mir::{ + Body, BorrowKind, Local, Location, Operand, PlaceElem, PlaceRef, Promoted, RETURN_PLACE, + }, ty::{BoundRegionKind, PlaceholderRegion, RegionVid, Ty, TyCtxt, TyKind}, }, - span::{Span, Symbol, def_id::DefId}, + span::{def_id::DefId, Span, Symbol}, }; use crate::{ @@ -47,17 +49,23 @@ pub struct GenericArgRegion<'tcx> { impl<'tcx> GenericArgRegion<'tcx> { fn to_string(gars: &Vec, cgx: &CgContext<'_, 'tcx>) -> String { - gars.iter().map(|gar| { - let tcx = cgx.rp.tcx(); - let generic = tcx.generics_of(gar.did).param_at(gar.gen_idx, tcx); - assert_eq!(generic.kind.is_ty_or_const(), gar.full_ty.is_some()); - let ty = gar.full_ty.map(|ty| format!(" = {ty}")).unwrap_or_default(); - if tcx.is_closure(gar.did) { - format!(" closure::<{}{ty}>", generic.name.as_str()) - } else { - format!(" {}::<{}{ty}>", tcx.item_name(gar.did).as_str(), generic.name.as_str()) - } - }).collect() + gars.iter() + .map(|gar| { + let tcx = cgx.rp.tcx(); + let generic = tcx.generics_of(gar.did).param_at(gar.gen_idx, tcx); + assert_eq!(generic.kind.is_ty_or_const(), gar.full_ty.is_some()); + let ty = gar.full_ty.map(|ty| format!(" = {ty}")).unwrap_or_default(); + if tcx.is_closure(gar.did) { + format!(" closure::<{}{ty}>", generic.name.as_str()) + } else { + format!( + " {}::<{}{ty}>", + tcx.item_name(gar.did).as_str(), + generic.name.as_str() + ) + } + }) + .collect() } } @@ -131,7 +139,6 @@ pub enum RegionKind<'tcx> { // AggregateGeneric(DefId, usize, Option>), // FnGeneric(DefId, usize, Option>), - EarlyBound(Symbol), LateBound { // span: Span, @@ -193,13 +200,17 @@ impl<'tcx> RegionKind<'tcx> { } pub fn promoted(&self) -> bool { - matches!(self, - Self::Place { promoted: Promote::Promoted(..), .. } - | Self::Borrow(_, Promote::Promoted(..)) - | Self::UnusedReturnBug(Promote::Promoted(..)) - | Self::OtherAnnotation(OtherAnnotationKind::RvalueTy(Promote::Promoted(..)), _, _) - | Self::ConstRef(ConstRegionKind::Const(Promote::Promoted(..)), _) - | Self::ConstRef(ConstRegionKind::Aggregate(Promote::Promoted(..)), _)) + matches!( + self, + Self::Place { + promoted: Promote::Promoted(..), + .. + } | Self::Borrow(_, Promote::Promoted(..)) + | Self::UnusedReturnBug(Promote::Promoted(..)) + | Self::OtherAnnotation(OtherAnnotationKind::RvalueTy(Promote::Promoted(..)), _, _) + | Self::ConstRef(ConstRegionKind::Const(Promote::Promoted(..)), _) + | Self::ConstRef(ConstRegionKind::Aggregate(Promote::Promoted(..)), _) + ) } pub fn unknown(&self) -> bool { matches!(self, Self::UnknownUniversal | Self::UnknownLocal) @@ -217,30 +228,33 @@ impl<'tcx> RegionKind<'tcx> { pub fn from_function_depth(&self) -> usize { match self { - Self::LateBound { ctime: LateBoundRegionConversionTime::FnCall, .. } => 1, - Self::ConstRef(_, fn_generic) | - Self::Place { fn_generic, .. } | - Self::ProjectionAnnotation(_, _, fn_generic) | - Self::OtherAnnotation(_, _, fn_generic) => fn_generic.len(), + Self::LateBound { + ctime: LateBoundRegionConversionTime::FnCall, + .. + } => 1, + Self::ConstRef(_, fn_generic) + | Self::Place { fn_generic, .. } + | Self::ProjectionAnnotation(_, _, fn_generic) + | Self::OtherAnnotation(_, _, fn_generic) => fn_generic.len(), _ => 0, } } pub fn set_fn_generic(&mut self, generic: GenericArgRegion<'tcx>) { match self { - Self::ConstRef(_, fn_generic) | - Self::Place { fn_generic, .. } | - Self::ProjectionAnnotation(_, _, fn_generic) | - Self::OtherAnnotation(_, _, fn_generic) => fn_generic.push(generic), + Self::ConstRef(_, fn_generic) + | Self::Place { fn_generic, .. } + | Self::ProjectionAnnotation(_, _, fn_generic) + | Self::OtherAnnotation(_, _, fn_generic) => fn_generic.push(generic), _ => panic!("{self:?} ({generic:?})"), } } pub fn unset_fn_generic(&mut self) { match self { - Self::ConstRef(_, fn_generic) | - Self::Place { fn_generic, .. } | - Self::ProjectionAnnotation(_, _, fn_generic) | - Self::OtherAnnotation(_, _, fn_generic) => assert!(fn_generic.pop().is_some()), + Self::ConstRef(_, fn_generic) + | Self::Place { fn_generic, .. } + | Self::ProjectionAnnotation(_, _, fn_generic) + | Self::OtherAnnotation(_, _, fn_generic) => assert!(fn_generic.pop().is_some()), _ => panic!(), } } @@ -248,7 +262,11 @@ impl<'tcx> RegionKind<'tcx> { // #[tracing::instrument(name = "RegionKind::get_place", level = "trace", ret)] pub fn get_place(&self) -> Option { match self { - Self::Place { local, promoted: Promote::NotPromoted, .. } => Some(*local), + Self::Place { + local, + promoted: Promote::NotPromoted, + .. + } => Some(*local), _ => None, } } @@ -289,29 +307,41 @@ impl<'tcx> RegionKind<'tcx> { Self::ConstRef(kind, fn_generic) => { format!("{kind}{}", GenericArgRegion::to_string(fn_generic, cgx)) } - Self::Place { local, ty, promoted, fn_generic } => { + Self::Place { + local, + ty, + promoted, + fn_generic, + } => { let place = Place::from(*local); // let exact = place.deref_to_region(*region, cgx.rp); // let display = exact.unwrap_or(place).to_string(cgx.rp); // if exact.is_some() { // format!("{display:?}{promoted}") // } else { - format!("AllIn({:?} = {ty}){promoted}", place.to_string(cgx.rp)) + format!("AllIn({:?} = {ty}){promoted}", place.to_string(cgx.rp)) // } } - Self::Borrow(b, promoted) => { - match b.kind { - BorrowKind::Shared => { - format!("& {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) - } - BorrowKind::Mut { .. } => { - format!("&mut {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) - } - BorrowKind::Shallow => { - format!("&sh {:?}{promoted}", Place::from(b.borrowed_place).to_string(cgx.rp)) - } + Self::Borrow(b, promoted) => match b.kind { + BorrowKind::Shared => { + format!( + "& {:?}{promoted}", + Place::from(b.borrowed_place).to_string(cgx.rp) + ) } - } + BorrowKind::Mut { .. } => { + format!( + "&mut {:?}{promoted}", + Place::from(b.borrowed_place).to_string(cgx.rp) + ) + } + BorrowKind::Shallow => { + format!( + "&sh {:?}{promoted}", + Place::from(b.borrowed_place).to_string(cgx.rp) + ) + } + }, Self::EarlyBound(name) => name.as_str().to_string(), Self::LateBound { kind, ctime } => { let kind = match kind { @@ -335,10 +365,15 @@ impl<'tcx> RegionKind<'tcx> { }; format!("{kind}@{:?}", p.universe) } - Self::ProjectionAnnotation(place, ty, fn_generic) => - format!("{:?}: {ty}{}", place.to_string(cgx.rp), GenericArgRegion::to_string(fn_generic, cgx)), - &Self::OtherAnnotation(kind, ty, ref fn_generic) => - format!("{kind} {ty}{}", GenericArgRegion::to_string(fn_generic, cgx)), + Self::ProjectionAnnotation(place, ty, fn_generic) => format!( + "{:?}: {ty}{}", + place.to_string(cgx.rp), + GenericArgRegion::to_string(fn_generic, cgx) + ), + &Self::OtherAnnotation(kind, ty, ref fn_generic) => format!( + "{kind} {ty}{}", + GenericArgRegion::to_string(fn_generic, cgx) + ), Self::MiscLocal => "?misc?".to_string(), Self::UnusedReturnBug(..) => unreachable!(), Self::UnknownLocal => "???".to_string(), @@ -472,8 +507,8 @@ impl<'tcx> RegionInfoMap<'tcx> { (0..self.region_info.len()).map(RegionVid::from) } pub fn for_local(&self, r: RegionVid, l: Local) -> bool { - self.get(r).get_place() == Some(l) || - self.get(r).get_borrow_or_projection_local() == Some(l) + self.get(r).get_place() == Some(l) + || self.get(r).get_borrow_or_projection_local() == Some(l) } pub fn const_regions(&self) -> &[RegionVid] { &self.constant_regions diff --git a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs index 110ba0718f7..275d20a8a79 100644 --- a/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs +++ b/mir-state-analysis/src/coupling_graph/context/region_info/mod.rs @@ -4,7 +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/. -use std::{ops::ControlFlow, marker::PhantomData}; +use std::{marker::PhantomData, ops::ControlFlow}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ @@ -16,18 +16,28 @@ use prusti_rustc_interface::{ dataflow::{Analysis, ResultsCursor}, index::Idx, middle::{ - mir::{visit::{Visitor, TyContext, PlaceContext}, Body, Constant, Local, Location, Operand, RETURN_PLACE, Rvalue, ConstantKind, Terminator, TerminatorKind, PlaceRef, PlaceElem, ProjectionElem, AggregateKind, Promoted, Place as MirPlace}, - ty::{TypeVisitor, TypeSuperVisitable, TypeVisitable, GenericArg, RegionVid, Ty, TyCtxt, TyKind, Region, GenericArgsRef, BoundVariableKind, Const, GenericArgKind}, + mir::{ + visit::{PlaceContext, TyContext, Visitor}, + AggregateKind, Body, Constant, ConstantKind, Local, Location, Operand, + Place as MirPlace, PlaceElem, PlaceRef, ProjectionElem, Promoted, Rvalue, Terminator, + TerminatorKind, RETURN_PLACE, + }, + ty::{ + BoundVariableKind, Const, GenericArg, GenericArgKind, GenericArgsRef, Region, + RegionVid, Ty, TyCtxt, TyKind, TypeSuperVisitable, TypeVisitable, TypeVisitor, + }, }, - span::{Span, Symbol, def_id::DefId}, + span::{def_id::DefId, Span, Symbol}, }; use crate::{ coupling_graph::region_info::map::ParamRegion, - utils::{Place, PlaceRepacker, r#const::ConstEval}, + utils::{r#const::ConstEval, Place, PlaceRepacker}, }; -use self::map::{RegionInfoMap, RegionKind, GenericArgRegion, ConstRegionKind, OtherAnnotationKind, Promote}; +use self::map::{ + ConstRegionKind, GenericArgRegion, OtherAnnotationKind, Promote, RegionInfoMap, RegionKind, +}; use super::{shared_borrow_set::SharedBorrowSet, CgContext}; @@ -153,9 +163,21 @@ impl<'tcx> RegionInfo<'tcx> { (static_region, function_region) } - pub fn initialize_consts(map: &mut RegionInfoMap<'tcx>, rp: PlaceRepacker<'_, 'tcx>, facts2: &BorrowckFacts2<'tcx>) { + pub fn initialize_consts( + map: &mut RegionInfoMap<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, + facts2: &BorrowckFacts2<'tcx>, + ) { let mut collector = ConstantRegionCollector { - map, inner_kind: None, fn_ptr: false, rp, facts2, return_ty: None, promoted_idx: Promote::NotPromoted, regions_set: None, max_region: None, + map, + inner_kind: None, + fn_ptr: false, + rp, + facts2, + return_ty: None, + promoted_idx: Promote::NotPromoted, + regions_set: None, + max_region: None, }; for (idx, promoted) in rp.promoted().iter_enumerated() { collector.promoted_idx = Promote::Promoted(idx); @@ -215,18 +237,25 @@ impl<'tcx> ConstantRegionCollector<'_, '_, 'tcx> { self.inner_kind = old_kind; t } - + fn visit_generics_args(&mut self, did: DefId, generics: GenericArgsRef<'tcx>) { for (gen_idx, arg) in generics.iter().enumerate() { let inner_kind = self.inner_kind.as_mut().unwrap(); - inner_kind.set_fn_generic(GenericArgRegion { did, gen_idx, full_ty: arg.as_type() }); + inner_kind.set_fn_generic(GenericArgRegion { + did, + gen_idx, + full_ty: arg.as_type(), + }); arg.visit_with(self); self.inner_kind.as_mut().unwrap().unset_fn_generic(); } } fn set_region(&mut self, r: RegionVid, kind: RegionKind<'tcx>) { - assert!(self.promoted_idx == Promote::NotPromoted || kind.promoted(), "{kind:?} {r:?}"); + assert!( + self.promoted_idx == Promote::NotPromoted || kind.promoted(), + "{kind:?} {r:?}" + ); self.map.set(r, kind); if let Some(regions_set) = &mut self.regions_set { *regions_set += 1; @@ -248,15 +277,24 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { assert!(self.regions_set.is_none() && self.max_region.is_none()); self.regions_set = Some(0); } - self.with_kind(RegionKind::Place { local, ty, promoted: self.promoted_idx, fn_generic: Vec::new() }, - |this| ty.visit_with(this) + self.with_kind( + RegionKind::Place { + local, + ty, + promoted: self.promoted_idx, + fn_generic: Vec::new(), + }, + |this| ty.visit_with(this), ); if local == RETURN_PLACE { // TODO: remove this once `https://github.com/rust-lang/rust/pull/116792` lands let return_regions = self.regions_set.take().unwrap(); if let Some(new_max) = self.max_region.take() { - for r in (0..return_regions).rev().map(|sub| RegionVid::from_usize(new_max.index() - return_regions - sub)) { - self.map.set(r, RegionKind::UnusedReturnBug(self.promoted_idx)); + for r in (0..return_regions).rev().map(|sub| { + RegionVid::from_usize(new_max.index() - return_regions - sub) + }) { + self.map + .set(r, RegionKind::UnusedReturnBug(self.promoted_idx)); } } } @@ -265,13 +303,15 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { assert_eq!(ty, self.return_ty.unwrap()) } TyContext::UserTy(_) => { - self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::UserTy, ty, Vec::new()), - |this| ty.visit_with(this) + self.with_kind( + RegionKind::OtherAnnotation(OtherAnnotationKind::UserTy, ty, Vec::new()), + |this| ty.visit_with(this), ); } - TyContext::YieldTy(_) => { - self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::YieldTy, ty, Vec::new()), - |this| ty.visit_with(this) + TyContext::YieldTy(_) => { + self.with_kind( + RegionKind::OtherAnnotation(OtherAnnotationKind::YieldTy, ty, Vec::new()), + |this| ty.visit_with(this), ); } TyContext::Location(_location) => { @@ -282,13 +322,23 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { } #[tracing::instrument(name = "ConstantRegionCollector::visit_projection_elem", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] - fn visit_projection_elem(&mut self, place_ref: PlaceRef<'tcx>, elem: PlaceElem<'tcx>, ctx: PlaceContext, location: Location) { + fn visit_projection_elem( + &mut self, + place_ref: PlaceRef<'tcx>, + elem: PlaceElem<'tcx>, + ctx: PlaceContext, + location: Location, + ) { // println!("Projection elem ({ctx:?}): {place_ref:?} ({elem:?}) [{location:?}]"); let place = Place::from(place_ref).mk_place_elem(elem, self.rp); if let Some(ty) = place.last_projection_ty() { - assert!(matches!(elem, ProjectionElem::Field(..) | ProjectionElem::OpaqueCast(..))); - self.with_kind(RegionKind::ProjectionAnnotation(place, ty, Vec::new()), - |this| this.super_projection_elem(place_ref, elem, ctx, location) + assert!(matches!( + elem, + ProjectionElem::Field(..) | ProjectionElem::OpaqueCast(..) + )); + self.with_kind( + RegionKind::ProjectionAnnotation(place, ty, Vec::new()), + |this| this.super_projection_elem(place_ref, elem, ctx, location), ) } else { self.super_projection_elem(place_ref, elem, ctx, location) @@ -297,17 +347,23 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { #[tracing::instrument(name = "ConstantRegionCollector::visit_ty_const", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] fn visit_ty_const(&mut self, ct: Const<'tcx>, _location: Location) { // e.g. from `Rvalue::Repeat` - self.with_kind(RegionKind::ConstRef(ConstRegionKind::TyConst, Vec::new()), |this| { - ct.visit_with(this); - }); + self.with_kind( + RegionKind::ConstRef(ConstRegionKind::TyConst, Vec::new()), + |this| { + ct.visit_with(this); + }, + ); } #[tracing::instrument(name = "ConstantRegionCollector::visit_constant", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] fn visit_constant(&mut self, constant: &Constant<'tcx>, _location: Location) { // println!("Constant: {:?}", constant.ty()); assert!(self.inner_kind.is_none()); - self.with_kind(RegionKind::ConstRef(ConstRegionKind::Const(self.promoted_idx), Vec::new()), |this| { - constant.visit_with(this); - }); + self.with_kind( + RegionKind::ConstRef(ConstRegionKind::Const(self.promoted_idx), Vec::new()), + |this| { + constant.visit_with(this); + }, + ); } #[tracing::instrument(name = "ConstantRegionCollector::visit_args", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] fn visit_args(&mut self, args: &GenericArgsRef<'tcx>, location: Location) { @@ -321,7 +377,14 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { #[tracing::instrument(name = "ConstantRegionCollector::visit_operand", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) { // Temporarily remove `OtherAnnotationKind::RvalueTy` - let from_rvalue = matches!(self.inner_kind, Some(RegionKind::OtherAnnotation(OtherAnnotationKind::RvalueTy(_), _, _))); + let from_rvalue = matches!( + self.inner_kind, + Some(RegionKind::OtherAnnotation( + OtherAnnotationKind::RvalueTy(_), + _, + _ + )) + ); if from_rvalue { let kind = self.inner_kind.take(); self.super_operand(operand, location); @@ -333,11 +396,13 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { #[tracing::instrument(name = "ConstantRegionCollector::visit_assign", level = "trace", skip(self), fields(promoted_idx = ?self.promoted_idx, inner_kind = ?self.inner_kind))] fn visit_assign(&mut self, place: &MirPlace<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { match rvalue { - &Rvalue::Aggregate(box AggregateKind::Adt(did, _, generics, _, _), _) | - &Rvalue::Aggregate(box AggregateKind::Closure(did, generics), _) | - &Rvalue::Aggregate(box AggregateKind::Generator(did, generics, _), _) => { - self.with_kind(RegionKind::ConstRef(ConstRegionKind::Aggregate(self.promoted_idx), Vec::new()), - |this| this.visit_generics_args(did, generics)); + &Rvalue::Aggregate(box AggregateKind::Adt(did, _, generics, _, _), _) + | &Rvalue::Aggregate(box AggregateKind::Closure(did, generics), _) + | &Rvalue::Aggregate(box AggregateKind::Generator(did, generics, _), _) => { + self.with_kind( + RegionKind::ConstRef(ConstRegionKind::Aggregate(self.promoted_idx), Vec::new()), + |this| this.visit_generics_args(did, generics), + ); self.super_assign(place, rvalue, location); // For the operand } &Rvalue::Ref(region, kind, borrowed_place) => { @@ -346,7 +411,10 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { if is_non_promoted && location_map.contains_key(&location) { let borrow = location_map.get(&location).unwrap(); assert_eq!(borrow.region, region.as_var()); - self.set_region(borrow.region, RegionKind::Borrow(borrow.clone(), Promote::NotPromoted)) + self.set_region( + borrow.region, + RegionKind::Borrow(borrow.clone(), Promote::NotPromoted), + ) } else { let region = region.as_var(); let borrow = BorrowData { @@ -361,12 +429,17 @@ impl<'tcx> Visitor<'tcx> for ConstantRegionCollector<'_, '_, 'tcx> { } self.super_assign(place, rvalue, location) } - Rvalue::Aggregate(box AggregateKind::Array(ty), _) | - Rvalue::Cast(_, _, ty) | - Rvalue::NullaryOp(_, ty) | - Rvalue::ShallowInitBox(_, ty) => { - self.with_kind(RegionKind::OtherAnnotation(OtherAnnotationKind::RvalueTy(self.promoted_idx), *ty, Vec::new()), - |this| this.super_assign(place, rvalue, location) + Rvalue::Aggregate(box AggregateKind::Array(ty), _) + | Rvalue::Cast(_, _, ty) + | Rvalue::NullaryOp(_, ty) + | Rvalue::ShallowInitBox(_, ty) => { + self.with_kind( + RegionKind::OtherAnnotation( + OtherAnnotationKind::RvalueTy(self.promoted_idx), + *ty, + Vec::new(), + ), + |this| this.super_assign(place, rvalue, location), ); } _ => self.super_assign(place, rvalue, location), @@ -396,8 +469,7 @@ impl<'tcx> TypeVisitor> for ConstantRegionCollector<'_, '_, 'tcx> { fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { // println!("Ty inner: {ty:?}"); match ty.kind() { - &TyKind::FnDef(did, generics) | - &TyKind::Closure(did, generics) => { + &TyKind::FnDef(did, generics) | &TyKind::Closure(did, generics) => { self.visit_generics_args(did, generics); ControlFlow::Continue(()) } diff --git a/mir-state-analysis/src/coupling_graph/impl/dot.rs b/mir-state-analysis/src/coupling_graph/impl/dot.rs index 1ae4e01b8fe..e0647f4af0e 100644 --- a/mir-state-analysis/src/coupling_graph/impl/dot.rs +++ b/mir-state-analysis/src/coupling_graph/impl/dot.rs @@ -12,14 +12,14 @@ use prusti_rustc_interface::{ dataflow::fmt::DebugWithContext, index::{bit_set::BitSet, IndexVec}, middle::{ - mir::{BorrowKind, ConstraintCategory, Local, BasicBlock}, + mir::{BasicBlock, BorrowKind, ConstraintCategory, Local}, ty::{RegionVid, TyKind}, }, }; -use crate::coupling_graph::outlives_info::edge::{EdgeInfo, Edge as CgEdge}; +use crate::coupling_graph::outlives_info::edge::{Edge as CgEdge, EdgeInfo}; -use super::{triple::Cg}; +use super::triple::Cg; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Edge<'tcx> { @@ -29,7 +29,11 @@ pub struct Edge<'tcx> { } impl<'tcx> Edge<'tcx> { - pub(crate) fn new(from: RegionVid, to: RegionVid, reasons: FxHashSet>>) -> Self { + pub(crate) fn new( + from: RegionVid, + to: RegionVid, + reasons: FxHashSet>>, + ) -> Self { Self { from, to, reasons } } } @@ -40,7 +44,9 @@ impl<'a, 'tcx> Cg<'a, 'tcx> { "start".to_string() } else { let pre = if self.is_pre { "_pre" } else { "" }; - format!("{:?}{pre}", self.location).replace('[', "_").replace(']', "") + format!("{:?}{pre}", self.location) + .replace('[', "_") + .replace(']', "") } } } diff --git a/mir-state-analysis/src/coupling_graph/impl/engine.rs b/mir-state-analysis/src/coupling_graph/impl/engine.rs index deabc6f45c8..bfc4cfdcd00 100644 --- a/mir-state-analysis/src/coupling_graph/impl/engine.rs +++ b/mir-state-analysis/src/coupling_graph/impl/engine.rs @@ -4,7 +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/. -use std::cell::{RefCell, Cell}; +use std::cell::{Cell, RefCell}; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ @@ -33,9 +33,10 @@ use prusti_rustc_interface::{ use crate::{ coupling_graph::{ + consistency::CouplingConsistency, graph::{Graph, Node}, outlives_info::AssignsToPlace, - CgContext, consistency::CouplingConsistency, + CgContext, }, free_pcs::{CapabilityKind, CapabilityLocal, Fpcs}, utils::PlaceRepacker, @@ -100,7 +101,11 @@ pub(crate) fn draw_dots<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph< .expect("Unable to write file"); } -fn print_after_loc<'tcx, 'a>(c: &mut ResultsCursor<'_, 'tcx, CouplingGraph<'a, 'tcx>>, location: Location, graph: &mut Vec) { +fn print_after_loc<'tcx, 'a>( + c: &mut ResultsCursor<'_, 'tcx, CouplingGraph<'a, 'tcx>>, + location: Location, + graph: &mut Vec, +) { c.seek_after_primary_effect(location); let mut g = c.get().clone(); g.dot_node_filter = |k| k.local(); @@ -150,7 +155,13 @@ impl<'a, 'tcx> CouplingGraph<'a, 'tcx> { println!("{pt:?} -> {:?} ({:?})", lt.to_location(pt), ""); //, facts.output_facts.origins_live_at(pt)); } println!("out_of_scope: {:?}", out_of_scope); - println!("outlives_constraints: {:#?}\n", cgx.facts2.region_inference_context.outlives_constraints().collect::>()); + println!( + "outlives_constraints: {:#?}\n", + cgx.facts2 + .region_inference_context + .outlives_constraints() + .collect::>() + ); println!("cgx: {:#?}\n", cgx); for r in cgx.region_info.map.all_regions() { println!( @@ -179,7 +190,14 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CouplingGraph<'a, 'tcx> { fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain { let block = self.bb_index.get(); self.bb_index.set(block.plus(1)); - Cg::new(self.cgx, self.top_crates, Location { block, statement_index: 0 }) + Cg::new( + self.cgx, + self.top_crates, + Location { + block, + statement_index: 0, + }, + ) } fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) { @@ -189,7 +207,11 @@ impl<'a, 'tcx> AnalysisDomain<'tcx> for CouplingGraph<'a, 'tcx> { } impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { - #[tracing::instrument(name = "CouplingGraph::apply_before_statement_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "CouplingGraph::apply_before_statement_effect", + level = "debug", + skip(self) + )] fn apply_before_statement_effect( &mut self, state: &mut Self::Domain, @@ -204,7 +226,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { // println!("\nblock: {:?}", location.block); let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.sum_version()), + format!( + "log/coupling/individual/{l}_v{}_start.dot", + state.sum_version() + ), false, ); self.flow_borrows @@ -223,7 +248,11 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { state.handle_kills(&delta, oos, location); } - #[tracing::instrument(name = "CouplingGraph::apply_statement_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "CouplingGraph::apply_statement_effect", + level = "debug", + skip(self) + )] fn apply_statement_effect( &mut self, state: &mut Self::Domain, @@ -241,8 +270,11 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { ); } - - #[tracing::instrument(name = "CouplingGraph::apply_before_terminator_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "CouplingGraph::apply_before_terminator_effect", + level = "debug", + skip(self) + )] fn apply_before_terminator_effect( &mut self, state: &mut Self::Domain, @@ -258,7 +290,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { // println!("\nblock: {:?}", location.block); let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_start.dot", state.sum_version()), + format!( + "log/coupling/individual/{l}_v{}_start.dot", + state.sum_version() + ), false, ); self.flow_borrows @@ -278,7 +313,11 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { state.handle_kills(&delta, oos, location); } - #[tracing::instrument(name = "CouplingGraph::apply_terminator_effect", level = "debug", skip(self))] + #[tracing::instrument( + name = "CouplingGraph::apply_terminator_effect", + level = "debug", + skip(self) + )] fn apply_terminator_effect<'mir>( &mut self, state: &mut Self::Domain, @@ -293,7 +332,10 @@ impl<'a, 'tcx> Analysis<'tcx> for CouplingGraph<'a, 'tcx> { TerminatorKind::Return => { let l = format!("{location:?}").replace('[', "_").replace(']', ""); state.output_to_dot( - format!("log/coupling/individual/{l}_v{}_pre.dot", state.sum_version()), + format!( + "log/coupling/individual/{l}_v{}_pre.dot", + state.sum_version() + ), false, ); // Pretend we have a storage dead for all `always_live_locals` other than the args/return diff --git a/mir-state-analysis/src/coupling_graph/impl/graph.rs b/mir-state-analysis/src/coupling_graph/impl/graph.rs index 43dc5786d12..761a6b43f44 100644 --- a/mir-state-analysis/src/coupling_graph/impl/graph.rs +++ b/mir-state-analysis/src/coupling_graph/impl/graph.rs @@ -26,7 +26,10 @@ use prusti_rustc_interface::{ }; use crate::{ - coupling_graph::{CgContext, outlives_info::edge::{Edge, EdgeOrigin, EdgeKind}}, + coupling_graph::{ + outlives_info::edge::{Edge, EdgeKind, EdgeOrigin}, + CgContext, + }, free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, @@ -57,10 +60,7 @@ impl<'tcx> Graph<'tcx> { // sup outlives sub, or `sup: sub` (i.e. sup gets blocked) #[tracing::instrument(name = "Graph::outlives_inner", level = "trace", skip(self), ret)] - fn outlives_inner( - &mut self, - edge: Vec>, - ) -> Option<(RegionVid, RegionVid, bool)> { + fn outlives_inner(&mut self, edge: Vec>) -> Option<(RegionVid, RegionVid, bool)> { let (sup, sub) = self.outlives_unwrap_edge(&edge); self.nodes[sup].blocked_by.insert(sub); let blocks = self.nodes[sub].blocks.entry(sup).or_default(); @@ -71,7 +71,7 @@ impl<'tcx> Graph<'tcx> { None } } - fn outlives_unwrap_edge(&mut self, edge: &Vec>) -> (RegionVid, RegionVid) { + fn outlives_unwrap_edge(&mut self, edge: &Vec>) -> (RegionVid, RegionVid) { let (sup, sub) = (edge.last().unwrap().sup(), edge.first().unwrap().sub()); if self.static_regions.contains(&sub) { Self::set_static_region(&self.nodes, &mut self.static_regions, sup); @@ -84,12 +84,15 @@ impl<'tcx> Graph<'tcx> { pub(super) fn outlives_join( &mut self, edge: Vec>, - ) -> Option<(RegionVid, RegionVid)> { + ) -> Option<(RegionVid, RegionVid)> { let (sup, sub) = self.outlives_unwrap_edge(&edge); self.nodes[sup].blocked_by.insert(sub); let blocks = self.nodes[sub].blocks.entry(sup).or_default(); - if blocks.iter().any(|other| Edge::generalized_by(&edge, other)) { + if blocks + .iter() + .any(|other| Edge::generalized_by(&edge, other)) + { None } else { blocks.retain(|other| !Edge::generalized_by(other, &edge)); @@ -122,7 +125,13 @@ impl<'tcx> Graph<'tcx> { fn kill(&mut self, r: RegionVid) -> Vec { assert!(!self.static_regions.contains(&r)); let (_, blocked_by) = self.remove_all_edges(r); - [r].into_iter().chain(blocked_by.iter().flat_map(|(blocked_by, _)| self.kill(*blocked_by))).collect() + [r].into_iter() + .chain( + blocked_by + .iter() + .flat_map(|(blocked_by, _)| self.kill(*blocked_by)), + ) + .collect() } #[tracing::instrument(name = "Graph::remove", level = "trace", ret)] @@ -130,7 +139,10 @@ impl<'tcx> Graph<'tcx> { // Set `remove_dangling_children` when removing regions which are not tracked by the regular borrowck, // to remove in e.g. `let y: &'a i32 = &'b *x;` the region `'b` when removing `'a` (if `x: &'c i32`). // NOTE: Maybe shouldn't be set, since it seems that the regular borrowck does not kill off `'b` this eagerly (if `x: &'c mut i32`). - pub fn remove(&mut self, r: RegionVid) -> Option<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { + pub fn remove( + &mut self, + r: RegionVid, + ) -> Option<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { let (blocks, blocked_by) = self.remove_all_edges(r); let changed = !(blocks.is_empty() && blocked_by.is_empty()); let mut rejoins = Vec::new(); @@ -172,9 +184,14 @@ impl<'tcx> Graph<'tcx> { } #[tracing::instrument(name = "Graph::remove_many", level = "trace")] - pub fn remove_many(&mut self, rs: &FxHashSet) -> Vec<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { + pub fn remove_many( + &mut self, + rs: &FxHashSet, + ) -> Vec<(RegionVid, Vec<(RegionVid, RegionVid, bool)>)> { for &r in rs { - if self.predecessors(r).iter().all(|pre| rs.contains(pre)) || self.successors(r).iter().all(|suc| rs.contains(suc)) { + if self.predecessors(r).iter().all(|pre| rs.contains(pre)) + || self.successors(r).iter().all(|suc| rs.contains(suc)) + { self.static_regions.remove(&r); self.remove_all_edges(r); } @@ -200,7 +217,10 @@ impl<'tcx> Graph<'tcx> { self.nodes[*block].blocked_by.remove(&r); } let blocked_by = std::mem::replace(&mut self.nodes[r].blocked_by, FxHashSet::default()); - let blocked_by = blocked_by.into_iter().map(|bb| (bb, self.nodes[bb].blocks.remove(&r).unwrap())).collect(); + let blocked_by = blocked_by + .into_iter() + .map(|bb| (bb, self.nodes[bb].blocks.remove(&r).unwrap())) + .collect(); (blocks, blocked_by) } @@ -210,7 +230,12 @@ impl<'tcx> Graph<'tcx> { predecessors } fn predecessors_helper(&self, r: RegionVid, visited: &mut FxHashSet) { - let tp: Vec<_> = self.nodes[r].blocked_by.iter().copied().filter(|r| visited.insert(*r)).collect(); + let tp: Vec<_> = self.nodes[r] + .blocked_by + .iter() + .copied() + .filter(|r| visited.insert(*r)) + .collect(); for r in tp { self.predecessors_helper(r, visited) } @@ -221,7 +246,12 @@ impl<'tcx> Graph<'tcx> { successors } fn successors_helper(&self, r: RegionVid, visited: &mut FxHashSet) { - let tp: Vec<_> = self.nodes[r].blocks.iter().map(|(r, _)| *r).filter(|r| visited.insert(*r)).collect(); + let tp: Vec<_> = self.nodes[r] + .blocks + .iter() + .map(|(r, _)| *r) + .filter(|r| visited.insert(*r)) + .collect(); for r in tp { self.successors_helper(r, visited) } @@ -242,8 +272,10 @@ impl<'tcx> Node<'tcx> { } } pub fn true_edges(&self) -> Vec { - self.blocks.iter().filter(|(_, edges)| edges.iter().any( - |edge| Edge::is_blocking(edge) - )).map(|(&r, _)| r).collect() + self.blocks + .iter() + .filter(|(_, edges)| edges.iter().any(|edge| Edge::is_blocking(edge))) + .map(|(&r, _)| r) + .collect() } } diff --git a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs index 3fef1a908f3..d7b635ab2ba 100644 --- a/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs +++ b/mir-state-analysis/src/coupling_graph/impl/join_semi_lattice.rs @@ -6,16 +6,18 @@ use std::collections::hash_map::Entry; -use prusti_rustc_interface::{ - middle::mir::Location, - dataflow::JoinSemiLattice, -}; +use prusti_rustc_interface::{dataflow::JoinSemiLattice, middle::mir::Location}; use crate::{ + coupling_graph::{ + consistency::CouplingConsistency, + coupling::{Block, CouplingOp}, + outlives_info::edge::{Edge, EdgeInfo, EdgeOrigin}, + }, free_pcs::{ CapabilityKind, CapabilityLocal, CapabilityProjections, CapabilitySummary, Fpcs, RepackOp, }, - utils::{PlaceOrdering, PlaceRepacker}, coupling_graph::{coupling::{CouplingOp, Block}, consistency::CouplingConsistency, outlives_info::edge::{EdgeInfo, EdgeOrigin, Edge}}, + utils::{PlaceOrdering, PlaceRepacker}, }; use super::{graph::Graph, triple::Cg}; @@ -28,8 +30,14 @@ impl JoinSemiLattice for Cg<'_, '_> { assert!(*version < 20, "{:?} -> {:?}", other.location, self.location); let loop_head = self.cgx.loops.loop_head_of(self.location.block); - let top = |sup, sub| EdgeInfo::no_reason(sup, sub, Some(self.location), EdgeOrigin::Opaque).to_edge(self.cgx); - let needs_widening = |loc: Location| loop_head.map(|l| self.cgx.loops.in_loop(loc.block, l)).unwrap_or_default(); + let top = |sup, sub| { + EdgeInfo::no_reason(sup, sub, Some(self.location), EdgeOrigin::Opaque).to_edge(self.cgx) + }; + let needs_widening = |loc: Location| { + loop_head + .map(|l| self.cgx.loops.in_loop(loc.block, l)) + .unwrap_or_default() + }; // Are we looping back into the loop head from within the loop? let loop_into = loop_head.map(|l| self.cgx.loops.in_loop(other.location.block, l)); let mut changed = false; @@ -43,7 +51,9 @@ impl JoinSemiLattice for Cg<'_, '_> { } } let old_len = self.graph.inactive_loans.len(); - self.graph.inactive_loans.extend(other.graph.inactive_loans.iter().copied()); + self.graph + .inactive_loans + .extend(other.graph.inactive_loans.iter().copied()); changed = changed || old_len != self.graph.inactive_loans.len(); changed } @@ -52,14 +62,16 @@ impl JoinSemiLattice for Cg<'_, '_> { impl Cg<'_, '_> { #[tracing::instrument(name = "Cg::bridge", level = "debug", fields(self.graph = ?self.graph, other.graph = ?self.graph), ret)] pub fn bridge(&self, other: &Self) -> Vec { - other.graph.all_nodes().flat_map(|(sub, node)| - node.true_edges() - .into_iter() - .filter(move |sup| !self.graph.nodes[sub].blocks.contains_key(sup)) - .map(move |sup| - self.outlives_to_block((sup, sub, true)).unwrap() - ) - .map(|block| CouplingOp::Add(block)) - ).collect() + other + .graph + .all_nodes() + .flat_map(|(sub, node)| { + node.true_edges() + .into_iter() + .filter(move |sup| !self.graph.nodes[sub].blocks.contains_key(sup)) + .map(move |sup| self.outlives_to_block((sup, sub, true)).unwrap()) + .map(|block| CouplingOp::Add(block)) + }) + .collect() } } diff --git a/mir-state-analysis/src/coupling_graph/impl/triple.rs b/mir-state-analysis/src/coupling_graph/impl/triple.rs index 376627cf3f7..4b0343ab0fe 100644 --- a/mir-state-analysis/src/coupling_graph/impl/triple.rs +++ b/mir-state-analysis/src/coupling_graph/impl/triple.rs @@ -22,18 +22,25 @@ use prusti_rustc_interface::{ middle::{ mir::{ interpret::{ConstValue, GlobalAlloc, Scalar}, - visit::Visitor, BorrowKind, - BasicBlock, ConstraintCategory, Local, Location, Operand, Place as MirPlace, Rvalue, - Statement, StatementKind, Terminator, TerminatorKind, RETURN_PLACE, ConstantKind, + visit::Visitor, + BasicBlock, BorrowKind, ConstantKind, ConstraintCategory, Local, Location, Operand, + Place as MirPlace, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, + RETURN_PLACE, }, - ty::{GenericArgKind, RegionVid, TyKind, ParamEnv}, + ty::{GenericArgKind, ParamEnv, RegionVid, TyKind}, }, }; use crate::{ - coupling_graph::{region_info::map::{RegionKind, Promote}, CgContext, coupling::{CouplingOp, Block}, outlives_info::edge::{EdgeOrigin, EdgeInfo}}, + coupling_graph::{ + coupling::{Block, CouplingOp}, + outlives_info::edge::{EdgeInfo, EdgeOrigin}, + region_info::map::{Promote, RegionKind}, + CgContext, + }, free_pcs::{ - engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, CapabilityKind, + engine::FreePlaceCapabilitySummary, CapabilityKind, CapabilityLocal, CapabilityProjections, + RepackOp, }, utils::{r#const::ConstEval, Place, PlaceRepacker}, }; @@ -158,7 +165,12 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } self.outlives_placeholder(r, static_region, origin) } - pub(crate) fn outlives_placeholder(&mut self, r: RegionVid, placeholder: RegionVid, origin: EdgeOrigin) { + pub(crate) fn outlives_placeholder( + &mut self, + r: RegionVid, + placeholder: RegionVid, + origin: EdgeOrigin, + ) { let edge = EdgeInfo::no_reason(r, placeholder, None, origin).to_edge(self.cgx); // let new = self.graph.outlives_placeholder(r, placeholder, origin); let new = self.graph.outlives(edge); @@ -176,7 +188,11 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { let (sup, sub, is_blocking) = op; if is_blocking { let waiting_to_activate = self.graph.inactive_loans.contains(&sup); - Some(Block { sup, sub, waiting_to_activate, }) + Some(Block { + sup, + sub, + waiting_to_activate, + }) } else { None } @@ -190,7 +206,10 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } #[tracing::instrument(name = "Cg::outlives_op", level = "trace", skip(self))] fn remove_op(&mut self, op: (RegionVid, Vec<(RegionVid, RegionVid, bool)>)) { - let rejoins = op.1.into_iter().flat_map(|c| self.outlives_to_block(c)).collect(); + let rejoins = + op.1.into_iter() + .flat_map(|c| self.outlives_to_block(c)) + .collect(); self.couplings.push(CouplingOp::Remove(op.0, rejoins)); } #[tracing::instrument(name = "Cg::remove", level = "debug", skip(self), ret)] @@ -213,7 +232,7 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { self.couplings.push(CouplingOp::Remove(removed, Vec::new())); } } - pub (crate) fn reset_ops(&mut self) { + pub(crate) fn reset_ops(&mut self) { for c in self.couplings.drain(..) { self.touched.extend(c.regions()); } @@ -327,7 +346,11 @@ impl<'a, 'tcx: 'a> Cg<'a, 'tcx> { } #[tracing::instrument(name = "kill_shared_borrows_on_place", level = "debug", skip(self))] - pub fn kill_shared_borrows_on_place(&mut self, location: Option, place: MirPlace<'tcx>) { + pub fn kill_shared_borrows_on_place( + &mut self, + location: Option, + place: MirPlace<'tcx>, + ) { let Some(local) = place.as_local() else { // Only remove nodes if assigned to the entire local (this is what rustc allows too) return; @@ -385,7 +408,11 @@ impl<'tcx> Cg<'_, 'tcx> { | RegionKind::Param(_) | RegionKind::UnknownUniversal | RegionKind::Function => continue, - RegionKind::Place { local, promoted: Promote::NotPromoted, .. } => { + RegionKind::Place { + local, + promoted: Promote::NotPromoted, + .. + } => { if local.index() > self.cgx.rp.body().arg_count { self.output_to_dot("log/coupling/error.dot", true); panic!("{r:?} ({location:?}) {:?}", self.graph.nodes[r]); @@ -398,7 +425,10 @@ impl<'tcx> Cg<'_, 'tcx> { } // Ignore (and thus delete) early/late bound (mostly fn call) regions RegionKind::UnusedReturnBug(..) => unreachable!(), - RegionKind::Place { promoted: Promote::Promoted(..), .. } => (), + RegionKind::Place { + promoted: Promote::Promoted(..), + .. + } => (), RegionKind::Borrow(_, Promote::Promoted(..)) => (), RegionKind::ConstRef(..) => (), RegionKind::EarlyBound(..) => (), diff --git a/mir-state-analysis/src/coupling_graph/mod.rs b/mir-state-analysis/src/coupling_graph/mod.rs index 25361aa0139..5357a6596d6 100644 --- a/mir-state-analysis/src/coupling_graph/mod.rs +++ b/mir-state-analysis/src/coupling_graph/mod.rs @@ -9,7 +9,7 @@ mod context; mod results; mod check; +pub use check::*; pub use context::*; pub use r#impl::*; pub use results::*; -pub use check::*; diff --git a/mir-state-analysis/src/coupling_graph/results/coupling.rs b/mir-state-analysis/src/coupling_graph/results/coupling.rs index d7ddea56678..722bbb62b6c 100644 --- a/mir-state-analysis/src/coupling_graph/results/coupling.rs +++ b/mir-state-analysis/src/coupling_graph/results/coupling.rs @@ -6,9 +6,7 @@ use std::fmt::{Display, Formatter, Result}; -use prusti_rustc_interface::{ - middle::{mir::Local, ty::RegionVid} -}; +use prusti_rustc_interface::middle::{mir::Local, ty::RegionVid}; use crate::{free_pcs::CapabilityKind, utils::Place}; @@ -23,8 +21,11 @@ impl CouplingOp { pub fn regions(&self) -> Box + '_> { match self { CouplingOp::Add(block) => Box::new([block.sup, block.sub].into_iter()), - CouplingOp::Remove(remove, block) => - Box::new([*remove].into_iter().chain(block.iter().flat_map(|b| [b.sup, b.sub].into_iter()))), + CouplingOp::Remove(remove, block) => Box::new( + [*remove] + .into_iter() + .chain(block.iter().flat_map(|b| [b.sup, b.sub].into_iter())), + ), CouplingOp::Activate(region) => Box::new([*region].into_iter()), } } @@ -60,7 +61,15 @@ pub struct Block { } impl Display for Block { fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let Block { sup, sub, waiting_to_activate } = *self; - write!(f, "Block({sup:?}, {sub:?}){}", if waiting_to_activate { "?" } else { "" }) + let Block { + sup, + sub, + waiting_to_activate, + } = *self; + write!( + f, + "Block({sup:?}, {sub:?}){}", + if waiting_to_activate { "?" } else { "" } + ) } } diff --git a/mir-state-analysis/src/coupling_graph/results/cursor.rs b/mir-state-analysis/src/coupling_graph/results/cursor.rs index c7df879585b..0ac4766aa35 100644 --- a/mir-state-analysis/src/coupling_graph/results/cursor.rs +++ b/mir-state-analysis/src/coupling_graph/results/cursor.rs @@ -10,11 +10,12 @@ use prusti_rustc_interface::{ }; use crate::{ + coupling_graph::{engine::CouplingGraph, graph::Graph, CgContext}, free_pcs::{ engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, CapabilitySummary, RepackOp, }, - utils::PlaceRepacker, coupling_graph::{engine::CouplingGraph, graph::Graph, CgContext}, + utils::PlaceRepacker, }; use super::coupling::CouplingOp; diff --git a/mir-state-analysis/src/free_pcs/check/checker.rs b/mir-state-analysis/src/free_pcs/check/checker.rs index 5ed75e01201..08db6e80aea 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -11,7 +11,8 @@ use prusti_rustc_interface::{ use crate::{ free_pcs::{ - CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FreePcsAnalysis, RepackOp, FpcsBound, + CapabilityKind, CapabilityLocal, CapabilitySummary, Fpcs, FpcsBound, FreePcsAnalysis, + RepackOp, }, utils::PlaceRepacker, }; diff --git a/mir-state-analysis/src/free_pcs/check/consistency.rs b/mir-state-analysis/src/free_pcs/check/consistency.rs index cc595dcd514..a7492e4d7c6 100644 --- a/mir-state-analysis/src/free_pcs/check/consistency.rs +++ b/mir-state-analysis/src/free_pcs/check/consistency.rs @@ -14,7 +14,11 @@ pub trait CapabilityConsistency<'tcx> { } impl<'tcx, T: CapabilityConsistency<'tcx>> CapabilityConsistency<'tcx> for Summary { - #[tracing::instrument(name = "Summary::consistency_check", level = "trace", skip(self, repacker))] + #[tracing::instrument( + name = "Summary::consistency_check", + level = "trace", + skip(self, repacker) + )] fn consistency_check(&self, repacker: PlaceRepacker<'_, 'tcx>) { for p in self.iter() { p.consistency_check(repacker) diff --git a/mir-state-analysis/src/free_pcs/impl/engine.rs b/mir-state-analysis/src/free_pcs/impl/engine.rs index 83322455713..3f58b2c6bba 100644 --- a/mir-state-analysis/src/free_pcs/impl/engine.rs +++ b/mir-state-analysis/src/free_pcs/impl/engine.rs @@ -9,8 +9,8 @@ use prusti_rustc_interface::{ index::{Idx, IndexVec}, middle::{ mir::{ - visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Location, Statement, - Terminator, TerminatorEdges, RETURN_PLACE, Promoted, + visit::Visitor, BasicBlock, Body, CallReturnPlaces, Local, Location, Promoted, + Statement, Terminator, TerminatorEdges, RETURN_PLACE, }, ty::TyCtxt, }, @@ -23,7 +23,11 @@ use crate::{ 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>, promoted: &'a IndexVec>) -> Self { + pub(crate) fn new( + tcx: TyCtxt<'tcx>, + body: &'a Body<'tcx>, + promoted: &'a IndexVec>, + ) -> Self { let repacker = PlaceRepacker::new(body, promoted, tcx); FreePlaceCapabilitySummary(repacker) } @@ -65,13 +69,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 +100,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 +115,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/free_pcs/impl/fpcs.rs b/mir-state-analysis/src/free_pcs/impl/fpcs.rs index 156631cf73b..b553a1052c9 100644 --- a/mir-state-analysis/src/free_pcs/impl/fpcs.rs +++ b/mir-state-analysis/src/free_pcs/impl/fpcs.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 std::{fmt::{Debug, Formatter, Result}, cell::RefCell}; +use std::{ + cell::RefCell, + fmt::{Debug, Formatter, Result}, +}; use derive_more::{Deref, DerefMut}; use prusti_rustc_interface::{ @@ -15,12 +18,15 @@ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, CapabilityLocal, CapabilityProjections, RepackOp, }, - utils::{PlaceRepacker, Place}, + utils::{Place, PlaceRepacker}, }; use super::CapabilityKind; -pub struct FpcsBound<'tcx>(pub Option) -> CapabilityKind>>, pub bool); +pub struct FpcsBound<'tcx>( + pub Option) -> CapabilityKind>>, + pub bool, +); impl FpcsBound<'_> { pub fn empty(expect: bool) -> RefCell { RefCell::new(Self(None, expect)) @@ -51,14 +57,25 @@ impl<'a, 'tcx> Fpcs<'a, 'tcx> { pub(crate) fn bound(&self, place: Place<'tcx>) -> CapabilityKind { let bound = self.bound.borrow(); assert_eq!(bound.1, bound.0.is_some()); - bound.0.as_ref().map(|b| b(place)).unwrap_or(CapabilityKind::Exclusive) + bound + .0 + .as_ref() + .map(|b| b(place)) + .unwrap_or(CapabilityKind::Exclusive) } } impl Clone for Fpcs<'_, '_> { fn clone(&self) -> Self { let expect_bound = self.bound.borrow().1; - Self { repacker: self.repacker, bottom: self.bottom, apply_pre_effect: self.apply_pre_effect, summary: self.summary.clone(), repackings: self.repackings.clone(), bound: FpcsBound::empty(expect_bound) } + Self { + repacker: self.repacker, + bottom: self.bottom, + apply_pre_effect: self.apply_pre_effect, + summary: self.summary.clone(), + repackings: self.repackings.clone(), + bound: FpcsBound::empty(expect_bound), + } } } impl PartialEq for Fpcs<'_, '_> { diff --git a/mir-state-analysis/src/free_pcs/impl/place.rs b/mir-state-analysis/src/free_pcs/impl/place.rs index 6fb55887ed7..fb31c557c09 100644 --- a/mir-state-analysis/src/free_pcs/impl/place.rs +++ b/mir-state-analysis/src/free_pcs/impl/place.rs @@ -108,8 +108,7 @@ impl CapabilityKind { #[tracing::instrument(name = "CapabilityKind::sum", level = "trace", ret)] pub fn sum(self, other: Self) -> Option { match (self, other) { - (other, CapabilityKind::None) | - (CapabilityKind::None, other) => Some(other), + (other, CapabilityKind::None) | (CapabilityKind::None, other) => Some(other), (CapabilityKind::Write, CapabilityKind::Read) => Some(CapabilityKind::Exclusive), _ => None, } diff --git a/mir-state-analysis/src/free_pcs/impl/update.rs b/mir-state-analysis/src/free_pcs/impl/update.rs index 0d03842ee9a..b2d60424e2e 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -101,7 +101,12 @@ impl<'tcx> Fpcs<'_, 'tcx> { } impl<'tcx> CapabilityProjections<'tcx> { - #[tracing::instrument(name = "CapabilityProjections::repack", level = "trace", skip(repacker), ret)] + #[tracing::instrument( + name = "CapabilityProjections::repack", + level = "trace", + skip(repacker), + ret + )] pub(super) fn repack( &mut self, to: Place<'tcx>, diff --git a/mir-state-analysis/src/free_pcs/results/cursor.rs b/mir-state-analysis/src/free_pcs/results/cursor.rs index e974d0f82e6..f7f90adbf92 100644 --- a/mir-state-analysis/src/free_pcs/results/cursor.rs +++ b/mir-state-analysis/src/free_pcs/results/cursor.rs @@ -12,9 +12,9 @@ use prusti_rustc_interface::{ use crate::{ free_pcs::{ engine::FreePlaceCapabilitySummary, join_semi_lattice::RepackingJoinSemiLattice, - CapabilitySummary, RepackOp, CapabilityKind, + CapabilityKind, CapabilitySummary, RepackOp, }, - utils::{PlaceRepacker, Place}, + utils::{Place, PlaceRepacker}, }; type Cursor<'mir, 'tcx> = ResultsCursor<'mir, 'tcx, FreePlaceCapabilitySummary<'mir, 'tcx>>; diff --git a/mir-state-analysis/src/lib.rs b/mir-state-analysis/src/lib.rs index 2654c0d9d8a..ea477109a7e 100644 --- a/mir-state-analysis/src/lib.rs +++ b/mir-state-analysis/src/lib.rs @@ -14,9 +14,12 @@ pub mod r#loop; use prusti_interface::environment::borrowck::facts::{BorrowckFacts, BorrowckFacts2}; use prusti_rustc_interface::{ - index::IndexVec, dataflow::Analysis, - middle::{mir::{Body, START_BLOCK, Promoted}, ty::TyCtxt}, + index::IndexVec, + middle::{ + mir::{Body, Promoted, START_BLOCK}, + ty::TyCtxt, + }, }; #[tracing::instrument(name = "run_free_pcs", level = "debug", skip(tcx))] @@ -33,7 +36,11 @@ pub fn run_free_pcs<'mir, 'tcx>( free_pcs::FreePcsAnalysis::new(analysis.into_results_cursor(mir)) } -pub fn test_free_pcs<'tcx>(mir: &Body<'tcx>, promoted: &IndexVec>, tcx: TyCtxt<'tcx>) { +pub fn test_free_pcs<'tcx>( + mir: &Body<'tcx>, + promoted: &IndexVec>, + tcx: TyCtxt<'tcx>, +) { let analysis = run_free_pcs(mir, promoted, tcx); free_pcs::check(analysis); } @@ -76,12 +83,10 @@ pub fn test_coupling_graph<'tcx>( // return; // } - let fpcs_analysis = run_free_pcs(mir, promoted, tcx); let cgx = coupling_graph::CgContext::new(tcx, mir, promoted, facts, facts2); let cg_analysis = run_coupling_graph(mir, &cgx, tcx, top_crates); coupling_graph::check(cg_analysis, fpcs_analysis); - // panic!() } diff --git a/mir-state-analysis/src/loop/mod.rs b/mir-state-analysis/src/loop/mod.rs index 6bae3812c4e..c6a954e4895 100644 --- a/mir-state-analysis/src/loop/mod.rs +++ b/mir-state-analysis/src/loop/mod.rs @@ -147,7 +147,10 @@ impl LoopAnalysis { assert!(start_loops.is_empty()); // A bb can only be the loop head of a single loop for lh in &self.loop_heads { - assert_eq!(self.loop_heads.iter().filter(|other| *other == lh).count(), 1); + assert_eq!( + self.loop_heads.iter().filter(|other| *other == lh).count(), + 1 + ); } } } diff --git a/mir-state-analysis/src/utils/const/mod.rs b/mir-state-analysis/src/utils/const/mod.rs index 0dc58907d12..a8ce58982d9 100644 --- a/mir-state-analysis/src/utils/const/mod.rs +++ b/mir-state-analysis/src/utils/const/mod.rs @@ -8,7 +8,7 @@ // I've kept this around as a skeleton for working with `ConstValue` and `ConstAlloc` if we ever need that in the future. use prusti_rustc_interface::{ - abi::{HasDataLayout, Size, Variants, TargetDataLayout, TagEncoding, VariantIdx}, + abi::{HasDataLayout, Size, TagEncoding, TargetDataLayout, VariantIdx, Variants}, borrowck::{ borrow_set::BorrowData, consumers::{BorrowIndex, OutlivesConstraint, RichLocation}, @@ -28,8 +28,9 @@ use prusti_rustc_interface::{ RETURN_PLACE, }, ty::{ - FloatTy, GenericArgKind, Instance, ParamEnv, ParamEnvAnd, RegionVid, ScalarInt, Ty, - TyKind, layout::{TyAndLayout, LayoutError, HasTyCtxt, HasParamEnv}, TyCtxt, FieldDef, + layout::{HasParamEnv, HasTyCtxt, LayoutError, TyAndLayout}, + FieldDef, FloatTy, GenericArgKind, Instance, ParamEnv, ParamEnvAnd, RegionVid, + ScalarInt, Ty, TyCtxt, TyKind, }, }, }; @@ -238,7 +239,12 @@ fn eval_range<'tcx>( let adt_layout = layout_of(ty, rp).unwrap(); let index = match adt_layout.variants { Variants::Single { index } => index, - Variants::Multiple { tag, ref tag_encoding, tag_field, ref variants } => { + Variants::Multiple { + tag, + ref tag_encoding, + tag_field, + ref variants, + } => { let discr_type = ty.discriminant_ty(rp.tcx()); // TODO: compare with `tag.primitive().to_int_ty(rp.tcx())` @@ -250,16 +256,26 @@ fn eval_range<'tcx>( let discr_bits = discr_bits.kind.to_bits().unwrap(); match *tag_encoding { TagEncoding::Direct => { - adt.discriminants(rp.tcx()).find(|(_, var)| var.val == discr_bits).unwrap().0 + adt.discriminants(rp.tcx()) + .find(|(_, var)| var.val == discr_bits) + .unwrap() + .0 } - TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => { + TagEncoding::Niche { + untagged_variant, + ref niche_variants, + niche_start, + } => { let variants_start = niche_variants.start().as_u32(); let variants_end = niche_variants.end().as_u32(); let variant_index_relative = discr_bits - niche_start; if variant_index_relative <= u128::from(variants_end - variants_start) { // We should now be within the `u32` range. - let variant_index_relative = u32::try_from(variant_index_relative).unwrap(); - VariantIdx::from_u32(variants_start.checked_add(variant_index_relative).unwrap()) + let variant_index_relative = + u32::try_from(variant_index_relative).unwrap(); + VariantIdx::from_u32( + variants_start.checked_add(variant_index_relative).unwrap(), + ) } else { untagged_variant } @@ -269,11 +285,18 @@ fn eval_range<'tcx>( }; let layout = adt_layout.for_variant(cx, index); let variant = adt.variant(index); - let fields = variant.fields.iter_enumerated().map(|(idx, val)| { - let field = layout.field(cx, idx.as_usize()); - let range = range.subrange(alloc_range(layout.fields.offset(idx.as_usize()), field.size)); - (val, eval_range(ca, range, val.ty(rp.tcx(), sub), rp)) - }).collect(); + let fields = variant + .fields + .iter_enumerated() + .map(|(idx, val)| { + let field = layout.field(cx, idx.as_usize()); + let range = range.subrange(alloc_range( + layout.fields.offset(idx.as_usize()), + field.size, + )); + (val, eval_range(ca, range, val.ty(rp.tcx(), sub), rp)) + }) + .collect(); EvaluatedConst { ty, kind: EcKind::Adt(index, fields), @@ -289,7 +312,7 @@ fn eval_range<'tcx>( ca.inner().provenance() ); let mut range = range; - let is_ptr = ty.is_any_ptr();// !ca.inner().provenance().range_empty(range, &rp.tcx()); + let is_ptr = ty.is_any_ptr(); // !ca.inner().provenance().range_empty(range, &rp.tcx()); if is_ptr { let ptr_size = rp.tcx().data_layout().pointer_size; if ptr_size != range.size { @@ -306,7 +329,13 @@ fn eval_range<'tcx>( }; } } - assert_eq!(is_ptr, ty.is_any_ptr(), "({range:?}, {is_ptr}, {ty:?}): {:?} / {:?}", ca.inner().init_mask(), ca.inner().provenance()); + assert_eq!( + is_ptr, + ty.is_any_ptr(), + "({range:?}, {is_ptr}, {ty:?}): {:?} / {:?}", + ca.inner().init_mask(), + ca.inner().provenance() + ); match ca.inner().read_scalar(&rp.tcx(), range, is_ptr) { Ok(scalar) => scalar.eval(ty, rp), Err(err) => panic!( @@ -328,7 +357,10 @@ fn eval_range<'tcx>( // for (_, ptr) in provenance.ptrs().iter() {} } -fn layout_of<'tcx>(ty: Ty<'tcx>, rp: PlaceRepacker<'_, 'tcx>) -> Result, &'tcx LayoutError<'tcx>> { +fn layout_of<'tcx>( + ty: Ty<'tcx>, + rp: PlaceRepacker<'_, 'tcx>, +) -> Result, &'tcx LayoutError<'tcx>> { let peat = ParamEnvAnd { param_env: ParamEnv::reveal_all(), value: ty, diff --git a/mir-state-analysis/src/utils/place.rs b/mir-state-analysis/src/utils/place.rs index 57a371fbc3b..a58d95972f4 100644 --- a/mir-state-analysis/src/utils/place.rs +++ b/mir-state-analysis/src/utils/place.rs @@ -214,8 +214,7 @@ impl<'tcx> Place<'tcx> { pub fn last_projection_ty(self) -> Option> { self.last_projection().and_then(|(_, proj)| match proj { - ProjectionElem::Field(_, ty) | - ProjectionElem::OpaqueCast(ty) => Some(ty), + ProjectionElem::Field(_, ty) | ProjectionElem::OpaqueCast(ty) => Some(ty), _ => None, }) } diff --git a/mir-state-analysis/src/utils/repacker.rs b/mir-state-analysis/src/utils/repacker.rs index 86057293c12..83b9d099b3a 100644 --- a/mir-state-analysis/src/utils/repacker.rs +++ b/mir-state-analysis/src/utils/repacker.rs @@ -13,7 +13,7 @@ use prusti_rustc_interface::{ tcx::PlaceTy, Body, HasLocalDecls, Local, Mutability, Place as MirPlace, PlaceElem, ProjectionElem, Promoted, }, - ty::{RegionVid, Region, Ty, TyCtxt, TyKind}, + ty::{Region, RegionVid, Ty, TyCtxt, TyKind}, }, target::abi::FieldIdx, }; @@ -56,7 +56,11 @@ pub struct PlaceRepacker<'a, 'tcx: 'a> { } impl<'a, 'tcx: 'a> PlaceRepacker<'a, 'tcx> { - pub fn new(mir: &'a Body<'tcx>, promoted: &'a IndexVec>, tcx: TyCtxt<'tcx>) -> Self { + pub fn new( + mir: &'a Body<'tcx>, + promoted: &'a IndexVec>, + tcx: TyCtxt<'tcx>, + ) -> Self { Self { mir, promoted, tcx } } @@ -365,11 +369,12 @@ impl<'tcx> Place<'tcx> { self, repacker: PlaceRepacker<'_, 'tcx>, ) -> impl Iterator, Ty<'tcx>, Mutability)>> { - self.projection_tys(repacker).filter_map(|(ty, _)| match ty.ty.kind() { - &TyKind::Ref(r, ty, m) => Some(Some((r, ty, m))), - &TyKind::RawPtr(_) => Some(None), - _ => None, - }) + self.projection_tys(repacker) + .filter_map(|(ty, _)| match ty.ty.kind() { + &TyKind::Ref(r, ty, m) => Some(Some((r, ty, m))), + &TyKind::RawPtr(_) => Some(None), + _ => None, + }) } pub fn projects_shared_ref(self, repacker: PlaceRepacker<'_, 'tcx>) -> bool { @@ -428,10 +433,13 @@ impl<'tcx> Place<'tcx> { } None })(); - let new_result = self.projection_tys(repacker).find(|(typ, _)| predicate(*typ)).map(|(_, proj)| { - let projection = repacker.tcx.mk_place_elems(proj); - Self::new(self.local, projection) - }); + let new_result = self + .projection_tys(repacker) + .find(|(typ, _)| predicate(*typ)) + .map(|(_, proj)| { + let projection = repacker.tcx.mk_place_elems(proj); + Self::new(self.local, projection) + }); assert_eq!(old_result, new_result); new_result } @@ -468,12 +476,9 @@ impl<'tcx> Place<'tcx> { } pub fn mk_place_elem(self, elem: PlaceElem<'tcx>, repacker: PlaceRepacker<'_, 'tcx>) -> Self { - let elems = repacker.tcx.mk_place_elems_from_iter( - self.projection - .iter() - .copied() - .chain([elem]), - ); + let elems = repacker + .tcx + .mk_place_elems_from_iter(self.projection.iter().copied().chain([elem])); Self::new(self.local, elems) } diff --git a/prusti-interface/src/environment/body.rs b/prusti-interface/src/environment/body.rs index 7db5fdac4af..9492cce70a4 100644 --- a/prusti-interface/src/environment/body.rs +++ b/prusti-interface/src/environment/body.rs @@ -19,7 +19,10 @@ use crate::environment::{ /// Stores any possible MIR body (from the compiler) that /// Prusti might want to work with. Cheap to clone #[derive(Clone, TyEncodable, TyDecodable)] -pub struct MirBody<'tcx>(Rc>, Rc>>); +pub struct MirBody<'tcx>( + Rc>, + Rc>>, +); impl<'tcx> MirBody<'tcx> { pub fn body(&self) -> Rc> { self.0.clone() @@ -152,7 +155,10 @@ impl<'tcx> EnvBody<'tcx> { }; BodyWithBorrowckFacts { - body: MirBody(Rc::new(body_with_facts.body), Rc::new(body_with_facts.promoted)), + body: MirBody( + Rc::new(body_with_facts.body), + Rc::new(body_with_facts.promoted), + ), borrowck_facts: Rc::new(facts), borrowck_facts2: Rc::new(facts2), } diff --git a/prusti-interface/src/environment/mir_storage.rs b/prusti-interface/src/environment/mir_storage.rs index 6a67958ca56..dc9d490576e 100644 --- a/prusti-interface/src/environment/mir_storage.rs +++ b/prusti-interface/src/environment/mir_storage.rs @@ -7,10 +7,10 @@ //! `'tcx`. use prusti_rustc_interface::{ - index::IndexVec, borrowck::consumers::BodyWithBorrowckFacts, data_structures::fx::FxHashMap, hir::def_id::LocalDefId, + index::IndexVec, middle::{mir, ty::TyCtxt}, }; use std::{cell::RefCell, thread_local}; @@ -71,7 +71,8 @@ pub unsafe fn store_promoted_mir_body<'tcx>( ) { // SAFETY: See the module level comment. let body: mir::Body<'static> = unsafe { std::mem::transmute(body) }; - let promoted: IndexVec> = unsafe { std::mem::transmute(promoted) }; + let promoted: IndexVec> = + unsafe { std::mem::transmute(promoted) }; SHARED_STATE_WITHOUT_FACTS.with(|state| { let mut map = state.borrow_mut(); assert!(map.insert(def_id, (body, promoted)).is_none()); @@ -83,7 +84,10 @@ pub(super) unsafe fn retrieve_promoted_mir_body<'tcx>( _tcx: TyCtxt<'tcx>, def_id: LocalDefId, ) -> (mir::Body<'tcx>, IndexVec>) { - let body_without_facts: (mir::Body<'static>, IndexVec>) = SHARED_STATE_WITHOUT_FACTS.with(|state| { + let body_without_facts: ( + mir::Body<'static>, + IndexVec>, + ) = SHARED_STATE_WITHOUT_FACTS.with(|state| { let mut map = state.borrow_mut(); map.remove(&def_id).unwrap() }); diff --git a/prusti-interface/src/environment/procedure.rs b/prusti-interface/src/environment/procedure.rs index be4cc4fbd29..a11c6a141b4 100644 --- a/prusti-interface/src/environment/procedure.rs +++ b/prusti-interface/src/environment/procedure.rs @@ -11,11 +11,13 @@ use crate::{ }; use log::{debug, trace}; use prusti_rustc_interface::{ - index::IndexVec, data_structures::fx::{FxHashMap, FxHashSet}, hir::def_id, + index::IndexVec, middle::{ - mir::{self, AggregateKind, BasicBlock, BasicBlockData, Body, Rvalue, StatementKind, Promoted}, + mir::{ + self, AggregateKind, BasicBlock, BasicBlockData, Body, Promoted, Rvalue, StatementKind, + }, ty::{Ty, TyCtxt}, }, span::Span, diff --git a/prusti/src/callbacks.rs b/prusti/src/callbacks.rs index f564b2c7bc2..6948f49ca02 100644 --- a/prusti/src/callbacks.rs +++ b/prusti/src/callbacks.rs @@ -73,7 +73,12 @@ fn mir_promoted<'tcx>( // SAFETY: This is safe because we are feeding in the same `tcx` that is // going to be used as a witness when pulling out the data. unsafe { - mir_storage::store_promoted_mir_body(tcx, def_id, result.0.borrow().clone(), result.1.borrow().clone()); + mir_storage::store_promoted_mir_body( + tcx, + def_id, + result.0.borrow().clone(), + result.1.borrow().clone(), + ); } result } @@ -188,7 +193,14 @@ impl prusti_rustc_interface::driver::Callbacks for PrustiCompilerCalls { let name = env.name.get_unique_item_name(*proc_id); println!("Calculating CG for: {name} ({:?})", mir.span); - test_coupling_graph(&*mir.body(), &*mir.promoted(), &*facts, &*facts2, tcx, config::top_crates()); + test_coupling_graph( + &*mir.body(), + &*mir.promoted(), + &*facts, + &*facts2, + tcx, + config::top_crates(), + ); } } if !config::test_free_pcs() && !config::test_coupling_graph() { From 8ac71f4850f43306c2d5f5ba61acef53c5892467 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 56/58] 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 be0e664f38a..561649a60c0 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); @@ -95,27 +95,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 bc36bd212dde5b15599616f3fabecabb32765b08 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 57/58] 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 08db6e80aea..ca5c728b7e5 100644 --- a/mir-state-analysis/src/free_pcs/check/checker.rs +++ b/mir-state-analysis/src/free_pcs/check/checker.rs @@ -44,7 +44,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); @@ -56,7 +56,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; @@ -73,7 +73,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); @@ -85,7 +85,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; @@ -104,6 +104,7 @@ pub(crate) fn check(mut cursor: FreePcsAnalysis<'_, '_>) { op.update_free( &mut from.summary, body.basic_blocks[succ.location.block].is_cleanup, + false, rp, ); } @@ -118,6 +119,7 @@ impl<'tcx> RepackOp<'tcx> { self, state: &mut CapabilitySummary<'tcx>, is_cleanup: bool, + can_downcast: bool, rp: PlaceRepacker<'_, 'tcx>, ) { match self { @@ -147,6 +149,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 4924592065f..0b98b36cb0b 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 @@ -127,16 +127,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 @@ -153,7 +157,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 b2d60424e2e..7d276213cea 100644 --- a/mir-state-analysis/src/free_pcs/impl/update.rs +++ b/mir-state-analysis/src/free_pcs/impl/update.rs @@ -83,8 +83,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) From 54b75fb9b163de61e639c514ba7b26119421fdef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=C3=A1=C5=A1=20Fiala?= Date: Mon, 27 Nov 2023 14:05:53 +0100 Subject: [PATCH 58/58] Update checker.rs --- mir-state-analysis/src/coupling_graph/check/checker.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mir-state-analysis/src/coupling_graph/check/checker.rs b/mir-state-analysis/src/coupling_graph/check/checker.rs index 39b7ea73309..73b66f621dc 100644 --- a/mir-state-analysis/src/coupling_graph/check/checker.rs +++ b/mir-state-analysis/src/coupling_graph/check/checker.rs @@ -107,7 +107,7 @@ pub(crate) fn check<'tcx>( // Repacks for op in fpcs_after.repacks_middle { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, false, true, rp); } // Couplings bound set let bound: Box) -> CapabilityKind> = @@ -121,7 +121,7 @@ pub(crate) fn check<'tcx>( fpcs.visit_terminator(data.terminator(), loc); // Repacks for op in fpcs_after.repacks { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, false, true, rp); } // Statement post assert!(fpcs.repackings.is_empty()); @@ -153,6 +153,7 @@ pub(crate) fn check<'tcx>( op.update_free( &mut fpcs_from.summary, body.basic_blocks[fpcs_succ.location.block].is_cleanup, + false, rp, ); } @@ -213,7 +214,7 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { // Repacks for op in fpcs_after.repacks_middle { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, false, true, rp); } // Couplings bound set let bound: Box) -> CapabilityKind> = @@ -228,7 +229,7 @@ impl<'a, 'tcx> CouplingState<'a, 'tcx> { assert!(fpcs.repackings.is_empty()); // Repacks for op in fpcs_after.repacks { - op.update_free(&mut fpcs.summary, false, rp); + op.update_free(&mut fpcs.summary, false, true, rp); } // Statement post assert!(fpcs.repackings.is_empty());