mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-28 11:07:42 +00:00
Auto merge of #3033 - rust-lang:rustup-2023-08-21, r=RalfJung
Automatic sync from rustc
This commit is contained in:
commit
83ca92243c
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@ -323,17 +323,6 @@ jobs:
|
||||
NO_DEBUG_ASSERTIONS: 1
|
||||
NO_OVERFLOW_CHECKS: 1
|
||||
os: macos-13
|
||||
- name: dist-x86_64-apple-alt
|
||||
env:
|
||||
SCRIPT: "./x.py dist bootstrap --include-default-paths"
|
||||
RUST_CONFIGURE_ARGS: "--enable-extended --enable-profiler --set rust.jemalloc --set llvm.ninja=false"
|
||||
RUSTC_RETRY_LINKER_ON_SEGFAULT: 1
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.7
|
||||
SELECT_XCODE: /Applications/Xcode_13.4.1.app
|
||||
NO_LLVM_ASSERTIONS: 1
|
||||
NO_DEBUG_ASSERTIONS: 1
|
||||
NO_OVERFLOW_CHECKS: 1
|
||||
os: macos-13
|
||||
- name: x86_64-apple-1
|
||||
env:
|
||||
SCRIPT: "./x.py --stage 2 test --skip tests/ui --skip tests/rustdoc --skip tests/run-make-fulldeps"
|
||||
|
12
Cargo.lock
12
Cargo.lock
@ -167,9 +167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.71"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -5126,9 +5126,9 @@ checksum = "aac81b6fd6beb5884b0cf3321b8117e6e5d47ecb6fc89f414cfdcca8b2fe2dd8"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
@ -5155,9 +5155,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
105
RELEASES.md
105
RELEASES.md
@ -1,3 +1,108 @@
|
||||
Version 1.72.0 (2023-08-24)
|
||||
==========================
|
||||
|
||||
<a id="1.72.0-Language"></a>
|
||||
|
||||
Language
|
||||
--------
|
||||
|
||||
- [Replace const eval limit by a lint and add an exponential backoff warning](https://github.com/rust-lang/rust/pull/103877/)
|
||||
- [expand: Change how `#![cfg(FALSE)]` behaves on crate root](https://github.com/rust-lang/rust/pull/110141/)
|
||||
- [Stabilize inline asm for LoongArch64](https://github.com/rust-lang/rust/pull/111235/)
|
||||
- [Uplift `clippy::undropped_manually_drops` lint](https://github.com/rust-lang/rust/pull/111530/)
|
||||
- [Uplift `clippy::invalid_utf8_in_unchecked` lint](https://github.com/rust-lang/rust/pull/111543/)
|
||||
- [Uplift `clippy::cast_ref_to_mut` lint](https://github.com/rust-lang/rust/pull/111567/)
|
||||
- [Uplift `clippy::cmp_nan` lint](https://github.com/rust-lang/rust/pull/111818/)
|
||||
- [resolve: Remove artificial import ambiguity errors](https://github.com/rust-lang/rust/pull/112086/)
|
||||
- [Don't require associated types with Self: Sized bounds in `dyn Trait` objects](https://github.com/rust-lang/rust/pull/112319/)
|
||||
|
||||
<a id="1.72.0-Compiler"></a>
|
||||
|
||||
Compiler
|
||||
--------
|
||||
|
||||
- [Remember names of `cfg`-ed out items to mention them in diagnostics](https://github.com/rust-lang/rust/pull/109005/)
|
||||
- [Support for native WASM exceptions](https://github.com/rust-lang/rust/pull/111322/)
|
||||
- [Add support for NetBSD/aarch64-be (big-endian arm64).](https://github.com/rust-lang/rust/pull/111326/)
|
||||
- [Write to stdout if `-` is given as output file](https://github.com/rust-lang/rust/pull/111626/)
|
||||
- [Force all native libraries to be statically linked when linking a static binary](https://github.com/rust-lang/rust/pull/111698/)
|
||||
- [Add Tier 3 support for `loongarch64-unknown-none*`](https://github.com/rust-lang/rust/pull/112310/)
|
||||
- [Prevent `.eh_frame` from being emitted for `-C panic=abort`](https://github.com/rust-lang/rust/pull/112403/)
|
||||
- [Support 128-bit enum variant in debuginfo codegen](https://github.com/rust-lang/rust/pull/112474/)
|
||||
- [compiler: update solaris/illumos to enable tsan support.](https://github.com/rust-lang/rust/pull/112039/)
|
||||
|
||||
Refer to Rust's [platform support page][platform-support-doc]
|
||||
for more information on Rust's tiered platform support.
|
||||
|
||||
<a id="1.72.0-Libraries"></a>
|
||||
|
||||
Libraries
|
||||
---------
|
||||
|
||||
- [Document memory orderings of `thread::{park, unpark}`](https://github.com/rust-lang/rust/pull/99587/)
|
||||
- [io: soften ‘at most one write attempt’ requirement in io::Write::write](https://github.com/rust-lang/rust/pull/107200/)
|
||||
- [Specify behavior of HashSet::insert](https://github.com/rust-lang/rust/pull/107619/)
|
||||
- [Relax implicit `T: Sized` bounds on `BufReader<T>`, `BufWriter<T>` and `LineWriter<T>`](https://github.com/rust-lang/rust/pull/111074/)
|
||||
- [Update runtime guarantee for `select_nth_unstable`](https://github.com/rust-lang/rust/pull/111974/)
|
||||
- [Return `Ok` on kill if process has already exited](https://github.com/rust-lang/rust/pull/112594/)
|
||||
- [Implement PartialOrd for `Vec`s over different allocators](https://github.com/rust-lang/rust/pull/112632/)
|
||||
- [Use 128 bits for TypeId hash](https://github.com/rust-lang/rust/pull/109953/)
|
||||
- [Don't drain-on-drop in DrainFilter impls of various collections.](https://github.com/rust-lang/rust/pull/104455/)
|
||||
- [Make `{Arc,Rc,Weak}::ptr_eq` ignore pointer metadata](https://github.com/rust-lang/rust/pull/106450/)
|
||||
|
||||
<a id="1.72.0-Rustdoc"></a>
|
||||
|
||||
Rustdoc
|
||||
-------
|
||||
|
||||
- [Allow whitespace as path separator like double colon](https://github.com/rust-lang/rust/pull/108537/)
|
||||
- [Add search result item types after their name](https://github.com/rust-lang/rust/pull/110688/)
|
||||
- [Search for slices and arrays by type with `[]`](https://github.com/rust-lang/rust/pull/111958/)
|
||||
- [Clean up type unification and "unboxing"](https://github.com/rust-lang/rust/pull/112233/)
|
||||
|
||||
<a id="1.72.0-Stabilized-APIs"></a>
|
||||
|
||||
Stabilized APIs
|
||||
---------------
|
||||
|
||||
- [`impl<T: Send> Sync for mpsc::Sender<T>`](https://doc.rust-lang.org/nightly/std/sync/mpsc/struct.Sender.html#impl-Sync-for-Sender%3CT%3E)
|
||||
- [`impl TryFrom<&OsStr> for &str`](https://doc.rust-lang.org/nightly/std/primitive.str.html#impl-TryFrom%3C%26'a+OsStr%3E-for-%26'a+str)
|
||||
- [`String::leak`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html#method.leak)
|
||||
|
||||
These APIs are now stable in const contexts:
|
||||
|
||||
- [`CStr::from_bytes_with_nul`](https://doc.rust-lang.org/nightly/std/ffi/struct.CStr.html#method.from_bytes_with_nul)
|
||||
- [`CStr::to_bytes`](https://doc.rust-lang.org/nightly/std/ffi/struct.CStr.html#method.from_bytes_with_nul)
|
||||
- [`CStr::to_bytes_with_nul`](https://doc.rust-lang.org/nightly/std/ffi/struct.CStr.html#method.from_bytes_with_nul)
|
||||
- [`CStr::to_str`](https://doc.rust-lang.org/nightly/std/ffi/struct.CStr.html#method.from_bytes_with_nul)
|
||||
|
||||
<a id="1.72.0-Cargo"></a>
|
||||
|
||||
Cargo
|
||||
-----
|
||||
|
||||
- Enable `-Zdoctest-in-workspace` by default. When running each documentation
|
||||
test, the working directory is set to the root directory of the package the
|
||||
test belongs to.
|
||||
[docs](https://doc.rust-lang.org/nightly/cargo/commands/cargo-test.html#working-directory-of-tests)
|
||||
[#12221](https://github.com/rust-lang/cargo/pull/12221)
|
||||
[#12288](https://github.com/rust-lang/cargo/pull/12288)
|
||||
- Add support of the "default" keyword to reset previously set `build.jobs`
|
||||
parallelism back to the default.
|
||||
[#12222](https://github.com/rust-lang/cargo/pull/12222)
|
||||
|
||||
<a id="1.72.0-Compatibility-Notes"></a>
|
||||
|
||||
Compatibility Notes
|
||||
-------------------
|
||||
|
||||
- [Alter `Display` for `Ipv6Addr` for IPv4-compatible addresses](https://github.com/rust-lang/rust/pull/112606/)
|
||||
- Cargo changed feature name validation check to a hard error. The warning was
|
||||
added in Rust 1.49. These extended characters aren't allowed on crates.io, so
|
||||
this should only impact users of other registries, or people who don't publish
|
||||
to a registry.
|
||||
[#12291](https://github.com/rust-lang/cargo/pull/12291)
|
||||
|
||||
Version 1.71.0 (2023-07-13)
|
||||
==========================
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/",
|
||||
test(no_crate_inject, attr(deny(warnings)))
|
||||
)]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(dropck_eyepatch)]
|
||||
#![feature(new_uninit)]
|
||||
#![feature(maybe_uninit_slice)]
|
||||
@ -30,11 +31,11 @@ use smallvec::SmallVec;
|
||||
|
||||
use std::alloc::Layout;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cmp;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ptr::{self, NonNull};
|
||||
use std::slice;
|
||||
use std::{cmp, intrinsics};
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
@ -363,6 +364,22 @@ unsafe impl<#[may_dangle] T> Drop for TypedArena<T> {
|
||||
|
||||
unsafe impl<T: Send> Send for TypedArena<T> {}
|
||||
|
||||
#[inline(always)]
|
||||
fn align_down(val: usize, align: usize) -> usize {
|
||||
debug_assert!(align.is_power_of_two());
|
||||
val & !(align - 1)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn align_up(val: usize, align: usize) -> usize {
|
||||
debug_assert!(align.is_power_of_two());
|
||||
(val + align - 1) & !(align - 1)
|
||||
}
|
||||
|
||||
// Pointer alignment is common in compiler types, so keep `DroplessArena` aligned to them
|
||||
// to optimize away alignment code.
|
||||
const DROPLESS_ALIGNMENT: usize = mem::align_of::<usize>();
|
||||
|
||||
/// An arena that can hold objects of multiple different types that impl `Copy`
|
||||
/// and/or satisfy `!mem::needs_drop`.
|
||||
pub struct DroplessArena {
|
||||
@ -375,6 +392,8 @@ pub struct DroplessArena {
|
||||
/// start. (This is slightly simpler and faster than allocating upwards,
|
||||
/// see <https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html>.)
|
||||
/// When this pointer crosses the start pointer, a new chunk is allocated.
|
||||
///
|
||||
/// This is kept aligned to DROPLESS_ALIGNMENT.
|
||||
end: Cell<*mut u8>,
|
||||
|
||||
/// A vector of arena chunks.
|
||||
@ -395,9 +414,11 @@ impl Default for DroplessArena {
|
||||
}
|
||||
|
||||
impl DroplessArena {
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn grow(&self, additional: usize) {
|
||||
fn grow(&self, layout: Layout) {
|
||||
// Add some padding so we can align `self.end` while
|
||||
// stilling fitting in a `layout` allocation.
|
||||
let additional = layout.size() + cmp::max(DROPLESS_ALIGNMENT, layout.align()) - 1;
|
||||
|
||||
unsafe {
|
||||
let mut chunks = self.chunks.borrow_mut();
|
||||
let mut new_cap;
|
||||
@ -416,13 +437,35 @@ impl DroplessArena {
|
||||
// Also ensure that this chunk can fit `additional`.
|
||||
new_cap = cmp::max(additional, new_cap);
|
||||
|
||||
let mut chunk = ArenaChunk::new(new_cap);
|
||||
let mut chunk = ArenaChunk::new(align_up(new_cap, PAGE));
|
||||
self.start.set(chunk.start());
|
||||
self.end.set(chunk.end());
|
||||
|
||||
// Align the end to DROPLESS_ALIGNMENT
|
||||
let end = align_down(chunk.end().addr(), DROPLESS_ALIGNMENT);
|
||||
|
||||
// Make sure we don't go past `start`. This should not happen since the allocation
|
||||
// should be at least DROPLESS_ALIGNMENT - 1 bytes.
|
||||
debug_assert!(chunk.start().addr() <= end);
|
||||
|
||||
self.end.set(chunk.end().with_addr(end));
|
||||
|
||||
chunks.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn grow_and_alloc_raw(&self, layout: Layout) -> *mut u8 {
|
||||
self.grow(layout);
|
||||
self.alloc_raw_without_grow(layout).unwrap()
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn grow_and_alloc<T>(&self) -> *mut u8 {
|
||||
self.grow_and_alloc_raw(Layout::new::<T>())
|
||||
}
|
||||
|
||||
/// Allocates a byte slice with specified layout from the current memory
|
||||
/// chunk. Returns `None` if there is no free space left to satisfy the
|
||||
/// request.
|
||||
@ -432,12 +475,17 @@ impl DroplessArena {
|
||||
let old_end = self.end.get();
|
||||
let end = old_end.addr();
|
||||
|
||||
let align = layout.align();
|
||||
let bytes = layout.size();
|
||||
// Align allocated bytes so that `self.end` stays aligned to DROPLESS_ALIGNMENT
|
||||
let bytes = align_up(layout.size(), DROPLESS_ALIGNMENT);
|
||||
|
||||
let new_end = end.checked_sub(bytes)? & !(align - 1);
|
||||
// Tell LLVM that `end` is aligned to DROPLESS_ALIGNMENT
|
||||
unsafe { intrinsics::assume(end == align_down(end, DROPLESS_ALIGNMENT)) };
|
||||
|
||||
let new_end = align_down(end.checked_sub(bytes)?, layout.align());
|
||||
if start <= new_end {
|
||||
let new_end = old_end.with_addr(new_end);
|
||||
// `new_end` is aligned to DROPLESS_ALIGNMENT as `align_down` preserves alignment
|
||||
// as both `end` and `bytes` are already aligned to DROPLESS_ALIGNMENT.
|
||||
self.end.set(new_end);
|
||||
Some(new_end)
|
||||
} else {
|
||||
@ -448,21 +496,26 @@ impl DroplessArena {
|
||||
#[inline]
|
||||
pub fn alloc_raw(&self, layout: Layout) -> *mut u8 {
|
||||
assert!(layout.size() != 0);
|
||||
loop {
|
||||
if let Some(a) = self.alloc_raw_without_grow(layout) {
|
||||
break a;
|
||||
return a;
|
||||
}
|
||||
// No free space left. Allocate a new chunk to satisfy the request.
|
||||
// On failure the grow will panic or abort.
|
||||
self.grow(layout.size());
|
||||
}
|
||||
self.grow_and_alloc_raw(layout)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn alloc<T>(&self, object: T) -> &mut T {
|
||||
assert!(!mem::needs_drop::<T>());
|
||||
assert!(mem::size_of::<T>() != 0);
|
||||
|
||||
let mem = self.alloc_raw(Layout::for_value::<T>(&object)) as *mut T;
|
||||
let mem = if let Some(a) = self.alloc_raw_without_grow(Layout::for_value::<T>(&object)) {
|
||||
a
|
||||
} else {
|
||||
// No free space left. Allocate a new chunk to satisfy the request.
|
||||
// On failure the grow will panic or abort.
|
||||
self.grow_and_alloc::<T>()
|
||||
} as *mut T;
|
||||
|
||||
unsafe {
|
||||
// Write into uninitialized memory.
|
||||
|
@ -13,6 +13,7 @@ use crate::tokenstream::*;
|
||||
use crate::{ast::*, StaticItem};
|
||||
|
||||
use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
|
||||
use rustc_data_structures::stack::ensure_sufficient_stack;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::Ident;
|
||||
@ -1369,7 +1370,7 @@ pub fn noop_visit_expr<T: MutVisitor>(
|
||||
ExprKind::If(cond, tr, fl) => {
|
||||
vis.visit_expr(cond);
|
||||
vis.visit_block(tr);
|
||||
visit_opt(fl, |fl| vis.visit_expr(fl));
|
||||
visit_opt(fl, |fl| ensure_sufficient_stack(|| vis.visit_expr(fl)));
|
||||
}
|
||||
ExprKind::While(cond, body, label) => {
|
||||
vis.visit_expr(cond);
|
||||
|
@ -150,6 +150,8 @@ pub fn print_crate<'a>(
|
||||
/// and also addresses some specific regressions described in #63896 and #73345.
|
||||
fn tt_prepend_space(tt: &TokenTree, prev: &TokenTree) -> bool {
|
||||
if let TokenTree::Token(token, _) = prev {
|
||||
// No space after these tokens, e.g. `x.y`, `$e`
|
||||
// (The carets point to `prev`.) ^ ^
|
||||
if matches!(token.kind, token::Dot | token::Dollar) {
|
||||
return false;
|
||||
}
|
||||
@ -158,10 +160,19 @@ fn tt_prepend_space(tt: &TokenTree, prev: &TokenTree) -> bool {
|
||||
}
|
||||
}
|
||||
match tt {
|
||||
// No space before these tokens, e.g. `foo,`, `println!`, `x.y`
|
||||
// (The carets point to `token`.) ^ ^ ^
|
||||
//
|
||||
// FIXME: having `Not` here works well for macro invocations like
|
||||
// `println!()`, but is bad when `!` means "logical not" or "the never
|
||||
// type", where the lack of space causes ugliness like this:
|
||||
// `Fn() ->!`, `x =! y`, `if! x { f(); }`.
|
||||
TokenTree::Token(token, _) => !matches!(token.kind, token::Comma | token::Not | token::Dot),
|
||||
// No space before parentheses if preceded by these tokens, e.g. `foo(...)`
|
||||
TokenTree::Delimited(_, Delimiter::Parenthesis, _) => {
|
||||
!matches!(prev, TokenTree::Token(Token { kind: token::Ident(..), .. }, _))
|
||||
}
|
||||
// No space before brackets if preceded by these tokens, e.g. `#[...]`
|
||||
TokenTree::Delimited(_, Delimiter::Bracket, _) => {
|
||||
!matches!(prev, TokenTree::Token(Token { kind: token::Pound, .. }, _))
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ pub use super::{
|
||||
/// will be retrieved.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ConsumerOptions {
|
||||
/// Retrieve the [`Body`] along with the [`BorrowSet`](super::borrow_set::BorrowSet)
|
||||
/// Retrieve the [`Body`] along with the [`BorrowSet`]
|
||||
/// and [`RegionInferenceContext`]. If you would like the body only, use
|
||||
/// [`TyCtxt::mir_promoted`].
|
||||
///
|
||||
|
@ -2,12 +2,14 @@
|
||||
#![deny(rustc::diagnostic_outside_of_impl)]
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::{self, BasicBlock, Body, Location, Place};
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, Body, CallReturnPlaces, Location, Place, TerminatorEdges,
|
||||
};
|
||||
use rustc_middle::ty::RegionVid;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces};
|
||||
use rustc_mir_dataflow::ResultsVisitable;
|
||||
use rustc_mir_dataflow::{self, fmt::DebugWithContext, CallReturnPlaces, GenKill};
|
||||
use rustc_mir_dataflow::{self, fmt::DebugWithContext, GenKill};
|
||||
use rustc_mir_dataflow::{Analysis, Direction, Results};
|
||||
use std::fmt;
|
||||
|
||||
@ -334,6 +336,10 @@ impl<'tcx> rustc_mir_dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
|
||||
impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
|
||||
type Idx = BorrowIndex;
|
||||
|
||||
fn domain_size(&self, _: &mir::Body<'tcx>) -> usize {
|
||||
self.borrow_set.len()
|
||||
}
|
||||
|
||||
fn before_statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
@ -400,12 +406,12 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
|
||||
self.kill_loans_out_of_scope_at_location(trans, location);
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
_location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
if let mir::TerminatorKind::InlineAsm { operands, .. } = &terminator.kind {
|
||||
for op in operands {
|
||||
if let mir::InlineAsmOperand::Out { place: Some(place), .. }
|
||||
@ -415,6 +421,7 @@ impl<'tcx> rustc_mir_dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
|
@ -159,7 +159,9 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
|
||||
|
||||
self.mutate_place(location, *resume_arg, Deep);
|
||||
}
|
||||
TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
|
||||
TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::GeneratorDrop => {
|
||||
// Invalidate all borrows of local places
|
||||
let borrow_set = self.borrow_set;
|
||||
let start = self.location_table.start_index(location);
|
||||
@ -200,7 +202,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> {
|
||||
}
|
||||
}
|
||||
TerminatorKind::Goto { target: _ }
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
|
||||
| TerminatorKind::FalseUnwind { real_target: _, unwind: _ } => {
|
||||
|
@ -770,9 +770,9 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
|
||||
}
|
||||
|
||||
TerminatorKind::Goto { target: _ }
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
|
||||
@ -803,7 +803,9 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
|
||||
}
|
||||
}
|
||||
|
||||
TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => {
|
||||
TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::GeneratorDrop => {
|
||||
// Returning from the function implicitly kills storage for all locals and statics.
|
||||
// Often, the storage will already have been killed by an explicit
|
||||
// StorageDead, but we don't always emit those (notably on unwind paths),
|
||||
@ -815,7 +817,7 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
|
||||
}
|
||||
}
|
||||
|
||||
TerminatorKind::Terminate
|
||||
TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::Drop { .. }
|
||||
|
@ -1333,8 +1333,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
||||
debug!("terminator kind: {:?}", term.kind);
|
||||
match &term.kind {
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Unreachable
|
||||
@ -1608,12 +1608,12 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
||||
self.assert_iscleanup(body, block_data, *target, is_cleanup);
|
||||
}
|
||||
}
|
||||
TerminatorKind::Resume => {
|
||||
TerminatorKind::UnwindResume => {
|
||||
if !is_cleanup {
|
||||
span_mirbug!(self, block_data, "resume on non-cleanup block!")
|
||||
}
|
||||
}
|
||||
TerminatorKind::Terminate => {
|
||||
TerminatorKind::UnwindTerminate => {
|
||||
if !is_cleanup {
|
||||
span_mirbug!(self, block_data, "abort on non-cleanup block!")
|
||||
}
|
||||
|
@ -474,10 +474,10 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) {
|
||||
*destination,
|
||||
);
|
||||
}
|
||||
TerminatorKind::Terminate => {
|
||||
TerminatorKind::UnwindTerminate => {
|
||||
codegen_panic_cannot_unwind(fx, source_info);
|
||||
}
|
||||
TerminatorKind::Resume => {
|
||||
TerminatorKind::UnwindResume => {
|
||||
// FIXME implement unwinding
|
||||
fx.bcx.ins().trap(TrapCode::UnreachableCodeReached);
|
||||
}
|
||||
|
@ -550,8 +550,8 @@ pub(crate) fn mir_operand_get_const_val<'tcx>(
|
||||
match &bb_data.terminator().kind {
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. }
|
||||
|
@ -128,7 +128,10 @@ fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attr
|
||||
|
||||
// The function name varies on platforms.
|
||||
// See test/CodeGen/mcount.c in clang.
|
||||
let mcount_name = cx.sess().target.mcount.as_ref();
|
||||
let mcount_name = match &cx.sess().target.llvm_mcount_intrinsic {
|
||||
Some(llvm_mcount_intrinsic) => llvm_mcount_intrinsic.as_ref(),
|
||||
None => cx.sess().target.mcount.as_ref(),
|
||||
};
|
||||
|
||||
attrs.push(llvm::CreateAttrStringValue(
|
||||
cx.llcx,
|
||||
|
@ -16,6 +16,7 @@ use rustc_metadata::fs::METADATA_FILENAME;
|
||||
use rustc_metadata::EncodedMetadata;
|
||||
use rustc_session::cstore::MetadataLoader;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::sym;
|
||||
use rustc_target::abi::Endian;
|
||||
use rustc_target::spec::{ef_avr_arch, RelocModel, Target};
|
||||
|
||||
@ -272,35 +273,38 @@ pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static
|
||||
Architecture::Riscv32 | Architecture::Riscv64 => {
|
||||
// Source: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/079772828bd10933d34121117a222b4cc0ee2200/riscv-elf.adoc
|
||||
let mut e_flags: u32 = 0x0;
|
||||
let features = &sess.target.options.features;
|
||||
|
||||
// Check if compressed is enabled
|
||||
if features.contains("+c") {
|
||||
// `unstable_target_features` is used here because "c" is gated behind riscv_target_feature.
|
||||
if sess.unstable_target_features.contains(&sym::c) {
|
||||
e_flags |= elf::EF_RISCV_RVC;
|
||||
}
|
||||
|
||||
// Select the appropriate floating-point ABI
|
||||
if features.contains("+d") {
|
||||
e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE;
|
||||
} else if features.contains("+f") {
|
||||
e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE;
|
||||
} else {
|
||||
e_flags |= elf::EF_RISCV_FLOAT_ABI_SOFT;
|
||||
// Set the appropriate flag based on ABI
|
||||
// This needs to match LLVM `RISCVELFStreamer.cpp`
|
||||
match &*sess.target.llvm_abiname {
|
||||
"" | "ilp32" | "lp64" => (),
|
||||
"ilp32f" | "lp64f" => e_flags |= elf::EF_RISCV_FLOAT_ABI_SINGLE,
|
||||
"ilp32d" | "lp64d" => e_flags |= elf::EF_RISCV_FLOAT_ABI_DOUBLE,
|
||||
"ilp32e" => e_flags |= elf::EF_RISCV_RVE,
|
||||
_ => bug!("unknown RISC-V ABI name"),
|
||||
}
|
||||
|
||||
e_flags
|
||||
}
|
||||
Architecture::LoongArch64 => {
|
||||
// Source: https://github.com/loongson/la-abi-specs/blob/release/laelf.adoc#e_flags-identifies-abi-type-and-version
|
||||
let mut e_flags: u32 = elf::EF_LARCH_OBJABI_V1;
|
||||
let features = &sess.target.options.features;
|
||||
|
||||
// Select the appropriate floating-point ABI
|
||||
if features.contains("+d") {
|
||||
e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT;
|
||||
} else if features.contains("+f") {
|
||||
e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT;
|
||||
} else {
|
||||
e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT;
|
||||
// Set the appropriate flag based on ABI
|
||||
// This needs to match LLVM `LoongArchELFStreamer.cpp`
|
||||
match &*sess.target.llvm_abiname {
|
||||
"ilp32s" | "lp64s" => e_flags |= elf::EF_LARCH_ABI_SOFT_FLOAT,
|
||||
"ilp32f" | "lp64f" => e_flags |= elf::EF_LARCH_ABI_SINGLE_FLOAT,
|
||||
"ilp32d" | "lp64d" => e_flags |= elf::EF_LARCH_ABI_DOUBLE_FLOAT,
|
||||
_ => bug!("unknown RISC-V ABI name"),
|
||||
}
|
||||
|
||||
e_flags
|
||||
}
|
||||
Architecture::Avr => {
|
||||
|
@ -284,8 +284,8 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec<mir::BasicBlock, CleanupKi
|
||||
for (bb, data) in mir.basic_blocks.iter_enumerated() {
|
||||
match data.terminator().kind {
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Unreachable
|
||||
|
@ -1224,12 +1224,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
|
||||
self.set_debug_loc(bx, terminator.source_info);
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Resume => {
|
||||
mir::TerminatorKind::UnwindResume => {
|
||||
self.codegen_resume_terminator(helper, bx);
|
||||
MergingSucc::False
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Terminate => {
|
||||
mir::TerminatorKind::UnwindTerminate => {
|
||||
self.codegen_terminate_terminator(helper, bx, terminator);
|
||||
MergingSucc::False
|
||||
}
|
||||
|
@ -42,9 +42,6 @@ pub struct PerLocalVarDebugInfo<'tcx, D> {
|
||||
|
||||
/// `.place.projection` from `mir::VarDebugInfo`.
|
||||
pub projection: &'tcx ty::List<mir::PlaceElem<'tcx>>,
|
||||
|
||||
/// `references` from `mir::VarDebugInfo`.
|
||||
pub references: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -323,7 +320,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
dbg_var,
|
||||
fragment: None,
|
||||
projection: ty::List::empty(),
|
||||
references: 0,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@ -399,15 +395,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
&self,
|
||||
bx: &mut Bx,
|
||||
local: mir::Local,
|
||||
mut base: PlaceRef<'tcx, Bx::Value>,
|
||||
base: PlaceRef<'tcx, Bx::Value>,
|
||||
var: PerLocalVarDebugInfo<'tcx, Bx::DIVariable>,
|
||||
) {
|
||||
let Some(dbg_var) = var.dbg_var else { return };
|
||||
let Some(dbg_loc) = self.dbg_loc(var.source_info) else { return };
|
||||
|
||||
let DebugInfoOffset { mut direct_offset, indirect_offsets, result: _ } =
|
||||
let DebugInfoOffset { direct_offset, indirect_offsets, result: _ } =
|
||||
calculate_debuginfo_offset(bx, local, &var, base.layout);
|
||||
let mut indirect_offsets = &indirect_offsets[..];
|
||||
|
||||
// When targeting MSVC, create extra allocas for arguments instead of pointing multiple
|
||||
// dbg_var_addr() calls into the same alloca with offsets. MSVC uses CodeView records
|
||||
@ -421,9 +416,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
// LLVM can handle simple things but anything more complex than just a direct
|
||||
// offset or one indirect offset of 0 is too complex for it to generate CV records
|
||||
// correctly.
|
||||
&& (direct_offset != Size::ZERO || !matches!(indirect_offsets, [Size::ZERO] | []));
|
||||
&& (direct_offset != Size::ZERO || !matches!(&indirect_offsets[..], [Size::ZERO] | []));
|
||||
|
||||
if should_create_individual_allocas {
|
||||
let DebugInfoOffset { direct_offset: _, indirect_offsets: _, result: place } =
|
||||
calculate_debuginfo_offset(bx, local, &var, base);
|
||||
|
||||
let create_alloca = |bx: &mut Bx, place: PlaceRef<'tcx, Bx::Value>, refcount| {
|
||||
// Create a variable which will be a pointer to the actual value
|
||||
let ptr_ty = Ty::new_ptr(
|
||||
bx.tcx(),
|
||||
@ -431,35 +429,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
);
|
||||
let ptr_layout = bx.layout_of(ptr_ty);
|
||||
let alloca = PlaceRef::alloca(bx, ptr_layout);
|
||||
bx.set_var_name(alloca.llval, &format!("{}.ref{}.dbg.spill", var.name, refcount));
|
||||
bx.set_var_name(alloca.llval, &(var.name.to_string() + ".dbg.spill"));
|
||||
|
||||
// Write the pointer to the variable
|
||||
bx.store(place.llval, alloca.llval, alloca.align);
|
||||
|
||||
// Point the debug info to `*alloca` for the current variable
|
||||
alloca
|
||||
};
|
||||
|
||||
if var.references > 0 {
|
||||
base = calculate_debuginfo_offset(bx, local, &var, base).result;
|
||||
|
||||
// Point the debug info to `&...&base == alloca` for the current variable
|
||||
for refcount in 0..var.references {
|
||||
base = create_alloca(bx, base, refcount);
|
||||
bx.dbg_var_addr(dbg_var, dbg_loc, alloca.llval, Size::ZERO, &[Size::ZERO], None);
|
||||
} else {
|
||||
bx.dbg_var_addr(dbg_var, dbg_loc, base.llval, direct_offset, &indirect_offsets, None);
|
||||
}
|
||||
|
||||
direct_offset = Size::ZERO;
|
||||
indirect_offsets = &[];
|
||||
} else if should_create_individual_allocas {
|
||||
let place = calculate_debuginfo_offset(bx, local, &var, base).result;
|
||||
|
||||
// Point the debug info to `*alloca` for the current variable
|
||||
base = create_alloca(bx, place, 0);
|
||||
direct_offset = Size::ZERO;
|
||||
indirect_offsets = &[Size::ZERO];
|
||||
}
|
||||
|
||||
bx.dbg_var_addr(dbg_var, dbg_loc, base.llval, direct_offset, indirect_offsets, None);
|
||||
}
|
||||
|
||||
pub fn debug_introduce_locals(&self, bx: &mut Bx) {
|
||||
@ -492,7 +471,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
};
|
||||
|
||||
let dbg_var = dbg_scope_and_span.map(|(dbg_scope, _, span)| {
|
||||
let (mut var_ty, var_kind) = match var.value {
|
||||
let (var_ty, var_kind) = match var.value {
|
||||
mir::VarDebugInfoContents::Place(place) => {
|
||||
let var_ty = self.monomorphized_place_ty(place.as_ref());
|
||||
let var_kind = if let Some(arg_index) = var.argument_index
|
||||
@ -529,13 +508,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
}
|
||||
};
|
||||
|
||||
for _ in 0..var.references {
|
||||
var_ty = Ty::new_ptr(
|
||||
bx.tcx(),
|
||||
ty::TypeAndMut { mutbl: mir::Mutability::Mut, ty: var_ty },
|
||||
);
|
||||
}
|
||||
|
||||
self.cx.create_dbg_var(var.name, var_ty, dbg_scope, var_kind, span)
|
||||
});
|
||||
|
||||
@ -547,7 +519,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
dbg_var,
|
||||
fragment: None,
|
||||
projection: place.projection,
|
||||
references: var.references,
|
||||
});
|
||||
}
|
||||
mir::VarDebugInfoContents::Const(c) => {
|
||||
@ -601,7 +572,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
Some(fragment_start..fragment_start + fragment_layout.size)
|
||||
},
|
||||
projection: place.projection,
|
||||
references: var.references,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ pub enum ConstEvalErrKind {
|
||||
ModifiedGlobal,
|
||||
AssertFailure(AssertKind<ConstInt>),
|
||||
Panic { msg: Symbol, line: u32, col: u32, file: Symbol },
|
||||
Abort(String),
|
||||
}
|
||||
|
||||
impl MachineStopType for ConstEvalErrKind {
|
||||
@ -30,7 +29,6 @@ impl MachineStopType for ConstEvalErrKind {
|
||||
ModifiedGlobal => const_eval_modified_global,
|
||||
Panic { .. } => const_eval_panic,
|
||||
AssertFailure(x) => x.diagnostic_message(),
|
||||
Abort(msg) => msg.to_string().into(),
|
||||
}
|
||||
}
|
||||
fn add_args(
|
||||
@ -39,7 +37,7 @@ impl MachineStopType for ConstEvalErrKind {
|
||||
) {
|
||||
use ConstEvalErrKind::*;
|
||||
match *self {
|
||||
ConstAccessesStatic | ModifiedGlobal | Abort(_) => {}
|
||||
ConstAccessesStatic | ModifiedGlobal => {}
|
||||
AssertFailure(kind) => kind.add_args(adder),
|
||||
Panic { msg, line, col, file } => {
|
||||
adder("msg".into(), msg.into_diagnostic_arg());
|
||||
|
@ -427,52 +427,48 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
||||
|
||||
fn find_mir_or_eval_fn(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
instance: ty::Instance<'tcx>,
|
||||
orig_instance: ty::Instance<'tcx>,
|
||||
_abi: CallAbi,
|
||||
args: &[FnArg<'tcx>],
|
||||
dest: &PlaceTy<'tcx>,
|
||||
ret: Option<mir::BasicBlock>,
|
||||
_unwind: mir::UnwindAction, // unwinding is not supported in consts
|
||||
) -> InterpResult<'tcx, Option<(&'mir mir::Body<'tcx>, ty::Instance<'tcx>)>> {
|
||||
debug!("find_mir_or_eval_fn: {:?}", instance);
|
||||
debug!("find_mir_or_eval_fn: {:?}", orig_instance);
|
||||
|
||||
// Replace some functions.
|
||||
let Some(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else {
|
||||
// Call has already been handled.
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Only check non-glue functions
|
||||
if let ty::InstanceDef::Item(def) = instance.def {
|
||||
// Execution might have wandered off into other crates, so we cannot do a stability-
|
||||
// sensitive check here. But we can at least rule out functions that are not const
|
||||
// at all.
|
||||
if !ecx.tcx.is_const_fn_raw(def) {
|
||||
// allow calling functions inside a trait marked with #[const_trait].
|
||||
if !ecx.tcx.is_const_default_method(def) {
|
||||
// sensitive check here. But we can at least rule out functions that are not const at
|
||||
// all. That said, we have to allow calling functions inside a trait marked with
|
||||
// #[const_trait]. These *are* const-checked!
|
||||
// FIXME: why does `is_const_fn_raw` not classify them as const?
|
||||
if (!ecx.tcx.is_const_fn_raw(def) && !ecx.tcx.is_const_default_method(def))
|
||||
|| ecx.tcx.has_attr(def, sym::rustc_do_not_const_check)
|
||||
{
|
||||
// We certainly do *not* want to actually call the fn
|
||||
// though, so be sure we return here.
|
||||
throw_unsup_format!("calling non-const function `{}`", instance)
|
||||
}
|
||||
}
|
||||
|
||||
let Some(new_instance) = ecx.hook_special_const_fn(instance, args, dest, ret)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if new_instance != instance {
|
||||
// We call another const fn instead.
|
||||
// However, we return the *original* instance to make backtraces work out
|
||||
// (and we hope this does not confuse the FnAbi checks too much).
|
||||
return Ok(Self::find_mir_or_eval_fn(
|
||||
ecx,
|
||||
new_instance,
|
||||
_abi,
|
||||
args,
|
||||
dest,
|
||||
ret,
|
||||
_unwind,
|
||||
)?
|
||||
.map(|(body, _instance)| (body, instance)));
|
||||
}
|
||||
}
|
||||
|
||||
// This is a const fn. Call it.
|
||||
Ok(Some((ecx.load_mir(instance.def, None)?, instance)))
|
||||
// In case of replacement, we return the *original* instance to make backtraces work out
|
||||
// (and we hope this does not confuse the FnAbi checks too much).
|
||||
Ok(Some((ecx.load_mir(instance.def, None)?, orig_instance)))
|
||||
}
|
||||
|
||||
fn panic_nounwind(ecx: &mut InterpCx<'mir, 'tcx, Self>, msg: &str) -> InterpResult<'tcx> {
|
||||
let msg = Symbol::intern(msg);
|
||||
let span = ecx.find_closest_untracked_caller_location();
|
||||
let (file, line, col) = ecx.location_triple_for_span(span);
|
||||
Err(ConstEvalErrKind::Panic { msg, file, line, col }.into())
|
||||
}
|
||||
|
||||
fn call_intrinsic(
|
||||
@ -595,10 +591,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
|
||||
Err(ConstEvalErrKind::AssertFailure(err).into())
|
||||
}
|
||||
|
||||
fn abort(_ecx: &mut InterpCx<'mir, 'tcx, Self>, msg: String) -> InterpResult<'tcx, !> {
|
||||
Err(ConstEvalErrKind::Abort(msg).into())
|
||||
}
|
||||
|
||||
fn binary_ptr_op(
|
||||
_ecx: &InterpCx<'mir, 'tcx, Self>,
|
||||
_bin_op: mir::BinOp,
|
||||
|
@ -765,7 +765,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
}
|
||||
mir::UnwindAction::Terminate => {
|
||||
self.frame_mut().loc = Right(self.frame_mut().body.span);
|
||||
M::abort(self, "panic in a function that cannot unwind".to_owned())?;
|
||||
M::unwind_terminate(self)?;
|
||||
// This might have pushed a new stack frame, or it terminated execution.
|
||||
// Either way, `loc` will not be updated.
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
@ -865,6 +868,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
panic!("encountered StackPopCleanup::Root when unwinding!")
|
||||
}
|
||||
};
|
||||
// This must be the very last thing that happens, since it can in fact push a new stack frame.
|
||||
self.unwind_to_block(unwind)
|
||||
} else {
|
||||
// Follow the normal return edge.
|
||||
|
@ -125,15 +125,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
) -> InterpResult<'tcx, bool> {
|
||||
let instance_args = instance.args;
|
||||
let intrinsic_name = self.tcx.item_name(instance.def_id());
|
||||
|
||||
// First handle intrinsics without return place.
|
||||
let ret = match ret {
|
||||
None => match intrinsic_name {
|
||||
sym::abort => M::abort(self, "the program aborted execution".to_owned())?,
|
||||
// Unsupported diverging intrinsic.
|
||||
_ => return Ok(false),
|
||||
},
|
||||
Some(p) => p,
|
||||
let Some(ret) = ret else {
|
||||
// We don't support any intrinsic without return place.
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
match intrinsic_name {
|
||||
@ -410,7 +404,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
ValidityRequirement::Uninit => bug!("assert_uninit_valid doesn't exist"),
|
||||
};
|
||||
|
||||
M::abort(self, msg)?;
|
||||
M::panic_nounwind(self, &msg)?;
|
||||
// Skip the `go_to_block` at the end.
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
sym::simd_insert => {
|
||||
|
@ -218,10 +218,11 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
|
||||
unwind: mir::UnwindAction,
|
||||
) -> InterpResult<'tcx>;
|
||||
|
||||
/// Called to abort evaluation.
|
||||
fn abort(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _msg: String) -> InterpResult<'tcx, !> {
|
||||
throw_unsup_format!("aborting execution is not supported")
|
||||
}
|
||||
/// Called to trigger a non-unwinding panic.
|
||||
fn panic_nounwind(_ecx: &mut InterpCx<'mir, 'tcx, Self>, msg: &str) -> InterpResult<'tcx>;
|
||||
|
||||
/// Called when unwinding reached a state where execution should be terminated.
|
||||
fn unwind_terminate(_ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx>;
|
||||
|
||||
/// Called for all binary operations where the LHS has pointer type.
|
||||
///
|
||||
@ -499,6 +500,11 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unwind_terminate(_ecx: &mut InterpCx<$mir, $tcx, Self>) -> InterpResult<$tcx> {
|
||||
unreachable!("unwinding cannot happen during compile-time evaluation")
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn call_extra_fn(
|
||||
_ecx: &mut InterpCx<$mir, $tcx, Self>,
|
||||
|
@ -196,15 +196,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
}
|
||||
}
|
||||
|
||||
Terminate => {
|
||||
// FIXME: maybe should call `panic_no_unwind` lang item instead.
|
||||
M::abort(self, "panic in a function that cannot unwind".to_owned())?;
|
||||
UnwindTerminate => {
|
||||
M::unwind_terminate(self)?;
|
||||
}
|
||||
|
||||
// When we encounter Resume, we've finished unwinding
|
||||
// cleanup for the current stack frame. We pop it in order
|
||||
// to continue unwinding the next frame
|
||||
Resume => {
|
||||
UnwindResume => {
|
||||
trace!("unwinding: resuming from cleanup");
|
||||
// By definition, a Resume terminator means
|
||||
// that we're unwinding
|
||||
|
@ -1037,7 +1037,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||
self.check_op(ops::Generator(hir::GeneratorKind::Gen))
|
||||
}
|
||||
|
||||
TerminatorKind::Terminate => {
|
||||
TerminatorKind::UnwindTerminate => {
|
||||
// Cleanup blocks are skipped for const checking (see `visit_basic_block_data`).
|
||||
span_bug!(self.span, "`Terminate` terminator outside of cleanup block")
|
||||
}
|
||||
@ -1046,7 +1046,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable => {}
|
||||
|
@ -106,7 +106,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Terminate
|
||||
mir::TerminatorKind::UnwindTerminate
|
||||
| mir::TerminatorKind::Call { .. }
|
||||
| mir::TerminatorKind::Assert { .. }
|
||||
| mir::TerminatorKind::FalseEdge { .. }
|
||||
@ -114,7 +114,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
|
||||
| mir::TerminatorKind::GeneratorDrop
|
||||
| mir::TerminatorKind::Goto { .. }
|
||||
| mir::TerminatorKind::InlineAsm { .. }
|
||||
| mir::TerminatorKind::Resume
|
||||
| mir::TerminatorKind::UnwindResume
|
||||
| mir::TerminatorKind::Return
|
||||
| mir::TerminatorKind::SwitchInt { .. }
|
||||
| mir::TerminatorKind::Unreachable
|
||||
|
@ -4,10 +4,12 @@
|
||||
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementKind};
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, TerminatorEdges,
|
||||
};
|
||||
use rustc_mir_dataflow::fmt::DebugWithContext;
|
||||
use rustc_mir_dataflow::JoinSemiLattice;
|
||||
use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces};
|
||||
use rustc_mir_dataflow::{Analysis, AnalysisDomain};
|
||||
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
@ -345,13 +347,14 @@ where
|
||||
self.transfer_function(state).visit_statement(statement, location);
|
||||
}
|
||||
|
||||
fn apply_terminator_effect(
|
||||
fn apply_terminator_effect<'mir>(
|
||||
&mut self,
|
||||
state: &mut Self::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
self.transfer_function(state).visit_terminator(terminator, location);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn apply_call_return_effect(
|
||||
|
@ -18,6 +18,7 @@ use rustc_mir_dataflow::impls::MaybeStorageLive;
|
||||
use rustc_mir_dataflow::storage::always_storage_live_locals;
|
||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||
use rustc_target::abi::{Size, FIRST_VARIANT};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum EdgeKind {
|
||||
@ -58,6 +59,25 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
|
||||
let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
|
||||
// In this case `AbortUnwindingCalls` haven't yet been executed.
|
||||
true
|
||||
} else if !tcx.def_kind(def_id).is_fn_like() {
|
||||
true
|
||||
} else {
|
||||
let body_ty = tcx.type_of(def_id).skip_binder();
|
||||
let body_abi = match body_ty.kind() {
|
||||
ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
|
||||
ty::Closure(..) => Abi::RustCall,
|
||||
ty::Generator(..) => Abi::Rust,
|
||||
_ => {
|
||||
span_bug!(body.span, "unexpected body ty: {:?} phase {:?}", body_ty, mir_phase)
|
||||
}
|
||||
};
|
||||
|
||||
ty::layout::fn_can_unwind(tcx, Some(def_id), body_abi)
|
||||
};
|
||||
|
||||
let mut cfg_checker = CfgChecker {
|
||||
when: &self.when,
|
||||
body,
|
||||
@ -68,6 +88,7 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
||||
storage_liveness,
|
||||
place_cache: FxHashSet::default(),
|
||||
value_cache: FxHashSet::default(),
|
||||
can_unwind,
|
||||
};
|
||||
cfg_checker.visit_body(body);
|
||||
cfg_checker.check_cleanup_control_flow();
|
||||
@ -99,6 +120,9 @@ struct CfgChecker<'a, 'tcx> {
|
||||
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
|
||||
place_cache: FxHashSet<PlaceRef<'tcx>>,
|
||||
value_cache: FxHashSet<u128>,
|
||||
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
|
||||
// `TerminatorKind::Resume`.
|
||||
can_unwind: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
||||
@ -237,13 +261,17 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
||||
match unwind {
|
||||
UnwindAction::Cleanup(unwind) => {
|
||||
if is_cleanup {
|
||||
self.fail(location, "unwind on cleanup block");
|
||||
self.fail(location, "`UnwindAction::Cleanup` in cleanup block");
|
||||
}
|
||||
self.check_edge(location, unwind, EdgeKind::Unwind);
|
||||
}
|
||||
UnwindAction::Continue => {
|
||||
if is_cleanup {
|
||||
self.fail(location, "unwind on cleanup block");
|
||||
self.fail(location, "`UnwindAction::Continue` in cleanup block");
|
||||
}
|
||||
|
||||
if !self.can_unwind {
|
||||
self.fail(location, "`UnwindAction::Continue` in no-unwind function");
|
||||
}
|
||||
}
|
||||
UnwindAction::Unreachable | UnwindAction::Terminate => (),
|
||||
@ -464,13 +492,19 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
||||
);
|
||||
}
|
||||
}
|
||||
TerminatorKind::Resume | TerminatorKind::Terminate => {
|
||||
TerminatorKind::UnwindResume => {
|
||||
let bb = location.block;
|
||||
if !self.body.basic_blocks[bb].is_cleanup {
|
||||
self.fail(
|
||||
location,
|
||||
"Cannot `Resume` or `Terminate` from non-cleanup basic block",
|
||||
)
|
||||
self.fail(location, "Cannot `UnwindResume` from non-cleanup basic block")
|
||||
}
|
||||
if !self.can_unwind {
|
||||
self.fail(location, "Cannot `UnwindResume` in a function that cannot unwind")
|
||||
}
|
||||
}
|
||||
TerminatorKind::UnwindTerminate => {
|
||||
let bb = location.block;
|
||||
if !self.body.basic_blocks[bb].is_cleanup {
|
||||
self.fail(location, "Cannot `UnwindTerminate` from non-cleanup basic block")
|
||||
}
|
||||
}
|
||||
TerminatorKind::Return => {
|
||||
@ -665,7 +699,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
||||
return;
|
||||
};
|
||||
|
||||
f_ty.ty
|
||||
ty::EarlyBinder::bind(f_ty.ty).instantiate(self.tcx, args)
|
||||
} else {
|
||||
let Some(&f_ty) = args.as_generator().prefix_tys().get(f.index())
|
||||
else {
|
||||
@ -701,12 +735,6 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
||||
VarDebugInfoContents::Const(_) => {}
|
||||
VarDebugInfoContents::Place(place) => {
|
||||
check_place(self, place);
|
||||
if debuginfo.references != 0 && place.projection.last() == Some(&PlaceElem::Deref) {
|
||||
self.fail(
|
||||
START_BLOCK.start_location(),
|
||||
format!("debuginfo {debuginfo:?}, has both ref and deref"),
|
||||
);
|
||||
}
|
||||
}
|
||||
VarDebugInfoContents::Composite { ty, ref fragments } => {
|
||||
for f in fragments {
|
||||
@ -1204,8 +1232,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::InlineAsm { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable => {}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ It is not possible to declare type parameters on a function that has the `start`
|
||||
attribute. Such a function must have the following type signature (for more
|
||||
information, view [the unstable book][1]):
|
||||
|
||||
[1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib
|
||||
[1]: https://doc.rust-lang.org/unstable-book/language-features/start.html
|
||||
|
||||
```
|
||||
# let _:
|
||||
|
@ -20,6 +20,6 @@ attributes:
|
||||
#![no_std]
|
||||
```
|
||||
|
||||
See also the [unstable book][1].
|
||||
See also [this section of the Rustonomicon][beneath std].
|
||||
|
||||
[1]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html#writing-an-executable-without-stdlib
|
||||
[beneath std]: https://doc.rust-lang.org/nomicon/beneath-std.html
|
||||
|
@ -452,11 +452,11 @@ struct HandlerInner {
|
||||
/// have been converted.
|
||||
check_unstable_expect_diagnostics: bool,
|
||||
|
||||
/// Expected [`Diagnostic`][diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of
|
||||
/// Expected [`Diagnostic`][struct@diagnostic::Diagnostic]s store a [`LintExpectationId`] as part of
|
||||
/// the lint level. [`LintExpectationId`]s created early during the compilation
|
||||
/// (before `HirId`s have been defined) are not stable and can therefore not be
|
||||
/// stored on disk. This buffer stores these diagnostics until the ID has been
|
||||
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][diagnostic::Diagnostic]s are the
|
||||
/// replaced by a stable [`LintExpectationId`]. The [`Diagnostic`][struct@diagnostic::Diagnostic]s are the
|
||||
/// submitted for storage and added to the list of fulfilled expectations.
|
||||
unstable_expect_diagnostics: Vec<Diagnostic>,
|
||||
|
||||
|
@ -81,7 +81,7 @@ use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::ErrorGuaranteed;
|
||||
use rustc_lint_defs::pluralize;
|
||||
use rustc_parse::parser::{NtOrTt, Parser};
|
||||
use rustc_parse::parser::{ParseNtResult, Parser};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::symbol::MacroRulesNormalizedIdent;
|
||||
use rustc_span::Span;
|
||||
@ -692,8 +692,8 @@ impl TtParser {
|
||||
Ok(nt) => nt,
|
||||
};
|
||||
let m = match nt {
|
||||
NtOrTt::Nt(nt) => MatchedNonterminal(Lrc::new(nt)),
|
||||
NtOrTt::Tt(tt) => MatchedTokenTree(tt),
|
||||
ParseNtResult::Nt(nt) => MatchedNonterminal(Lrc::new(nt)),
|
||||
ParseNtResult::Tt(tt) => MatchedTokenTree(tt),
|
||||
};
|
||||
mp.push_match(next_metavar, seq_depth, m);
|
||||
mp.idx += 1;
|
||||
|
@ -1328,7 +1328,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow {
|
||||
_ => IsInFollow::No(TOKENS),
|
||||
}
|
||||
}
|
||||
NonterminalKind::PatWithOr { .. } => {
|
||||
NonterminalKind::PatWithOr => {
|
||||
const TOKENS: &[&str] = &["`=>`", "`,`", "`=`", "`if`", "`in`"];
|
||||
match tok {
|
||||
TokenTree::Token(token) => match token.kind {
|
||||
|
@ -93,7 +93,17 @@ fn parse_count<'sess>(
|
||||
span: Span,
|
||||
) -> PResult<'sess, MetaVarExpr> {
|
||||
let ident = parse_ident(iter, sess, span)?;
|
||||
let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None };
|
||||
let depth = if try_eat_comma(iter) {
|
||||
if iter.look_ahead(0).is_none() {
|
||||
return Err(sess.span_diagnostic.struct_span_err(
|
||||
span,
|
||||
"`count` followed by a comma must have an associated index indicating its depth",
|
||||
));
|
||||
}
|
||||
Some(parse_depth(iter, sess, span)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(MetaVarExpr::Count(ident, depth))
|
||||
}
|
||||
|
||||
|
@ -220,16 +220,15 @@ pub(super) fn transcribe<'a>(
|
||||
MatchedTokenTree(tt) => {
|
||||
// `tt`s are emitted into the output stream directly as "raw tokens",
|
||||
// without wrapping them into groups.
|
||||
let token = tt.clone();
|
||||
result.push(token);
|
||||
result.push(tt.clone());
|
||||
}
|
||||
MatchedNonterminal(nt) => {
|
||||
// Other variables are emitted into the output stream as groups with
|
||||
// `Delimiter::Invisible` to maintain parsing priorities.
|
||||
// `Interpolated` is currently used for such groups in rustc parser.
|
||||
marker.visit_span(&mut sp);
|
||||
let token = TokenTree::token_alone(token::Interpolated(nt.clone()), sp);
|
||||
result.push(token);
|
||||
result
|
||||
.push(TokenTree::token_alone(token::Interpolated(nt.clone()), sp));
|
||||
}
|
||||
MatchedSeq(..) => {
|
||||
// We were unable to descend far enough. This is an error.
|
||||
|
@ -352,6 +352,8 @@ declare_features! (
|
||||
(active, c_variadic, "1.34.0", Some(44930), None),
|
||||
/// Allows the use of `#[cfg(overflow_checks)` to check if integer overflow behaviour.
|
||||
(active, cfg_overflow_checks, "1.71.0", Some(111466), None),
|
||||
/// Provides the relocation model information as cfg entry
|
||||
(active, cfg_relocation_model, "CURRENT_RUSTC_VERSION", Some(114929), None),
|
||||
/// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used.
|
||||
(active, cfg_sanitize, "1.41.0", Some(39699), None),
|
||||
/// Allows `cfg(target_abi = "...")`.
|
||||
|
@ -35,6 +35,7 @@ const GATED_CFGS: &[GatedCfg] = &[
|
||||
(sym::target_has_atomic_load_store, sym::cfg_target_has_atomic, cfg_fn!(cfg_target_has_atomic)),
|
||||
(sym::sanitize, sym::cfg_sanitize, cfg_fn!(cfg_sanitize)),
|
||||
(sym::version, sym::cfg_version, cfg_fn!(cfg_version)),
|
||||
(sym::relocation_model, sym::cfg_relocation_model, cfg_fn!(cfg_relocation_model)),
|
||||
];
|
||||
|
||||
/// Find a gated cfg determined by the `pred`icate which is given the cfg's name.
|
||||
|
@ -1,6 +1,9 @@
|
||||
hir_analysis_ambiguous_lifetime_bound =
|
||||
ambiguous lifetime bound, explicit lifetime bound required
|
||||
|
||||
hir_analysis_assoc_bound_on_const = expected associated type, found {$descr}
|
||||
.note = trait bounds not allowed on {$descr}
|
||||
|
||||
hir_analysis_assoc_type_binding_not_allowed =
|
||||
associated type bindings are not allowed here
|
||||
.label = associated type not allowed here
|
||||
|
@ -13,7 +13,7 @@ use crate::astconv::{
|
||||
AstConv, ConvertedBinding, ConvertedBindingKind, OnlySelfBounds, PredicateFilter,
|
||||
};
|
||||
use crate::bounds::Bounds;
|
||||
use crate::errors::{MultipleRelaxedDefaultBounds, ValueOfAssociatedStructAlreadySpecified};
|
||||
use crate::errors;
|
||||
|
||||
impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
/// Sets `implicitly_sized` to true on `Bounds` if necessary
|
||||
@ -35,7 +35,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
if unbound.is_none() {
|
||||
unbound = Some(&ptr.trait_ref);
|
||||
} else {
|
||||
tcx.sess.emit_err(MultipleRelaxedDefaultBounds { span });
|
||||
tcx.sess.emit_err(errors::MultipleRelaxedDefaultBounds { span });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -326,7 +326,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
dup_bindings
|
||||
.entry(assoc_item.def_id)
|
||||
.and_modify(|prev_span| {
|
||||
tcx.sess.emit_err(ValueOfAssociatedStructAlreadySpecified {
|
||||
tcx.sess.emit_err(errors::ValueOfAssociatedStructAlreadySpecified {
|
||||
span: binding.span,
|
||||
prev_span: *prev_span,
|
||||
item_name: binding.item_name,
|
||||
@ -488,6 +488,8 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
}
|
||||
}
|
||||
|
||||
let assoc_item_def_id = projection_ty.skip_binder().def_id;
|
||||
let def_kind = tcx.def_kind(assoc_item_def_id);
|
||||
match binding.kind {
|
||||
ConvertedBindingKind::Equality(..) if return_type_notation => {
|
||||
return Err(self.tcx().sess.emit_err(
|
||||
@ -499,11 +501,9 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
// the "projection predicate" for:
|
||||
//
|
||||
// `<T as Iterator>::Item = u32`
|
||||
let assoc_item_def_id = projection_ty.skip_binder().def_id;
|
||||
let def_kind = tcx.def_kind(assoc_item_def_id);
|
||||
match (def_kind, term.unpack()) {
|
||||
(hir::def::DefKind::AssocTy, ty::TermKind::Ty(_))
|
||||
| (hir::def::DefKind::AssocConst, ty::TermKind::Const(_)) => (),
|
||||
(DefKind::AssocTy, ty::TermKind::Ty(_))
|
||||
| (DefKind::AssocConst, ty::TermKind::Const(_)) => (),
|
||||
(_, _) => {
|
||||
let got = if let Some(_) = term.ty() { "type" } else { "constant" };
|
||||
let expected = tcx.def_descr(assoc_item_def_id);
|
||||
@ -516,7 +516,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
format!("{expected} defined here"),
|
||||
);
|
||||
|
||||
if let hir::def::DefKind::AssocConst = def_kind
|
||||
if let DefKind::AssocConst = def_kind
|
||||
&& let Some(t) = term.ty() && (t.is_enum() || t.references_error())
|
||||
&& tcx.features().associated_const_equality {
|
||||
err.span_suggestion(
|
||||
@ -528,8 +528,8 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
}
|
||||
let reported = err.emit();
|
||||
term = match def_kind {
|
||||
hir::def::DefKind::AssocTy => Ty::new_error(tcx, reported).into(),
|
||||
hir::def::DefKind::AssocConst => ty::Const::new_error(
|
||||
DefKind::AssocTy => Ty::new_error(tcx, reported).into(),
|
||||
DefKind::AssocConst => ty::Const::new_error(
|
||||
tcx,
|
||||
reported,
|
||||
tcx.type_of(assoc_item_def_id)
|
||||
@ -548,6 +548,15 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
|
||||
);
|
||||
}
|
||||
ConvertedBindingKind::Constraint(ast_bounds) => {
|
||||
match def_kind {
|
||||
DefKind::AssocTy => {}
|
||||
_ => {
|
||||
return Err(tcx.sess.emit_err(errors::AssocBoundOnConst {
|
||||
span: assoc_ident.span,
|
||||
descr: tcx.def_descr(assoc_item_def_id),
|
||||
}));
|
||||
}
|
||||
}
|
||||
// "Desugar" a constraint like `T: Iterator<Item: Debug>` to
|
||||
//
|
||||
// `<T as Iterator>::Item: Debug`
|
||||
|
@ -918,3 +918,12 @@ pub struct UnusedAssociatedTypeBounds {
|
||||
#[suggestion(code = "")]
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(hir_analysis_assoc_bound_on_const)]
|
||||
#[note]
|
||||
pub struct AssocBoundOnConst {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
pub descr: &'static str,
|
||||
}
|
||||
|
@ -599,6 +599,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
= self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id)
|
||||
// Only suggest removing parens if there are no arguments
|
||||
&& arg_exprs.is_empty()
|
||||
&& call_expr.span.contains(callee_expr.span)
|
||||
{
|
||||
let descr = match kind {
|
||||
def::CtorOf::Struct => "struct",
|
||||
|
@ -389,34 +389,26 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
if let ty::Ref(reg, cast_ty, mutbl) = *self.cast_ty.kind() {
|
||||
if let ty::RawPtr(TypeAndMut { ty: expr_ty, .. }) = *self.expr_ty.kind()
|
||||
&& fcx
|
||||
.try_coerce(
|
||||
self.expr,
|
||||
.can_coerce(
|
||||
Ty::new_ref(fcx.tcx,
|
||||
fcx.tcx.lifetimes.re_erased,
|
||||
TypeAndMut { ty: expr_ty, mutbl },
|
||||
),
|
||||
self.cast_ty,
|
||||
AllowTwoPhase::No,
|
||||
None,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
sugg = Some((format!("&{}*", mutbl.prefix_str()), cast_ty == expr_ty));
|
||||
} else if let ty::Ref(expr_reg, expr_ty, expr_mutbl) = *self.expr_ty.kind()
|
||||
&& expr_mutbl == Mutability::Not
|
||||
&& mutbl == Mutability::Mut
|
||||
&& fcx
|
||||
.try_coerce(
|
||||
self.expr,
|
||||
.can_coerce(
|
||||
Ty::new_ref(fcx.tcx,
|
||||
expr_reg,
|
||||
TypeAndMut { ty: expr_ty, mutbl: Mutability::Mut },
|
||||
),
|
||||
self.cast_ty,
|
||||
AllowTwoPhase::No,
|
||||
None,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
sugg_mutref = true;
|
||||
}
|
||||
@ -424,30 +416,22 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
if !sugg_mutref
|
||||
&& sugg == None
|
||||
&& fcx
|
||||
.try_coerce(
|
||||
self.expr,
|
||||
.can_coerce(
|
||||
Ty::new_ref(fcx.tcx,reg, TypeAndMut { ty: self.expr_ty, mutbl }),
|
||||
self.cast_ty,
|
||||
AllowTwoPhase::No,
|
||||
None,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
sugg = Some((format!("&{}", mutbl.prefix_str()), false));
|
||||
}
|
||||
} else if let ty::RawPtr(TypeAndMut { mutbl, .. }) = *self.cast_ty.kind()
|
||||
&& fcx
|
||||
.try_coerce(
|
||||
self.expr,
|
||||
.can_coerce(
|
||||
Ty::new_ref(fcx.tcx,
|
||||
fcx.tcx.lifetimes.re_erased,
|
||||
TypeAndMut { ty: self.expr_ty, mutbl },
|
||||
),
|
||||
self.cast_ty,
|
||||
AllowTwoPhase::No,
|
||||
None,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
sugg = Some((format!("&{}", mutbl.prefix_str()), false));
|
||||
}
|
||||
@ -760,7 +744,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
ty::FnDef(..) => {
|
||||
// Attempt a coercion to a fn pointer type.
|
||||
let f = fcx.normalize(self.expr_span, self.expr_ty.fn_sig(fcx.tcx));
|
||||
let res = fcx.try_coerce(
|
||||
let res = fcx.coerce(
|
||||
self.expr,
|
||||
self.expr_ty,
|
||||
Ty::new_fn_ptr(fcx.tcx, f),
|
||||
@ -860,7 +844,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
|
||||
(_, DynStar) => {
|
||||
if fcx.tcx.features().dyn_star {
|
||||
bug!("should be handled by `try_coerce`")
|
||||
bug!("should be handled by `coerce`")
|
||||
} else {
|
||||
Err(CastError::IllegalCast)
|
||||
}
|
||||
@ -956,7 +940,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
|
||||
// Coerce to a raw pointer so that we generate AddressOf in MIR.
|
||||
let array_ptr_type = Ty::new_ptr(fcx.tcx, m_expr);
|
||||
fcx.try_coerce(self.expr, self.expr_ty, array_ptr_type, AllowTwoPhase::No, None)
|
||||
fcx.coerce(self.expr, self.expr_ty, array_ptr_type, AllowTwoPhase::No, None)
|
||||
.unwrap_or_else(|_| {
|
||||
bug!(
|
||||
"could not cast from reference to array to pointer to array ({:?} to {:?})",
|
||||
@ -992,7 +976,7 @@ impl<'a, 'tcx> CastCheck<'tcx> {
|
||||
}
|
||||
|
||||
fn try_coercion_cast(&self, fcx: &FnCtxt<'a, 'tcx>) -> Result<(), ty::error::TypeError<'tcx>> {
|
||||
match fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty, AllowTwoPhase::No, None) {
|
||||
match fcx.coerce(self.expr, self.expr_ty, self.cast_ty, AllowTwoPhase::No, None) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
|
@ -711,6 +711,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
}
|
||||
};
|
||||
|
||||
let span = self.tcx.def_span(expr_def_id);
|
||||
|
||||
let output_ty = match *ret_ty.kind() {
|
||||
ty::Infer(ty::TyVar(ret_vid)) => {
|
||||
self.obligations_for_self_ty(ret_vid).find_map(|obligation| {
|
||||
@ -724,20 +726,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
.find_map(|(p, s)| get_future_output(p.as_predicate(), s))?,
|
||||
ty::Error(_) => return None,
|
||||
_ => span_bug!(
|
||||
self.tcx.def_span(expr_def_id),
|
||||
span,
|
||||
"async fn generator return type not an inference variable: {ret_ty}"
|
||||
),
|
||||
};
|
||||
|
||||
let output_ty = self.normalize(span, output_ty);
|
||||
|
||||
// async fn that have opaque types in their return type need to redo the conversion to inference variables
|
||||
// as they fetch the still opaque version from the signature.
|
||||
let InferOk { value: output_ty, obligations } = self
|
||||
.replace_opaque_types_with_inference_vars(
|
||||
output_ty,
|
||||
body_def_id,
|
||||
self.tcx.def_span(expr_def_id),
|
||||
self.param_env,
|
||||
);
|
||||
.replace_opaque_types_with_inference_vars(output_ty, body_def_id, span, self.param_env);
|
||||
self.register_predicates(obligations);
|
||||
|
||||
Some(output_ty)
|
||||
|
@ -1005,7 +1005,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// adjusted type of the expression, if successful.
|
||||
/// Adjustments are only recorded if the coercion succeeded.
|
||||
/// The expressions *must not* have any preexisting adjustments.
|
||||
pub fn try_coerce(
|
||||
pub fn coerce(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
expr_ty: Ty<'tcx>,
|
||||
@ -1036,7 +1036,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Same as `try_coerce()`, but without side-effects.
|
||||
/// Same as `coerce()`, but without side-effects.
|
||||
///
|
||||
/// Returns false if the coercion creates any obligations that result in
|
||||
/// errors.
|
||||
@ -1494,7 +1494,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
|
||||
// Special-case the first expression we are coercing.
|
||||
// To be honest, I'm not entirely sure why we do this.
|
||||
// We don't allow two-phase borrows, see comment in try_find_coercion_lub for why
|
||||
fcx.try_coerce(
|
||||
fcx.coerce(
|
||||
expression,
|
||||
expression_ty,
|
||||
self.expected_ty,
|
||||
|
@ -53,7 +53,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
|| self.suggest_no_capture_closure(err, expected, expr_ty)
|
||||
|| self.suggest_boxing_when_appropriate(err, expr.span, expr.hir_id, expected, expr_ty)
|
||||
|| self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected)
|
||||
|| self.suggest_copied_cloned_or_as_ref(err, expr, expr_ty, expected, expected_ty_expr)
|
||||
|| self.suggest_copied_cloned_or_as_ref(err, expr, expr_ty, expected)
|
||||
|| self.suggest_clone_for_ref(err, expr, expr_ty, expected)
|
||||
|| self.suggest_into(err, expr, expr_ty, expected)
|
||||
|| self.suggest_floating_point_literal(err, expr, expected)
|
||||
@ -254,7 +254,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
) -> (Ty<'tcx>, Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>>) {
|
||||
let expected = self.resolve_vars_with_obligations(expected);
|
||||
|
||||
let e = match self.try_coerce(expr, checked_ty, expected, allow_two_phase, None) {
|
||||
let e = match self.coerce(expr, checked_ty, expected, allow_two_phase, None) {
|
||||
Ok(ty) => return (ty, None),
|
||||
Err(e) => e,
|
||||
};
|
||||
@ -475,7 +475,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
{
|
||||
let Some(arg_ty) = self.node_ty_opt(arg_expr.hir_id) else { continue; };
|
||||
let arg_ty = arg_ty.fold_with(&mut fudger);
|
||||
let _ = self.try_coerce(
|
||||
let _ = self.coerce(
|
||||
arg_expr,
|
||||
arg_ty,
|
||||
*expected_arg_ty,
|
||||
|
@ -253,7 +253,7 @@ impl HelpUseLatestEdition {
|
||||
}
|
||||
|
||||
#[derive(Subdiagnostic)]
|
||||
pub enum OptionResultRefMismatch<'tcx> {
|
||||
pub enum OptionResultRefMismatch {
|
||||
#[suggestion(
|
||||
hir_typeck_option_result_copied,
|
||||
code = ".copied()",
|
||||
@ -276,19 +276,20 @@ pub enum OptionResultRefMismatch<'tcx> {
|
||||
span: Span,
|
||||
def_path: String,
|
||||
},
|
||||
#[suggestion(
|
||||
hir_typeck_option_result_asref,
|
||||
code = ".as_ref()",
|
||||
style = "verbose",
|
||||
applicability = "machine-applicable"
|
||||
)]
|
||||
AsRef {
|
||||
#[primary_span]
|
||||
span: Span,
|
||||
def_path: String,
|
||||
expected_ty: Ty<'tcx>,
|
||||
expr_ty: Ty<'tcx>,
|
||||
},
|
||||
// FIXME: #114050
|
||||
// #[suggestion(
|
||||
// hir_typeck_option_result_asref,
|
||||
// code = ".as_ref()",
|
||||
// style = "verbose",
|
||||
// applicability = "machine-applicable"
|
||||
// )]
|
||||
// AsRef {
|
||||
// #[primary_span]
|
||||
// span: Span,
|
||||
// def_path: String,
|
||||
// expected_ty: Ty<'tcx>,
|
||||
// expr_ty: Ty<'tcx>,
|
||||
// },
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
|
@ -260,9 +260,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
// fulfillment error to be more accurate.
|
||||
let coerced_ty = self.resolve_vars_with_obligations(coerced_ty);
|
||||
|
||||
let coerce_error = self
|
||||
.try_coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None)
|
||||
.err();
|
||||
let coerce_error =
|
||||
self.coerce(provided_arg, checked_ty, coerced_ty, AllowTwoPhase::Yes, None).err();
|
||||
|
||||
if coerce_error.is_some() {
|
||||
return Compatibility::Incompatible(coerce_error);
|
||||
@ -519,7 +518,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
// suggestions and labels are (more) correct when an arg is a
|
||||
// macro invocation.
|
||||
let normalize_span = |span: Span| -> Span {
|
||||
let normalized_span = span.find_ancestor_inside(error_span).unwrap_or(span);
|
||||
let normalized_span = span.find_ancestor_inside_same_ctxt(error_span).unwrap_or(span);
|
||||
// Sometimes macros mess up the spans, so do not normalize the
|
||||
// arg span to equal the error span, because that's less useful
|
||||
// than pointing out the arg expr in the wrong context.
|
||||
@ -929,7 +928,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
};
|
||||
labels.push((provided_span, format!("unexpected argument{provided_ty_name}")));
|
||||
let mut span = provided_span;
|
||||
if span.can_be_used_for_suggestions() {
|
||||
if span.can_be_used_for_suggestions()
|
||||
&& error_span.can_be_used_for_suggestions()
|
||||
{
|
||||
if arg_idx.index() > 0
|
||||
&& let Some((_, prev)) = provided_arg_tys
|
||||
.get(ProvidedIdx::from_usize(arg_idx.index() - 1)
|
||||
@ -1220,8 +1221,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
};
|
||||
if let Some(suggestion_text) = suggestion_text {
|
||||
let source_map = self.sess().source_map();
|
||||
let (mut suggestion, suggestion_span) =
|
||||
if let Some(call_span) = full_call_span.find_ancestor_inside(error_span) {
|
||||
let (mut suggestion, suggestion_span) = if let Some(call_span) =
|
||||
full_call_span.find_ancestor_inside_same_ctxt(error_span)
|
||||
{
|
||||
("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi()))
|
||||
} else {
|
||||
(
|
||||
|
@ -1097,7 +1097,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
expr: &hir::Expr<'_>,
|
||||
expr_ty: Ty<'tcx>,
|
||||
expected_ty: Ty<'tcx>,
|
||||
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
|
||||
) -> bool {
|
||||
let ty::Adt(adt_def, args) = expr_ty.kind() else {
|
||||
return false;
|
||||
@ -1115,7 +1114,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
{
|
||||
let expr_inner_ty = args.type_at(0);
|
||||
let expected_inner_ty = expected_args.type_at(0);
|
||||
if let &ty::Ref(_, ty, mutability) = expr_inner_ty.kind()
|
||||
if let &ty::Ref(_, ty, _mutability) = expr_inner_ty.kind()
|
||||
&& self.can_eq(self.param_env, ty, expected_inner_ty)
|
||||
{
|
||||
let def_path = self.tcx.def_path_str(adt_def.did());
|
||||
@ -1124,14 +1123,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
errors::OptionResultRefMismatch::Copied {
|
||||
span, def_path
|
||||
}
|
||||
} else if let Some(expected_ty_expr) = expected_ty_expr
|
||||
// FIXME: suggest changes to both expressions to convert both to
|
||||
// Option/Result<&T>
|
||||
&& mutability.is_not()
|
||||
{
|
||||
errors::OptionResultRefMismatch::AsRef {
|
||||
span: expected_ty_expr.span.shrink_to_hi(), expected_ty, expr_ty, def_path
|
||||
}
|
||||
} else if let Some(clone_did) = self.tcx.lang_items().clone_trait()
|
||||
&& rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions(
|
||||
self,
|
||||
|
@ -14,6 +14,7 @@ use rustc_span::symbol::kw::{Empty, Underscore};
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
use std::fmt::Write;
|
||||
|
||||
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
pub(super) fn lint_dot_call_from_2018(
|
||||
@ -143,16 +144,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
|
||||
let (self_adjusted, precise) = self.adjust_expr(pick, self_expr, sp);
|
||||
if precise {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let args = args.iter().fold(String::new(), |mut string, arg| {
|
||||
let span = arg.span.find_ancestor_inside(sp).unwrap_or_default();
|
||||
format!(
|
||||
write!(
|
||||
string,
|
||||
", {}",
|
||||
self.sess().source_map().span_to_snippet(span).unwrap()
|
||||
)
|
||||
})
|
||||
.collect::<String>();
|
||||
.unwrap();
|
||||
string
|
||||
});
|
||||
|
||||
lint.span_suggestion(
|
||||
sp,
|
||||
|
@ -538,9 +538,13 @@ where
|
||||
continue;
|
||||
}
|
||||
|
||||
let timestamp = extract_timestamp_from_session_dir(&directory_name).unwrap_or_else(|_| {
|
||||
bug!("unexpected incr-comp session dir: {}", session_dir.display())
|
||||
});
|
||||
let timestamp = match extract_timestamp_from_session_dir(&directory_name) {
|
||||
Ok(timestamp) => timestamp,
|
||||
Err(e) => {
|
||||
debug!("unexpected incr-comp session dir: {}: {}", session_dir.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if timestamp > best_candidate.0 {
|
||||
best_candidate = (timestamp, Some(session_dir.clone()));
|
||||
@ -562,14 +566,14 @@ fn is_session_directory_lock_file(file_name: &str) -> bool {
|
||||
file_name.starts_with("s-") && file_name.ends_with(LOCK_FILE_EXT)
|
||||
}
|
||||
|
||||
fn extract_timestamp_from_session_dir(directory_name: &str) -> Result<SystemTime, ()> {
|
||||
fn extract_timestamp_from_session_dir(directory_name: &str) -> Result<SystemTime, &'static str> {
|
||||
if !is_session_directory(directory_name) {
|
||||
return Err(());
|
||||
return Err("not a directory");
|
||||
}
|
||||
|
||||
let dash_indices: Vec<_> = directory_name.match_indices('-').map(|(idx, _)| idx).collect();
|
||||
if dash_indices.len() != 3 {
|
||||
return Err(());
|
||||
return Err("not three dashes in name");
|
||||
}
|
||||
|
||||
string_to_timestamp(&directory_name[dash_indices[0] + 1..dash_indices[1]])
|
||||
@ -581,11 +585,11 @@ fn timestamp_to_string(timestamp: SystemTime) -> String {
|
||||
base_n::encode(micros as u128, INT_ENCODE_BASE)
|
||||
}
|
||||
|
||||
fn string_to_timestamp(s: &str) -> Result<SystemTime, ()> {
|
||||
fn string_to_timestamp(s: &str) -> Result<SystemTime, &'static str> {
|
||||
let micros_since_unix_epoch = u64::from_str_radix(s, INT_ENCODE_BASE as u32);
|
||||
|
||||
if micros_since_unix_epoch.is_err() {
|
||||
return Err(());
|
||||
return Err("timestamp not an int");
|
||||
}
|
||||
|
||||
let micros_since_unix_epoch = micros_since_unix_epoch.unwrap();
|
||||
|
@ -9,6 +9,7 @@ mod structural_impls;
|
||||
pub mod util;
|
||||
|
||||
use std::cmp;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use hir::def_id::LocalDefId;
|
||||
use rustc_hir as hir;
|
||||
@ -36,7 +37,7 @@ pub use rustc_middle::traits::*;
|
||||
/// either identifying an `impl` (e.g., `impl Eq for i32`) that
|
||||
/// satisfies the obligation, or else finding a bound that is in
|
||||
/// scope. The eventual result is usually a `Selection` (defined below).
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone)]
|
||||
pub struct Obligation<'tcx, T> {
|
||||
/// The reason we have to prove this thing.
|
||||
pub cause: ObligationCause<'tcx>,
|
||||
@ -55,6 +56,27 @@ pub struct Obligation<'tcx, T> {
|
||||
pub recursion_depth: usize,
|
||||
}
|
||||
|
||||
impl<'tcx, T: PartialEq> PartialEq<Obligation<'tcx, T>> for Obligation<'tcx, T> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Obligation<'tcx, T>) -> bool {
|
||||
// Ignore `cause` and `recursion_depth`. This is a small performance
|
||||
// win for a few crates, and a huge performance win for the crate in
|
||||
// https://github.com/rust-lang/rustc-perf/pull/1680, which greatly
|
||||
// stresses the trait system.
|
||||
self.param_env == other.param_env && self.predicate == other.predicate
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for Obligation<'_, T> {}
|
||||
|
||||
impl<T: Hash> Hash for Obligation<'_, T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) -> () {
|
||||
// See the comment on `Obligation::eq`.
|
||||
self.param_env.hash(state);
|
||||
self.predicate.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, P> From<Obligation<'tcx, P>> for solve::Goal<'tcx, P> {
|
||||
fn from(value: Obligation<'tcx, P>) -> Self {
|
||||
solve::Goal { param_env: value.param_env, predicate: value.predicate }
|
||||
|
@ -100,7 +100,7 @@ fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>)
|
||||
|
||||
fn from_casts<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
// <expr> as *mut ...
|
||||
let e = if let ExprKind::Cast(e, t) = e.kind
|
||||
let mut e = if let ExprKind::Cast(e, t) = e.kind
|
||||
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Mut, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
|
||||
e
|
||||
// <expr>.cast_mut()
|
||||
@ -112,23 +112,36 @@ fn is_cast_from_const_to_mut<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>)
|
||||
return None;
|
||||
};
|
||||
|
||||
let e = e.peel_blocks();
|
||||
|
||||
// <expr> as *const ...
|
||||
let e = if let ExprKind::Cast(e, t) = e.kind
|
||||
&& let ty::RawPtr(TypeAndMut { mutbl: Mutability::Not, .. }) = cx.typeck_results().node_type(t.hir_id).kind() {
|
||||
e
|
||||
let mut had_at_least_one_cast = false;
|
||||
loop {
|
||||
e = e.peel_blocks();
|
||||
// <expr> as *mut/const ... or <expr> as <uint>
|
||||
e = if let ExprKind::Cast(expr, t) = e.kind
|
||||
&& matches!(cx.typeck_results().node_type(t.hir_id).kind(), ty::RawPtr(_) | ty::Uint(_)) {
|
||||
had_at_least_one_cast = true;
|
||||
expr
|
||||
// <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const()
|
||||
} else if let ExprKind::MethodCall(_, expr, [], _) = e.kind
|
||||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
&& matches!(
|
||||
cx.tcx.get_diagnostic_name(def_id),
|
||||
Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const)
|
||||
)
|
||||
{
|
||||
had_at_least_one_cast = true;
|
||||
expr
|
||||
// ptr::from_ref(<expr>)
|
||||
} else if let ExprKind::Call(path, [arg]) = e.kind
|
||||
&& let ExprKind::Path(ref qpath) = path.kind
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
|
||||
&& cx.tcx.is_diagnostic_item(sym::ptr_from_ref, def_id) {
|
||||
arg
|
||||
return Some(arg);
|
||||
} else if had_at_least_one_cast {
|
||||
return Some(e);
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(e)
|
||||
}
|
||||
}
|
||||
|
||||
fn from_transmute<'tcx>(
|
||||
|
@ -59,14 +59,14 @@ fn decodable_body(
|
||||
})
|
||||
.collect();
|
||||
let message = format!(
|
||||
"invalid enum variant tag while decoding `{}`, expected 0..{}",
|
||||
"invalid enum variant tag while decoding `{}`, expected 0..{}, actual {{}}",
|
||||
ty_name,
|
||||
variants.len()
|
||||
);
|
||||
quote! {
|
||||
match ::rustc_serialize::Decoder::read_usize(__decoder) {
|
||||
#match_inner
|
||||
_ => panic!(#message),
|
||||
n => panic!(#message, n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,18 +164,15 @@ pub fn provide(providers: &mut Providers) {
|
||||
tcx.hir_crate(()).owners[id.def_id].as_owner().map_or(AttributeMap::EMPTY, |o| &o.attrs)
|
||||
};
|
||||
providers.def_span = |tcx, def_id| {
|
||||
let def_id = def_id;
|
||||
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
tcx.hir().opt_span(hir_id).unwrap_or(DUMMY_SP)
|
||||
};
|
||||
providers.def_ident_span = |tcx, def_id| {
|
||||
let def_id = def_id;
|
||||
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
tcx.hir().opt_ident_span(hir_id)
|
||||
};
|
||||
providers.fn_arg_names = |tcx, id| {
|
||||
providers.fn_arg_names = |tcx, def_id| {
|
||||
let hir = tcx.hir();
|
||||
let def_id = id;
|
||||
let hir_id = hir.local_def_id_to_hir_id(def_id);
|
||||
if let Some(body_id) = hir.maybe_body_owned_by(def_id) {
|
||||
tcx.arena.alloc_from_iter(hir.body_param_names(body_id))
|
||||
@ -190,7 +187,7 @@ pub fn provide(providers: &mut Providers) {
|
||||
{
|
||||
idents
|
||||
} else {
|
||||
span_bug!(hir.span(hir_id), "fn_arg_names: unexpected item {:?}", id);
|
||||
span_bug!(hir.span(hir_id), "fn_arg_names: unexpected item {:?}", def_id);
|
||||
}
|
||||
};
|
||||
providers.opt_def_kind = |tcx, def_id| tcx.hir().opt_def_kind(def_id);
|
||||
|
@ -96,21 +96,6 @@ pub enum CoverageKind {
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl CoverageKind {
|
||||
pub fn as_operand(&self) -> Operand {
|
||||
use CoverageKind::*;
|
||||
match *self {
|
||||
Counter { id, .. } => Operand::Counter(id),
|
||||
Expression { id, .. } => Operand::Expression(id),
|
||||
Unreachable => bug!("Unreachable coverage cannot be part of an expression"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_expression(&self) -> bool {
|
||||
matches!(self, Self::Expression { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for CoverageKind {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
|
||||
use CoverageKind::*;
|
||||
|
@ -1109,10 +1109,6 @@ pub struct VarDebugInfo<'tcx> {
|
||||
/// originated from (starting from 1). Note, if MIR inlining is enabled, then this is the
|
||||
/// argument number in the original function before it was inlined.
|
||||
pub argument_index: Option<u16>,
|
||||
|
||||
/// The data represents `name` dereferenced `references` times,
|
||||
/// and not the direct value.
|
||||
pub references: u8,
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -35,7 +35,7 @@ impl<'tcx> MirPatch<'tcx> {
|
||||
|
||||
for (bb, block) in body.basic_blocks.iter_enumerated() {
|
||||
// Check if we already have a resume block
|
||||
if let TerminatorKind::Resume = block.terminator().kind && block.statements.is_empty() {
|
||||
if let TerminatorKind::UnwindResume = block.terminator().kind && block.statements.is_empty() {
|
||||
result.resume_block = Some(bb);
|
||||
continue;
|
||||
}
|
||||
@ -50,7 +50,7 @@ impl<'tcx> MirPatch<'tcx> {
|
||||
}
|
||||
|
||||
// Check if we already have a terminate block
|
||||
if let TerminatorKind::Terminate = block.terminator().kind && block.statements.is_empty() {
|
||||
if let TerminatorKind::UnwindTerminate = block.terminator().kind && block.statements.is_empty() {
|
||||
result.terminate_block = Some(bb);
|
||||
continue;
|
||||
}
|
||||
@ -68,7 +68,7 @@ impl<'tcx> MirPatch<'tcx> {
|
||||
statements: vec![],
|
||||
terminator: Some(Terminator {
|
||||
source_info: SourceInfo::outermost(self.body_span),
|
||||
kind: TerminatorKind::Resume,
|
||||
kind: TerminatorKind::UnwindResume,
|
||||
}),
|
||||
is_cleanup: true,
|
||||
});
|
||||
@ -102,7 +102,7 @@ impl<'tcx> MirPatch<'tcx> {
|
||||
statements: vec![],
|
||||
terminator: Some(Terminator {
|
||||
source_info: SourceInfo::outermost(self.body_span),
|
||||
kind: TerminatorKind::Terminate,
|
||||
kind: TerminatorKind::UnwindTerminate,
|
||||
}),
|
||||
is_cleanup: true,
|
||||
});
|
||||
|
@ -555,13 +555,8 @@ fn write_scope_tree(
|
||||
}
|
||||
|
||||
let indented_debug_info = format!(
|
||||
"{0:1$}debug {2} => {3:&<4$}{5:?};",
|
||||
INDENT,
|
||||
indent,
|
||||
var_debug_info.name,
|
||||
"",
|
||||
var_debug_info.references as usize,
|
||||
var_debug_info.value,
|
||||
"{0:1$}debug {2} => {3:?};",
|
||||
INDENT, indent, var_debug_info.name, var_debug_info.value,
|
||||
);
|
||||
|
||||
if tcx.sess.opts.unstable_opts.mir_include_spans {
|
||||
|
@ -238,45 +238,6 @@ pub fn source_range_no_file(tcx: TyCtxt<'_>, span: Span) -> String {
|
||||
format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1)
|
||||
}
|
||||
|
||||
pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
|
||||
use StatementKind::*;
|
||||
match statement.kind {
|
||||
Assign(..) => "Assign",
|
||||
FakeRead(..) => "FakeRead",
|
||||
SetDiscriminant { .. } => "SetDiscriminant",
|
||||
Deinit(..) => "Deinit",
|
||||
StorageLive(..) => "StorageLive",
|
||||
StorageDead(..) => "StorageDead",
|
||||
Retag(..) => "Retag",
|
||||
PlaceMention(..) => "PlaceMention",
|
||||
AscribeUserType(..) => "AscribeUserType",
|
||||
Coverage(..) => "Coverage",
|
||||
Intrinsic(..) => "Intrinsic",
|
||||
ConstEvalCounter => "ConstEvalCounter",
|
||||
Nop => "Nop",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
|
||||
use TerminatorKind::*;
|
||||
match term.kind {
|
||||
Goto { .. } => "Goto",
|
||||
SwitchInt { .. } => "SwitchInt",
|
||||
Resume => "Resume",
|
||||
Terminate => "Terminate",
|
||||
Return => "Return",
|
||||
Unreachable => "Unreachable",
|
||||
Drop { .. } => "Drop",
|
||||
Call { .. } => "Call",
|
||||
Assert { .. } => "Assert",
|
||||
Yield { .. } => "Yield",
|
||||
GeneratorDrop => "GeneratorDrop",
|
||||
FalseEdge { .. } => "FalseEdge",
|
||||
FalseUnwind { .. } => "FalseUnwind",
|
||||
InlineAsm { .. } => "InlineAsm",
|
||||
}
|
||||
}
|
||||
|
||||
fn statement_span_viewable<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_span: Span,
|
||||
@ -304,7 +265,7 @@ fn terminator_span_viewable<'tcx>(
|
||||
if !body_span.contains(span) {
|
||||
return None;
|
||||
}
|
||||
let id = format!("{}:{}", bb.index(), terminator_kind_name(term));
|
||||
let id = format!("{}:{}", bb.index(), term.kind.name());
|
||||
let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator);
|
||||
Some(SpanViewable { bb, span, id, tooltip })
|
||||
}
|
||||
@ -631,7 +592,7 @@ fn tooltip<'tcx>(
|
||||
"\n{}{}: {}: {:?}",
|
||||
TOOLTIP_INDENT,
|
||||
source_range,
|
||||
statement_kind_name(&statement),
|
||||
statement.kind.name(),
|
||||
statement
|
||||
));
|
||||
}
|
||||
@ -641,7 +602,7 @@ fn tooltip<'tcx>(
|
||||
"\n{}{}: {}: {:?}",
|
||||
TOOLTIP_INDENT,
|
||||
source_range,
|
||||
terminator_kind_name(term),
|
||||
term.kind.name(),
|
||||
term.kind
|
||||
));
|
||||
}
|
||||
|
@ -380,6 +380,28 @@ pub enum StatementKind<'tcx> {
|
||||
Nop,
|
||||
}
|
||||
|
||||
impl StatementKind<'_> {
|
||||
/// Returns a simple string representation of a `StatementKind` variant, independent of any
|
||||
/// values it might hold (e.g. `StatementKind::Assign` always returns `"Assign"`).
|
||||
pub const fn name(&self) -> &'static str {
|
||||
match self {
|
||||
StatementKind::Assign(..) => "Assign",
|
||||
StatementKind::FakeRead(..) => "FakeRead",
|
||||
StatementKind::SetDiscriminant { .. } => "SetDiscriminant",
|
||||
StatementKind::Deinit(..) => "Deinit",
|
||||
StatementKind::StorageLive(..) => "StorageLive",
|
||||
StatementKind::StorageDead(..) => "StorageDead",
|
||||
StatementKind::Retag(..) => "Retag",
|
||||
StatementKind::PlaceMention(..) => "PlaceMention",
|
||||
StatementKind::AscribeUserType(..) => "AscribeUserType",
|
||||
StatementKind::Coverage(..) => "Coverage",
|
||||
StatementKind::Intrinsic(..) => "Intrinsic",
|
||||
StatementKind::ConstEvalCounter => "ConstEvalCounter",
|
||||
StatementKind::Nop => "Nop",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
TyEncodable,
|
||||
@ -593,13 +615,13 @@ pub enum TerminatorKind<'tcx> {
|
||||
///
|
||||
/// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after
|
||||
/// deaggregation runs.
|
||||
Resume,
|
||||
UnwindResume,
|
||||
|
||||
/// Indicates that the landing pad is finished and that the process should terminate.
|
||||
///
|
||||
/// Used to prevent unwinding for foreign items or with `-C unwind=abort`. Only permitted in
|
||||
/// cleanup blocks.
|
||||
Terminate,
|
||||
UnwindTerminate,
|
||||
|
||||
/// Returns from the function.
|
||||
///
|
||||
@ -790,8 +812,8 @@ impl TerminatorKind<'_> {
|
||||
match self {
|
||||
TerminatorKind::Goto { .. } => "Goto",
|
||||
TerminatorKind::SwitchInt { .. } => "SwitchInt",
|
||||
TerminatorKind::Resume => "Resume",
|
||||
TerminatorKind::Terminate => "Terminate",
|
||||
TerminatorKind::UnwindResume => "UnwindResume",
|
||||
TerminatorKind::UnwindTerminate => "UnwindTerminate",
|
||||
TerminatorKind::Return => "Return",
|
||||
TerminatorKind::Unreachable => "Unreachable",
|
||||
TerminatorKind::Drop { .. } => "Drop",
|
||||
|
@ -10,6 +10,7 @@ use std::iter;
|
||||
use std::slice;
|
||||
|
||||
pub use super::query::*;
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, TyEncodable, TyDecodable, Hash, HashStable, PartialEq)]
|
||||
pub struct SwitchTargets {
|
||||
@ -154,8 +155,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
| InlineAsm { destination: Some(t), unwind: _, .. } => {
|
||||
Some(t).into_iter().chain((&[]).into_iter().copied())
|
||||
}
|
||||
Resume
|
||||
| Terminate
|
||||
UnwindResume
|
||||
| UnwindTerminate
|
||||
| GeneratorDrop
|
||||
| Return
|
||||
| Unreachable
|
||||
@ -196,8 +197,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
| InlineAsm { destination: Some(ref mut t), unwind: _, .. } => {
|
||||
Some(t).into_iter().chain(&mut [])
|
||||
}
|
||||
Resume
|
||||
| Terminate
|
||||
UnwindResume
|
||||
| UnwindTerminate
|
||||
| GeneratorDrop
|
||||
| Return
|
||||
| Unreachable
|
||||
@ -213,8 +214,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn unwind(&self) -> Option<&UnwindAction> {
|
||||
match *self {
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::GeneratorDrop
|
||||
@ -232,8 +233,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn unwind_mut(&mut self) -> Option<&mut UnwindAction> {
|
||||
match *self {
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::GeneratorDrop
|
||||
@ -310,8 +311,8 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
SwitchInt { discr, .. } => write!(fmt, "switchInt({discr:?})"),
|
||||
Return => write!(fmt, "return"),
|
||||
GeneratorDrop => write!(fmt, "generator_drop"),
|
||||
Resume => write!(fmt, "resume"),
|
||||
Terminate => write!(fmt, "abort"),
|
||||
UnwindResume => write!(fmt, "resume"),
|
||||
UnwindTerminate => write!(fmt, "abort"),
|
||||
Yield { value, resume_arg, .. } => write!(fmt, "{resume_arg:?} = yield({value:?})"),
|
||||
Unreachable => write!(fmt, "unreachable"),
|
||||
Drop { place, .. } => write!(fmt, "drop({place:?})"),
|
||||
@ -390,7 +391,7 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
|
||||
use self::TerminatorKind::*;
|
||||
match *self {
|
||||
Return | Resume | Terminate | Unreachable | GeneratorDrop => vec![],
|
||||
Return | UnwindResume | UnwindTerminate | Unreachable | GeneratorDrop => vec![],
|
||||
Goto { .. } => vec!["".into()],
|
||||
SwitchInt { ref targets, .. } => targets
|
||||
.values
|
||||
@ -430,3 +431,110 @@ impl<'tcx> TerminatorKind<'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum TerminatorEdges<'mir, 'tcx> {
|
||||
/// For terminators that have no successor, like `return`.
|
||||
None,
|
||||
/// For terminators that a single successor, like `goto`, and `assert` without cleanup block.
|
||||
Single(BasicBlock),
|
||||
/// For terminators that two successors, `assert` with cleanup block and `falseEdge`.
|
||||
Double(BasicBlock, BasicBlock),
|
||||
/// Special action for `Yield`, `Call` and `InlineAsm` terminators.
|
||||
AssignOnReturn {
|
||||
return_: Option<BasicBlock>,
|
||||
unwind: UnwindAction,
|
||||
place: CallReturnPlaces<'mir, 'tcx>,
|
||||
},
|
||||
/// Special edge for `SwitchInt`.
|
||||
SwitchInt { targets: &'mir SwitchTargets, discr: &'mir Operand<'tcx> },
|
||||
}
|
||||
|
||||
/// List of places that are written to after a successful (non-unwind) return
|
||||
/// from a `Call`, `Yield` or `InlineAsm`.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CallReturnPlaces<'a, 'tcx> {
|
||||
Call(Place<'tcx>),
|
||||
Yield(Place<'tcx>),
|
||||
InlineAsm(&'a [InlineAsmOperand<'tcx>]),
|
||||
}
|
||||
|
||||
impl<'tcx> CallReturnPlaces<'_, 'tcx> {
|
||||
pub fn for_each(&self, mut f: impl FnMut(Place<'tcx>)) {
|
||||
match *self {
|
||||
Self::Call(place) | Self::Yield(place) => f(place),
|
||||
Self::InlineAsm(operands) => {
|
||||
for op in operands {
|
||||
match *op {
|
||||
InlineAsmOperand::Out { place: Some(place), .. }
|
||||
| InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Terminator<'tcx> {
|
||||
pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> {
|
||||
self.kind.edges()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> TerminatorKind<'tcx> {
|
||||
pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> {
|
||||
use TerminatorKind::*;
|
||||
match *self {
|
||||
Return | UnwindResume | UnwindTerminate | GeneratorDrop | Unreachable => {
|
||||
TerminatorEdges::None
|
||||
}
|
||||
|
||||
Goto { target } => TerminatorEdges::Single(target),
|
||||
|
||||
Assert { target, unwind, expected: _, msg: _, cond: _ }
|
||||
| Drop { target, unwind, place: _, replace: _ }
|
||||
| FalseUnwind { real_target: target, unwind } => match unwind {
|
||||
UnwindAction::Cleanup(unwind) => TerminatorEdges::Double(target, unwind),
|
||||
UnwindAction::Continue | UnwindAction::Terminate | UnwindAction::Unreachable => {
|
||||
TerminatorEdges::Single(target)
|
||||
}
|
||||
},
|
||||
|
||||
FalseEdge { real_target, imaginary_target } => {
|
||||
TerminatorEdges::Double(real_target, imaginary_target)
|
||||
}
|
||||
|
||||
Yield { resume: target, drop, resume_arg, value: _ } => {
|
||||
TerminatorEdges::AssignOnReturn {
|
||||
return_: Some(target),
|
||||
unwind: drop.map_or(UnwindAction::Terminate, UnwindAction::Cleanup),
|
||||
place: CallReturnPlaces::Yield(resume_arg),
|
||||
}
|
||||
}
|
||||
|
||||
Call { unwind, destination, target, func: _, args: _, fn_span: _, call_source: _ } => {
|
||||
TerminatorEdges::AssignOnReturn {
|
||||
return_: target,
|
||||
unwind,
|
||||
place: CallReturnPlaces::Call(destination),
|
||||
}
|
||||
}
|
||||
|
||||
InlineAsm {
|
||||
template: _,
|
||||
ref operands,
|
||||
options: _,
|
||||
line_spans: _,
|
||||
destination,
|
||||
unwind,
|
||||
} => TerminatorEdges::AssignOnReturn {
|
||||
return_: destination,
|
||||
unwind,
|
||||
place: CallReturnPlaces::InlineAsm(operands),
|
||||
},
|
||||
|
||||
SwitchInt { ref targets, ref discr } => TerminatorEdges::SwitchInt { targets, discr },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -469,8 +469,8 @@ macro_rules! make_mir_visitor {
|
||||
self.visit_source_info(source_info);
|
||||
match kind {
|
||||
TerminatorKind::Goto { .. } |
|
||||
TerminatorKind::Resume |
|
||||
TerminatorKind::Terminate |
|
||||
TerminatorKind::UnwindResume |
|
||||
TerminatorKind::UnwindTerminate |
|
||||
TerminatorKind::GeneratorDrop |
|
||||
TerminatorKind::Unreachable |
|
||||
TerminatorKind::FalseEdge { .. } |
|
||||
@ -840,7 +840,6 @@ macro_rules! make_mir_visitor {
|
||||
source_info,
|
||||
value,
|
||||
argument_index: _,
|
||||
references: _,
|
||||
} = var_debug_info;
|
||||
|
||||
self.visit_source_info(source_info);
|
||||
|
@ -388,7 +388,6 @@ rustc_queries! {
|
||||
}
|
||||
|
||||
query shallow_lint_levels_on(key: hir::OwnerId) -> &'tcx rustc_middle::lint::ShallowLintLevelMap {
|
||||
eval_always // fetches `resolutions`
|
||||
arena_cache
|
||||
desc { |tcx| "looking up lint levels for `{}`", tcx.def_path_str(key) }
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Diagnostics related methods for `Ty`.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use crate::ty::{
|
||||
@ -335,10 +336,10 @@ pub fn suggest_constraining_type_params<'a>(
|
||||
// - insert: `, X: Bar`
|
||||
suggestions.push((
|
||||
generics.tail_span_for_predicate_suggestion(),
|
||||
constraints
|
||||
.iter()
|
||||
.map(|&(constraint, _)| format!(", {param_name}: {constraint}"))
|
||||
.collect::<String>(),
|
||||
constraints.iter().fold(String::new(), |mut string, &(constraint, _)| {
|
||||
write!(string, ", {param_name}: {constraint}").unwrap();
|
||||
string
|
||||
}),
|
||||
SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
|
||||
));
|
||||
continue;
|
||||
|
@ -438,7 +438,6 @@ CloneLiftImpls! {
|
||||
(),
|
||||
bool,
|
||||
usize,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
|
@ -2827,11 +2827,11 @@ impl<'tcx> Ty<'tcx> {
|
||||
|
||||
ty::Adt(def, _args) => def.sized_constraint(tcx).skip_binder().is_empty(),
|
||||
|
||||
ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => false,
|
||||
ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) | ty::Bound(..) => false,
|
||||
|
||||
ty::Infer(ty::TyVar(_)) => false,
|
||||
|
||||
ty::Bound(..) | ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
|
||||
ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => {
|
||||
bug!("`is_trivially_sized` applied to unexpected type: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
@ -61,9 +61,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
|
||||
})
|
||||
},
|
||||
@call("mir_call", args) => {
|
||||
let destination = self.parse_place(args[0])?;
|
||||
let target = self.parse_block(args[1])?;
|
||||
self.parse_call(args[2], destination, target)
|
||||
self.parse_call(args)
|
||||
},
|
||||
ExprKind::Match { scrutinee, arms, .. } => {
|
||||
let discr = self.parse_operand(*scrutinee)?;
|
||||
@ -109,13 +107,14 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> {
|
||||
Ok(SwitchTargets::new(values.into_iter().zip(targets), otherwise))
|
||||
}
|
||||
|
||||
fn parse_call(
|
||||
&self,
|
||||
expr_id: ExprId,
|
||||
destination: Place<'tcx>,
|
||||
target: BasicBlock,
|
||||
) -> PResult<TerminatorKind<'tcx>> {
|
||||
parse_by_kind!(self, expr_id, _, "function call",
|
||||
fn parse_call(&self, args: &[ExprId]) -> PResult<TerminatorKind<'tcx>> {
|
||||
let (destination, call) = parse_by_kind!(self, args[0], _, "function call",
|
||||
ExprKind::Assign { lhs, rhs } => (*lhs, *rhs),
|
||||
);
|
||||
let destination = self.parse_place(destination)?;
|
||||
let target = self.parse_block(args[1])?;
|
||||
|
||||
parse_by_kind!(self, call, _, "function call",
|
||||
ExprKind::Call { fun, args, from_hir_call, fn_span, .. } => {
|
||||
let fun = self.parse_operand(*fun)?;
|
||||
let args = args
|
||||
|
@ -2242,7 +2242,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
self.var_debug_info.push(VarDebugInfo {
|
||||
name,
|
||||
source_info: debug_source_info,
|
||||
references: 0,
|
||||
value: VarDebugInfoContents::Place(for_arm_body.into()),
|
||||
argument_index: None,
|
||||
});
|
||||
@ -2262,7 +2261,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
self.var_debug_info.push(VarDebugInfo {
|
||||
name,
|
||||
source_info: debug_source_info,
|
||||
references: 0,
|
||||
value: VarDebugInfoContents::Place(ref_for_guard.into()),
|
||||
argument_index: None,
|
||||
});
|
||||
|
@ -820,7 +820,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
};
|
||||
self.var_debug_info.push(VarDebugInfo {
|
||||
name,
|
||||
references: 0,
|
||||
source_info: SourceInfo::outermost(captured_place.var_ident.span),
|
||||
value: VarDebugInfoContents::Place(use_place),
|
||||
argument_index: None,
|
||||
@ -851,7 +850,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
self.var_debug_info.push(VarDebugInfo {
|
||||
name,
|
||||
source_info,
|
||||
references: 0,
|
||||
value: VarDebugInfoContents::Place(arg_local.into()),
|
||||
argument_index: Some(argument_index as u16 + 1),
|
||||
});
|
||||
|
@ -685,9 +685,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
drops.add_entry(block, drop_idx);
|
||||
|
||||
// `build_drop_trees` doesn't have access to our source_info, so we
|
||||
// create a dummy terminator now. `TerminatorKind::Resume` is used
|
||||
// create a dummy terminator now. `TerminatorKind::UnwindResume` is used
|
||||
// because MIR type checking will panic if it hasn't been overwritten.
|
||||
self.cfg.terminate(block, source_info, TerminatorKind::Resume);
|
||||
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
|
||||
|
||||
self.cfg.start_new_block().unit()
|
||||
}
|
||||
@ -717,9 +717,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
drops.add_entry(block, drop_idx);
|
||||
|
||||
// `build_drop_trees` doesn't have access to our source_info, so we
|
||||
// create a dummy terminator now. `TerminatorKind::Resume` is used
|
||||
// create a dummy terminator now. `TerminatorKind::UnwindResume` is used
|
||||
// because MIR type checking will panic if it hasn't been overwritten.
|
||||
self.cfg.terminate(block, source_info, TerminatorKind::Resume);
|
||||
self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume);
|
||||
}
|
||||
|
||||
// Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue`
|
||||
@ -1441,7 +1441,7 @@ impl<'a, 'tcx: 'a> Builder<'a, 'tcx> {
|
||||
blocks[ROOT_NODE] = *resume_block;
|
||||
drops.build_mir::<Unwind>(cfg, &mut blocks);
|
||||
if let (None, Some(resume)) = (*resume_block, blocks[ROOT_NODE]) {
|
||||
cfg.terminate(resume, SourceInfo::outermost(fn_span), TerminatorKind::Resume);
|
||||
cfg.terminate(resume, SourceInfo::outermost(fn_span), TerminatorKind::UnwindResume);
|
||||
|
||||
*resume_block = blocks[ROOT_NODE];
|
||||
}
|
||||
@ -1506,8 +1506,8 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind {
|
||||
}
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Yield { .. }
|
||||
|
@ -186,9 +186,9 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor<BasicBlocks<'tcx
|
||||
|
||||
match self.body[bb].terminator().kind {
|
||||
// These terminators return control flow to the caller.
|
||||
TerminatorKind::Terminate
|
||||
TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
|
||||
|
@ -1,11 +1,10 @@
|
||||
use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets, UnwindAction};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::mir::{
|
||||
self, BasicBlock, CallReturnPlaces, Location, SwitchTargets, TerminatorEdges, UnwindAction,
|
||||
};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use super::visitor::{ResultsVisitable, ResultsVisitor};
|
||||
use super::{
|
||||
Analysis, CallReturnPlaces, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget,
|
||||
};
|
||||
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget};
|
||||
|
||||
pub trait Direction {
|
||||
const IS_FORWARD: bool;
|
||||
@ -24,15 +23,17 @@ pub trait Direction {
|
||||
) where
|
||||
A: Analysis<'tcx>;
|
||||
|
||||
fn apply_effects_in_block<'tcx, A>(
|
||||
fn apply_effects_in_block<'mir, 'tcx, A>(
|
||||
analysis: &mut A,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
|
||||
) -> TerminatorEdges<'mir, 'tcx>
|
||||
where
|
||||
A: Analysis<'tcx>;
|
||||
|
||||
fn gen_kill_effects_in_block<'tcx, A>(
|
||||
fn gen_kill_statement_effects_in_block<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
trans: &mut GenKillSet<A::Idx>,
|
||||
block: BasicBlock,
|
||||
@ -51,10 +52,10 @@ pub trait Direction {
|
||||
|
||||
fn join_state_into_successors_of<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &mir::Body<'tcx>,
|
||||
exit_state: &mut A::Domain,
|
||||
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
block: BasicBlock,
|
||||
edges: TerminatorEdges<'_, 'tcx>,
|
||||
propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>;
|
||||
@ -66,27 +67,33 @@ pub struct Backward;
|
||||
impl Direction for Backward {
|
||||
const IS_FORWARD: bool = false;
|
||||
|
||||
fn apply_effects_in_block<'tcx, A>(
|
||||
fn apply_effects_in_block<'mir, 'tcx, A>(
|
||||
analysis: &mut A,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
|
||||
) -> TerminatorEdges<'mir, 'tcx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
let terminator = block_data.terminator();
|
||||
let location = Location { block, statement_index: block_data.statements.len() };
|
||||
analysis.apply_before_terminator_effect(state, terminator, location);
|
||||
analysis.apply_terminator_effect(state, terminator, location);
|
||||
|
||||
let edges = analysis.apply_terminator_effect(state, terminator, location);
|
||||
if let Some(statement_effect) = statement_effect {
|
||||
statement_effect(block, state)
|
||||
} else {
|
||||
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
|
||||
let location = Location { block, statement_index };
|
||||
analysis.apply_before_statement_effect(state, statement, location);
|
||||
analysis.apply_statement_effect(state, statement, location);
|
||||
}
|
||||
}
|
||||
edges
|
||||
}
|
||||
|
||||
fn gen_kill_effects_in_block<'tcx, A>(
|
||||
fn gen_kill_statement_effects_in_block<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
trans: &mut GenKillSet<A::Idx>,
|
||||
block: BasicBlock,
|
||||
@ -94,11 +101,6 @@ impl Direction for Backward {
|
||||
) where
|
||||
A: GenKillAnalysis<'tcx>,
|
||||
{
|
||||
let terminator = block_data.terminator();
|
||||
let location = Location { block, statement_index: block_data.statements.len() };
|
||||
analysis.before_terminator_effect(trans, terminator, location);
|
||||
analysis.terminator_effect(trans, terminator, location);
|
||||
|
||||
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
|
||||
let location = Location { block, statement_index };
|
||||
analysis.before_statement_effect(trans, statement, location);
|
||||
@ -217,10 +219,10 @@ impl Direction for Backward {
|
||||
|
||||
fn join_state_into_successors_of<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
_tcx: TyCtxt<'tcx>,
|
||||
body: &mir::Body<'tcx>,
|
||||
exit_state: &mut A::Domain,
|
||||
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
bb: BasicBlock,
|
||||
_edges: TerminatorEdges<'_, 'tcx>,
|
||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>,
|
||||
@ -254,7 +256,11 @@ impl Direction for Backward {
|
||||
|
||||
mir::TerminatorKind::Yield { resume, resume_arg, .. } if resume == bb => {
|
||||
let mut tmp = exit_state.clone();
|
||||
analysis.apply_yield_resume_effect(&mut tmp, resume, resume_arg);
|
||||
analysis.apply_call_return_effect(
|
||||
&mut tmp,
|
||||
resume,
|
||||
CallReturnPlaces::Yield(resume_arg),
|
||||
);
|
||||
propagate(pred, &tmp);
|
||||
}
|
||||
|
||||
@ -318,27 +324,33 @@ pub struct Forward;
|
||||
impl Direction for Forward {
|
||||
const IS_FORWARD: bool = true;
|
||||
|
||||
fn apply_effects_in_block<'tcx, A>(
|
||||
fn apply_effects_in_block<'mir, 'tcx, A>(
|
||||
analysis: &mut A,
|
||||
state: &mut A::Domain,
|
||||
block: BasicBlock,
|
||||
block_data: &mir::BasicBlockData<'tcx>,
|
||||
) where
|
||||
block_data: &'mir mir::BasicBlockData<'tcx>,
|
||||
statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
|
||||
) -> TerminatorEdges<'mir, 'tcx>
|
||||
where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
if let Some(statement_effect) = statement_effect {
|
||||
statement_effect(block, state)
|
||||
} else {
|
||||
for (statement_index, statement) in block_data.statements.iter().enumerate() {
|
||||
let location = Location { block, statement_index };
|
||||
analysis.apply_before_statement_effect(state, statement, location);
|
||||
analysis.apply_statement_effect(state, statement, location);
|
||||
}
|
||||
}
|
||||
|
||||
let terminator = block_data.terminator();
|
||||
let location = Location { block, statement_index: block_data.statements.len() };
|
||||
analysis.apply_before_terminator_effect(state, terminator, location);
|
||||
analysis.apply_terminator_effect(state, terminator, location);
|
||||
analysis.apply_terminator_effect(state, terminator, location)
|
||||
}
|
||||
|
||||
fn gen_kill_effects_in_block<'tcx, A>(
|
||||
fn gen_kill_statement_effects_in_block<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
trans: &mut GenKillSet<A::Idx>,
|
||||
block: BasicBlock,
|
||||
@ -351,11 +363,6 @@ impl Direction for Forward {
|
||||
analysis.before_statement_effect(trans, statement, location);
|
||||
analysis.statement_effect(trans, statement, location);
|
||||
}
|
||||
|
||||
let terminator = block_data.terminator();
|
||||
let location = Location { block, statement_index: block_data.statements.len() };
|
||||
analysis.before_terminator_effect(trans, terminator, location);
|
||||
analysis.terminator_effect(trans, terminator, location);
|
||||
}
|
||||
|
||||
fn apply_effects_in_range<'tcx, A>(
|
||||
@ -464,86 +471,32 @@ impl Direction for Forward {
|
||||
|
||||
fn join_state_into_successors_of<'tcx, A>(
|
||||
analysis: &mut A,
|
||||
_tcx: TyCtxt<'tcx>,
|
||||
_body: &mir::Body<'tcx>,
|
||||
exit_state: &mut A::Domain,
|
||||
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
|
||||
bb: BasicBlock,
|
||||
edges: TerminatorEdges<'_, 'tcx>,
|
||||
mut propagate: impl FnMut(BasicBlock, &A::Domain),
|
||||
) where
|
||||
A: Analysis<'tcx>,
|
||||
{
|
||||
use mir::TerminatorKind::*;
|
||||
match bb_data.terminator().kind {
|
||||
Return | Resume | Terminate | GeneratorDrop | Unreachable => {}
|
||||
|
||||
Goto { target } => propagate(target, exit_state),
|
||||
|
||||
Assert { target, unwind, expected: _, msg: _, cond: _ }
|
||||
| Drop { target, unwind, place: _, replace: _ }
|
||||
| FalseUnwind { real_target: target, unwind } => {
|
||||
match edges {
|
||||
TerminatorEdges::None => {}
|
||||
TerminatorEdges::Single(target) => propagate(target, exit_state),
|
||||
TerminatorEdges::Double(target, unwind) => {
|
||||
propagate(target, exit_state);
|
||||
propagate(unwind, exit_state);
|
||||
}
|
||||
TerminatorEdges::AssignOnReturn { return_, unwind, place } => {
|
||||
// This must be done *first*, otherwise the unwind path will see the assignments.
|
||||
if let UnwindAction::Cleanup(unwind) = unwind {
|
||||
propagate(unwind, exit_state);
|
||||
}
|
||||
|
||||
propagate(target, exit_state);
|
||||
}
|
||||
|
||||
FalseEdge { real_target, imaginary_target } => {
|
||||
propagate(real_target, exit_state);
|
||||
propagate(imaginary_target, exit_state);
|
||||
}
|
||||
|
||||
Yield { resume: target, drop, resume_arg, value: _ } => {
|
||||
if let Some(drop) = drop {
|
||||
propagate(drop, exit_state);
|
||||
}
|
||||
|
||||
analysis.apply_yield_resume_effect(exit_state, target, resume_arg);
|
||||
propagate(target, exit_state);
|
||||
}
|
||||
|
||||
Call { unwind, destination, target, func: _, args: _, call_source: _, fn_span: _ } => {
|
||||
if let UnwindAction::Cleanup(unwind) = unwind {
|
||||
propagate(unwind, exit_state);
|
||||
}
|
||||
|
||||
if let Some(target) = target {
|
||||
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
||||
// return effect.
|
||||
analysis.apply_call_return_effect(
|
||||
exit_state,
|
||||
bb,
|
||||
CallReturnPlaces::Call(destination),
|
||||
);
|
||||
propagate(target, exit_state);
|
||||
if let Some(return_) = return_ {
|
||||
analysis.apply_call_return_effect(exit_state, bb, place);
|
||||
propagate(return_, exit_state);
|
||||
}
|
||||
}
|
||||
|
||||
InlineAsm {
|
||||
template: _,
|
||||
ref operands,
|
||||
options: _,
|
||||
line_spans: _,
|
||||
destination,
|
||||
unwind,
|
||||
} => {
|
||||
if let UnwindAction::Cleanup(unwind) = unwind {
|
||||
propagate(unwind, exit_state);
|
||||
}
|
||||
|
||||
if let Some(target) = destination {
|
||||
// N.B.: This must be done *last*, otherwise the unwind path will see the call
|
||||
// return effect.
|
||||
analysis.apply_call_return_effect(
|
||||
exit_state,
|
||||
bb,
|
||||
CallReturnPlaces::InlineAsm(operands),
|
||||
);
|
||||
propagate(target, exit_state);
|
||||
}
|
||||
}
|
||||
|
||||
SwitchInt { ref targets, ref discr } => {
|
||||
TerminatorEdges::SwitchInt { targets, discr } => {
|
||||
let mut applier = ForwardSwitchIntEdgeEffectsApplier {
|
||||
exit_state,
|
||||
targets,
|
||||
|
@ -144,7 +144,7 @@ where
|
||||
// gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
|
||||
// performance in practice. I've tried a few ways to avoid this, but they have downsides. See
|
||||
// the message for the commit that added this FIXME for more information.
|
||||
apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
|
||||
apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx, A, D, T> Engine<'a, 'tcx, A>
|
||||
@ -165,12 +165,17 @@ where
|
||||
|
||||
// Otherwise, compute and store the cumulative transfer function for each block.
|
||||
|
||||
let identity = GenKillSet::identity(analysis.bottom_value(body).domain_size());
|
||||
let identity = GenKillSet::identity(analysis.domain_size(body));
|
||||
let mut trans_for_block = IndexVec::from_elem(identity, &body.basic_blocks);
|
||||
|
||||
for (block, block_data) in body.basic_blocks.iter_enumerated() {
|
||||
let trans = &mut trans_for_block[block];
|
||||
A::Direction::gen_kill_effects_in_block(&mut analysis, trans, block, block_data);
|
||||
A::Direction::gen_kill_statement_effects_in_block(
|
||||
&mut analysis,
|
||||
trans,
|
||||
block,
|
||||
block_data,
|
||||
);
|
||||
}
|
||||
|
||||
let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
|
||||
@ -199,17 +204,18 @@ where
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
analysis: A,
|
||||
apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
|
||||
apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
|
||||
) -> Self {
|
||||
let bottom_value = analysis.bottom_value(body);
|
||||
let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), &body.basic_blocks);
|
||||
let mut entry_sets =
|
||||
IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
|
||||
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
|
||||
|
||||
if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != bottom_value {
|
||||
if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != analysis.bottom_value(body)
|
||||
{
|
||||
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
|
||||
}
|
||||
|
||||
Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block }
|
||||
Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_statement_trans_for_block }
|
||||
}
|
||||
|
||||
/// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
|
||||
@ -231,7 +237,7 @@ where
|
||||
body,
|
||||
mut entry_sets,
|
||||
tcx,
|
||||
apply_trans_for_block,
|
||||
apply_statement_trans_for_block,
|
||||
pass_name,
|
||||
..
|
||||
} = self;
|
||||
@ -263,19 +269,20 @@ where
|
||||
state.clone_from(&entry_sets[bb]);
|
||||
|
||||
// Apply the block transfer function, using the cached one if it exists.
|
||||
match &apply_trans_for_block {
|
||||
Some(apply) => apply(bb, &mut state),
|
||||
None => {
|
||||
A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data)
|
||||
}
|
||||
}
|
||||
let edges = A::Direction::apply_effects_in_block(
|
||||
&mut analysis,
|
||||
&mut state,
|
||||
bb,
|
||||
bb_data,
|
||||
apply_statement_trans_for_block.as_deref(),
|
||||
);
|
||||
|
||||
A::Direction::join_state_into_successors_of(
|
||||
&mut analysis,
|
||||
tcx,
|
||||
body,
|
||||
&mut state,
|
||||
(bb, bb_data),
|
||||
bb,
|
||||
edges,
|
||||
|target: BasicBlock, state: &A::Domain| {
|
||||
let set_changed = entry_sets[target].join(state);
|
||||
if set_changed {
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow
|
||||
//! analysis.
|
||||
|
||||
use super::lattice::MaybeReachable;
|
||||
use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
|
||||
use rustc_index::Idx;
|
||||
use std::fmt;
|
||||
@ -124,6 +125,37 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C> DebugWithContext<C> for MaybeReachable<S>
|
||||
where
|
||||
S: DebugWithContext<C>,
|
||||
{
|
||||
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
MaybeReachable::Unreachable => {
|
||||
write!(f, "unreachable")
|
||||
}
|
||||
MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match (self, old) {
|
||||
(MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()),
|
||||
(MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => {
|
||||
write!(f, "\u{001f}+")?;
|
||||
set.fmt_with(ctxt, f)
|
||||
}
|
||||
(MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => {
|
||||
write!(f, "\u{001f}-")?;
|
||||
set.fmt_with(ctxt, f)
|
||||
}
|
||||
(MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => {
|
||||
this.fmt_diff_with(old, ctxt, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_diff<T, C>(
|
||||
inserted: &HybridBitSet<T>,
|
||||
removed: &HybridBitSet<T>,
|
||||
|
@ -269,7 +269,11 @@ where
|
||||
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
|
||||
let state_on_generator_drop = this.results.get().clone();
|
||||
this.results.apply_custom_effect(|analysis, state| {
|
||||
analysis.apply_yield_resume_effect(state, resume, resume_arg);
|
||||
analysis.apply_call_return_effect(
|
||||
state,
|
||||
resume,
|
||||
CallReturnPlaces::Yield(resume_arg),
|
||||
);
|
||||
});
|
||||
|
||||
write!(
|
||||
|
@ -187,10 +187,6 @@ impl<T: Idx> MeetSemiLattice for ChunkedBitSet<T> {
|
||||
pub struct Dual<T>(pub T);
|
||||
|
||||
impl<T: Idx> BitSetExt<T> for Dual<BitSet<T>> {
|
||||
fn domain_size(&self) -> usize {
|
||||
self.0.domain_size()
|
||||
}
|
||||
|
||||
fn contains(&self, elem: T) -> bool {
|
||||
self.0.contains(elem)
|
||||
}
|
||||
@ -276,3 +272,93 @@ impl<T> HasBottom for FlatSet<T> {
|
||||
impl<T> HasTop for FlatSet<T> {
|
||||
const TOP: Self = Self::Top;
|
||||
}
|
||||
|
||||
/// Extend a lattice with a bottom value to represent an unreachable execution.
|
||||
///
|
||||
/// The only useful action on an unreachable state is joining it with a reachable one to make it
|
||||
/// reachable. All other actions, gen/kill for instance, are no-ops.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum MaybeReachable<T> {
|
||||
Unreachable,
|
||||
Reachable(T),
|
||||
}
|
||||
|
||||
impl<T> MaybeReachable<T> {
|
||||
pub fn is_reachable(&self) -> bool {
|
||||
matches!(self, MaybeReachable::Reachable(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> HasBottom for MaybeReachable<T> {
|
||||
const BOTTOM: Self = MaybeReachable::Unreachable;
|
||||
}
|
||||
|
||||
impl<T: HasTop> HasTop for MaybeReachable<T> {
|
||||
const TOP: Self = MaybeReachable::Reachable(T::TOP);
|
||||
}
|
||||
|
||||
impl<S> MaybeReachable<S> {
|
||||
/// Return whether the current state contains the given element. If the state is unreachable,
|
||||
/// it does no contain anything.
|
||||
pub fn contains<T>(&self, elem: T) -> bool
|
||||
where
|
||||
S: BitSetExt<T>,
|
||||
{
|
||||
match self {
|
||||
MaybeReachable::Unreachable => false,
|
||||
MaybeReachable::Reachable(set) => set.contains(elem),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: BitSetExt<T>> BitSetExt<T> for MaybeReachable<S> {
|
||||
fn contains(&self, elem: T) -> bool {
|
||||
self.contains(elem)
|
||||
}
|
||||
|
||||
fn union(&mut self, other: &HybridBitSet<T>) {
|
||||
match self {
|
||||
MaybeReachable::Unreachable => {}
|
||||
MaybeReachable::Reachable(set) => set.union(other),
|
||||
}
|
||||
}
|
||||
|
||||
fn subtract(&mut self, other: &HybridBitSet<T>) {
|
||||
match self {
|
||||
MaybeReachable::Unreachable => {}
|
||||
MaybeReachable::Reachable(set) => set.subtract(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone> Clone for MaybeReachable<V> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
MaybeReachable::Reachable(x) => MaybeReachable::Reachable(x.clone()),
|
||||
MaybeReachable::Unreachable => MaybeReachable::Unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
match (&mut *self, source) {
|
||||
(MaybeReachable::Reachable(x), MaybeReachable::Reachable(y)) => {
|
||||
x.clone_from(&y);
|
||||
}
|
||||
_ => *self = source.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JoinSemiLattice + Clone> JoinSemiLattice for MaybeReachable<T> {
|
||||
fn join(&mut self, other: &Self) -> bool {
|
||||
// Unreachable acts as a bottom.
|
||||
match (&mut *self, &other) {
|
||||
(_, MaybeReachable::Unreachable) => false,
|
||||
(MaybeReachable::Unreachable, _) => {
|
||||
*self = other.clone();
|
||||
true
|
||||
}
|
||||
(MaybeReachable::Reachable(this), MaybeReachable::Reachable(other)) => this.join(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ use std::cmp::Ordering;
|
||||
|
||||
use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
|
||||
use rustc_index::Idx;
|
||||
use rustc_middle::mir::{self, BasicBlock, Location};
|
||||
use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
mod cursor;
|
||||
@ -48,23 +48,18 @@ mod visitor;
|
||||
pub use self::cursor::{AnalysisResults, ResultsClonedCursor, ResultsCursor, ResultsRefCursor};
|
||||
pub use self::direction::{Backward, Direction, Forward};
|
||||
pub use self::engine::{Engine, EntrySets, Results, ResultsCloned};
|
||||
pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
|
||||
pub use self::lattice::{JoinSemiLattice, MaybeReachable, MeetSemiLattice};
|
||||
pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor};
|
||||
|
||||
/// Analysis domains are all bitsets of various kinds. This trait holds
|
||||
/// operations needed by all of them.
|
||||
pub trait BitSetExt<T> {
|
||||
fn domain_size(&self) -> usize;
|
||||
fn contains(&self, elem: T) -> bool;
|
||||
fn union(&mut self, other: &HybridBitSet<T>);
|
||||
fn subtract(&mut self, other: &HybridBitSet<T>);
|
||||
}
|
||||
|
||||
impl<T: Idx> BitSetExt<T> for BitSet<T> {
|
||||
fn domain_size(&self) -> usize {
|
||||
self.domain_size()
|
||||
}
|
||||
|
||||
fn contains(&self, elem: T) -> bool {
|
||||
self.contains(elem)
|
||||
}
|
||||
@ -79,10 +74,6 @@ impl<T: Idx> BitSetExt<T> for BitSet<T> {
|
||||
}
|
||||
|
||||
impl<T: Idx> BitSetExt<T> for ChunkedBitSet<T> {
|
||||
fn domain_size(&self) -> usize {
|
||||
self.domain_size()
|
||||
}
|
||||
|
||||
fn contains(&self, elem: T) -> bool {
|
||||
self.contains(elem)
|
||||
}
|
||||
@ -172,12 +163,12 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
||||
/// in this function. That should go in `apply_call_return_effect`. For example, in the
|
||||
/// `InitializedPlaces` analyses, the return place for a function call is not marked as
|
||||
/// initialized here.
|
||||
fn apply_terminator_effect(
|
||||
fn apply_terminator_effect<'mir>(
|
||||
&mut self,
|
||||
state: &mut Self::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
) -> TerminatorEdges<'mir, 'tcx>;
|
||||
|
||||
/// Updates the current dataflow state with an effect that occurs immediately *before* the
|
||||
/// given terminator.
|
||||
@ -207,20 +198,6 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
);
|
||||
|
||||
/// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
|
||||
///
|
||||
/// This is similar to `apply_call_return_effect` in that it only takes place after the
|
||||
/// generator is resumed, not when it is dropped.
|
||||
///
|
||||
/// By default, no effects happen.
|
||||
fn apply_yield_resume_effect(
|
||||
&mut self,
|
||||
_state: &mut Self::Domain,
|
||||
_resume_block: BasicBlock,
|
||||
_resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Updates the current dataflow state with the effect of taking a particular branch in a
|
||||
/// `SwitchInt` terminator.
|
||||
///
|
||||
@ -295,6 +272,8 @@ where
|
||||
pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
|
||||
type Idx: Idx;
|
||||
|
||||
fn domain_size(&self, body: &mir::Body<'tcx>) -> usize;
|
||||
|
||||
/// See `Analysis::apply_statement_effect`.
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
@ -313,12 +292,12 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
|
||||
}
|
||||
|
||||
/// See `Analysis::apply_terminator_effect`.
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
);
|
||||
) -> TerminatorEdges<'mir, 'tcx>;
|
||||
|
||||
/// See `Analysis::apply_before_terminator_effect`.
|
||||
fn before_terminator_effect(
|
||||
@ -339,15 +318,6 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
);
|
||||
|
||||
/// See `Analysis::apply_yield_resume_effect`.
|
||||
fn yield_resume_effect(
|
||||
&mut self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_resume_block: BasicBlock,
|
||||
_resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// See `Analysis::apply_switch_int_edge_effects`.
|
||||
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
|
||||
&mut self,
|
||||
@ -381,13 +351,13 @@ where
|
||||
self.before_statement_effect(state, statement, location);
|
||||
}
|
||||
|
||||
fn apply_terminator_effect(
|
||||
fn apply_terminator_effect<'mir>(
|
||||
&mut self,
|
||||
state: &mut A::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.terminator_effect(state, terminator, location);
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
self.terminator_effect(state, terminator, location)
|
||||
}
|
||||
|
||||
fn apply_before_terminator_effect(
|
||||
@ -410,15 +380,6 @@ where
|
||||
self.call_return_effect(state, block, return_places);
|
||||
}
|
||||
|
||||
fn apply_yield_resume_effect(
|
||||
&mut self,
|
||||
state: &mut A::Domain,
|
||||
resume_block: BasicBlock,
|
||||
resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
self.yield_resume_effect(state, resume_block, resume_place);
|
||||
}
|
||||
|
||||
fn apply_switch_int_edge_effects(
|
||||
&mut self,
|
||||
block: BasicBlock,
|
||||
@ -531,6 +492,24 @@ impl<T: Idx> GenKill<T> for ChunkedBitSet<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: GenKill<T>> GenKill<T> for MaybeReachable<S> {
|
||||
fn gen(&mut self, elem: T) {
|
||||
match self {
|
||||
// If the state is not reachable, adding an element does nothing.
|
||||
MaybeReachable::Unreachable => {}
|
||||
MaybeReachable::Reachable(set) => set.gen(elem),
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(&mut self, elem: T) {
|
||||
match self {
|
||||
// If the state is not reachable, killing an element does nothing.
|
||||
MaybeReachable::Unreachable => {}
|
||||
MaybeReachable::Reachable(set) => set.kill(elem),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Idx> GenKill<T> for lattice::Dual<BitSet<T>> {
|
||||
fn gen(&mut self, elem: T) {
|
||||
self.0.insert(elem);
|
||||
@ -612,29 +591,5 @@ pub trait SwitchIntEdgeEffects<D> {
|
||||
fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
|
||||
}
|
||||
|
||||
/// List of places that are written to after a successful (non-unwind) return
|
||||
/// from a `Call` or `InlineAsm`.
|
||||
pub enum CallReturnPlaces<'a, 'tcx> {
|
||||
Call(mir::Place<'tcx>),
|
||||
InlineAsm(&'a [mir::InlineAsmOperand<'tcx>]),
|
||||
}
|
||||
|
||||
impl<'tcx> CallReturnPlaces<'_, 'tcx> {
|
||||
pub fn for_each(&self, mut f: impl FnMut(mir::Place<'tcx>)) {
|
||||
match *self {
|
||||
Self::Call(place) => f(place),
|
||||
Self::InlineAsm(operands) => {
|
||||
for op in operands {
|
||||
match *op {
|
||||
mir::InlineAsmOperand::Out { place: Some(place), .. }
|
||||
| mir::InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -198,14 +198,15 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
|
||||
assert!(state.insert(idx));
|
||||
}
|
||||
|
||||
fn apply_terminator_effect(
|
||||
fn apply_terminator_effect<'mir>(
|
||||
&mut self,
|
||||
state: &mut Self::Domain,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
let idx = self.effect(Effect::Primary.at_index(location.statement_index));
|
||||
assert!(state.insert(idx));
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn apply_before_terminator_effect(
|
||||
|
@ -1,9 +1,9 @@
|
||||
use super::*;
|
||||
|
||||
use crate::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::*;
|
||||
|
||||
use crate::{AnalysisDomain, GenKill, GenKillAnalysis};
|
||||
|
||||
/// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points
|
||||
/// to a given local.
|
||||
///
|
||||
@ -14,7 +14,7 @@ use rustc_middle::mir::*;
|
||||
pub struct MaybeBorrowedLocals;
|
||||
|
||||
impl MaybeBorrowedLocals {
|
||||
fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
|
||||
pub(super) fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
|
||||
TransferFunction { trans }
|
||||
}
|
||||
}
|
||||
@ -23,12 +23,12 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
|
||||
type Domain = BitSet<Local>;
|
||||
const NAME: &'static str = "maybe_borrowed_locals";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
|
||||
// bottom = unborrowed
|
||||
BitSet::new_empty(body.local_decls().len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
|
||||
fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
|
||||
// No locals are aliased on function entry
|
||||
}
|
||||
}
|
||||
@ -36,35 +36,40 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeBorrowedLocals {
|
||||
type Idx = Local;
|
||||
|
||||
fn domain_size(&self, body: &Body<'tcx>) -> usize {
|
||||
body.local_decls.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
statement: &Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
self.transfer_function(trans).visit_statement(statement, location);
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
self.transfer_function(trans).visit_terminator(terminator, location);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
_block: BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`.
|
||||
struct TransferFunction<'a, T> {
|
||||
pub(super) struct TransferFunction<'a, T> {
|
||||
trans: &'a mut T,
|
||||
}
|
||||
|
||||
@ -82,37 +87,37 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
|
||||
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
|
||||
self.super_rvalue(rvalue, location);
|
||||
|
||||
match rvalue {
|
||||
mir::Rvalue::AddressOf(_, borrowed_place) | mir::Rvalue::Ref(_, _, borrowed_place) => {
|
||||
Rvalue::AddressOf(_, borrowed_place) | Rvalue::Ref(_, _, borrowed_place) => {
|
||||
if !borrowed_place.is_indirect() {
|
||||
self.trans.gen(borrowed_place.local);
|
||||
}
|
||||
}
|
||||
|
||||
mir::Rvalue::Cast(..)
|
||||
| mir::Rvalue::ShallowInitBox(..)
|
||||
| mir::Rvalue::Use(..)
|
||||
| mir::Rvalue::ThreadLocalRef(..)
|
||||
| mir::Rvalue::Repeat(..)
|
||||
| mir::Rvalue::Len(..)
|
||||
| mir::Rvalue::BinaryOp(..)
|
||||
| mir::Rvalue::CheckedBinaryOp(..)
|
||||
| mir::Rvalue::NullaryOp(..)
|
||||
| mir::Rvalue::UnaryOp(..)
|
||||
| mir::Rvalue::Discriminant(..)
|
||||
| mir::Rvalue::Aggregate(..)
|
||||
| mir::Rvalue::CopyForDeref(..) => {}
|
||||
Rvalue::Cast(..)
|
||||
| Rvalue::ShallowInitBox(..)
|
||||
| Rvalue::Use(..)
|
||||
| Rvalue::ThreadLocalRef(..)
|
||||
| Rvalue::Repeat(..)
|
||||
| Rvalue::Len(..)
|
||||
| Rvalue::BinaryOp(..)
|
||||
| Rvalue::CheckedBinaryOp(..)
|
||||
| Rvalue::NullaryOp(..)
|
||||
| Rvalue::UnaryOp(..)
|
||||
| Rvalue::Discriminant(..)
|
||||
| Rvalue::Aggregate(..)
|
||||
| Rvalue::CopyForDeref(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
|
||||
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
|
||||
self.super_terminator(terminator, location);
|
||||
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Drop { place: dropped_place, .. } => {
|
||||
TerminatorKind::Drop { place: dropped_place, .. } => {
|
||||
// Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut
|
||||
// self` as a parameter. In the general case, a drop impl could launder that
|
||||
// reference into the surrounding environment through a raw pointer, thus creating
|
||||
@ -126,7 +131,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
TerminatorKind::Terminate
|
||||
TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Call { .. }
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
@ -134,7 +139,7 @@ where
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::InlineAsm { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable
|
||||
|
778
compiler/rustc_mir_dataflow/src/impls/initialized.rs
Normal file
778
compiler/rustc_mir_dataflow/src/impls/initialized.rs
Normal file
@ -0,0 +1,778 @@
|
||||
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
|
||||
use rustc_index::Idx;
|
||||
use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges};
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
|
||||
use crate::drop_flag_effects_for_function_entry;
|
||||
use crate::drop_flag_effects_for_location;
|
||||
use crate::elaborate_drops::DropFlagState;
|
||||
use crate::framework::SwitchIntEdgeEffects;
|
||||
use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
|
||||
use crate::on_lookup_result_bits;
|
||||
use crate::MoveDataParamEnv;
|
||||
use crate::{drop_flag_effects, on_all_children_bits, on_all_drop_children_bits};
|
||||
use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis, MaybeReachable};
|
||||
|
||||
/// `MaybeInitializedPlaces` tracks all places that might be
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-init:
|
||||
/// // {}
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // { b}
|
||||
/// b = S; // { b}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a}
|
||||
/// d = S; // {a, d}
|
||||
///
|
||||
/// } // {a, b, d}
|
||||
///
|
||||
/// c = S; // {a, b, c, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *must* be initialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeUninitializedPlaces` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeUninitializedPlaces` yields the set of
|
||||
/// places that would require a dynamic drop-flag at that statement.
|
||||
pub struct MaybeInitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
skip_unreachable_unwind: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
MaybeInitializedPlaces { tcx, body, mdpe, skip_unreachable_unwind: false }
|
||||
}
|
||||
|
||||
pub fn skipping_unreachable_unwind(mut self) -> Self {
|
||||
self.skip_unreachable_unwind = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_unwind_dead(
|
||||
&self,
|
||||
place: mir::Place<'tcx>,
|
||||
state: &MaybeReachable<ChunkedBitSet<MovePathIndex>>,
|
||||
) -> bool {
|
||||
if let LookupResult::Exact(path) = self.move_data().rev_lookup.find(place.as_ref()) {
|
||||
let mut maybe_live = false;
|
||||
on_all_drop_children_bits(self.tcx, self.body, self.mdpe, path, |child| {
|
||||
maybe_live |= state.contains(child);
|
||||
});
|
||||
!maybe_live
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `MaybeUninitializedPlaces` tracks all places that might be
|
||||
/// uninitialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-uninit:
|
||||
/// // {a, b, c, d}
|
||||
/// let a = S; let mut b = S; let c; let d; // { c, d}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // {a, c, d}
|
||||
/// b = S; // {a, c, d}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // { b, c, d}
|
||||
/// d = S; // { b, c }
|
||||
///
|
||||
/// } // {a, b, c, d}
|
||||
///
|
||||
/// c = S; // {a, b, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *must* be uninitialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeInitializedPlaces` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeInitializedPlaces` yields the set of
|
||||
/// places that would require a dynamic drop-flag at that statement.
|
||||
pub struct MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
|
||||
mark_inactive_variants_as_uninit: bool,
|
||||
skip_unreachable_unwind: BitSet<mir::BasicBlock>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
MaybeUninitializedPlaces {
|
||||
tcx,
|
||||
body,
|
||||
mdpe,
|
||||
mark_inactive_variants_as_uninit: false,
|
||||
skip_unreachable_unwind: BitSet::new_empty(body.basic_blocks.len()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
|
||||
/// enum discriminant.
|
||||
///
|
||||
/// This is correct in a vacuum but is not the default because it causes problems in the borrow
|
||||
/// checker, where this information gets propagated along `FakeEdge`s.
|
||||
pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
|
||||
self.mark_inactive_variants_as_uninit = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn skipping_unreachable_unwind(
|
||||
mut self,
|
||||
unreachable_unwind: BitSet<mir::BasicBlock>,
|
||||
) -> Self {
|
||||
self.skip_unreachable_unwind = unreachable_unwind;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `DefinitelyInitializedPlaces` tracks all places that are definitely
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // definite-init:
|
||||
/// // { }
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b }
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // { b, }
|
||||
/// b = S; // { b, }
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a, }
|
||||
/// d = S; // {a, d}
|
||||
///
|
||||
/// } // { }
|
||||
///
|
||||
/// c = S; // { c }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *may* be uninitialized at a
|
||||
/// particular control-flow point, one can take the set-complement
|
||||
/// of this data.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-difference between
|
||||
/// this data and `MaybeInitializedPlaces` yields the set of places
|
||||
/// that would require a dynamic drop-flag at that statement.
|
||||
pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
DefinitelyInitializedPlaces { tcx, body, mdpe }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `EverInitializedPlaces` tracks all places that might have ever been
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function, without an intervening `StorageDead`.
|
||||
///
|
||||
/// This dataflow is used to determine if an immutable local variable may
|
||||
/// be assigned to.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // ever-init:
|
||||
/// // { }
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b }
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // {a, b, }
|
||||
/// b = S; // {a, b, }
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a, b, }
|
||||
/// d = S; // {a, b, d }
|
||||
///
|
||||
/// } // {a, b, d }
|
||||
///
|
||||
/// c = S; // {a, b, c, d }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct EverInitializedPlaces<'a, 'tcx> {
|
||||
#[allow(dead_code)]
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
EverInitializedPlaces { tcx, body, mdpe }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.kill(path),
|
||||
DropFlagState::Present => trans.gen(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.gen(path),
|
||||
DropFlagState::Present => trans.kill(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.kill(path),
|
||||
DropFlagState::Present => trans.gen(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
|
||||
type Domain = MaybeReachable<ChunkedBitSet<MovePathIndex>>;
|
||||
const NAME: &'static str = "maybe_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = uninitialized
|
||||
MaybeReachable::Unreachable
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
*state =
|
||||
MaybeReachable::Reachable(ChunkedBitSet::new_empty(self.move_data().move_paths.len()));
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.gen(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn domain_size(&self, _: &Body<'tcx>) -> usize {
|
||||
self.move_data().move_paths.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
|
||||
// Mark all places as "maybe init" if they are mutably borrowed. See #90752.
|
||||
if self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration
|
||||
&& let Some((_, rvalue)) = statement.kind.as_assign()
|
||||
&& let mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
|
||||
// FIXME: Does `&raw const foo` allow mutation? See #90413.
|
||||
| mir::Rvalue::AddressOf(_, place) = rvalue
|
||||
&& let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref())
|
||||
{
|
||||
on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
|
||||
trans.gen(child);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
state: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
let mut edges = terminator.edges();
|
||||
if self.skip_unreachable_unwind
|
||||
&& let mir::TerminatorKind::Drop { target, unwind, place, replace: _ } = terminator.kind
|
||||
&& matches!(unwind, mir::UnwindAction::Cleanup(_))
|
||||
&& self.is_unwind_dead(place, state)
|
||||
{
|
||||
edges = TerminatorEdges::Single(target);
|
||||
}
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(state, path, s)
|
||||
});
|
||||
edges
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 1 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.gen(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
|
||||
&mut self,
|
||||
block: mir::BasicBlock,
|
||||
discr: &mir::Operand<'tcx>,
|
||||
edge_effects: &mut impl SwitchIntEdgeEffects<G>,
|
||||
) {
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
let enum_ = discr.place().and_then(|discr| {
|
||||
switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
|
||||
});
|
||||
|
||||
let Some((enum_place, enum_def)) = enum_ else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut discriminants = enum_def.discriminants(self.tcx);
|
||||
edge_effects.apply(|trans, edge| {
|
||||
let Some(value) = edge.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
// MIR building adds discriminants to the `values` array in the same order as they
|
||||
// are yielded by `AdtDef::discriminants`. We rely on this to match each
|
||||
// discriminant in `values` to its corresponding variant in linear time.
|
||||
let (variant, _) = discriminants
|
||||
.find(|&(_, discr)| discr.val == value)
|
||||
.expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
|
||||
|
||||
// Kill all move paths that correspond to variants we know to be inactive along this
|
||||
// particular outgoing edge of a `SwitchInt`.
|
||||
drop_flag_effects::on_all_inactive_variants(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
enum_place,
|
||||
variant,
|
||||
|mpi| trans.kill(mpi),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
|
||||
type Domain = ChunkedBitSet<MovePathIndex>;
|
||||
|
||||
const NAME: &'static str = "maybe_uninit";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = initialized (start_block_effect counters this at outset)
|
||||
ChunkedBitSet::new_empty(self.move_data().move_paths.len())
|
||||
}
|
||||
|
||||
// sets on_entry bits for Arg places
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
// set all bits to 1 (uninit) before gathering counter-evidence
|
||||
state.insert_all();
|
||||
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.remove(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn domain_size(&self, _: &Body<'tcx>) -> usize {
|
||||
self.move_data().move_paths.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
|
||||
// Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
|
||||
// mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
|
||||
}
|
||||
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
if self.skip_unreachable_unwind.contains(location.block) {
|
||||
let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() };
|
||||
assert!(matches!(unwind, mir::UnwindAction::Cleanup(_)));
|
||||
TerminatorEdges::Single(target)
|
||||
} else {
|
||||
terminator.edges()
|
||||
}
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 0 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.kill(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
|
||||
&mut self,
|
||||
block: mir::BasicBlock,
|
||||
discr: &mir::Operand<'tcx>,
|
||||
edge_effects: &mut impl SwitchIntEdgeEffects<G>,
|
||||
) {
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.mark_inactive_variants_as_uninit {
|
||||
return;
|
||||
}
|
||||
|
||||
let enum_ = discr.place().and_then(|discr| {
|
||||
switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
|
||||
});
|
||||
|
||||
let Some((enum_place, enum_def)) = enum_ else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut discriminants = enum_def.discriminants(self.tcx);
|
||||
edge_effects.apply(|trans, edge| {
|
||||
let Some(value) = edge.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
// MIR building adds discriminants to the `values` array in the same order as they
|
||||
// are yielded by `AdtDef::discriminants`. We rely on this to match each
|
||||
// discriminant in `values` to its corresponding variant in linear time.
|
||||
let (variant, _) = discriminants
|
||||
.find(|&(_, discr)| discr.val == value)
|
||||
.expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
|
||||
|
||||
// Mark all move paths that correspond to variants other than this one as maybe
|
||||
// uninitialized (in reality, they are *definitely* uninitialized).
|
||||
drop_flag_effects::on_all_inactive_variants(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
enum_place,
|
||||
variant,
|
||||
|mpi| trans.gen(mpi),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
/// Use set intersection as the join operator.
|
||||
type Domain = lattice::Dual<BitSet<MovePathIndex>>;
|
||||
|
||||
const NAME: &'static str = "definite_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = initialized (start_block_effect counters this at outset)
|
||||
lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
|
||||
}
|
||||
|
||||
// sets on_entry bits for Arg places
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
state.0.clear();
|
||||
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.0.insert(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn domain_size(&self, _: &Body<'tcx>) -> usize {
|
||||
self.move_data().move_paths.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
})
|
||||
}
|
||||
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 1 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.gen(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
|
||||
type Domain = ChunkedBitSet<InitIndex>;
|
||||
|
||||
const NAME: &'static str = "ever_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = no initialized variables by default
|
||||
ChunkedBitSet::new_empty(self.move_data().inits.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg_init in 0..body.arg_count {
|
||||
state.insert(InitIndex::new(arg_init));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = InitIndex;
|
||||
|
||||
fn domain_size(&self, _: &Body<'tcx>) -> usize {
|
||||
self.move_data().inits.len()
|
||||
}
|
||||
|
||||
#[instrument(skip(self, trans), level = "debug")]
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
stmt: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
let move_data = self.move_data();
|
||||
let init_path_map = &move_data.init_path_map;
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
let rev_lookup = &move_data.rev_lookup;
|
||||
|
||||
debug!("initializes move_indexes {:?}", &init_loc_map[location]);
|
||||
trans.gen_all(init_loc_map[location].iter().copied());
|
||||
|
||||
if let mir::StatementKind::StorageDead(local) = stmt.kind {
|
||||
// End inits for StorageDead, so that an immutable variable can
|
||||
// be reinitialized on the next iteration of the loop.
|
||||
let move_path_index = rev_lookup.find_local(local);
|
||||
debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
|
||||
trans.kill_all(init_path_map[move_path_index].iter().copied());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, trans, terminator), level = "debug")]
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
let (body, move_data) = (self.body, self.move_data());
|
||||
let term = body[location.block].terminator();
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
debug!(?term);
|
||||
debug!("initializes move_indexes {:?}", init_loc_map[location]);
|
||||
trans.gen_all(
|
||||
init_loc_map[location]
|
||||
.iter()
|
||||
.filter(|init_index| {
|
||||
move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
|
||||
})
|
||||
.copied(),
|
||||
);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
let move_data = self.move_data();
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
|
||||
let call_loc = self.body.terminator_loc(block);
|
||||
for init_index in &init_loc_map[call_loc] {
|
||||
trans.gen(*init_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
|
||||
/// an enum discriminant.
|
||||
///
|
||||
/// We expect such blocks to have a call to `discriminant` as their last statement like so:
|
||||
///
|
||||
/// ```text
|
||||
/// ...
|
||||
/// _42 = discriminant(_1)
|
||||
/// SwitchInt(_42, ..)
|
||||
/// ```
|
||||
///
|
||||
/// If the basic block matches this pattern, this function returns the place corresponding to the
|
||||
/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
|
||||
fn switch_on_enum_discriminant<'mir, 'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
block: &'mir mir::BasicBlockData<'tcx>,
|
||||
switch_on: mir::Place<'tcx>,
|
||||
) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
|
||||
for statement in block.statements.iter().rev() {
|
||||
match &statement.kind {
|
||||
mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
|
||||
if *lhs == switch_on =>
|
||||
{
|
||||
match discriminated.ty(body, tcx).ty.kind() {
|
||||
ty::Adt(def, _) => return Some((*discriminated, *def)),
|
||||
|
||||
// `Rvalue::Discriminant` is also used to get the active yield point for a
|
||||
// generator, but we do not need edge-specific effects in that case. This may
|
||||
// change in the future.
|
||||
ty::Generator(..) => return None,
|
||||
|
||||
t => bug!("`discriminant` called on unexpected type {:?}", t),
|
||||
}
|
||||
}
|
||||
mir::StatementKind::Coverage(_) => continue,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::{self, Local, Location, Place, StatementKind};
|
||||
use rustc_middle::mir::{
|
||||
self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges,
|
||||
};
|
||||
|
||||
use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis};
|
||||
use crate::{Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis};
|
||||
|
||||
/// A [live-variable dataflow analysis][liveness].
|
||||
///
|
||||
@ -43,6 +45,10 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
|
||||
type Idx = Local;
|
||||
|
||||
fn domain_size(&self, body: &mir::Body<'tcx>) -> usize {
|
||||
body.local_decls.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
@ -52,13 +58,14 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
|
||||
TransferFunction(trans).visit_statement(statement, location);
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
TransferFunction(trans).visit_terminator(terminator, location);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
@ -67,24 +74,19 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
if let CallReturnPlaces::Yield(resume_place) = return_places {
|
||||
YieldResumeEffect(trans).visit_place(
|
||||
&resume_place,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Yield),
|
||||
Location::START,
|
||||
)
|
||||
} else {
|
||||
return_places.for_each(|place| {
|
||||
if let Some(local) = place.as_local() {
|
||||
trans.kill(local);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn yield_resume_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_resume_block: mir::BasicBlock,
|
||||
resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
YieldResumeEffect(trans).visit_place(
|
||||
&resume_place,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Yield),
|
||||
Location::START,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +99,7 @@ where
|
||||
fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
|
||||
if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context {
|
||||
// The resume place is evaluated and assigned to only after generator resumes, so its
|
||||
// effect is handled separately in `yield_resume_effect`.
|
||||
// effect is handled separately in `call_resume_effect`.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -283,13 +285,14 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
|
||||
TransferFunction(trans).visit_statement(statement, location);
|
||||
}
|
||||
|
||||
fn apply_terminator_effect(
|
||||
fn apply_terminator_effect<'mir>(
|
||||
&mut self,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &'mir mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
TransferFunction(trans).visit_terminator(terminator, location);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn apply_call_return_effect(
|
||||
@ -298,23 +301,18 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
if let CallReturnPlaces::Yield(resume_place) = return_places {
|
||||
YieldResumeEffect(trans).visit_place(
|
||||
&resume_place,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Yield),
|
||||
Location::START,
|
||||
)
|
||||
} else {
|
||||
return_places.for_each(|place| {
|
||||
if let Some(local) = place.as_local() {
|
||||
trans.remove(local);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_yield_resume_effect(
|
||||
&mut self,
|
||||
trans: &mut Self::Domain,
|
||||
_resume_block: mir::BasicBlock,
|
||||
resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
YieldResumeEffect(trans).visit_place(
|
||||
&resume_place,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Yield),
|
||||
Location::START,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2,768 +2,18 @@
|
||||
//! bitvectors attached to each basic block, represented via a
|
||||
//! zero-sized structure.
|
||||
|
||||
use rustc_index::bit_set::{BitSet, ChunkedBitSet};
|
||||
use rustc_index::Idx;
|
||||
use rustc_middle::mir::visit::{MirVisitable, Visitor};
|
||||
use rustc_middle::mir::{self, Body, Location};
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
|
||||
use crate::drop_flag_effects_for_function_entry;
|
||||
use crate::drop_flag_effects_for_location;
|
||||
use crate::elaborate_drops::DropFlagState;
|
||||
use crate::framework::{CallReturnPlaces, SwitchIntEdgeEffects};
|
||||
use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
|
||||
use crate::on_lookup_result_bits;
|
||||
use crate::MoveDataParamEnv;
|
||||
use crate::{drop_flag_effects, on_all_children_bits};
|
||||
use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis};
|
||||
|
||||
mod borrowed_locals;
|
||||
mod initialized;
|
||||
mod liveness;
|
||||
mod storage_liveness;
|
||||
|
||||
pub use self::borrowed_locals::borrowed_locals;
|
||||
pub use self::borrowed_locals::MaybeBorrowedLocals;
|
||||
pub use self::initialized::{
|
||||
DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeInitializedPlaces,
|
||||
MaybeUninitializedPlaces,
|
||||
};
|
||||
pub use self::liveness::MaybeLiveLocals;
|
||||
pub use self::liveness::MaybeTransitiveLiveLocals;
|
||||
pub use self::liveness::TransferFunction as LivenessTransferFunction;
|
||||
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
|
||||
|
||||
/// `MaybeInitializedPlaces` tracks all places that might be
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-init:
|
||||
/// // {}
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // { b}
|
||||
/// b = S; // { b}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a}
|
||||
/// d = S; // {a, d}
|
||||
///
|
||||
/// } // {a, b, d}
|
||||
///
|
||||
/// c = S; // {a, b, c, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *must* be initialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeUninitializedPlaces` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeUninitializedPlaces` yields the set of
|
||||
/// places that would require a dynamic drop-flag at that statement.
|
||||
pub struct MaybeInitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
MaybeInitializedPlaces { tcx, body, mdpe }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `MaybeUninitializedPlaces` tracks all places that might be
|
||||
/// uninitialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // maybe-uninit:
|
||||
/// // {a, b, c, d}
|
||||
/// let a = S; let mut b = S; let c; let d; // { c, d}
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // {a, c, d}
|
||||
/// b = S; // {a, c, d}
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // { b, c, d}
|
||||
/// d = S; // { b, c }
|
||||
///
|
||||
/// } // {a, b, c, d}
|
||||
///
|
||||
/// c = S; // {a, b, d}
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *must* be uninitialized at a
|
||||
/// particular control-flow point, one can take the set-difference
|
||||
/// between this data and the data from `MaybeInitializedPlaces` at the
|
||||
/// corresponding control-flow point.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-intersection
|
||||
/// between this data and `MaybeInitializedPlaces` yields the set of
|
||||
/// places that would require a dynamic drop-flag at that statement.
|
||||
pub struct MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
|
||||
mark_inactive_variants_as_uninit: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
MaybeUninitializedPlaces { tcx, body, mdpe, mark_inactive_variants_as_uninit: false }
|
||||
}
|
||||
|
||||
/// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
|
||||
/// enum discriminant.
|
||||
///
|
||||
/// This is correct in a vacuum but is not the default because it causes problems in the borrow
|
||||
/// checker, where this information gets propagated along `FakeEdge`s.
|
||||
pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
|
||||
self.mark_inactive_variants_as_uninit = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `DefinitelyInitializedPlaces` tracks all places that are definitely
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // definite-init:
|
||||
/// // { }
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b }
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // { b, }
|
||||
/// b = S; // { b, }
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a, }
|
||||
/// d = S; // {a, d}
|
||||
///
|
||||
/// } // { }
|
||||
///
|
||||
/// c = S; // { c }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To determine whether a place *may* be uninitialized at a
|
||||
/// particular control-flow point, one can take the set-complement
|
||||
/// of this data.
|
||||
///
|
||||
/// Similarly, at a given `drop` statement, the set-difference between
|
||||
/// this data and `MaybeInitializedPlaces` yields the set of places
|
||||
/// that would require a dynamic drop-flag at that statement.
|
||||
pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
DefinitelyInitializedPlaces { tcx, body, mdpe }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
/// `EverInitializedPlaces` tracks all places that might have ever been
|
||||
/// initialized upon reaching a particular point in the control flow
|
||||
/// for a function, without an intervening `StorageDead`.
|
||||
///
|
||||
/// This dataflow is used to determine if an immutable local variable may
|
||||
/// be assigned to.
|
||||
///
|
||||
/// For example, in code like the following, we have corresponding
|
||||
/// dataflow information shown in the right-hand comments.
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// fn foo(pred: bool) { // ever-init:
|
||||
/// // { }
|
||||
/// let a = S; let mut b = S; let c; let d; // {a, b }
|
||||
///
|
||||
/// if pred {
|
||||
/// drop(a); // {a, b, }
|
||||
/// b = S; // {a, b, }
|
||||
///
|
||||
/// } else {
|
||||
/// drop(b); // {a, b, }
|
||||
/// d = S; // {a, b, d }
|
||||
///
|
||||
/// } // {a, b, d }
|
||||
///
|
||||
/// c = S; // {a, b, c, d }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct EverInitializedPlaces<'a, 'tcx> {
|
||||
#[allow(dead_code)]
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'a Body<'tcx>,
|
||||
mdpe: &'a MoveDataParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
|
||||
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
|
||||
EverInitializedPlaces { tcx, body, mdpe }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
|
||||
fn move_data(&self) -> &MoveData<'tcx> {
|
||||
&self.mdpe.move_data
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.kill(path),
|
||||
DropFlagState::Present => trans.gen(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.gen(path),
|
||||
DropFlagState::Present => trans.kill(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
fn update_bits(
|
||||
trans: &mut impl GenKill<MovePathIndex>,
|
||||
path: MovePathIndex,
|
||||
state: DropFlagState,
|
||||
) {
|
||||
match state {
|
||||
DropFlagState::Absent => trans.kill(path),
|
||||
DropFlagState::Present => trans.gen(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
|
||||
type Domain = ChunkedBitSet<MovePathIndex>;
|
||||
const NAME: &'static str = "maybe_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = uninitialized
|
||||
ChunkedBitSet::new_empty(self.move_data().move_paths.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.insert(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark all places as "maybe init" if they are mutably borrowed. See #90752.
|
||||
for_each_mut_borrow(statement, location, |place| {
|
||||
let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
|
||||
trans.gen(child);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
for_each_mut_borrow(terminator, location, |place| {
|
||||
let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else {
|
||||
return;
|
||||
};
|
||||
on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
|
||||
trans.gen(child);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 1 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.gen(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
|
||||
&mut self,
|
||||
block: mir::BasicBlock,
|
||||
discr: &mir::Operand<'tcx>,
|
||||
edge_effects: &mut impl SwitchIntEdgeEffects<G>,
|
||||
) {
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
let enum_ = discr.place().and_then(|discr| {
|
||||
switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
|
||||
});
|
||||
|
||||
let Some((enum_place, enum_def)) = enum_ else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut discriminants = enum_def.discriminants(self.tcx);
|
||||
edge_effects.apply(|trans, edge| {
|
||||
let Some(value) = edge.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
// MIR building adds discriminants to the `values` array in the same order as they
|
||||
// are yielded by `AdtDef::discriminants`. We rely on this to match each
|
||||
// discriminant in `values` to its corresponding variant in linear time.
|
||||
let (variant, _) = discriminants
|
||||
.find(|&(_, discr)| discr.val == value)
|
||||
.expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
|
||||
|
||||
// Kill all move paths that correspond to variants we know to be inactive along this
|
||||
// particular outgoing edge of a `SwitchInt`.
|
||||
drop_flag_effects::on_all_inactive_variants(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
enum_place,
|
||||
variant,
|
||||
|mpi| trans.kill(mpi),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
|
||||
type Domain = ChunkedBitSet<MovePathIndex>;
|
||||
|
||||
const NAME: &'static str = "maybe_uninit";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = initialized (start_block_effect counters this at outset)
|
||||
ChunkedBitSet::new_empty(self.move_data().move_paths.len())
|
||||
}
|
||||
|
||||
// sets on_entry bits for Arg places
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
// set all bits to 1 (uninit) before gathering counter-evidence
|
||||
state.insert_all();
|
||||
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.remove(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
|
||||
// Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
|
||||
// mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
});
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 0 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.kill(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
|
||||
&mut self,
|
||||
block: mir::BasicBlock,
|
||||
discr: &mir::Operand<'tcx>,
|
||||
edge_effects: &mut impl SwitchIntEdgeEffects<G>,
|
||||
) {
|
||||
if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.mark_inactive_variants_as_uninit {
|
||||
return;
|
||||
}
|
||||
|
||||
let enum_ = discr.place().and_then(|discr| {
|
||||
switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
|
||||
});
|
||||
|
||||
let Some((enum_place, enum_def)) = enum_ else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut discriminants = enum_def.discriminants(self.tcx);
|
||||
edge_effects.apply(|trans, edge| {
|
||||
let Some(value) = edge.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
// MIR building adds discriminants to the `values` array in the same order as they
|
||||
// are yielded by `AdtDef::discriminants`. We rely on this to match each
|
||||
// discriminant in `values` to its corresponding variant in linear time.
|
||||
let (variant, _) = discriminants
|
||||
.find(|&(_, discr)| discr.val == value)
|
||||
.expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
|
||||
|
||||
// Mark all move paths that correspond to variants other than this one as maybe
|
||||
// uninitialized (in reality, they are *definitely* uninitialized).
|
||||
drop_flag_effects::on_all_inactive_variants(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
enum_place,
|
||||
variant,
|
||||
|mpi| trans.gen(mpi),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
|
||||
/// Use set intersection as the join operator.
|
||||
type Domain = lattice::Dual<BitSet<MovePathIndex>>;
|
||||
|
||||
const NAME: &'static str = "definite_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = initialized (start_block_effect counters this at outset)
|
||||
lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
|
||||
}
|
||||
|
||||
// sets on_entry bits for Arg places
|
||||
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
state.0.clear();
|
||||
|
||||
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
|
||||
assert!(s == DropFlagState::Present);
|
||||
state.0.insert(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = MovePathIndex;
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_statement: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
})
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
|
||||
Self::update_bits(trans, path, s)
|
||||
})
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
return_places.for_each(|place| {
|
||||
// when a call returns successfully, that means we need to set
|
||||
// the bits for that dest_place to 1 (initialized).
|
||||
on_lookup_result_bits(
|
||||
self.tcx,
|
||||
self.body,
|
||||
self.move_data(),
|
||||
self.move_data().rev_lookup.find(place.as_ref()),
|
||||
|mpi| {
|
||||
trans.gen(mpi);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
|
||||
type Domain = ChunkedBitSet<InitIndex>;
|
||||
|
||||
const NAME: &'static str = "ever_init";
|
||||
|
||||
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = no initialized variables by default
|
||||
ChunkedBitSet::new_empty(self.move_data().inits.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg_init in 0..body.arg_count {
|
||||
state.insert(InitIndex::new(arg_init));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
|
||||
type Idx = InitIndex;
|
||||
|
||||
#[instrument(skip(self, trans), level = "debug")]
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
stmt: &mir::Statement<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
let move_data = self.move_data();
|
||||
let init_path_map = &move_data.init_path_map;
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
let rev_lookup = &move_data.rev_lookup;
|
||||
|
||||
debug!("initializes move_indexes {:?}", &init_loc_map[location]);
|
||||
trans.gen_all(init_loc_map[location].iter().copied());
|
||||
|
||||
if let mir::StatementKind::StorageDead(local) = stmt.kind {
|
||||
// End inits for StorageDead, so that an immutable variable can
|
||||
// be reinitialized on the next iteration of the loop.
|
||||
let move_path_index = rev_lookup.find_local(local);
|
||||
debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
|
||||
trans.kill_all(init_path_map[move_path_index].iter().copied());
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, trans, _terminator), level = "debug")]
|
||||
fn terminator_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
location: Location,
|
||||
) {
|
||||
let (body, move_data) = (self.body, self.move_data());
|
||||
let term = body[location.block].terminator();
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
debug!(?term);
|
||||
debug!("initializes move_indexes {:?}", init_loc_map[location]);
|
||||
trans.gen_all(
|
||||
init_loc_map[location]
|
||||
.iter()
|
||||
.filter(|init_index| {
|
||||
move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
|
||||
})
|
||||
.copied(),
|
||||
);
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
let move_data = self.move_data();
|
||||
let init_loc_map = &move_data.init_loc_map;
|
||||
|
||||
let call_loc = self.body.terminator_loc(block);
|
||||
for init_index in &init_loc_map[call_loc] {
|
||||
trans.gen(*init_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
|
||||
/// an enum discriminant.
|
||||
///
|
||||
/// We expect such blocks to have a call to `discriminant` as their last statement like so:
|
||||
///
|
||||
/// ```text
|
||||
/// ...
|
||||
/// _42 = discriminant(_1)
|
||||
/// SwitchInt(_42, ..)
|
||||
/// ```
|
||||
///
|
||||
/// If the basic block matches this pattern, this function returns the place corresponding to the
|
||||
/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
|
||||
fn switch_on_enum_discriminant<'mir, 'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &'mir mir::Body<'tcx>,
|
||||
block: &'mir mir::BasicBlockData<'tcx>,
|
||||
switch_on: mir::Place<'tcx>,
|
||||
) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
|
||||
for statement in block.statements.iter().rev() {
|
||||
match &statement.kind {
|
||||
mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
|
||||
if *lhs == switch_on =>
|
||||
{
|
||||
match discriminated.ty(body, tcx).ty.kind() {
|
||||
ty::Adt(def, _) => return Some((*discriminated, *def)),
|
||||
|
||||
// `Rvalue::Discriminant` is also used to get the active yield point for a
|
||||
// generator, but we do not need edge-specific effects in that case. This may
|
||||
// change in the future.
|
||||
ty::Generator(..) => return None,
|
||||
|
||||
t => bug!("`discriminant` called on unexpected type {:?}", t),
|
||||
}
|
||||
}
|
||||
mir::StatementKind::Coverage(_) => continue,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
struct OnMutBorrow<F>(F);
|
||||
|
||||
impl<'tcx, F> Visitor<'tcx> for OnMutBorrow<F>
|
||||
where
|
||||
F: FnMut(&mir::Place<'tcx>),
|
||||
{
|
||||
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
|
||||
// FIXME: Does `&raw const foo` allow mutation? See #90413.
|
||||
match rvalue {
|
||||
mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
|
||||
| mir::Rvalue::AddressOf(_, place) => (self.0)(place),
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.super_rvalue(rvalue, location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls `f` for each mutable borrow or raw reference in the program.
|
||||
///
|
||||
/// This DOES NOT call `f` for a shared borrow of a type with interior mutability. That's okay for
|
||||
/// initializedness, because we cannot move from an `UnsafeCell` (outside of `core::cell`), but
|
||||
/// other analyses will likely need to check for `!Freeze`.
|
||||
fn for_each_mut_borrow<'tcx>(
|
||||
mir: &impl MirVisitable<'tcx>,
|
||||
location: Location,
|
||||
f: impl FnMut(&mir::Place<'tcx>),
|
||||
) {
|
||||
let mut vis = OnMutBorrow(f);
|
||||
|
||||
mir.apply(location, &mut vis);
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
pub use super::*;
|
||||
|
||||
use crate::{CallReturnPlaces, GenKill, ResultsClonedCursor};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::MaybeBorrowedLocals;
|
||||
use crate::{GenKill, ResultsClonedCursor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MaybeStorageLive<'a> {
|
||||
always_live_locals: Cow<'a, BitSet<Local>>,
|
||||
@ -27,12 +29,12 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
|
||||
|
||||
const NAME: &'static str = "maybe_storage_live";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
|
||||
for local in self.always_live_locals.iter() {
|
||||
on_entry.insert(local);
|
||||
@ -47,10 +49,14 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
|
||||
impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
|
||||
type Idx = Local;
|
||||
|
||||
fn domain_size(&self, body: &Body<'tcx>) -> usize {
|
||||
body.local_decls.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
stmt: &mir::Statement<'tcx>,
|
||||
stmt: &Statement<'tcx>,
|
||||
_: Location,
|
||||
) {
|
||||
match stmt.kind {
|
||||
@ -60,13 +66,14 @@ impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_: &mir::Terminator<'tcx>,
|
||||
_trans: &mut Self::Domain,
|
||||
terminator: &'mir Terminator<'tcx>,
|
||||
_: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
// Terminators have no effect
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
@ -95,12 +102,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
|
||||
|
||||
const NAME: &'static str = "maybe_storage_dead";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
|
||||
// bottom = live
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
|
||||
// Do not iterate on return place and args, as they are trivially always live.
|
||||
for local in body.vars_and_temps_iter() {
|
||||
@ -114,10 +121,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
|
||||
impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
|
||||
type Idx = Local;
|
||||
|
||||
fn domain_size(&self, body: &Body<'tcx>) -> usize {
|
||||
body.local_decls.len()
|
||||
}
|
||||
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
stmt: &mir::Statement<'tcx>,
|
||||
stmt: &Statement<'tcx>,
|
||||
_: Location,
|
||||
) {
|
||||
match stmt.kind {
|
||||
@ -127,13 +138,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'mir>(
|
||||
&mut self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_: &mir::Terminator<'tcx>,
|
||||
_: &mut Self::Domain,
|
||||
terminator: &'mir Terminator<'tcx>,
|
||||
_: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
// Terminators have no effect
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
@ -172,12 +184,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
|
||||
const NAME: &'static str = "requires_storage";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
|
||||
// The resume argument is live on function entry (we don't care about
|
||||
// the `self` argument)
|
||||
for arg in body.args_iter().skip(1) {
|
||||
@ -189,10 +201,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
type Idx = Local;
|
||||
|
||||
fn domain_size(&self, body: &Body<'tcx>) -> usize {
|
||||
body.local_decls.len()
|
||||
}
|
||||
|
||||
fn before_statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
stmt: &mir::Statement<'tcx>,
|
||||
stmt: &Statement<'tcx>,
|
||||
loc: Location,
|
||||
) {
|
||||
// If a place is borrowed in a statement, it needs storage for that statement.
|
||||
@ -225,7 +241,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
fn statement_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_: &mir::Statement<'tcx>,
|
||||
_: &Statement<'tcx>,
|
||||
loc: Location,
|
||||
) {
|
||||
// If we move from a place then it only stops needing storage *after*
|
||||
@ -236,11 +252,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
fn before_terminator_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
terminator: &Terminator<'tcx>,
|
||||
loc: Location,
|
||||
) {
|
||||
// If a place is borrowed in a terminator, it needs storage for that terminator.
|
||||
self.borrowed_locals.mut_analysis().terminator_effect(trans, terminator, loc);
|
||||
self.borrowed_locals
|
||||
.mut_analysis()
|
||||
.transfer_function(trans)
|
||||
.visit_terminator(terminator, loc);
|
||||
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Call { destination, .. } => {
|
||||
@ -272,26 +291,26 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
|
||||
// Nothing to do for these. Match exhaustively so this fails to compile when new
|
||||
// variants are added.
|
||||
TerminatorKind::Terminate
|
||||
TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
fn terminator_effect<'t>(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
trans: &mut Self::Domain,
|
||||
terminator: &'t Terminator<'tcx>,
|
||||
loc: Location,
|
||||
) {
|
||||
) -> TerminatorEdges<'t, 'tcx> {
|
||||
match terminator.kind {
|
||||
// For call terminators the destination requires storage for the call
|
||||
// and after the call returns successfully, but not after a panic.
|
||||
@ -309,20 +328,21 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
// Nothing to do for these. Match exhaustively so this fails to compile when new
|
||||
// variants are added.
|
||||
TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
| TerminatorKind::FalseUnwind { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Unreachable => {}
|
||||
}
|
||||
|
||||
self.check_for_move(trans, loc);
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
@ -333,15 +353,6 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
) {
|
||||
return_places.for_each(|place| trans.gen(place.local));
|
||||
}
|
||||
|
||||
fn yield_resume_effect(
|
||||
&mut self,
|
||||
trans: &mut impl GenKill<Self::Idx>,
|
||||
_resume_block: BasicBlock,
|
||||
resume_place: mir::Place<'tcx>,
|
||||
) {
|
||||
trans.gen(resume_place.local);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> MaybeRequiresStorage<'_, '_, 'tcx> {
|
||||
|
@ -28,8 +28,8 @@ pub use self::drop_flag_effects::{
|
||||
};
|
||||
pub use self::framework::{
|
||||
fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, AnalysisResults, Backward,
|
||||
CallReturnPlaces, CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis,
|
||||
JoinSemiLattice, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
|
||||
CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice,
|
||||
MaybeReachable, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
|
||||
ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects,
|
||||
};
|
||||
|
||||
|
@ -370,8 +370,8 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
|
||||
// this that could possibly access the return place, this doesn't
|
||||
// need recording.
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. } => {}
|
||||
|
@ -47,8 +47,7 @@ use rustc_target::abi::{FieldIdx, VariantIdx};
|
||||
|
||||
use crate::lattice::{HasBottom, HasTop};
|
||||
use crate::{
|
||||
fmt::DebugWithContext, Analysis, AnalysisDomain, CallReturnPlaces, JoinSemiLattice,
|
||||
SwitchIntEdgeEffects,
|
||||
fmt::DebugWithContext, Analysis, AnalysisDomain, JoinSemiLattice, SwitchIntEdgeEffects,
|
||||
};
|
||||
|
||||
pub trait ValueAnalysis<'tcx> {
|
||||
@ -242,11 +241,19 @@ pub trait ValueAnalysis<'tcx> {
|
||||
|
||||
/// The effect of a successful function call return should not be
|
||||
/// applied here, see [`Analysis::apply_terminator_effect`].
|
||||
fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
|
||||
fn handle_terminator<'mir>(
|
||||
&self,
|
||||
terminator: &'mir Terminator<'tcx>,
|
||||
state: &mut State<Self::Value>,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
self.super_terminator(terminator, state)
|
||||
}
|
||||
|
||||
fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
|
||||
fn super_terminator<'mir>(
|
||||
&self,
|
||||
terminator: &'mir Terminator<'tcx>,
|
||||
state: &mut State<Self::Value>,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
|
||||
// Effect is applied by `handle_call_return`.
|
||||
@ -258,10 +265,12 @@ pub trait ValueAnalysis<'tcx> {
|
||||
// They would have an effect, but are not allowed in this phase.
|
||||
bug!("encountered disallowed terminator");
|
||||
}
|
||||
TerminatorKind::SwitchInt { discr, targets } => {
|
||||
return self.handle_switch_int(discr, targets, state);
|
||||
}
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Assert { .. }
|
||||
@ -271,6 +280,7 @@ pub trait ValueAnalysis<'tcx> {
|
||||
// These terminators have no effect on the analysis.
|
||||
}
|
||||
}
|
||||
terminator.edges()
|
||||
}
|
||||
|
||||
fn handle_call_return(
|
||||
@ -291,19 +301,22 @@ pub trait ValueAnalysis<'tcx> {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_switch_int(
|
||||
fn handle_switch_int<'mir>(
|
||||
&self,
|
||||
discr: &Operand<'tcx>,
|
||||
apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
|
||||
) {
|
||||
self.super_switch_int(discr, apply_edge_effects)
|
||||
discr: &'mir Operand<'tcx>,
|
||||
targets: &'mir SwitchTargets,
|
||||
state: &mut State<Self::Value>,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
self.super_switch_int(discr, targets, state)
|
||||
}
|
||||
|
||||
fn super_switch_int(
|
||||
fn super_switch_int<'mir>(
|
||||
&self,
|
||||
_discr: &Operand<'tcx>,
|
||||
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
|
||||
) {
|
||||
discr: &'mir Operand<'tcx>,
|
||||
targets: &'mir SwitchTargets,
|
||||
_state: &mut State<Self::Value>,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
TerminatorEdges::SwitchInt { discr, targets }
|
||||
}
|
||||
|
||||
fn wrap(self) -> ValueAnalysisWrapper<Self>
|
||||
@ -353,14 +366,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
if state.is_reachable() {
|
||||
self.0.handle_terminator(terminator, state);
|
||||
self.0.handle_terminator(terminator, state)
|
||||
} else {
|
||||
TerminatorEdges::None
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,7 +383,7 @@ where
|
||||
&mut self,
|
||||
state: &mut Self::Domain,
|
||||
_block: BasicBlock,
|
||||
return_places: crate::CallReturnPlaces<'_, 'tcx>,
|
||||
return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
if state.is_reachable() {
|
||||
self.0.handle_call_return(return_places, state)
|
||||
@ -378,11 +393,9 @@ where
|
||||
fn apply_switch_int_edge_effects(
|
||||
&mut self,
|
||||
_block: BasicBlock,
|
||||
discr: &Operand<'tcx>,
|
||||
apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
|
||||
_discr: &Operand<'tcx>,
|
||||
_apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
|
||||
) {
|
||||
// FIXME: Dataflow framework provides no access to current state here.
|
||||
self.0.handle_switch_int(discr, apply_edge_effects)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,8 +57,8 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
|
||||
| TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::Assert { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::FalseEdge { .. }
|
||||
|
@ -180,6 +180,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx>
|
||||
throw_machine_stop_str!("calling functions isn't supported in ConstProp")
|
||||
}
|
||||
|
||||
fn panic_nounwind(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _msg: &str) -> InterpResult<'tcx> {
|
||||
throw_machine_stop_str!("panicking isn't supported in ConstProp")
|
||||
}
|
||||
|
||||
fn find_mir_or_eval_fn(
|
||||
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
_instance: ty::Instance<'tcx>,
|
||||
|
@ -678,8 +678,8 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
|
||||
}
|
||||
// None of these have Operands to const-propagate.
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. }
|
||||
|
@ -14,36 +14,76 @@ use rustc_index::bit_set::BitSet;
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_middle::mir::coverage::*;
|
||||
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
/// The coverage counter or counter expression associated with a particular
|
||||
/// BCB node or BCB edge.
|
||||
#[derive(Clone)]
|
||||
pub(super) enum BcbCounter {
|
||||
Counter { id: CounterId },
|
||||
Expression { id: ExpressionId, lhs: Operand, op: Op, rhs: Operand },
|
||||
}
|
||||
|
||||
impl BcbCounter {
|
||||
fn is_expression(&self) -> bool {
|
||||
matches!(self, Self::Expression { .. })
|
||||
}
|
||||
|
||||
pub(super) fn as_operand(&self) -> Operand {
|
||||
match *self {
|
||||
BcbCounter::Counter { id, .. } => Operand::Counter(id),
|
||||
BcbCounter::Expression { id, .. } => Operand::Expression(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BcbCounter {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Counter { id, .. } => write!(fmt, "Counter({:?})", id.index()),
|
||||
Self::Expression { id, lhs, op, rhs } => write!(
|
||||
fmt,
|
||||
"Expression({:?}) = {:?} {} {:?}",
|
||||
id.index(),
|
||||
lhs,
|
||||
match op {
|
||||
Op::Add => "+",
|
||||
Op::Subtract => "-",
|
||||
},
|
||||
rhs,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates and stores coverage counter and coverage expression information
|
||||
/// associated with nodes/edges in the BCB graph.
|
||||
pub(super) struct CoverageCounters {
|
||||
function_source_hash: u64,
|
||||
next_counter_id: CounterId,
|
||||
next_expression_id: ExpressionId,
|
||||
|
||||
/// Coverage counters/expressions that are associated with individual BCBs.
|
||||
bcb_counters: IndexVec<BasicCoverageBlock, Option<CoverageKind>>,
|
||||
bcb_counters: IndexVec<BasicCoverageBlock, Option<BcbCounter>>,
|
||||
/// Coverage counters/expressions that are associated with the control-flow
|
||||
/// edge between two BCBs.
|
||||
bcb_edge_counters: FxHashMap<(BasicCoverageBlock, BasicCoverageBlock), CoverageKind>,
|
||||
bcb_edge_counters: FxHashMap<(BasicCoverageBlock, BasicCoverageBlock), BcbCounter>,
|
||||
/// Tracks which BCBs have a counter associated with some incoming edge.
|
||||
/// Only used by debug assertions, to verify that BCBs with incoming edge
|
||||
/// counters do not have their own physical counters (expressions are allowed).
|
||||
bcb_has_incoming_edge_counters: BitSet<BasicCoverageBlock>,
|
||||
/// Expression nodes that are not directly associated with any particular
|
||||
/// BCB/edge, but are needed as operands to more complex expressions.
|
||||
/// These are always `CoverageKind::Expression`.
|
||||
pub(super) intermediate_expressions: Vec<CoverageKind>,
|
||||
/// These are always [`BcbCounter::Expression`].
|
||||
pub(super) intermediate_expressions: Vec<BcbCounter>,
|
||||
|
||||
pub debug_counters: DebugCounters,
|
||||
}
|
||||
|
||||
impl CoverageCounters {
|
||||
pub(super) fn new(function_source_hash: u64, basic_coverage_blocks: &CoverageGraph) -> Self {
|
||||
pub(super) fn new(basic_coverage_blocks: &CoverageGraph) -> Self {
|
||||
let num_bcbs = basic_coverage_blocks.num_nodes();
|
||||
|
||||
Self {
|
||||
function_source_hash,
|
||||
next_counter_id: CounterId::START,
|
||||
next_expression_id: ExpressionId::START,
|
||||
|
||||
@ -57,12 +97,12 @@ impl CoverageCounters {
|
||||
}
|
||||
|
||||
/// Activate the `DebugCounters` data structures, to provide additional debug formatting
|
||||
/// features when formatting `CoverageKind` (counter) values.
|
||||
/// features when formatting [`BcbCounter`] (counter) values.
|
||||
pub fn enable_debug(&mut self) {
|
||||
self.debug_counters.enable();
|
||||
}
|
||||
|
||||
/// Makes `CoverageKind` `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
|
||||
/// Makes [`BcbCounter`] `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
|
||||
/// indirectly associated with `CoverageSpans`, and accumulates additional `Expression`s
|
||||
/// representing intermediate values.
|
||||
pub fn make_bcb_counters(
|
||||
@ -73,14 +113,11 @@ impl CoverageCounters {
|
||||
MakeBcbCounters::new(self, basic_coverage_blocks).make_bcb_counters(coverage_spans)
|
||||
}
|
||||
|
||||
fn make_counter<F>(&mut self, debug_block_label_fn: F) -> CoverageKind
|
||||
fn make_counter<F>(&mut self, debug_block_label_fn: F) -> BcbCounter
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let counter = CoverageKind::Counter {
|
||||
function_source_hash: self.function_source_hash,
|
||||
id: self.next_counter(),
|
||||
};
|
||||
let counter = BcbCounter::Counter { id: self.next_counter() };
|
||||
if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.add_counter(&counter, (debug_block_label_fn)());
|
||||
}
|
||||
@ -93,19 +130,19 @@ impl CoverageCounters {
|
||||
op: Op,
|
||||
rhs: Operand,
|
||||
debug_block_label_fn: F,
|
||||
) -> CoverageKind
|
||||
) -> BcbCounter
|
||||
where
|
||||
F: Fn() -> Option<String>,
|
||||
{
|
||||
let id = self.next_expression();
|
||||
let expression = CoverageKind::Expression { id, lhs, op, rhs };
|
||||
let expression = BcbCounter::Expression { id, lhs, op, rhs };
|
||||
if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.add_counter(&expression, (debug_block_label_fn)());
|
||||
}
|
||||
expression
|
||||
}
|
||||
|
||||
pub fn make_identity_counter(&mut self, counter_operand: Operand) -> CoverageKind {
|
||||
pub fn make_identity_counter(&mut self, counter_operand: Operand) -> BcbCounter {
|
||||
let some_debug_block_label = if self.debug_counters.is_enabled() {
|
||||
self.debug_counters.some_block_label(counter_operand).cloned()
|
||||
} else {
|
||||
@ -134,7 +171,7 @@ impl CoverageCounters {
|
||||
fn set_bcb_counter(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
counter_kind: CoverageKind,
|
||||
counter_kind: BcbCounter,
|
||||
) -> Result<Operand, Error> {
|
||||
debug_assert!(
|
||||
// If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also
|
||||
@ -158,7 +195,7 @@ impl CoverageCounters {
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bcb: BasicCoverageBlock,
|
||||
counter_kind: CoverageKind,
|
||||
counter_kind: BcbCounter,
|
||||
) -> Result<Operand, Error> {
|
||||
if level_enabled!(tracing::Level::DEBUG) {
|
||||
// If the BCB has an edge counter (to be injected into a new `BasicBlock`), it can also
|
||||
@ -183,17 +220,17 @@ impl CoverageCounters {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bcb_counter(&self, bcb: BasicCoverageBlock) -> Option<&CoverageKind> {
|
||||
pub(super) fn bcb_counter(&self, bcb: BasicCoverageBlock) -> Option<&BcbCounter> {
|
||||
self.bcb_counters[bcb].as_ref()
|
||||
}
|
||||
|
||||
pub(super) fn take_bcb_counter(&mut self, bcb: BasicCoverageBlock) -> Option<CoverageKind> {
|
||||
pub(super) fn take_bcb_counter(&mut self, bcb: BasicCoverageBlock) -> Option<BcbCounter> {
|
||||
self.bcb_counters[bcb].take()
|
||||
}
|
||||
|
||||
pub(super) fn drain_bcb_counters(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = (BasicCoverageBlock, CoverageKind)> + '_ {
|
||||
) -> impl Iterator<Item = (BasicCoverageBlock, BcbCounter)> + '_ {
|
||||
self.bcb_counters
|
||||
.iter_enumerated_mut()
|
||||
.filter_map(|(bcb, counter)| Some((bcb, counter.take()?)))
|
||||
@ -201,7 +238,7 @@ impl CoverageCounters {
|
||||
|
||||
pub(super) fn drain_bcb_edge_counters(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = ((BasicCoverageBlock, BasicCoverageBlock), CoverageKind)> + '_ {
|
||||
) -> impl Iterator<Item = ((BasicCoverageBlock, BasicCoverageBlock), BcbCounter)> + '_ {
|
||||
self.bcb_edge_counters.drain()
|
||||
}
|
||||
}
|
||||
@ -653,7 +690,7 @@ impl<'a> MakeBcbCounters<'a> {
|
||||
self.branch_counter(branch).is_none()
|
||||
}
|
||||
|
||||
fn branch_counter(&self, branch: &BcbBranch) -> Option<&CoverageKind> {
|
||||
fn branch_counter(&self, branch: &BcbBranch) -> Option<&BcbCounter> {
|
||||
let to_bcb = branch.target_bcb;
|
||||
if let Some(from_bcb) = branch.edge_from_bcb {
|
||||
self.coverage_counters.bcb_edge_counters.get(&(from_bcb, to_bcb))
|
||||
@ -675,7 +712,7 @@ impl<'a> MakeBcbCounters<'a> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
fn format_counter(&self, counter_kind: &BcbCounter) -> String {
|
||||
self.coverage_counters.debug_counters.format_counter(counter_kind)
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@
|
||||
//! recursively, generating labels with nested operations, enclosed in parentheses
|
||||
//! (for example: `bcb2 + (bcb0 - bcb1)`).
|
||||
|
||||
use super::counters::CoverageCounters;
|
||||
use super::counters::{BcbCounter, CoverageCounters};
|
||||
use super::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
use super::spans::CoverageSpan;
|
||||
|
||||
@ -199,9 +199,9 @@ impl DebugOptions {
|
||||
|
||||
fn bool_option_val(option: &str, some_strval: Option<&str>) -> bool {
|
||||
if let Some(val) = some_strval {
|
||||
if vec!["yes", "y", "on", "true"].contains(&val) {
|
||||
if ["yes", "y", "on", "true"].contains(&val) {
|
||||
true
|
||||
} else if vec!["no", "n", "off", "false"].contains(&val) {
|
||||
} else if ["no", "n", "off", "false"].contains(&val) {
|
||||
false
|
||||
} else {
|
||||
bug!(
|
||||
@ -247,11 +247,11 @@ impl Default for ExpressionFormat {
|
||||
}
|
||||
}
|
||||
|
||||
/// If enabled, this struct maintains a map from `CoverageKind` IDs (as `Operand`) to
|
||||
/// the `CoverageKind` data and optional label (normally, the counter's associated
|
||||
/// If enabled, this struct maintains a map from `BcbCounter` IDs (as `Operand`) to
|
||||
/// the `BcbCounter` data and optional label (normally, the counter's associated
|
||||
/// `BasicCoverageBlock` format string, if any).
|
||||
///
|
||||
/// Use `format_counter` to convert one of these `CoverageKind` counters to a debug output string,
|
||||
/// Use `format_counter` to convert one of these `BcbCounter` counters to a debug output string,
|
||||
/// as directed by the `DebugOptions`. This allows the format of counter labels in logs and dump
|
||||
/// files (including the `CoverageGraph` graphviz file) to be changed at runtime, via environment
|
||||
/// variable.
|
||||
@ -276,7 +276,7 @@ impl DebugCounters {
|
||||
self.some_counters.is_some()
|
||||
}
|
||||
|
||||
pub fn add_counter(&mut self, counter_kind: &CoverageKind, some_block_label: Option<String>) {
|
||||
pub fn add_counter(&mut self, counter_kind: &BcbCounter, some_block_label: Option<String>) {
|
||||
if let Some(counters) = &mut self.some_counters {
|
||||
let id = counter_kind.as_operand();
|
||||
counters
|
||||
@ -291,21 +291,20 @@ impl DebugCounters {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
pub fn format_counter(&self, counter_kind: &BcbCounter) -> String {
|
||||
match *counter_kind {
|
||||
CoverageKind::Counter { .. } => {
|
||||
BcbCounter::Counter { .. } => {
|
||||
format!("Counter({})", self.format_counter_kind(counter_kind))
|
||||
}
|
||||
CoverageKind::Expression { .. } => {
|
||||
BcbCounter::Expression { .. } => {
|
||||
format!("Expression({})", self.format_counter_kind(counter_kind))
|
||||
}
|
||||
CoverageKind::Unreachable { .. } => "Unreachable".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_counter_kind(&self, counter_kind: &CoverageKind) -> String {
|
||||
fn format_counter_kind(&self, counter_kind: &BcbCounter) -> String {
|
||||
let counter_format = &debug_options().counter_format;
|
||||
if let CoverageKind::Expression { id, lhs, op, rhs } = *counter_kind {
|
||||
if let BcbCounter::Expression { id, lhs, op, rhs } = *counter_kind {
|
||||
if counter_format.operation {
|
||||
return format!(
|
||||
"{}{} {} {}",
|
||||
@ -346,7 +345,7 @@ impl DebugCounters {
|
||||
}
|
||||
if let Some(counters) = &self.some_counters {
|
||||
if let Some(DebugCounter { counter_kind, some_block_label }) = counters.get(&operand) {
|
||||
if let CoverageKind::Expression { .. } = counter_kind {
|
||||
if let BcbCounter::Expression { .. } = counter_kind {
|
||||
if let Some(label) = some_block_label && debug_options().counter_format.block {
|
||||
return format!(
|
||||
"{}:({})",
|
||||
@ -366,12 +365,12 @@ impl DebugCounters {
|
||||
/// A non-public support class to `DebugCounters`.
|
||||
#[derive(Debug)]
|
||||
struct DebugCounter {
|
||||
counter_kind: CoverageKind,
|
||||
counter_kind: BcbCounter,
|
||||
some_block_label: Option<String>,
|
||||
}
|
||||
|
||||
impl DebugCounter {
|
||||
fn new(counter_kind: CoverageKind, some_block_label: Option<String>) -> Self {
|
||||
fn new(counter_kind: BcbCounter, some_block_label: Option<String>) -> Self {
|
||||
Self { counter_kind, some_block_label }
|
||||
}
|
||||
}
|
||||
@ -380,9 +379,9 @@ impl DebugCounter {
|
||||
/// a Graphviz (.dot file) representation of the `CoverageGraph`, for debugging purposes.
|
||||
pub(super) struct GraphvizData {
|
||||
some_bcb_to_coverage_spans_with_counters:
|
||||
Option<FxHashMap<BasicCoverageBlock, Vec<(CoverageSpan, CoverageKind)>>>,
|
||||
some_bcb_to_dependency_counters: Option<FxHashMap<BasicCoverageBlock, Vec<CoverageKind>>>,
|
||||
some_edge_to_counter: Option<FxHashMap<(BasicCoverageBlock, BasicBlock), CoverageKind>>,
|
||||
Option<FxHashMap<BasicCoverageBlock, Vec<(CoverageSpan, BcbCounter)>>>,
|
||||
some_bcb_to_dependency_counters: Option<FxHashMap<BasicCoverageBlock, Vec<BcbCounter>>>,
|
||||
some_edge_to_counter: Option<FxHashMap<(BasicCoverageBlock, BasicBlock), BcbCounter>>,
|
||||
}
|
||||
|
||||
impl GraphvizData {
|
||||
@ -409,7 +408,7 @@ impl GraphvizData {
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
coverage_span: &CoverageSpan,
|
||||
counter_kind: &CoverageKind,
|
||||
counter_kind: &BcbCounter,
|
||||
) {
|
||||
if let Some(bcb_to_coverage_spans_with_counters) =
|
||||
self.some_bcb_to_coverage_spans_with_counters.as_mut()
|
||||
@ -424,7 +423,7 @@ impl GraphvizData {
|
||||
pub fn get_bcb_coverage_spans_with_counters(
|
||||
&self,
|
||||
bcb: BasicCoverageBlock,
|
||||
) -> Option<&[(CoverageSpan, CoverageKind)]> {
|
||||
) -> Option<&[(CoverageSpan, BcbCounter)]> {
|
||||
if let Some(bcb_to_coverage_spans_with_counters) =
|
||||
self.some_bcb_to_coverage_spans_with_counters.as_ref()
|
||||
{
|
||||
@ -437,7 +436,7 @@ impl GraphvizData {
|
||||
pub fn add_bcb_dependency_counter(
|
||||
&mut self,
|
||||
bcb: BasicCoverageBlock,
|
||||
counter_kind: &CoverageKind,
|
||||
counter_kind: &BcbCounter,
|
||||
) {
|
||||
if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_mut() {
|
||||
bcb_to_dependency_counters
|
||||
@ -447,7 +446,7 @@ impl GraphvizData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bcb_dependency_counters(&self, bcb: BasicCoverageBlock) -> Option<&[CoverageKind]> {
|
||||
pub fn get_bcb_dependency_counters(&self, bcb: BasicCoverageBlock) -> Option<&[BcbCounter]> {
|
||||
if let Some(bcb_to_dependency_counters) = self.some_bcb_to_dependency_counters.as_ref() {
|
||||
bcb_to_dependency_counters.get(&bcb).map(Deref::deref)
|
||||
} else {
|
||||
@ -459,7 +458,7 @@ impl GraphvizData {
|
||||
&mut self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bb: BasicBlock,
|
||||
counter_kind: &CoverageKind,
|
||||
counter_kind: &BcbCounter,
|
||||
) {
|
||||
if let Some(edge_to_counter) = self.some_edge_to_counter.as_mut() {
|
||||
edge_to_counter
|
||||
@ -472,7 +471,7 @@ impl GraphvizData {
|
||||
&self,
|
||||
from_bcb: BasicCoverageBlock,
|
||||
to_bb: BasicBlock,
|
||||
) -> Option<&CoverageKind> {
|
||||
) -> Option<&BcbCounter> {
|
||||
if let Some(edge_to_counter) = self.some_edge_to_counter.as_ref() {
|
||||
edge_to_counter.get(&(from_bcb, to_bb))
|
||||
} else {
|
||||
@ -488,7 +487,7 @@ impl GraphvizData {
|
||||
pub(super) struct UsedExpressions {
|
||||
some_used_expression_operands: Option<FxHashMap<Operand, Vec<ExpressionId>>>,
|
||||
some_unused_expressions:
|
||||
Option<Vec<(CoverageKind, Option<BasicCoverageBlock>, BasicCoverageBlock)>>,
|
||||
Option<Vec<(BcbCounter, Option<BasicCoverageBlock>, BasicCoverageBlock)>>,
|
||||
}
|
||||
|
||||
impl UsedExpressions {
|
||||
@ -506,16 +505,16 @@ impl UsedExpressions {
|
||||
self.some_used_expression_operands.is_some()
|
||||
}
|
||||
|
||||
pub fn add_expression_operands(&mut self, expression: &CoverageKind) {
|
||||
pub fn add_expression_operands(&mut self, expression: &BcbCounter) {
|
||||
if let Some(used_expression_operands) = self.some_used_expression_operands.as_mut() {
|
||||
if let CoverageKind::Expression { id, lhs, rhs, .. } = *expression {
|
||||
if let BcbCounter::Expression { id, lhs, rhs, .. } = *expression {
|
||||
used_expression_operands.entry(lhs).or_insert_with(Vec::new).push(id);
|
||||
used_expression_operands.entry(rhs).or_insert_with(Vec::new).push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expression_is_used(&self, expression: &CoverageKind) -> bool {
|
||||
pub fn expression_is_used(&self, expression: &BcbCounter) -> bool {
|
||||
if let Some(used_expression_operands) = self.some_used_expression_operands.as_ref() {
|
||||
used_expression_operands.contains_key(&expression.as_operand())
|
||||
} else {
|
||||
@ -525,7 +524,7 @@ impl UsedExpressions {
|
||||
|
||||
pub fn add_unused_expression_if_not_found(
|
||||
&mut self,
|
||||
expression: &CoverageKind,
|
||||
expression: &BcbCounter,
|
||||
edge_from_bcb: Option<BasicCoverageBlock>,
|
||||
target_bcb: BasicCoverageBlock,
|
||||
) {
|
||||
@ -540,11 +539,11 @@ impl UsedExpressions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the list of unused counters (if any) as a tuple with the counter (`CoverageKind`),
|
||||
/// Return the list of unused counters (if any) as a tuple with the counter (`BcbCounter`),
|
||||
/// optional `from_bcb` (if it was an edge counter), and `target_bcb`.
|
||||
pub fn get_unused_expressions(
|
||||
&self,
|
||||
) -> Vec<(CoverageKind, Option<BasicCoverageBlock>, BasicCoverageBlock)> {
|
||||
) -> Vec<(BcbCounter, Option<BasicCoverageBlock>, BasicCoverageBlock)> {
|
||||
if let Some(unused_expressions) = self.some_unused_expressions.as_ref() {
|
||||
unused_expressions.clone()
|
||||
} else {
|
||||
@ -560,7 +559,7 @@ impl UsedExpressions {
|
||||
bcb_counters_without_direct_coverage_spans: &[(
|
||||
Option<BasicCoverageBlock>,
|
||||
BasicCoverageBlock,
|
||||
CoverageKind,
|
||||
BcbCounter,
|
||||
)],
|
||||
) {
|
||||
if self.is_enabled() {
|
||||
@ -662,7 +661,7 @@ pub(super) fn dump_coverage_graphviz<'tcx>(
|
||||
basic_coverage_blocks: &CoverageGraph,
|
||||
coverage_counters: &CoverageCounters,
|
||||
graphviz_data: &GraphvizData,
|
||||
intermediate_expressions: &[CoverageKind],
|
||||
intermediate_expressions: &[BcbCounter],
|
||||
debug_used_expressions: &UsedExpressions,
|
||||
) {
|
||||
let debug_counters = &coverage_counters.debug_counters;
|
||||
@ -743,9 +742,9 @@ fn bcb_to_string_sections<'tcx>(
|
||||
coverage_counters: &CoverageCounters,
|
||||
bcb: BasicCoverageBlock,
|
||||
bcb_data: &BasicCoverageBlockData,
|
||||
some_coverage_spans_with_counters: Option<&[(CoverageSpan, CoverageKind)]>,
|
||||
some_dependency_counters: Option<&[CoverageKind]>,
|
||||
some_intermediate_expressions: Option<&[CoverageKind]>,
|
||||
some_coverage_spans_with_counters: Option<&[(CoverageSpan, BcbCounter)]>,
|
||||
some_dependency_counters: Option<&[BcbCounter]>,
|
||||
some_intermediate_expressions: Option<&[BcbCounter]>,
|
||||
) -> Vec<String> {
|
||||
let debug_counters = &coverage_counters.debug_counters;
|
||||
|
||||
|
@ -116,7 +116,7 @@ impl CoverageGraph {
|
||||
|
||||
match term.kind {
|
||||
TerminatorKind::Return { .. }
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::SwitchInt { .. } => {
|
||||
// The `bb` has more than one _outgoing_ edge, or exits the function. Save the
|
||||
@ -146,7 +146,7 @@ impl CoverageGraph {
|
||||
// is as intended. (See Issue #78544 for a possible future option to support
|
||||
// coverage in test programs that panic.)
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. }
|
||||
| TerminatorKind::Call { .. }
|
||||
|
@ -8,9 +8,9 @@ mod spans;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use counters::CoverageCounters;
|
||||
use graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
use spans::{CoverageSpan, CoverageSpans};
|
||||
use self::counters::{BcbCounter, CoverageCounters};
|
||||
use self::graph::{BasicCoverageBlock, BasicCoverageBlockData, CoverageGraph};
|
||||
use self::spans::{CoverageSpan, CoverageSpans};
|
||||
|
||||
use crate::MirPass;
|
||||
|
||||
@ -106,6 +106,7 @@ struct Instrumentor<'a, 'tcx> {
|
||||
source_file: Lrc<SourceFile>,
|
||||
fn_sig_span: Span,
|
||||
body_span: Span,
|
||||
function_source_hash: u64,
|
||||
basic_coverage_blocks: CoverageGraph,
|
||||
coverage_counters: CoverageCounters,
|
||||
}
|
||||
@ -137,7 +138,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
|
||||
let function_source_hash = hash_mir_source(tcx, hir_body);
|
||||
let basic_coverage_blocks = CoverageGraph::from_mir(mir_body);
|
||||
let coverage_counters = CoverageCounters::new(function_source_hash, &basic_coverage_blocks);
|
||||
let coverage_counters = CoverageCounters::new(&basic_coverage_blocks);
|
||||
|
||||
Self {
|
||||
pass_name,
|
||||
@ -146,6 +147,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
source_file,
|
||||
fn_sig_span,
|
||||
body_span,
|
||||
function_source_hash,
|
||||
basic_coverage_blocks,
|
||||
coverage_counters,
|
||||
}
|
||||
@ -270,8 +272,11 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
// Finally, inject the intermediate expressions collected along the way.
|
||||
for intermediate_expression in self.coverage_counters.intermediate_expressions.drain(..) {
|
||||
inject_intermediate_expression(self.mir_body, intermediate_expression);
|
||||
for intermediate_expression in &self.coverage_counters.intermediate_expressions {
|
||||
inject_intermediate_expression(
|
||||
self.mir_body,
|
||||
self.make_mir_coverage_kind(intermediate_expression),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,19 +314,14 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
};
|
||||
graphviz_data.add_bcb_coverage_span_with_counter(bcb, &covspan, &counter_kind);
|
||||
|
||||
debug!(
|
||||
"Calling make_code_region(file_name={}, source_file={:?}, span={}, body_span={})",
|
||||
file_name,
|
||||
self.source_file,
|
||||
source_map.span_to_diagnostic_string(span),
|
||||
source_map.span_to_diagnostic_string(body_span)
|
||||
);
|
||||
let code_region =
|
||||
make_code_region(source_map, file_name, &self.source_file, span, body_span);
|
||||
|
||||
inject_statement(
|
||||
self.mir_body,
|
||||
counter_kind,
|
||||
self.make_mir_coverage_kind(&counter_kind),
|
||||
self.bcb_leader_bb(bcb),
|
||||
Some(make_code_region(source_map, file_name, &self.source_file, span, body_span)),
|
||||
Some(code_region),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -367,7 +367,7 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
);
|
||||
|
||||
match counter_kind {
|
||||
CoverageKind::Counter { .. } => {
|
||||
BcbCounter::Counter { .. } => {
|
||||
let inject_to_bb = if let Some(from_bcb) = edge_from_bcb {
|
||||
// The MIR edge starts `from_bb` (the outgoing / last BasicBlock in
|
||||
// `from_bcb`) and ends at `to_bb` (the incoming / first BasicBlock in the
|
||||
@ -400,12 +400,17 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
target_bb
|
||||
};
|
||||
|
||||
inject_statement(self.mir_body, counter_kind, inject_to_bb, None);
|
||||
inject_statement(
|
||||
self.mir_body,
|
||||
self.make_mir_coverage_kind(&counter_kind),
|
||||
inject_to_bb,
|
||||
None,
|
||||
);
|
||||
}
|
||||
CoverageKind::Expression { .. } => {
|
||||
inject_intermediate_expression(self.mir_body, counter_kind)
|
||||
}
|
||||
_ => bug!("CoverageKind should be a counter"),
|
||||
BcbCounter::Expression { .. } => inject_intermediate_expression(
|
||||
self.mir_body,
|
||||
self.make_mir_coverage_kind(&counter_kind),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -426,9 +431,20 @@ impl<'a, 'tcx> Instrumentor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format_counter(&self, counter_kind: &CoverageKind) -> String {
|
||||
fn format_counter(&self, counter_kind: &BcbCounter) -> String {
|
||||
self.coverage_counters.debug_counters.format_counter(counter_kind)
|
||||
}
|
||||
|
||||
fn make_mir_coverage_kind(&self, counter_kind: &BcbCounter) -> CoverageKind {
|
||||
match *counter_kind {
|
||||
BcbCounter::Counter { id } => {
|
||||
CoverageKind::Counter { function_source_hash: self.function_source_hash, id }
|
||||
}
|
||||
BcbCounter::Expression { id, lhs, op, rhs } => {
|
||||
CoverageKind::Expression { id, lhs, op, rhs }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_edge_counter_basic_block(
|
||||
@ -498,6 +514,14 @@ fn make_code_region(
|
||||
span: Span,
|
||||
body_span: Span,
|
||||
) -> CodeRegion {
|
||||
debug!(
|
||||
"Called make_code_region(file_name={}, source_file={:?}, span={}, body_span={})",
|
||||
file_name,
|
||||
source_file,
|
||||
source_map.span_to_diagnostic_string(span),
|
||||
source_map.span_to_diagnostic_string(body_span)
|
||||
);
|
||||
|
||||
let (start_line, mut start_col) = source_file.lookup_file_pos(span.lo());
|
||||
let (end_line, end_col) = if span.hi() == span.lo() {
|
||||
let (end_line, mut end_col) = (start_line, start_col);
|
||||
|
@ -867,8 +867,8 @@ pub(super) fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option<Sp
|
||||
}
|
||||
|
||||
// Retain spans from all other terminators
|
||||
TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Yield { .. }
|
||||
| TerminatorKind::GeneratorDrop
|
||||
|
@ -34,7 +34,6 @@ use itertools::Itertools;
|
||||
use rustc_data_structures::graph::WithNumNodes;
|
||||
use rustc_data_structures::graph::WithSuccessors;
|
||||
use rustc_index::{Idx, IndexVec};
|
||||
use rustc_middle::mir::coverage::CoverageKind;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::{self, BytePos, Pos, Span, DUMMY_SP};
|
||||
@ -675,8 +674,8 @@ fn test_make_bcb_counters() {
|
||||
));
|
||||
}
|
||||
}
|
||||
let mut coverage_counters = counters::CoverageCounters::new(0, &basic_coverage_blocks);
|
||||
let () = coverage_counters
|
||||
let mut coverage_counters = counters::CoverageCounters::new(&basic_coverage_blocks);
|
||||
coverage_counters
|
||||
.make_bcb_counters(&mut basic_coverage_blocks, &coverage_spans)
|
||||
.expect("should be Ok");
|
||||
assert_eq!(coverage_counters.intermediate_expressions.len(), 0);
|
||||
@ -685,7 +684,7 @@ fn test_make_bcb_counters() {
|
||||
assert_eq!(
|
||||
0, // bcb1 has a `Counter` with id = 0
|
||||
match coverage_counters.bcb_counter(bcb1).expect("should have a counter") {
|
||||
CoverageKind::Counter { id, .. } => id,
|
||||
counters::BcbCounter::Counter { id, .. } => id,
|
||||
_ => panic!("expected a Counter"),
|
||||
}
|
||||
.as_u32()
|
||||
@ -695,7 +694,7 @@ fn test_make_bcb_counters() {
|
||||
assert_eq!(
|
||||
1, // bcb2 has a `Counter` with id = 1
|
||||
match coverage_counters.bcb_counter(bcb2).expect("should have a counter") {
|
||||
CoverageKind::Counter { id, .. } => id,
|
||||
counters::BcbCounter::Counter { id, .. } => id,
|
||||
_ => panic!("expected a Counter"),
|
||||
}
|
||||
.as_u32()
|
||||
|
@ -13,9 +13,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_mir_dataflow::value_analysis::{
|
||||
Map, State, TrackElem, ValueAnalysis, ValueAnalysisWrapper, ValueOrPlace,
|
||||
};
|
||||
use rustc_mir_dataflow::{
|
||||
lattice::FlatSet, Analysis, Results, ResultsVisitor, SwitchIntEdgeEffects,
|
||||
};
|
||||
use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor};
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_target::abi::{Align, FieldIdx, VariantIdx};
|
||||
|
||||
@ -249,49 +247,27 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
|
||||
.unwrap_or(FlatSet::Top)
|
||||
}
|
||||
|
||||
fn handle_switch_int(
|
||||
fn handle_switch_int<'mir>(
|
||||
&self,
|
||||
discr: &Operand<'tcx>,
|
||||
apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
|
||||
) {
|
||||
// FIXME: The dataflow framework only provides the state if we call `apply()`, which makes
|
||||
// this more inefficient than it has to be.
|
||||
let mut discr_value = None;
|
||||
let mut handled = false;
|
||||
apply_edge_effects.apply(|state, target| {
|
||||
let discr_value = match discr_value {
|
||||
Some(value) => value,
|
||||
None => {
|
||||
discr: &'mir Operand<'tcx>,
|
||||
targets: &'mir SwitchTargets,
|
||||
state: &mut State<Self::Value>,
|
||||
) -> TerminatorEdges<'mir, 'tcx> {
|
||||
let value = match self.handle_operand(discr, state) {
|
||||
ValueOrPlace::Value(value) => value,
|
||||
ValueOrPlace::Place(place) => state.get_idx(place, self.map()),
|
||||
};
|
||||
let result = match value {
|
||||
FlatSet::Top => FlatSet::Top,
|
||||
match value {
|
||||
// We are branching on uninitialized data, this is UB, treat it as unreachable.
|
||||
// This allows the set of visited edges to grow monotonically with the lattice.
|
||||
FlatSet::Bottom => TerminatorEdges::None,
|
||||
FlatSet::Elem(ScalarTy(scalar, _)) => {
|
||||
let int = scalar.assert_int();
|
||||
FlatSet::Elem(int.assert_bits(int.size()))
|
||||
let choice = int.assert_bits(int.size());
|
||||
TerminatorEdges::Single(targets.target_for_value(choice))
|
||||
}
|
||||
FlatSet::Bottom => FlatSet::Bottom,
|
||||
};
|
||||
discr_value = Some(result);
|
||||
result
|
||||
FlatSet::Top => TerminatorEdges::SwitchInt { discr, targets },
|
||||
}
|
||||
};
|
||||
|
||||
let FlatSet::Elem(choice) = discr_value else {
|
||||
// Do nothing if we don't know which branch will be taken.
|
||||
return;
|
||||
};
|
||||
|
||||
if target.value.map(|n| n == choice).unwrap_or(!handled) {
|
||||
// Branch is taken. Has no effect on state.
|
||||
handled = true;
|
||||
} else {
|
||||
// Branch is not taken.
|
||||
state.mark_unreachable();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -565,6 +541,13 @@ impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for Dumm
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn panic_nounwind(
|
||||
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
_msg: &str,
|
||||
) -> interpret::InterpResult<'tcx> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn call_intrinsic(
|
||||
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
|
||||
_instance: ty::Instance<'tcx>,
|
||||
|
@ -203,7 +203,12 @@ pub fn deduced_param_attrs<'tcx>(
|
||||
body.local_decls.iter().skip(1).take(body.arg_count).enumerate().map(
|
||||
|(arg_index, local_decl)| DeducedParamAttrs {
|
||||
read_only: !deduce_read_only.mutable_args.contains(arg_index)
|
||||
&& local_decl.ty.is_freeze(tcx, param_env),
|
||||
// We must normalize here to reveal opaques and normalize
|
||||
// their substs, otherwise we'll see exponential blow-up in
|
||||
// compile times: #113372
|
||||
&& tcx
|
||||
.normalize_erasing_regions(param_env, local_decl.ty)
|
||||
.is_freeze(tcx, param_env),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -647,8 +647,8 @@ impl WriteInfo {
|
||||
}
|
||||
}
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable { .. } => (),
|
||||
TerminatorKind::Drop { .. } => {
|
||||
|
@ -48,6 +48,7 @@ use std::fmt;
|
||||
pub struct ElaborateDrops;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||
#[instrument(level = "trace", skip(self, tcx, body))]
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
debug!("elaborate_drops({:?} @ {:?})", body.source, body.span);
|
||||
|
||||
@ -65,23 +66,23 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||
};
|
||||
let elaborate_patch = {
|
||||
let env = MoveDataParamEnv { move_data, param_env };
|
||||
remove_dead_unwinds(tcx, body, &env);
|
||||
|
||||
let inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
||||
let mut inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
||||
.skipping_unreachable_unwind()
|
||||
.into_engine(tcx, body)
|
||||
.pass_name("elaborate_drops")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
let dead_unwinds = compute_dead_unwinds(&body, &mut inits);
|
||||
|
||||
let uninits = MaybeUninitializedPlaces::new(tcx, body, &env)
|
||||
.mark_inactive_variants_as_uninit()
|
||||
.skipping_unreachable_unwind(dead_unwinds)
|
||||
.into_engine(tcx, body)
|
||||
.pass_name("elaborate_drops")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
|
||||
let reachable = traversal::reachable_as_bitset(body);
|
||||
|
||||
let drop_flags = IndexVec::from_elem(None, &env.move_data.move_paths);
|
||||
ElaborateDropsCtxt {
|
||||
tcx,
|
||||
@ -90,7 +91,6 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||
init_data: InitializationData { inits, uninits },
|
||||
drop_flags,
|
||||
patch: MirPatch::new(body),
|
||||
reachable,
|
||||
}
|
||||
.elaborate()
|
||||
};
|
||||
@ -99,65 +99,30 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes unwind edges which are known to be unreachable, because they are in `drop` terminators
|
||||
/// Records unwind edges which are known to be unreachable, because they are in `drop` terminators
|
||||
/// that can't drop anything.
|
||||
fn remove_dead_unwinds<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &mut Body<'tcx>,
|
||||
env: &MoveDataParamEnv<'tcx>,
|
||||
) {
|
||||
debug!("remove_dead_unwinds({:?})", body.span);
|
||||
#[instrument(level = "trace", skip(body, flow_inits), ret)]
|
||||
fn compute_dead_unwinds<'mir, 'tcx>(
|
||||
body: &'mir Body<'tcx>,
|
||||
flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
|
||||
) -> BitSet<BasicBlock> {
|
||||
// We only need to do this pass once, because unwind edges can only
|
||||
// reach cleanup blocks, which can't have unwind edges themselves.
|
||||
let mut dead_unwinds = Vec::new();
|
||||
let mut flow_inits = MaybeInitializedPlaces::new(tcx, body, &env)
|
||||
.into_engine(tcx, body)
|
||||
.pass_name("remove_dead_unwinds")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
let mut dead_unwinds = BitSet::new_empty(body.basic_blocks.len());
|
||||
for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
|
||||
let place = match bb_data.terminator().kind {
|
||||
TerminatorKind::Drop { place, unwind: UnwindAction::Cleanup(_), .. } => place,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
debug!("remove_dead_unwinds @ {:?}: {:?}", bb, bb_data);
|
||||
|
||||
let LookupResult::Exact(path) = env.move_data.rev_lookup.find(place.as_ref()) else {
|
||||
debug!("remove_dead_unwinds: has parent; skipping");
|
||||
let TerminatorKind::Drop { place, unwind: UnwindAction::Cleanup(_), .. } =
|
||||
bb_data.terminator().kind
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
flow_inits.seek_before_primary_effect(body.terminator_loc(bb));
|
||||
debug!(
|
||||
"remove_dead_unwinds @ {:?}: path({:?})={:?}; init_data={:?}",
|
||||
bb,
|
||||
place,
|
||||
path,
|
||||
flow_inits.get()
|
||||
);
|
||||
|
||||
let mut maybe_live = false;
|
||||
on_all_drop_children_bits(tcx, body, &env, path, |child| {
|
||||
maybe_live |= flow_inits.contains(child);
|
||||
});
|
||||
|
||||
debug!("remove_dead_unwinds @ {:?}: maybe_live={}", bb, maybe_live);
|
||||
if !maybe_live {
|
||||
dead_unwinds.push(bb);
|
||||
if flow_inits.analysis().is_unwind_dead(place, flow_inits.get()) {
|
||||
dead_unwinds.insert(bb);
|
||||
}
|
||||
}
|
||||
|
||||
if dead_unwinds.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let basic_blocks = body.basic_blocks.as_mut();
|
||||
for &bb in dead_unwinds.iter() {
|
||||
if let Some(unwind) = basic_blocks[bb].terminator_mut().unwind_mut() {
|
||||
*unwind = UnwindAction::Unreachable;
|
||||
}
|
||||
}
|
||||
dead_unwinds
|
||||
}
|
||||
|
||||
struct InitializationData<'mir, 'tcx> {
|
||||
@ -290,7 +255,6 @@ struct ElaborateDropsCtxt<'a, 'tcx> {
|
||||
init_data: InitializationData<'a, 'tcx>,
|
||||
drop_flags: IndexVec<MovePathIndex, Option<Local>>,
|
||||
patch: MirPatch<'tcx>,
|
||||
reachable: BitSet<BasicBlock>,
|
||||
}
|
||||
|
||||
impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
@ -330,9 +294,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
|
||||
fn collect_drop_flags(&mut self) {
|
||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||
if !self.reachable.contains(bb) {
|
||||
continue;
|
||||
}
|
||||
let terminator = data.terminator();
|
||||
let place = match terminator.kind {
|
||||
TerminatorKind::Drop { ref place, .. } => place,
|
||||
@ -384,9 +345,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
|
||||
fn elaborate_drops(&mut self) {
|
||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||
if !self.reachable.contains(bb) {
|
||||
continue;
|
||||
}
|
||||
let loc = Location { block: bb, statement_index: data.statements.len() };
|
||||
let terminator = data.terminator();
|
||||
|
||||
@ -465,9 +423,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
|
||||
fn drop_flags_for_fn_rets(&mut self) {
|
||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||
if !self.reachable.contains(bb) {
|
||||
continue;
|
||||
}
|
||||
if let TerminatorKind::Call {
|
||||
destination,
|
||||
target: Some(tgt),
|
||||
@ -506,9 +461,6 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
// clobbered before they are read.
|
||||
|
||||
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
|
||||
if !self.reachable.contains(bb) {
|
||||
continue;
|
||||
}
|
||||
debug!("drop_flags_for_locs({:?})", data);
|
||||
for i in 0..(data.statements.len() + 1) {
|
||||
debug!("drop_flag_for_locs: stmt {}", i);
|
||||
@ -518,7 +470,7 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
|
||||
// drop elaboration should handle that by itself
|
||||
continue;
|
||||
}
|
||||
TerminatorKind::Resume => {
|
||||
TerminatorKind::UnwindResume => {
|
||||
// It is possible for `Resume` to be patched
|
||||
// (in particular it can be patched to be replaced with
|
||||
// a Goto; see `MirPatch::new`).
|
||||
|
@ -50,8 +50,10 @@
|
||||
//! For generators with state 1 (returned) and state 2 (poisoned) it does nothing.
|
||||
//! Otherwise it drops all the values in scope at the last suspension point.
|
||||
|
||||
use crate::abort_unwinding_calls;
|
||||
use crate::deref_separator::deref_finder;
|
||||
use crate::errors;
|
||||
use crate::pass_manager as pm;
|
||||
use crate::simplify;
|
||||
use crate::MirPass;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
@ -64,6 +66,7 @@ use rustc_index::{Idx, IndexVec};
|
||||
use rustc_middle::mir::dump_mir;
|
||||
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::InstanceDef;
|
||||
use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{GeneratorArgs, GenericArgsRef};
|
||||
use rustc_mir_dataflow::impls::{
|
||||
@ -1147,7 +1150,25 @@ fn create_generator_drop_shim<'tcx>(
|
||||
// unrelated code from the resume part of the function
|
||||
simplify::remove_dead_blocks(tcx, &mut body);
|
||||
|
||||
// Update the body's def to become the drop glue.
|
||||
// This needs to be updated before the AbortUnwindingCalls pass.
|
||||
let gen_instance = body.source.instance;
|
||||
let drop_in_place = tcx.require_lang_item(LangItem::DropInPlace, None);
|
||||
let drop_instance = InstanceDef::DropGlue(drop_in_place, Some(gen_ty));
|
||||
body.source.instance = drop_instance;
|
||||
|
||||
pm::run_passes_no_validate(
|
||||
tcx,
|
||||
&mut body,
|
||||
&[&abort_unwinding_calls::AbortUnwindingCalls],
|
||||
None,
|
||||
);
|
||||
|
||||
// Temporary change MirSource to generator's instance so that dump_mir produces more sensible
|
||||
// filename.
|
||||
body.source.instance = gen_instance;
|
||||
dump_mir(tcx, false, "generator_drop", &0, &body, |_, _| Ok(()));
|
||||
body.source.instance = drop_instance;
|
||||
|
||||
body
|
||||
}
|
||||
@ -1218,7 +1239,7 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
|
||||
// These never unwind.
|
||||
TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::GeneratorDrop
|
||||
@ -1227,7 +1248,7 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool {
|
||||
|
||||
// Resume will *continue* unwinding, but if there's no other unwinding terminator it
|
||||
// will never be reached.
|
||||
TerminatorKind::Resume => {}
|
||||
TerminatorKind::UnwindResume => {}
|
||||
|
||||
TerminatorKind::Yield { .. } => {
|
||||
unreachable!("`can_unwind` called before generator transform")
|
||||
@ -1258,14 +1279,14 @@ fn create_generator_resume_function<'tcx>(
|
||||
let source_info = SourceInfo::outermost(body.span);
|
||||
let poison_block = body.basic_blocks_mut().push(BasicBlockData {
|
||||
statements: vec![transform.set_discr(VariantIdx::new(POISONED), source_info)],
|
||||
terminator: Some(Terminator { source_info, kind: TerminatorKind::Resume }),
|
||||
terminator: Some(Terminator { source_info, kind: TerminatorKind::UnwindResume }),
|
||||
is_cleanup: true,
|
||||
});
|
||||
|
||||
for (idx, block) in body.basic_blocks_mut().iter_enumerated_mut() {
|
||||
let source_info = block.terminator().source_info;
|
||||
|
||||
if let TerminatorKind::Resume = block.terminator().kind {
|
||||
if let TerminatorKind::UnwindResume = block.terminator().kind {
|
||||
// An existing `Resume` terminator is redirected to jump to our dedicated
|
||||
// "poisoning block" above.
|
||||
if idx != poison_block {
|
||||
@ -1317,6 +1338,8 @@ fn create_generator_resume_function<'tcx>(
|
||||
// unrelated code from the drop part of the function
|
||||
simplify::remove_dead_blocks(tcx, body);
|
||||
|
||||
pm::run_passes_no_validate(tcx, body, &[&abort_unwinding_calls::AbortUnwindingCalls], None);
|
||||
|
||||
dump_mir(tcx, false, "generator_resume", &0, body, |_, _| Ok(()));
|
||||
}
|
||||
|
||||
@ -1735,8 +1758,8 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
|
||||
TerminatorKind::Call { .. }
|
||||
| TerminatorKind::Goto { .. }
|
||||
| TerminatorKind::SwitchInt { .. }
|
||||
| TerminatorKind::Resume
|
||||
| TerminatorKind::Terminate
|
||||
| TerminatorKind::UnwindResume
|
||||
| TerminatorKind::UnwindTerminate
|
||||
| TerminatorKind::Return
|
||||
| TerminatorKind::Unreachable
|
||||
| TerminatorKind::Drop { .. }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user