Merge from rustc

This commit is contained in:
The Miri Conjob Bot 2023-10-24 05:17:56 +00:00
commit ddc76e232a
310 changed files with 8271 additions and 3048 deletions

View file

@ -31,6 +31,8 @@ Alexis Beingessner <a.beingessner@gmail.com>
Alfie John <alfie@alfie.wtf> Alfie John <alfiej@fastmail.fm>
Alona Enraght-Moony <code@alona.page> <nixon.emoony@gmail.com>
Alona Enraght-Moony <code@alona.page> <nixon@caminus.local>
Amanda Stjerna <mail@amandastjerna.se> <albin.stjerna@gmail.com>
Amanda Stjerna <mail@amandastjerna.se> <amanda.stjerna@it.uu.se>
Amos Onn <amosonn@gmail.com>
Ana-Maria Mihalache <mihalacheana.maria@yahoo.com>
Anatoly Ikorsky <aikorsky@gmail.com>

View file

@ -143,9 +143,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.1"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
@ -370,11 +370,11 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "bytecount"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7"
dependencies = [
"packed_simd_2",
"packed_simd",
]
[[package]]
@ -501,6 +501,16 @@ dependencies = [
"clap_derive",
]
[[package]]
name = "clap-cargo"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383f21342a464d4af96e9a4cad22a0b4f2880d4a5b3bbf5c9654dd1d9a224ee4"
dependencies = [
"anstyle",
"clap",
]
[[package]]
name = "clap_builder"
version = "4.4.4"
@ -1777,7 +1787,7 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm 0.2.7",
"libm",
]
[[package]]
@ -2228,12 +2238,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "libm"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a"
[[package]]
name = "libm"
version = "0.2.7"
@ -2594,6 +2598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -2743,13 +2748,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "packed_simd_2"
version = "0.3.8"
name = "packed_simd"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282"
checksum = "1f9f08af0c877571712e2e3e686ad79efad9657dbf0f7c3c8ba943ff6c38932d"
dependencies = [
"cfg-if",
"libm 0.1.4",
"num-traits",
]
[[package]]
@ -3596,6 +3601,7 @@ version = "0.0.0"
dependencies = [
"bitflags 1.3.2",
"cstr",
"itertools",
"libc",
"measureme",
"object",
@ -4273,6 +4279,7 @@ dependencies = [
"coverage_test_macros",
"either",
"itertools",
"rustc_arena",
"rustc_ast",
"rustc_attr",
"rustc_const_eval",
@ -4514,9 +4521,7 @@ name = "rustc_smir"
version = "0.0.0"
dependencies = [
"rustc_data_structures",
"rustc_driver",
"rustc_hir",
"rustc_interface",
"rustc_middle",
"rustc_span",
"rustc_target",
@ -4758,21 +4763,20 @@ dependencies = [
[[package]]
name = "rustfmt-nightly"
version = "1.6.0"
version = "1.7.0"
dependencies = [
"annotate-snippets",
"anyhow",
"bytecount",
"cargo_metadata 0.15.4",
"clap",
"clap-cargo",
"diff",
"dirs",
"env_logger 0.10.0",
"getopts",
"ignore",
"itertools",
"lazy_static",
"log",
"regex",
"rustfmt-config_proc_macro",
"serde",
@ -4780,9 +4784,11 @@ dependencies = [
"term",
"thiserror",
"toml 0.7.5",
"tracing",
"tracing-subscriber",
"unicode-properties",
"unicode-segmentation",
"unicode-width",
"unicode_categories",
]
[[package]]
@ -5676,6 +5682,7 @@ dependencies = [
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
@ -5891,12 +5898,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unified-diff"
version = "0.2.1"

View file

@ -53,7 +53,6 @@ pub mod visit;
pub use self::ast::*;
pub use self::ast_traits::{AstDeref, AstNodeWrapper, HasAttrs, HasNodeId, HasSpan, HasTokens};
pub use self::format::*;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};

View file

@ -31,16 +31,6 @@ pub fn is_builtin_attr(attr: &Attribute) -> bool {
attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
}
enum AttrError {
MultipleItem(String),
UnknownMetaItem(String, &'static [&'static str]),
MissingSince,
NonIdentFeature,
MissingFeature,
MultipleStabilityLevels,
UnsupportedLiteral(UnsupportedLiteralReason, /* is_bytestr */ bool),
}
pub(crate) enum UnsupportedLiteralReason {
Generic,
CfgString,
@ -48,37 +38,6 @@ pub(crate) enum UnsupportedLiteralReason {
DeprecatedKvPair,
}
fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) {
match error {
AttrError::MultipleItem(item) => {
sess.emit_err(session_diagnostics::MultipleItem { span, item });
}
AttrError::UnknownMetaItem(item, expected) => {
sess.emit_err(session_diagnostics::UnknownMetaItem { span, item, expected });
}
AttrError::MissingSince => {
sess.emit_err(session_diagnostics::MissingSince { span });
}
AttrError::NonIdentFeature => {
sess.emit_err(session_diagnostics::NonIdentFeature { span });
}
AttrError::MissingFeature => {
sess.emit_err(session_diagnostics::MissingFeature { span });
}
AttrError::MultipleStabilityLevels => {
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span });
}
AttrError::UnsupportedLiteral(reason, is_bytestr) => {
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span,
reason,
is_bytestr,
start_point_span: sess.source_map().start_point(span),
});
}
}
}
#[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum InlineAttr {
None,
@ -241,7 +200,7 @@ pub fn find_stability(
sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true,
sym::unstable => {
if stab.is_some() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels);
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
break;
}
@ -251,7 +210,7 @@ pub fn find_stability(
}
sym::stable => {
if stab.is_some() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels);
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
break;
}
if let Some((feature, level)) = parse_stability(sess, attr) {
@ -295,7 +254,7 @@ pub fn find_const_stability(
sym::rustc_promotable => promotable = true,
sym::rustc_const_unstable => {
if const_stab.is_some() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels);
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
break;
}
@ -306,7 +265,7 @@ pub fn find_const_stability(
}
sym::rustc_const_stable => {
if const_stab.is_some() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels);
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
break;
}
if let Some((feature, level)) = parse_stability(sess, attr) {
@ -340,7 +299,7 @@ pub fn find_body_stability(
for attr in attrs {
if attr.has_name(sym::rustc_default_body_unstable) {
if body_stab.is_some() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels);
sess.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
break;
}
@ -355,11 +314,10 @@ pub fn find_body_stability(
fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option<Symbol>) -> Option<()> {
if item.is_some() {
handle_errors(
&sess.parse_sess,
meta.span,
AttrError::MultipleItem(pprust::path_to_string(&meta.path)),
);
sess.emit_err(session_diagnostics::MultipleItem {
span: meta.span,
item: pprust::path_to_string(&meta.path),
});
None
} else if let Some(v) = meta.value_str() {
*item = Some(v);
@ -380,11 +338,12 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit
let mut since = None;
for meta in metas {
let Some(mi) = meta.meta_item() else {
handle_errors(
&sess.parse_sess,
meta.span(),
AttrError::UnsupportedLiteral(UnsupportedLiteralReason::Generic, false),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: meta.span(),
reason: UnsupportedLiteralReason::Generic,
is_bytestr: false,
start_point_span: sess.source_map().start_point(meta.span()),
});
return None;
};
@ -392,14 +351,11 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit
sym::feature => insert_or_error(sess, mi, &mut feature)?,
sym::since => insert_or_error(sess, mi, &mut since)?,
_ => {
handle_errors(
&sess.parse_sess,
meta.span(),
AttrError::UnknownMetaItem(
pprust::path_to_string(&mi.path),
&["feature", "since"],
),
);
sess.emit_err(session_diagnostics::UnknownMetaItem {
span: meta.span(),
item: pprust::path_to_string(&mi.path),
expected: &["feature", "since"],
});
return None;
}
}
@ -417,11 +373,11 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit
Some((feature, level))
}
(None, _) => {
handle_errors(&sess.parse_sess, attr.span, AttrError::MissingFeature);
sess.emit_err(session_diagnostics::MissingFeature { span: attr.span });
None
}
_ => {
handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince);
sess.emit_err(session_diagnostics::MissingSince { span: attr.span });
None
}
}
@ -441,11 +397,12 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil
let mut implied_by = None;
for meta in metas {
let Some(mi) = meta.meta_item() else {
handle_errors(
&sess.parse_sess,
meta.span(),
AttrError::UnsupportedLiteral(UnsupportedLiteralReason::Generic, false),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: meta.span(),
reason: UnsupportedLiteralReason::Generic,
is_bytestr: false,
start_point_span: sess.source_map().start_point(meta.span()),
});
return None;
};
@ -484,14 +441,11 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil
}
sym::implied_by => insert_or_error(sess, mi, &mut implied_by)?,
_ => {
handle_errors(
&sess.parse_sess,
meta.span(),
AttrError::UnknownMetaItem(
pprust::path_to_string(&mi.path),
&["feature", "reason", "issue", "soft", "implied_by"],
),
);
sess.emit_err(session_diagnostics::UnknownMetaItem {
span: meta.span(),
item: pprust::path_to_string(&mi.path),
expected: &["feature", "reason", "issue", "soft", "implied_by"],
});
return None;
}
}
@ -500,7 +454,7 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil
match (feature, reason, issue) {
(Some(feature), reason, Some(_)) => {
if !rustc_lexer::is_ident(feature.as_str()) {
handle_errors(&sess.parse_sess, attr.span, AttrError::NonIdentFeature);
sess.emit_err(session_diagnostics::NonIdentFeature { span: attr.span });
return None;
}
let level = StabilityLevel::Unstable {
@ -512,7 +466,7 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil
Some((feature, level))
}
(None, _, _) => {
handle_errors(&sess.parse_sess, attr.span, AttrError::MissingFeature);
sess.emit_err(session_diagnostics::MissingFeature { span: attr.span });
return None;
}
_ => {
@ -659,11 +613,12 @@ pub fn eval_condition(
ast::MetaItemKind::List(mis) => {
for mi in mis.iter() {
if !mi.is_meta_item() {
handle_errors(
sess,
mi.span(),
AttrError::UnsupportedLiteral(UnsupportedLiteralReason::Generic, false),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: mi.span(),
reason: UnsupportedLiteralReason::Generic,
is_bytestr: false,
start_point_span: sess.source_map().start_point(mi.span()),
});
return false;
}
}
@ -731,14 +686,12 @@ pub fn eval_condition(
true
}
MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
handle_errors(
sess,
lit.span,
AttrError::UnsupportedLiteral(
UnsupportedLiteralReason::CfgString,
lit.kind.is_bytestr(),
),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: lit.span,
reason: UnsupportedLiteralReason::CfgString,
is_bytestr: lit.kind.is_bytestr(),
start_point_span: sess.source_map().start_point(lit.span),
});
true
}
ast::MetaItemKind::Word | ast::MetaItemKind::NameValue(..) => {
@ -795,11 +748,10 @@ pub fn find_deprecation(
MetaItemKind::List(list) => {
let get = |meta: &MetaItem, item: &mut Option<Symbol>| {
if item.is_some() {
handle_errors(
&sess.parse_sess,
meta.span,
AttrError::MultipleItem(pprust::path_to_string(&meta.path)),
);
sess.emit_err(session_diagnostics::MultipleItem {
span: meta.span,
item: pprust::path_to_string(&meta.path),
});
return false;
}
if let Some(v) = meta.value_str() {
@ -807,14 +759,12 @@ pub fn find_deprecation(
true
} else {
if let Some(lit) = meta.name_value_literal() {
handle_errors(
&sess.parse_sess,
lit.span,
AttrError::UnsupportedLiteral(
UnsupportedLiteralReason::DeprecatedString,
lit.kind.is_bytestr(),
),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: lit.span,
reason: UnsupportedLiteralReason::DeprecatedString,
is_bytestr: lit.kind.is_bytestr(),
start_point_span: sess.source_map().start_point(lit.span),
});
} else {
sess.emit_err(session_diagnostics::IncorrectMetaItem {
span: meta.span,
@ -852,30 +802,25 @@ pub fn find_deprecation(
}
}
_ => {
handle_errors(
&sess.parse_sess,
meta.span(),
AttrError::UnknownMetaItem(
pprust::path_to_string(&mi.path),
if features.deprecated_suggestion {
&["since", "note", "suggestion"]
} else {
&["since", "note"]
},
),
);
sess.emit_err(session_diagnostics::UnknownMetaItem {
span: meta.span(),
item: pprust::path_to_string(&mi.path),
expected: if features.deprecated_suggestion {
&["since", "note", "suggestion"]
} else {
&["since", "note"]
},
});
continue 'outer;
}
},
NestedMetaItem::Lit(lit) => {
handle_errors(
&sess.parse_sess,
lit.span,
AttrError::UnsupportedLiteral(
UnsupportedLiteralReason::DeprecatedKvPair,
false,
),
);
sess.emit_err(session_diagnostics::UnsupportedLiteral {
span: lit.span,
reason: UnsupportedLiteralReason::DeprecatedKvPair,
is_bytestr: false,
start_point_span: sess.source_map().start_point(lit.span),
});
continue 'outer;
}
}
@ -885,7 +830,7 @@ pub fn find_deprecation(
if is_rustc {
if since.is_none() {
handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince);
sess.emit_err(session_diagnostics::MissingSince { span: attr.span });
continue;
}

View file

@ -272,21 +272,28 @@ impl<'tcx> PoloniusOutOfScopePrecomputer<'_, 'tcx> {
loan_issued_at: Location,
) {
let sccs = self.regioncx.constraint_sccs();
let universal_regions = self.regioncx.universal_regions();
let issuing_region_scc = sccs.scc(issuing_region);
// We first handle the cases where the loan doesn't go out of scope, depending on the issuing
// region's successors.
for scc in sccs.depth_first_search(issuing_region_scc) {
// 1. Via member constraints
// 1. Via applied member constraints
//
// The issuing region can flow into the choice regions, and they are either:
// - placeholders or free regions themselves,
// - or also transitively outlive a free region.
//
// That is to say, if there are member constraints here, the loan escapes the function
// and cannot go out of scope. We can early return.
if self.regioncx.scc_has_member_constraints(scc) {
return;
// That is to say, if there are applied member constraints here, the loan escapes the
// function and cannot go out of scope. We could early return here.
//
// For additional insurance via fuzzing and crater, we verify that the constraint's min
// choice indeed escapes the function. In the future, we could e.g. turn this check into
// a debug assert and early return as an optimization.
for constraint in self.regioncx.applied_member_constraints(scc) {
if universal_regions.is_universal_region(constraint.min_choice) {
return;
}
}
// 2. Via regions that are live at all points: placeholders and free regions.
@ -413,12 +420,12 @@ impl<'a, 'tcx> Borrows<'a, 'tcx> {
let mut polonius_prec = PoloniusOutOfScopePrecomputer::new(body, regioncx);
for (loan_idx, loan_data) in borrow_set.iter_enumerated() {
let issuing_region = loan_data.region;
let issued_location = loan_data.reserve_location;
let loan_issued_at = loan_data.reserve_location;
polonius_prec.precompute_loans_out_of_scope(
loan_idx,
issuing_region,
issued_location,
loan_issued_at,
);
}

View file

@ -2633,9 +2633,10 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
/* Check if the mpi is initialized as an argument */
let mut is_argument = false;
for arg in self.body.args_iter() {
let path = self.move_data.rev_lookup.find_local(arg);
if mpis.contains(&path) {
is_argument = true;
if let Some(path) = self.move_data.rev_lookup.find_local(arg) {
if mpis.contains(&path) {
is_argument = true;
}
}
}

View file

@ -46,6 +46,7 @@ mod mutability_errors;
mod region_errors;
pub(crate) use bound_region_errors::{ToUniverseInfo, UniverseInfo};
pub(crate) use move_errors::{IllegalMoveOriginKind, MoveError};
pub(crate) use mutability_errors::AccessKind;
pub(crate) use outlives_suggestion::OutlivesSuggestionBuilder;
pub(crate) use region_errors::{ErrorConstraintInfo, RegionErrorKind, RegionErrors};
@ -470,7 +471,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
}
}
ty.print(printer).unwrap().into_buffer()
ty.print(&mut printer).unwrap();
printer.into_buffer()
}
/// Returns the name of the provided `Ty` (that must be a reference)'s region with a
@ -492,7 +494,8 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
bug!("ty for annotation of borrow region is not a reference");
};
region.print(printer).unwrap().into_buffer()
region.print(&mut printer).unwrap();
printer.into_buffer()
}
}

View file

@ -1,9 +1,7 @@
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
use rustc_middle::mir::*;
use rustc_middle::ty;
use rustc_mir_dataflow::move_paths::{
IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex,
};
use rustc_middle::ty::{self, Ty};
use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};
use rustc_span::{BytePos, Span};
use crate::diagnostics::CapturedMessageOpt;
@ -11,6 +9,42 @@ use crate::diagnostics::{DescribePlaceOpt, UseSpans};
use crate::prefixes::PrefixSet;
use crate::MirBorrowckCtxt;
#[derive(Debug)]
pub enum IllegalMoveOriginKind<'tcx> {
/// Illegal move due to attempt to move from behind a reference.
BorrowedContent {
/// The place the reference refers to: if erroneous code was trying to
/// move from `(*x).f` this will be `*x`.
target_place: Place<'tcx>,
},
/// Illegal move due to attempt to move from field of an ADT that
/// implements `Drop`. Rust maintains invariant that all `Drop`
/// ADT's remain fully-initialized so that user-defined destructor
/// can safely read from all of the ADT's fields.
InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
/// Illegal move due to attempt to move out of a slice or array.
InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },
}
#[derive(Debug)]
pub(crate) struct MoveError<'tcx> {
place: Place<'tcx>,
location: Location,
kind: IllegalMoveOriginKind<'tcx>,
}
impl<'tcx> MoveError<'tcx> {
pub(crate) fn new(
place: Place<'tcx>,
location: Location,
kind: IllegalMoveOriginKind<'tcx>,
) -> Self {
MoveError { place, location, kind }
}
}
// Often when desugaring a pattern match we may have many individual moves in
// MIR that are all part of one operation from the user's point-of-view. For
// example:
@ -53,20 +87,18 @@ enum GroupedMoveError<'tcx> {
}
impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) {
let grouped_errors = self.group_move_errors(move_errors);
pub(crate) fn report_move_errors(&mut self) {
let grouped_errors = self.group_move_errors();
for error in grouped_errors {
self.report(error);
}
}
fn group_move_errors(
&self,
errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
) -> Vec<GroupedMoveError<'tcx>> {
fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {
let mut grouped_errors = Vec::new();
for (original_path, error) in errors {
self.append_to_grouped_errors(&mut grouped_errors, original_path, error);
let errors = std::mem::take(&mut self.move_errors);
for error in errors {
self.append_to_grouped_errors(&mut grouped_errors, error);
}
grouped_errors
}
@ -74,66 +106,58 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
fn append_to_grouped_errors(
&self,
grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
original_path: Place<'tcx>,
error: MoveError<'tcx>,
) {
match error {
MoveError::UnionMove { .. } => {
unimplemented!("don't know how to report union move errors yet.")
}
MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => {
// Note: that the only time we assign a place isn't a temporary
// to a user variable is when initializing it.
// If that ever stops being the case, then the ever initialized
// flow could be used.
if let Some(StatementKind::Assign(box (
place,
Rvalue::Use(Operand::Move(move_from)),
))) = self.body.basic_blocks[location.block]
.statements
.get(location.statement_index)
.map(|stmt| &stmt.kind)
{
if let Some(local) = place.as_local() {
let local_decl = &self.body.local_decls[local];
// opt_match_place is the
// match_span is the span of the expression being matched on
// match *x.y { ... } match_place is Some(*x.y)
// ^^^^ match_span is the span of *x.y
//
// opt_match_place is None for let [mut] x = ... statements,
// whether or not the right-hand side is a place expression
if let LocalInfo::User(BindingForm::Var(VarBindingForm {
opt_match_place: Some((opt_match_place, match_span)),
binding_mode: _,
opt_ty_info: _,
pat_span: _,
})) = *local_decl.local_info()
{
let stmt_source_info = self.body.source_info(location);
self.append_binding_error(
grouped_errors,
kind,
original_path,
*move_from,
local,
opt_match_place,
match_span,
stmt_source_info.span,
);
return;
}
}
}
let MoveError { place: original_path, location, kind } = error;
let move_spans = self.move_spans(original_path.as_ref(), location);
grouped_errors.push(GroupedMoveError::OtherIllegalMove {
use_spans: move_spans,
original_path,
kind,
});
// Note: that the only time we assign a place isn't a temporary
// to a user variable is when initializing it.
// If that ever stops being the case, then the ever initialized
// flow could be used.
if let Some(StatementKind::Assign(box (place, Rvalue::Use(Operand::Move(move_from))))) =
self.body.basic_blocks[location.block]
.statements
.get(location.statement_index)
.map(|stmt| &stmt.kind)
{
if let Some(local) = place.as_local() {
let local_decl = &self.body.local_decls[local];
// opt_match_place is the
// match_span is the span of the expression being matched on
// match *x.y { ... } match_place is Some(*x.y)
// ^^^^ match_span is the span of *x.y
//
// opt_match_place is None for let [mut] x = ... statements,
// whether or not the right-hand side is a place expression
if let LocalInfo::User(BindingForm::Var(VarBindingForm {
opt_match_place: Some((opt_match_place, match_span)),
binding_mode: _,
opt_ty_info: _,
pat_span: _,
})) = *local_decl.local_info()
{
let stmt_source_info = self.body.source_info(location);
self.append_binding_error(
grouped_errors,
kind,
original_path,
*move_from,
local,
opt_match_place,
match_span,
stmt_source_info.span,
);
return;
}
}
}
let move_spans = self.move_spans(original_path.as_ref(), location);
grouped_errors.push(GroupedMoveError::OtherIllegalMove {
use_spans: move_spans,
original_path,
kind,
});
}
fn append_binding_error(

View file

@ -31,13 +31,8 @@ use rustc_index::{IndexSlice, IndexVec};
use rustc_infer::infer::{
InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, TyCtxtInferExt,
};
use rustc_middle::mir::{
traversal, Body, ClearCrossCrate, Local, Location, MutBorrowKind, Mutability,
NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef, VarDebugInfoContents,
};
use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind};
use rustc_middle::mir::{ProjectionElem, Promoted, Rvalue, Statement, StatementKind};
use rustc_middle::mir::tcx::PlaceTy;
use rustc_middle::mir::*;
use rustc_middle::query::Providers;
use rustc_middle::traits::DefiningAnchor;
use rustc_middle::ty::{self, CapturedPlace, ParamEnv, RegionVid, TyCtxt};
@ -55,13 +50,13 @@ use rustc_mir_dataflow::impls::{
EverInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
};
use rustc_mir_dataflow::move_paths::{InitIndex, MoveOutIndex, MovePathIndex};
use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveData, MoveError};
use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult, MoveData};
use rustc_mir_dataflow::Analysis;
use rustc_mir_dataflow::MoveDataParamEnv;
use crate::session_diagnostics::VarNeedNotMut;
use self::diagnostics::{AccessKind, RegionName};
use self::diagnostics::{AccessKind, IllegalMoveOriginKind, MoveError, RegionName};
use self::location::LocationTable;
use self::prefixes::PrefixSet;
use consumers::{BodyWithBorrowckFacts, ConsumerOptions};
@ -224,14 +219,10 @@ fn do_mir_borrowck<'tcx>(
let location_table_owned = LocationTable::new(body);
let location_table = &location_table_owned;
let (move_data, move_errors): (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>) =
match MoveData::gather_moves(&body, tcx, param_env) {
Ok(move_data) => (move_data, Vec::new()),
Err((move_data, move_errors)) => (move_data, move_errors),
};
let promoted_errors = promoted
let move_data = MoveData::gather_moves(&body, tcx, param_env, |_| true);
let promoted_move_data = promoted
.iter_enumerated()
.map(|(idx, body)| (idx, MoveData::gather_moves(&body, tcx, param_env)));
.map(|(idx, body)| (idx, MoveData::gather_moves(&body, tcx, param_env, |_| true)));
let mdpe = MoveDataParamEnv { move_data, param_env };
@ -313,36 +304,49 @@ fn do_mir_borrowck<'tcx>(
true
};
for (idx, move_data_results) in promoted_errors {
let promoted_body = &promoted[idx];
for (idx, move_data) in promoted_move_data {
use rustc_middle::mir::visit::Visitor;
if let Err((move_data, move_errors)) = move_data_results {
let mut promoted_mbcx = MirBorrowckCtxt {
infcx: &infcx,
param_env,
body: promoted_body,
move_data: &move_data,
location_table, // no need to create a real one for the promoted, it is not used
movable_coroutine,
fn_self_span_reported: Default::default(),
locals_are_invalidated_at_exit,
access_place_error_reported: Default::default(),
reservation_error_reported: Default::default(),
uninitialized_error_reported: Default::default(),
regioncx: regioncx.clone(),
used_mut: Default::default(),
used_mut_upvars: SmallVec::new(),
borrow_set: Rc::clone(&borrow_set),
upvars: Vec::new(),
local_names: IndexVec::from_elem(None, &promoted_body.local_decls),
region_names: RefCell::default(),
next_region_name: RefCell::new(1),
polonius_output: None,
errors,
};
promoted_mbcx.report_move_errors(move_errors);
errors = promoted_mbcx.errors;
let promoted_body = &promoted[idx];
let mut promoted_mbcx = MirBorrowckCtxt {
infcx: &infcx,
param_env,
body: promoted_body,
move_data: &move_data,
location_table, // no need to create a real one for the promoted, it is not used
movable_coroutine,
fn_self_span_reported: Default::default(),
locals_are_invalidated_at_exit,
access_place_error_reported: Default::default(),
reservation_error_reported: Default::default(),
uninitialized_error_reported: Default::default(),
regioncx: regioncx.clone(),
used_mut: Default::default(),
used_mut_upvars: SmallVec::new(),
borrow_set: Rc::clone(&borrow_set),
upvars: Vec::new(),
local_names: IndexVec::from_elem(None, &promoted_body.local_decls),
region_names: RefCell::default(),
next_region_name: RefCell::new(1),
polonius_output: None,
move_errors: Vec::new(),
errors,
};
MoveVisitor { ctxt: &mut promoted_mbcx }.visit_body(promoted_body);
promoted_mbcx.report_move_errors();
errors = promoted_mbcx.errors;
struct MoveVisitor<'a, 'cx, 'tcx> {
ctxt: &'a mut MirBorrowckCtxt<'cx, 'tcx>,
}
impl<'tcx> Visitor<'tcx> for MoveVisitor<'_, '_, 'tcx> {
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
if let Operand::Move(place) = operand {
self.ctxt.check_movable_place(location, *place);
}
}
}
}
let mut mbcx = MirBorrowckCtxt {
@ -366,6 +370,7 @@ fn do_mir_borrowck<'tcx>(
region_names: RefCell::default(),
next_region_name: RefCell::new(1),
polonius_output,
move_errors: Vec::new(),
errors,
};
@ -378,8 +383,6 @@ fn do_mir_borrowck<'tcx>(
borrows: flow_borrows,
};
mbcx.report_move_errors(move_errors);
rustc_mir_dataflow::visit_results(
body,
traversal::reverse_postorder(body).map(|(bb, _)| bb),
@ -387,6 +390,8 @@ fn do_mir_borrowck<'tcx>(
&mut mbcx,
);
mbcx.report_move_errors();
// For each non-user used mutable variable, check if it's been assigned from
// a user-declared local. If so, then put that local into the used_mut set.
// Note that this set is expected to be small - only upvars from closures
@ -595,6 +600,7 @@ struct MirBorrowckCtxt<'cx, 'tcx> {
polonius_output: Option<Rc<PoloniusOutput>>,
errors: error::BorrowckErrors<'tcx>,
move_errors: Vec<MoveError<'tcx>>,
}
// Check that:
@ -725,7 +731,6 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro
}
TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
self.consume_operand(loc, (cond, span), flow_state);
use rustc_middle::mir::AssertKind;
if let AssertKind::BoundsCheck { len, index } = &**msg {
self.consume_operand(loc, (len, span), flow_state);
self.consume_operand(loc, (index, span), flow_state);
@ -1409,7 +1414,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
// As such we have to search for the local that this
// capture comes from and mark it as being used as mut.
let temp_mpi = self.move_data.rev_lookup.find_local(local);
let Some(temp_mpi) = self.move_data.rev_lookup.find_local(local) else {
bug!("temporary should be tracked");
};
let init = if let [init_index] = *self.move_data.init_path_map[temp_mpi] {
&self.move_data.inits[init_index]
} else {
@ -1469,6 +1476,9 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
);
}
Operand::Move(place) => {
// Check if moving from this place makes sense.
self.check_movable_place(location, place);
// move of place: check if this is move of already borrowed path
self.access_place(
location,
@ -1590,6 +1600,131 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
}
}
fn check_movable_place(&mut self, location: Location, place: Place<'tcx>) {
use IllegalMoveOriginKind::*;
let body = self.body;
let tcx = self.infcx.tcx;
let mut place_ty = PlaceTy::from_ty(body.local_decls[place.local].ty);
for (place_ref, elem) in place.iter_projections() {
match elem {
ProjectionElem::Deref => match place_ty.ty.kind() {
ty::Ref(..) | ty::RawPtr(..) => {
self.move_errors.push(MoveError::new(
place,
location,
BorrowedContent {
target_place: place_ref.project_deeper(&[elem], tcx),
},
));
return;
}
ty::Adt(adt, _) => {
if !adt.is_box() {
bug!("Adt should be a box type when Place is deref");
}
}
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Slice(_)
| ty::FnDef(_, _)
| ty::FnPtr(_)
| ty::Dynamic(_, _, _)
| ty::Closure(_, _)
| ty::Coroutine(_, _, _)
| ty::CoroutineWitness(..)
| ty::Never
| ty::Tuple(_)
| ty::Alias(_, _)
| ty::Param(_)
| ty::Bound(_, _)
| ty::Infer(_)
| ty::Error(_)
| ty::Placeholder(_) => {
bug!("When Place is Deref it's type shouldn't be {place_ty:#?}")
}
},
ProjectionElem::Field(_, _) => match place_ty.ty.kind() {
ty::Adt(adt, _) => {
if adt.has_dtor(tcx) {
self.move_errors.push(MoveError::new(
place,
location,
InteriorOfTypeWithDestructor { container_ty: place_ty.ty },
));
return;
}
}
ty::Closure(_, _) | ty::Coroutine(_, _, _) | ty::Tuple(_) => (),
ty::Bool
| ty::Char
| ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Slice(_)
| ty::RawPtr(_)
| ty::Ref(_, _, _)
| ty::FnDef(_, _)
| ty::FnPtr(_)
| ty::Dynamic(_, _, _)
| ty::CoroutineWitness(..)
| ty::Never
| ty::Alias(_, _)
| ty::Param(_)
| ty::Bound(_, _)
| ty::Infer(_)
| ty::Error(_)
| ty::Placeholder(_) => bug!(
"When Place contains ProjectionElem::Field it's type shouldn't be {place_ty:#?}"
),
},
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
match place_ty.ty.kind() {
ty::Slice(_) => {
self.move_errors.push(MoveError::new(
place,
location,
InteriorOfSliceOrArray { ty: place_ty.ty, is_index: false },
));
return;
}
ty::Array(_, _) => (),
_ => bug!("Unexpected type {:#?}", place_ty.ty),
}
}
ProjectionElem::Index(_) => match place_ty.ty.kind() {
ty::Array(..) | ty::Slice(..) => {
self.move_errors.push(MoveError::new(
place,
location,
InteriorOfSliceOrArray { ty: place_ty.ty, is_index: true },
));
return;
}
_ => bug!("Unexpected type {place_ty:#?}"),
},
// `OpaqueCast`: only transmutes the type, so no moves there.
// `Downcast` : only changes information about a `Place` without moving.
// `Subtype` : only transmutes the type, so no moves.
// So it's safe to skip these.
ProjectionElem::OpaqueCast(_)
| ProjectionElem::Subtype(_)
| ProjectionElem::Downcast(_, _) => (),
}
place_ty = place_ty.projection_ty(tcx, elem);
}
}
fn check_if_full_path_is_moved(
&mut self,
location: Location,
@ -2074,7 +2209,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
local: Local,
flow_state: &Flows<'cx, 'tcx>,
) -> Option<InitIndex> {
let mpi = self.move_data.rev_lookup.find_local(local);
let mpi = self.move_data.rev_lookup.find_local(local)?;
let ii = &self.move_data.init_path_map[mpi];
ii.into_iter().find(|&&index| flow_state.ever_inits.contains(index)).copied()
}

View file

@ -644,11 +644,12 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.scc_universes[scc]
}
/// Once region solving has completed, this function will return
/// the member constraints that were applied to the value of a given
/// region `r`. See `AppliedMemberConstraint`.
pub(crate) fn applied_member_constraints(&self, r: RegionVid) -> &[AppliedMemberConstraint] {
let scc = self.constraint_sccs.scc(r);
/// Once region solving has completed, this function will return the member constraints that
/// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`.
pub(crate) fn applied_member_constraints(
&self,
scc: ConstraintSccIndex,
) -> &[AppliedMemberConstraint] {
binary_search_util::binary_search_slice(
&self.member_constraints_applied,
|applied| applied.member_region_scc,
@ -1945,7 +1946,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
// Member constraints can also give rise to `'r: 'x` edges that
// were not part of the graph initially, so watch out for those.
// (But they are extremely rare; this loop is very cold.)
for constraint in self.applied_member_constraints(r) {
for constraint in self.applied_member_constraints(self.constraint_sccs.scc(r)) {
let p_c = &self.member_constraints[constraint.member_constraint_index];
let constraint = OutlivesConstraint {
sup: r,
@ -2292,11 +2293,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.constraint_sccs.as_ref()
}
/// Returns whether the given SCC has any member constraints.
pub(crate) fn scc_has_member_constraints(&self, scc: ConstraintSccIndex) -> bool {
self.member_constraints.indices(scc).next().is_some()
}
/// Returns whether the given SCC is live at all points: whether the representative is a
/// placeholder or a free region.
pub(crate) fn scc_is_live_at_all_points(&self, scc: ConstraintSccIndex) -> bool {

View file

@ -317,7 +317,7 @@ impl<'me, 'typeck, 'flow, 'tcx> LivenessResults<'me, 'typeck, 'flow, 'tcx> {
fn compute_drop_live_points_for(&mut self, local: Local) {
debug!("compute_drop_live_points_for(local={:?})", local);
let mpi = self.cx.move_data.rev_lookup.find_local(local);
let Some(mpi) = self.cx.move_data.rev_lookup.find_local(local) else { return };
debug!("compute_drop_live_points_for: mpi = {:?}", mpi);
// Find the drops where `local` is initialized.

View file

@ -9,6 +9,7 @@ test = false
[dependencies]
bitflags = "1.0"
cstr = "0.2"
itertools = "0.10.5"
libc = "0.2"
measureme = "10.0.0"
object = { version = "0.32.0", default-features = false, features = [

View file

@ -1,16 +1,18 @@
use crate::coverageinfo::ffi::{Counter, CounterExpression, ExprKind};
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexSet;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::coverage::{
CodeRegion, CounterId, CovTerm, Expression, ExpressionId, FunctionCoverageInfo, Mapping, Op,
};
use rustc_middle::ty::Instance;
use rustc_span::Symbol;
/// Holds all of the coverage mapping data associated with a function instance,
/// collected during traversal of `Coverage` statements in the function's MIR.
#[derive(Debug)]
pub struct FunctionCoverage<'tcx> {
pub struct FunctionCoverageCollector<'tcx> {
/// Coverage info that was attached to this function by the instrumentor.
function_coverage_info: &'tcx FunctionCoverageInfo,
is_used: bool,
@ -26,7 +28,7 @@ pub struct FunctionCoverage<'tcx> {
expressions_seen: BitSet<ExpressionId>,
}
impl<'tcx> FunctionCoverage<'tcx> {
impl<'tcx> FunctionCoverageCollector<'tcx> {
/// Creates a new set of coverage data for a used (called) function.
pub fn new(
instance: Instance<'tcx>,
@ -76,11 +78,6 @@ impl<'tcx> FunctionCoverage<'tcx> {
}
}
/// Returns true for a used (called) function, and false for an unused function.
pub fn is_used(&self) -> bool {
self.is_used
}
/// Marks a counter ID as having been seen in a counter-increment statement.
#[instrument(level = "debug", skip(self))]
pub(crate) fn mark_counter_id_seen(&mut self, id: CounterId) {
@ -165,64 +162,71 @@ impl<'tcx> FunctionCoverage<'tcx> {
ZeroExpressions(zero_expressions)
}
pub(crate) fn into_finished(self) -> FunctionCoverage<'tcx> {
let zero_expressions = self.identify_zero_expressions();
let FunctionCoverageCollector { function_coverage_info, is_used, counters_seen, .. } = self;
FunctionCoverage { function_coverage_info, is_used, counters_seen, zero_expressions }
}
}
pub(crate) struct FunctionCoverage<'tcx> {
function_coverage_info: &'tcx FunctionCoverageInfo,
is_used: bool,
counters_seen: BitSet<CounterId>,
zero_expressions: ZeroExpressions,
}
impl<'tcx> FunctionCoverage<'tcx> {
/// Returns true for a used (called) function, and false for an unused function.
pub(crate) fn is_used(&self) -> bool {
self.is_used
}
/// Return the source hash, generated from the HIR node structure, and used to indicate whether
/// or not the source code structure changed between different compilations.
pub fn source_hash(&self) -> u64 {
if self.is_used { self.function_coverage_info.function_source_hash } else { 0 }
}
/// Generate an array of CounterExpressions, and an iterator over all `Counter`s and their
/// associated `Regions` (from which the LLVM-specific `CoverageMapGenerator` will create
/// `CounterMappingRegion`s.
pub fn get_expressions_and_counter_regions(
&self,
) -> (Vec<CounterExpression>, impl Iterator<Item = (Counter, &CodeRegion)>) {
let zero_expressions = self.identify_zero_expressions();
let counter_expressions = self.counter_expressions(&zero_expressions);
// Expression IDs are indices into `self.expressions`, and on the LLVM
// side they will be treated as indices into `counter_expressions`, so
// the two vectors should correspond 1:1.
assert_eq!(self.function_coverage_info.expressions.len(), counter_expressions.len());
let counter_regions = self.counter_regions(zero_expressions);
(counter_expressions, counter_regions)
/// Returns an iterator over all filenames used by this function's mappings.
pub(crate) fn all_file_names(&self) -> impl Iterator<Item = Symbol> + Captures<'_> {
self.function_coverage_info.mappings.iter().map(|mapping| mapping.code_region.file_name)
}
/// Convert this function's coverage expression data into a form that can be
/// passed through FFI to LLVM.
fn counter_expressions(&self, zero_expressions: &ZeroExpressions) -> Vec<CounterExpression> {
pub(crate) fn counter_expressions(
&self,
) -> impl Iterator<Item = CounterExpression> + ExactSizeIterator + Captures<'_> {
// We know that LLVM will optimize out any unused expressions before
// producing the final coverage map, so there's no need to do the same
// thing on the Rust side unless we're confident we can do much better.
// (See `CounterExpressionsMinimizer` in `CoverageMappingWriter.cpp`.)
let counter_from_operand = |operand: CovTerm| match operand {
CovTerm::Expression(id) if zero_expressions.contains(id) => Counter::ZERO,
CovTerm::Expression(id) if self.zero_expressions.contains(id) => Counter::ZERO,
_ => Counter::from_term(operand),
};
self.function_coverage_info
.expressions
.iter()
.map(|&Expression { lhs, op, rhs }| CounterExpression {
self.function_coverage_info.expressions.iter().map(move |&Expression { lhs, op, rhs }| {
CounterExpression {
lhs: counter_from_operand(lhs),
kind: match op {
Op::Add => ExprKind::Add,
Op::Subtract => ExprKind::Subtract,
},
rhs: counter_from_operand(rhs),
})
.collect::<Vec<_>>()
}
})
}
/// Converts this function's coverage mappings into an intermediate form
/// that will be used by `mapgen` when preparing for FFI.
fn counter_regions(
pub(crate) fn counter_regions(
&self,
zero_expressions: ZeroExpressions,
) -> impl Iterator<Item = (Counter, &CodeRegion)> {
) -> impl Iterator<Item = (Counter, &CodeRegion)> + ExactSizeIterator {
// Historically, mappings were stored directly in counter/expression
// statements in MIR, and MIR optimizations would sometimes remove them.
// That's mostly no longer true, so now we detect cases where that would
@ -230,7 +234,7 @@ impl<'tcx> FunctionCoverage<'tcx> {
let counter_for_term = move |term: CovTerm| {
let force_to_zero = match term {
CovTerm::Counter(id) => !self.counters_seen.contains(id),
CovTerm::Expression(id) => zero_expressions.contains(id),
CovTerm::Expression(id) => self.zero_expressions.contains(id),
CovTerm::Zero => false,
};
if force_to_zero { Counter::ZERO } else { Counter::from_term(term) }

View file

@ -1,11 +1,12 @@
use crate::common::CodegenCx;
use crate::coverageinfo;
use crate::coverageinfo::ffi::CounterMappingRegion;
use crate::coverageinfo::map_data::FunctionCoverage;
use crate::coverageinfo::map_data::{FunctionCoverage, FunctionCoverageCollector};
use crate::llvm;
use itertools::Itertools as _;
use rustc_codegen_ssa::traits::{BaseTypeMethods, ConstMethods};
use rustc_data_structures::fx::FxIndexSet;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_index::IndexVec;
@ -57,11 +58,32 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
return;
}
let mut global_file_table = GlobalFileTable::new(tcx);
let function_coverage_entries = function_coverage_map
.into_iter()
.map(|(instance, function_coverage)| (instance, function_coverage.into_finished()))
.collect::<Vec<_>>();
let all_file_names =
function_coverage_entries.iter().flat_map(|(_, fn_cov)| fn_cov.all_file_names());
let global_file_table = GlobalFileTable::new(all_file_names);
// Encode all filenames referenced by coverage mappings in this CGU.
let filenames_buffer = global_file_table.make_filenames_buffer(tcx);
let filenames_size = filenames_buffer.len();
let filenames_val = cx.const_bytes(&filenames_buffer);
let filenames_ref = coverageinfo::hash_bytes(&filenames_buffer);
// Generate the coverage map header, which contains the filenames used by
// this CGU's coverage mappings, and store it in a well-known global.
let cov_data_val = generate_coverage_map(cx, version, filenames_size, filenames_val);
coverageinfo::save_cov_data_to_mod(cx, cov_data_val);
let mut unused_function_names = Vec::new();
let covfun_section_name = coverageinfo::covfun_section_name(cx);
// Encode coverage mappings and generate function records
let mut function_data = Vec::new();
for (instance, function_coverage) in function_coverage_map {
for (instance, function_coverage) in function_coverage_entries {
debug!("Generate function coverage for {}, {:?}", cx.codegen_unit.name(), instance);
let mangled_function_name = tcx.symbol_name(instance).name;
@ -69,7 +91,7 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
let is_used = function_coverage.is_used();
let coverage_mapping_buffer =
encode_mappings_for_function(&mut global_file_table, &function_coverage);
encode_mappings_for_function(&global_file_table, &function_coverage);
if coverage_mapping_buffer.is_empty() {
if function_coverage.is_used() {
@ -83,23 +105,6 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
}
}
function_data.push((mangled_function_name, source_hash, is_used, coverage_mapping_buffer));
}
// Encode all filenames referenced by counters/expressions in this module
let filenames_buffer = global_file_table.into_filenames_buffer();
let filenames_size = filenames_buffer.len();
let filenames_val = cx.const_bytes(&filenames_buffer);
let filenames_ref = coverageinfo::hash_bytes(&filenames_buffer);
// Generate the LLVM IR representation of the coverage map and store it in a well-known global
let cov_data_val = generate_coverage_map(cx, version, filenames_size, filenames_val);
let mut unused_function_names = Vec::new();
let covfun_section_name = coverageinfo::covfun_section_name(cx);
for (mangled_function_name, source_hash, is_used, coverage_mapping_buffer) in function_data {
if !is_used {
unused_function_names.push(mangled_function_name);
}
@ -133,92 +138,125 @@ pub fn finalize(cx: &CodegenCx<'_, '_>) {
llvm::set_linkage(array, llvm::Linkage::InternalLinkage);
llvm::set_initializer(array, initializer);
}
// Save the coverage data value to LLVM IR
coverageinfo::save_cov_data_to_mod(cx, cov_data_val);
}
/// Maps "global" (per-CGU) file ID numbers to their underlying filenames.
struct GlobalFileTable {
global_file_table: FxIndexSet<Symbol>,
/// This "raw" table doesn't include the working dir, so a filename's
/// global ID is its index in this set **plus one**.
raw_file_table: FxIndexSet<Symbol>,
}
impl GlobalFileTable {
fn new(tcx: TyCtxt<'_>) -> Self {
let mut global_file_table = FxIndexSet::default();
fn new(all_file_names: impl IntoIterator<Item = Symbol>) -> Self {
// Collect all of the filenames into a set. Filenames usually come in
// contiguous runs, so we can dedup adjacent ones to save work.
let mut raw_file_table = all_file_names.into_iter().dedup().collect::<FxIndexSet<Symbol>>();
// Sort the file table by its actual string values, not the arbitrary
// ordering of its symbols.
raw_file_table.sort_unstable_by(|a, b| a.as_str().cmp(b.as_str()));
Self { raw_file_table }
}
fn global_file_id_for_file_name(&self, file_name: Symbol) -> u32 {
let raw_id = self.raw_file_table.get_index_of(&file_name).unwrap_or_else(|| {
bug!("file name not found in prepared global file table: {file_name}");
});
// The raw file table doesn't include an entry for the working dir
// (which has ID 0), so add 1 to get the correct ID.
(raw_id + 1) as u32
}
fn make_filenames_buffer(&self, tcx: TyCtxt<'_>) -> Vec<u8> {
// LLVM Coverage Mapping Format version 6 (zero-based encoded as 5)
// requires setting the first filename to the compilation directory.
// Since rustc generates coverage maps with relative paths, the
// compilation directory can be combined with the relative paths
// to get absolute paths, if needed.
use rustc_session::RemapFileNameExt;
let working_dir =
Symbol::intern(&tcx.sess.opts.working_dir.for_codegen(&tcx.sess).to_string_lossy());
global_file_table.insert(working_dir);
Self { global_file_table }
}
fn global_file_id_for_file_name(&mut self, file_name: Symbol) -> u32 {
let (global_file_id, _) = self.global_file_table.insert_full(file_name);
global_file_id as u32
}
fn into_filenames_buffer(self) -> Vec<u8> {
// This method takes `self` so that the caller can't accidentally
// modify the original file table after encoding it into a buffer.
let working_dir: &str = &tcx.sess.opts.working_dir.for_codegen(&tcx.sess).to_string_lossy();
llvm::build_byte_buffer(|buffer| {
coverageinfo::write_filenames_section_to_buffer(
self.global_file_table.iter().map(Symbol::as_str),
// Insert the working dir at index 0, before the other filenames.
std::iter::once(working_dir).chain(self.raw_file_table.iter().map(Symbol::as_str)),
buffer,
);
})
}
}
rustc_index::newtype_index! {
// Tell the newtype macro to not generate `Encode`/`Decode` impls.
#[custom_encodable]
struct LocalFileId {}
}
/// Holds a mapping from "local" (per-function) file IDs to "global" (per-CGU)
/// file IDs.
#[derive(Default)]
struct VirtualFileMapping {
local_to_global: IndexVec<LocalFileId, u32>,
global_to_local: FxIndexMap<u32, LocalFileId>,
}
impl VirtualFileMapping {
fn local_id_for_global(&mut self, global_file_id: u32) -> LocalFileId {
*self
.global_to_local
.entry(global_file_id)
.or_insert_with(|| self.local_to_global.push(global_file_id))
}
fn into_vec(self) -> Vec<u32> {
self.local_to_global.raw
}
}
/// Using the expressions and counter regions collected for a single function,
/// generate the variable-sized payload of its corresponding `__llvm_covfun`
/// entry. The payload is returned as a vector of bytes.
///
/// Newly-encountered filenames will be added to the global file table.
fn encode_mappings_for_function(
global_file_table: &mut GlobalFileTable,
global_file_table: &GlobalFileTable,
function_coverage: &FunctionCoverage<'_>,
) -> Vec<u8> {
let (expressions, counter_regions) = function_coverage.get_expressions_and_counter_regions();
let mut counter_regions = counter_regions.collect::<Vec<_>>();
let counter_regions = function_coverage.counter_regions();
if counter_regions.is_empty() {
return Vec::new();
}
let mut virtual_file_mapping = IndexVec::<u32, u32>::new();
let expressions = function_coverage.counter_expressions().collect::<Vec<_>>();
let mut virtual_file_mapping = VirtualFileMapping::default();
let mut mapping_regions = Vec::with_capacity(counter_regions.len());
// Sort and group the list of (counter, region) mapping pairs by filename.
// (Preserve any further ordering imposed by `FunctionCoverage`.)
// Group mappings into runs with the same filename, preserving the order
// yielded by `FunctionCoverage`.
// Prepare file IDs for each filename, and prepare the mapping data so that
// we can pass it through FFI to LLVM.
counter_regions.sort_by_key(|(_counter, region)| region.file_name);
for counter_regions_for_file in
counter_regions.group_by(|(_, a), (_, b)| a.file_name == b.file_name)
for (file_name, counter_regions_for_file) in
&counter_regions.group_by(|(_counter, region)| region.file_name)
{
// Look up (or allocate) the global file ID for this filename.
let file_name = counter_regions_for_file[0].1.file_name;
// Look up the global file ID for this filename.
let global_file_id = global_file_table.global_file_id_for_file_name(file_name);
// Associate that global file ID with a local file ID for this function.
let local_file_id: u32 = virtual_file_mapping.push(global_file_id);
debug!(" file id: local {local_file_id} => global {global_file_id} = '{file_name:?}'");
let local_file_id = virtual_file_mapping.local_id_for_global(global_file_id);
debug!(" file id: {local_file_id:?} => global {global_file_id} = '{file_name:?}'");
// For each counter/region pair in this function+file, convert it to a
// form suitable for FFI.
for &(counter, region) in counter_regions_for_file {
for (counter, region) in counter_regions_for_file {
let CodeRegion { file_name: _, start_line, start_col, end_line, end_col } = *region;
debug!("Adding counter {counter:?} to map for {region:?}");
mapping_regions.push(CounterMappingRegion::code_region(
counter,
local_file_id,
local_file_id.as_u32(),
start_line,
start_col,
end_line,
@ -230,7 +268,7 @@ fn encode_mappings_for_function(
// Encode the function's coverage mappings into a buffer.
llvm::build_byte_buffer(|buffer| {
coverageinfo::write_mapping_to_buffer(
virtual_file_mapping.raw,
virtual_file_mapping.into_vec(),
expressions,
mapping_regions,
buffer,
@ -419,7 +457,7 @@ fn add_unused_function_coverage<'tcx>(
) {
// An unused function's mappings will automatically be rewritten to map to
// zero, because none of its counters/expressions are marked as seen.
let function_coverage = FunctionCoverage::unused(instance, function_coverage_info);
let function_coverage = FunctionCoverageCollector::unused(instance, function_coverage_info);
if let Some(coverage_context) = cx.coverage_context() {
coverage_context.function_coverage_map.borrow_mut().insert(instance, function_coverage);

View file

@ -3,7 +3,7 @@ use crate::llvm;
use crate::builder::Builder;
use crate::common::CodegenCx;
use crate::coverageinfo::ffi::{CounterExpression, CounterMappingRegion};
use crate::coverageinfo::map_data::FunctionCoverage;
use crate::coverageinfo::map_data::FunctionCoverageCollector;
use libc::c_uint;
use rustc_codegen_ssa::traits::{
@ -29,7 +29,8 @@ const VAR_ALIGN_BYTES: usize = 8;
/// A context object for maintaining all state needed by the coverageinfo module.
pub struct CrateCoverageContext<'ll, 'tcx> {
/// Coverage data for each instrumented function identified by DefId.
pub(crate) function_coverage_map: RefCell<FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>>>,
pub(crate) function_coverage_map:
RefCell<FxHashMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>>>,
pub(crate) pgo_func_name_var_map: RefCell<FxHashMap<Instance<'tcx>, &'ll llvm::Value>>,
}
@ -41,7 +42,9 @@ impl<'ll, 'tcx> CrateCoverageContext<'ll, 'tcx> {
}
}
pub fn take_function_coverage_map(&self) -> FxHashMap<Instance<'tcx>, FunctionCoverage<'tcx>> {
pub fn take_function_coverage_map(
&self,
) -> FxHashMap<Instance<'tcx>, FunctionCoverageCollector<'tcx>> {
self.function_coverage_map.replace(FxHashMap::default())
}
}
@ -93,7 +96,7 @@ impl<'tcx> CoverageInfoBuilderMethods<'tcx> for Builder<'_, '_, 'tcx> {
let mut coverage_map = coverage_context.function_coverage_map.borrow_mut();
let func_coverage = coverage_map
.entry(instance)
.or_insert_with(|| FunctionCoverage::new(instance, function_coverage_info));
.or_insert_with(|| FunctionCoverageCollector::new(instance, function_coverage_info));
let Coverage { kind } = coverage;
match *kind {

View file

@ -50,7 +50,6 @@ mod utils;
pub use self::create_scope_map::compute_mir_scopes;
pub use self::metadata::build_global_var_di_node;
pub use self::metadata::extend_scope_to_file;
#[allow(non_upper_case_globals)]
const DW_TAG_auto_variable: c_uint = 0x100;

View file

@ -8,12 +8,13 @@
#![cfg_attr(not(bootstrap), feature(rustdoc_internals))]
#![cfg_attr(not(bootstrap), doc(rust_logo))]
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(exact_size_is_empty)]
#![feature(extern_types)]
#![feature(hash_raw_entry)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
#![feature(min_specialization)]
#![feature(never_type)]
#![feature(slice_group_by)]
#![feature(impl_trait_in_assoc_type)]
#![recursion_limit = "256"]
#![allow(rustc::potential_query_instability)]

View file

@ -6,7 +6,6 @@ use crate::llvm;
use crate::type_of::LayoutLlvmExt;
use rustc_codegen_ssa::traits::*;
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
pub use rustc_middle::mir::mono::MonoItem;
use rustc_middle::mir::mono::{Linkage, Visibility};
use rustc_middle::ty::layout::{FnAbiOf, LayoutOf};
use rustc_middle::ty::{self, Instance, TypeVisitableExt};

View file

@ -3,7 +3,7 @@ use std::mem;
use either::{Left, Right};
use rustc_hir::def::DefKind;
use rustc_middle::mir::interpret::{ErrorHandled, InterpErrorInfo};
use rustc_middle::mir::interpret::{AllocId, ErrorHandled, InterpErrorInfo};
use rustc_middle::mir::pretty::write_allocation_bytes;
use rustc_middle::mir::{self, ConstAlloc, ConstValue};
use rustc_middle::traits::Reveal;
@ -285,7 +285,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
let def = cid.instance.def.def_id();
let is_static = tcx.is_static(def);
let mut ecx = InterpCx::new(
let ecx = InterpCx::new(
tcx,
tcx.def_span(def),
key.param_env,
@ -293,7 +293,14 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
// they do not have to behave "as if" they were evaluated at runtime.
CompileTimeInterpreter::new(CanAccessStatics::from(is_static), CheckAlignment::Error),
);
eval_in_interpreter(ecx, cid, is_static)
}
pub fn eval_in_interpreter<'mir, 'tcx>(
mut ecx: InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
cid: GlobalId<'tcx>,
is_static: bool,
) -> ::rustc_middle::mir::interpret::EvalToAllocationRawResult<'tcx> {
let res = ecx.load_mir(cid.instance.def, cid.promoted);
match res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, &body)) {
Err(error) => {
@ -306,7 +313,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
// If the current item has generics, we'd like to enrich the message with the
// instance and its args: to show the actual compile-time values, in addition to
// the expression, leading to the const eval error.
let instance = &key.value.instance;
let instance = &cid.instance;
if !instance.args.is_empty() {
let instance = with_no_trimmed_paths!(instance.to_string());
("const_with_path", instance)
@ -331,56 +338,14 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
Ok(mplace) => {
// Since evaluation had no errors, validate the resulting constant.
// This is a separate `try` block to provide more targeted error reporting.
let validation: Result<_, InterpErrorInfo<'_>> = try {
let mut ref_tracking = RefTracking::new(mplace.clone());
let mut inner = false;
while let Some((mplace, path)) = ref_tracking.todo.pop() {
let mode = match tcx.static_mutability(cid.instance.def_id()) {
Some(_) if cid.promoted.is_some() => {
// Promoteds in statics are allowed to point to statics.
CtfeValidationMode::Const { inner, allow_static_ptrs: true }
}
Some(_) => CtfeValidationMode::Regular, // a `static`
None => CtfeValidationMode::Const { inner, allow_static_ptrs: false },
};
ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?;
inner = true;
}
};
let validation =
const_validate_mplace(&ecx, &mplace, is_static, cid.promoted.is_some());
let alloc_id = mplace.ptr().provenance.unwrap();
// Validation failed, report an error.
if let Err(error) = validation {
let (error, backtrace) = error.into_parts();
backtrace.print_backtrace();
let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
let mut bytes = String::new();
if alloc.size() != abi::Size::ZERO {
bytes = "\n".into();
// FIXME(translation) there might be pieces that are translatable.
write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
}
let raw_bytes = errors::RawBytesNote {
size: alloc.size().bytes(),
align: alloc.align.bytes(),
bytes,
};
Err(super::report(
*ecx.tcx,
error,
None,
|| super::get_span_and_frames(&ecx),
move |span, frames| errors::UndefinedBehavior {
span,
ub_note,
frames,
raw_bytes,
},
))
Err(const_report_error(&ecx, error, alloc_id))
} else {
// Convert to raw constant
Ok(ConstAlloc { alloc_id, ty: mplace.layout.ty })
@ -388,3 +353,61 @@ pub fn eval_to_allocation_raw_provider<'tcx>(
}
}
}
#[inline(always)]
pub fn const_validate_mplace<'mir, 'tcx>(
ecx: &InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
mplace: &MPlaceTy<'tcx>,
is_static: bool,
is_promoted: bool,
) -> InterpResult<'tcx> {
let mut ref_tracking = RefTracking::new(mplace.clone());
let mut inner = false;
while let Some((mplace, path)) = ref_tracking.todo.pop() {
let mode = if is_static {
if is_promoted {
// Promoteds in statics are allowed to point to statics.
CtfeValidationMode::Const { inner, allow_static_ptrs: true }
} else {
// a `static`
CtfeValidationMode::Regular
}
} else {
CtfeValidationMode::Const { inner, allow_static_ptrs: false }
};
ecx.const_validate_operand(&mplace.into(), path, &mut ref_tracking, mode)?;
inner = true;
}
Ok(())
}
#[inline(always)]
pub fn const_report_error<'mir, 'tcx>(
ecx: &InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
error: InterpErrorInfo<'tcx>,
alloc_id: AllocId,
) -> ErrorHandled {
let (error, backtrace) = error.into_parts();
backtrace.print_backtrace();
let ub_note = matches!(error, InterpError::UndefinedBehavior(_)).then(|| {});
let alloc = ecx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
let mut bytes = String::new();
if alloc.size() != abi::Size::ZERO {
bytes = "\n".into();
// FIXME(translation) there might be pieces that are translatable.
write_allocation_bytes(*ecx.tcx, alloc, &mut bytes, " ").unwrap();
}
let raw_bytes =
errors::RawBytesNote { size: alloc.size().bytes(), align: alloc.align.bytes(), bytes };
crate::const_eval::report(
*ecx.tcx,
error,
None,
|| crate::const_eval::get_span_and_frames(ecx),
move |span, frames| errors::UndefinedBehavior { span, ub_note, frames, raw_bytes },
)
}

View file

@ -1074,17 +1074,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
instance: ty::Instance<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
let gid = GlobalId { instance, promoted: None };
// For statics we pick `ParamEnv::reveal_all`, because statics don't have generics
// and thus don't care about the parameter environment. While we could just use
// `self.param_env`, that would mean we invoke the query to evaluate the static
// with different parameter environments, thus causing the static to be evaluated
// multiple times.
let param_env = if self.tcx.is_static(gid.instance.def_id()) {
ty::ParamEnv::reveal_all()
let val = if self.tcx.is_static(gid.instance.def_id()) {
let alloc_id = self.tcx.reserve_and_set_static_alloc(gid.instance.def_id());
let ty = instance.ty(self.tcx.tcx, self.param_env);
mir::ConstAlloc { alloc_id, ty }
} else {
self.param_env
self.ctfe_query(|tcx| tcx.eval_to_allocation_raw(self.param_env.and(gid)))?
};
let val = self.ctfe_query(|tcx| tcx.eval_to_allocation_raw(param_env.and(gid)))?;
self.raw_const_to_mplace(val)
}

View file

@ -107,10 +107,10 @@ impl<Prov: Provenance> std::fmt::Display for ImmTy<'_, Prov> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// Helper function for printing a scalar to a FmtPrinter
fn p<'a, 'tcx, Prov: Provenance>(
cx: FmtPrinter<'a, 'tcx>,
cx: &mut FmtPrinter<'a, 'tcx>,
s: Scalar<Prov>,
ty: Ty<'tcx>,
) -> Result<FmtPrinter<'a, 'tcx>, std::fmt::Error> {
) -> Result<(), std::fmt::Error> {
match s {
Scalar::Int(int) => cx.pretty_print_const_scalar_int(int, ty, true),
Scalar::Ptr(ptr, _sz) => {
@ -125,8 +125,9 @@ impl<Prov: Provenance> std::fmt::Display for ImmTy<'_, Prov> {
match self.imm {
Immediate::Scalar(s) => {
if let Some(ty) = tcx.lift(self.layout.ty) {
let cx = FmtPrinter::new(tcx, Namespace::ValueNS);
f.write_str(&p(cx, s, ty)?.into_buffer())?;
let s =
FmtPrinter::print_string(tcx, Namespace::ValueNS, |cx| p(cx, s, ty))?;
f.write_str(&s)?;
return Ok(());
}
write!(f, "{:x}: {}", s, self.layout.ty)

View file

@ -929,7 +929,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// - no pointers to statics.
/// - no `UnsafeCell` or non-ZST `&mut`.
#[inline(always)]
pub fn const_validate_operand(
pub(crate) fn const_validate_operand(
&self,
op: &OpTy<'tcx, M::Provenance>,
path: Vec<PathElem>,

View file

@ -18,11 +18,11 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
self.tcx
}
fn print_region(self, _region: ty::Region<'_>) -> Result<Self, PrintError> {
Ok(self)
fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
Ok(())
}
fn print_type(mut self, ty: Ty<'tcx>) -> Result<Self, PrintError> {
fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> {
match *ty.kind() {
// Types without identity.
ty::Bool
@ -43,7 +43,7 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
// Placeholders (all printed as `_` to uniformize them).
ty::Param(_) | ty::Bound(..) | ty::Placeholder(_) | ty::Infer(_) | ty::Error(_) => {
write!(self, "_")?;
Ok(self)
Ok(())
}
// Types with identity (print the module path).
@ -60,44 +60,44 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
}
}
fn print_const(self, ct: ty::Const<'tcx>) -> Result<Self, PrintError> {
fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError> {
self.pretty_print_const(ct, false)
}
fn print_dyn_existential(
self,
&mut self,
predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.pretty_print_dyn_existential(predicates)
}
fn path_crate(mut self, cnum: CrateNum) -> Result<Self, PrintError> {
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.path.push_str(self.tcx.crate_name(cnum).as_str());
Ok(self)
Ok(())
}
fn path_qualified(
self,
&mut self,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.pretty_path_qualified(self_ty, trait_ref)
}
fn path_append_impl(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_disambiguated_data: &DisambiguatedDefPathData,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.pretty_path_append_impl(
|mut cx| {
cx = print_prefix(cx)?;
|cx| {
print_prefix(cx)?;
cx.path.push_str("::");
Ok(cx)
Ok(())
},
self_ty,
trait_ref,
@ -105,29 +105,29 @@ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
}
fn path_append(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError> {
self = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
write!(self.path, "::{}", disambiguated_data.data).unwrap();
Ok(self)
Ok(())
}
fn path_generic_args(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError> {
self = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
let args =
args.iter().cloned().filter(|arg| !matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
if args.clone().next().is_some() {
self.generic_delimiters(|cx| cx.comma_sep(args))
} else {
Ok(self)
Ok(())
}
}
}
@ -136,31 +136,31 @@ impl<'tcx> PrettyPrinter<'tcx> for AbsolutePathPrinter<'tcx> {
fn should_print_region(&self, _region: ty::Region<'_>) -> bool {
false
}
fn comma_sep<T>(mut self, mut elems: impl Iterator<Item = T>) -> Result<Self, PrintError>
fn comma_sep<T>(&mut self, mut elems: impl Iterator<Item = T>) -> Result<(), PrintError>
where
T: Print<'tcx, Self>,
{
if let Some(first) = elems.next() {
self = first.print(self)?;
first.print(self)?;
for elem in elems {
self.path.push_str(", ");
self = elem.print(self)?;
elem.print(self)?;
}
}
Ok(self)
Ok(())
}
fn generic_delimiters(
mut self,
f: impl FnOnce(Self) -> Result<Self, PrintError>,
) -> Result<Self, PrintError> {
&mut self,
f: impl FnOnce(&mut Self) -> Result<(), PrintError>,
) -> Result<(), PrintError> {
write!(self, "<")?;
self = f(self)?;
f(self)?;
write!(self, ">")?;
Ok(self)
Ok(())
}
fn should_print_verbose(&self) -> bool {
@ -177,5 +177,7 @@ impl Write for AbsolutePathPrinter<'_> {
}
pub fn type_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> String {
AbsolutePathPrinter { tcx, path: String::new() }.print_type(ty).unwrap().path
let mut printer = AbsolutePathPrinter { tcx, path: String::new() };
printer.print_type(ty).unwrap();
printer.path
}

View file

@ -54,7 +54,7 @@ pub use worker_local::{Registry, WorkerLocal};
mod parallel;
#[cfg(parallel_compiler)]
pub use parallel::scope;
pub use parallel::{join, par_for_each_in, par_map, parallel_guard};
pub use parallel::{join, par_for_each_in, par_map, parallel_guard, try_par_for_each_in};
pub use std::sync::atomic::Ordering;
pub use std::sync::atomic::Ordering::SeqCst;

View file

@ -77,6 +77,15 @@ mod disabled {
})
}
pub fn try_par_for_each_in<T: IntoIterator, E: Copy>(
t: T,
mut for_each: impl FnMut(T::Item) -> Result<(), E>,
) -> Result<(), E> {
parallel_guard(|guard| {
t.into_iter().fold(Ok(()), |ret, i| guard.run(|| for_each(i)).unwrap_or(ret).and(ret))
})
}
pub fn par_map<T: IntoIterator, R, C: FromIterator<R>>(
t: T,
mut map: impl FnMut(<<T as IntoIterator>::IntoIter as Iterator>::Item) -> R,
@ -167,6 +176,26 @@ mod enabled {
});
}
pub fn try_par_for_each_in<
T: IntoIterator + IntoParallelIterator<Item = <T as IntoIterator>::Item>,
E: Copy + Send,
>(
t: T,
for_each: impl Fn(<T as IntoIterator>::Item) -> Result<(), E> + DynSync + DynSend,
) -> Result<(), E> {
parallel_guard(|guard| {
if mode::is_dyn_thread_safe() {
let for_each = FromDyn::from(for_each);
t.into_par_iter()
.fold_with(Ok(()), |ret, i| guard.run(|| for_each(i)).unwrap_or(ret).and(ret))
.reduce(|| Ok(()), |a, b| a.and(b))
} else {
t.into_iter()
.fold(Ok(()), |ret, i| guard.run(|| for_each(i)).unwrap_or(ret).and(ret))
}
})
}
pub fn par_map<
I,
T: IntoIterator<Item = I> + IntoParallelIterator<Item = I>,

View file

@ -99,7 +99,7 @@ declare_features! (
/// Allows generators to be cloned.
(removed, generator_clone, "1.65.0", Some(95360), None, Some("renamed to `coroutine_clone`")),
/// Allows defining generators.
(removed, generators, "1.21.0", Some(43122), None, Some("renamed to `coroutine`")),
(removed, generators, "1.21.0", Some(43122), None, Some("renamed to `coroutines`")),
/// Allows `impl Trait` in bindings (`let`, `const`, `static`).
(removed, impl_trait_in_bindings, "1.55.0", Some(63065), None,
Some("the implementation was not maintainable, the feature may get reintroduced once the current refactorings are done")),

View file

@ -92,7 +92,8 @@ pub(super) fn enter_wf_checking_ctxt<'tcx, F>(
span: Span,
body_def_id: LocalDefId,
f: F,
) where
) -> Result<(), ErrorGuaranteed>
where
F: for<'a> FnOnce(&WfCheckingCtxt<'a, 'tcx>),
{
let param_env = tcx.param_env(body_def_id);
@ -106,40 +107,46 @@ pub(super) fn enter_wf_checking_ctxt<'tcx, F>(
}
f(&mut wfcx);
let assumed_wf_types = match wfcx.ocx.assumed_wf_types_and_report_errors(param_env, body_def_id)
{
Ok(wf_types) => wf_types,
Err(_guar) => return,
};
let assumed_wf_types = wfcx.ocx.assumed_wf_types_and_report_errors(param_env, body_def_id)?;
let implied_bounds = infcx.implied_bounds_tys(param_env, body_def_id, assumed_wf_types);
let errors = wfcx.select_all_or_error();
if !errors.is_empty() {
infcx.err_ctxt().report_fulfillment_errors(errors);
return;
let err = infcx.err_ctxt().report_fulfillment_errors(errors);
if tcx.sess.err_count() > 0 {
return Err(err);
} else {
// HACK(oli-obk): tests/ui/specialization/min_specialization/specialize_on_type_error.rs causes an
// error (delay_span_bug) during normalization, without reporting an error, so we need to act as if
// no error happened, in order to let our callers continue and report an error later in
// check_impl_items_against_trait.
return Ok(());
}
}
let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds);
let _ = wfcx.ocx.resolve_regions_and_report_errors(body_def_id, &outlives_env);
wfcx.ocx.resolve_regions_and_report_errors(body_def_id, &outlives_env)?;
infcx.tainted_by_errors().error_reported()
}
fn check_well_formed(tcx: TyCtxt<'_>, def_id: hir::OwnerId) {
fn check_well_formed(tcx: TyCtxt<'_>, def_id: hir::OwnerId) -> Result<(), ErrorGuaranteed> {
let node = tcx.hir().owner(def_id);
match node {
hir::OwnerNode::Crate(_) => {}
let mut res = match node {
hir::OwnerNode::Crate(_) => bug!("check_well_formed cannot be applied to the crate root"),
hir::OwnerNode::Item(item) => check_item(tcx, item),
hir::OwnerNode::TraitItem(item) => check_trait_item(tcx, item),
hir::OwnerNode::ImplItem(item) => check_impl_item(tcx, item),
hir::OwnerNode::ForeignItem(item) => check_foreign_item(tcx, item),
}
};
if let Some(generics) = node.generics() {
for param in generics.params {
check_param_wf(tcx, param)
res = res.and(check_param_wf(tcx, param));
}
}
res
}
/// Checks that the field types (in a struct def'n) or argument types (in an enum def'n) are
@ -156,7 +163,7 @@ fn check_well_formed(tcx: TyCtxt<'_>, def_id: hir::OwnerId) {
/// not included it frequently leads to confusing errors in fn bodies. So it's better to check
/// the types first.
#[instrument(skip(tcx), level = "debug")]
fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) -> Result<(), ErrorGuaranteed> {
let def_id = item.owner_id.def_id;
debug!(
@ -186,31 +193,32 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
let is_auto = tcx
.impl_trait_ref(def_id)
.is_some_and(|trait_ref| tcx.trait_is_auto(trait_ref.skip_binder().def_id));
let mut res = Ok(());
if let (hir::Defaultness::Default { .. }, true) = (impl_.defaultness, is_auto) {
let sp = impl_.of_trait.as_ref().map_or(item.span, |t| t.path.span);
let mut err =
tcx.sess.struct_span_err(sp, "impls of auto traits cannot be default");
err.span_labels(impl_.defaultness_span, "default because of this");
err.span_label(sp, "auto trait");
err.emit();
res = Err(err.emit());
}
// We match on both `ty::ImplPolarity` and `ast::ImplPolarity` just to get the `!` span.
match (tcx.impl_polarity(def_id), impl_.polarity) {
(ty::ImplPolarity::Positive, _) => {
check_impl(tcx, item, impl_.self_ty, &impl_.of_trait);
res = res.and(check_impl(tcx, item, impl_.self_ty, &impl_.of_trait));
}
(ty::ImplPolarity::Negative, ast::ImplPolarity::Negative(span)) => {
// FIXME(#27579): what amount of WF checking do we need for neg impls?
if let hir::Defaultness::Default { .. } = impl_.defaultness {
let mut spans = vec![span];
spans.extend(impl_.defaultness_span);
struct_span_err!(
res = Err(struct_span_err!(
tcx.sess,
spans,
E0750,
"negative impls cannot be default impls"
)
.emit();
.emit());
}
}
(ty::ImplPolarity::Reservation, _) => {
@ -218,49 +226,52 @@ fn check_item<'tcx>(tcx: TyCtxt<'tcx>, item: &'tcx hir::Item<'tcx>) {
}
_ => unreachable!(),
}
res
}
hir::ItemKind::Fn(ref sig, ..) => {
check_item_fn(tcx, def_id, item.ident, item.span, sig.decl);
check_item_fn(tcx, def_id, item.ident, item.span, sig.decl)
}
hir::ItemKind::Static(ty, ..) => {
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid);
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid)
}
hir::ItemKind::Const(ty, ..) => {
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid);
check_item_type(tcx, def_id, ty.span, UnsizedHandling::Forbid)
}
hir::ItemKind::Struct(_, ast_generics) => {
check_type_defn(tcx, item, false);
let res = check_type_defn(tcx, item, false);
check_variances_for_type_defn(tcx, item, ast_generics);
res
}
hir::ItemKind::Union(_, ast_generics) => {
check_type_defn(tcx, item, true);
let res = check_type_defn(tcx, item, true);
check_variances_for_type_defn(tcx, item, ast_generics);
res
}
hir::ItemKind::Enum(_, ast_generics) => {
check_type_defn(tcx, item, true);
let res = check_type_defn(tcx, item, true);
check_variances_for_type_defn(tcx, item, ast_generics);
res
}
hir::ItemKind::Trait(..) => {
check_trait(tcx, item);
}
hir::ItemKind::TraitAlias(..) => {
check_trait(tcx, item);
}
hir::ItemKind::Trait(..) => check_trait(tcx, item),
hir::ItemKind::TraitAlias(..) => check_trait(tcx, item),
// `ForeignItem`s are handled separately.
hir::ItemKind::ForeignMod { .. } => {}
hir::ItemKind::ForeignMod { .. } => Ok(()),
hir::ItemKind::TyAlias(hir_ty, ast_generics) => {
if tcx.type_alias_is_lazy(item.owner_id) {
// Bounds of lazy type aliases and of eager ones that contain opaque types are respected.
// E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`.
check_item_type(tcx, def_id, hir_ty.span, UnsizedHandling::Allow);
let res = check_item_type(tcx, def_id, hir_ty.span, UnsizedHandling::Allow);
check_variances_for_type_defn(tcx, item, ast_generics);
res
} else {
Ok(())
}
}
_ => {}
_ => Ok(()),
}
}
fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) {
fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) -> Result<(), ErrorGuaranteed> {
let def_id = item.owner_id.def_id;
debug!(
@ -275,11 +286,14 @@ fn check_foreign_item(tcx: TyCtxt<'_>, item: &hir::ForeignItem<'_>) {
hir::ForeignItemKind::Static(ty, ..) => {
check_item_type(tcx, def_id, ty.span, UnsizedHandling::AllowIfForeignTail)
}
hir::ForeignItemKind::Type => (),
hir::ForeignItemKind::Type => Ok(()),
}
}
fn check_trait_item(tcx: TyCtxt<'_>, trait_item: &hir::TraitItem<'_>) {
fn check_trait_item(
tcx: TyCtxt<'_>,
trait_item: &hir::TraitItem<'_>,
) -> Result<(), ErrorGuaranteed> {
let def_id = trait_item.owner_id.def_id;
let (method_sig, span) = match trait_item.kind {
@ -288,18 +302,19 @@ fn check_trait_item(tcx: TyCtxt<'_>, trait_item: &hir::TraitItem<'_>) {
_ => (None, trait_item.span),
};
check_object_unsafe_self_trait_by_name(tcx, trait_item);
check_associated_item(tcx, def_id, span, method_sig);
let mut res = check_associated_item(tcx, def_id, span, method_sig);
if matches!(trait_item.kind, hir::TraitItemKind::Fn(..)) {
for &assoc_ty_def_id in tcx.associated_types_for_impl_traits_in_associated_fn(def_id) {
check_associated_item(
res = res.and(check_associated_item(
tcx,
assoc_ty_def_id.expect_local(),
tcx.def_span(assoc_ty_def_id),
None,
);
));
}
}
res
}
/// Require that the user writes where clauses on GATs for the implicit
@ -826,7 +841,7 @@ fn check_object_unsafe_self_trait_by_name(tcx: TyCtxt<'_>, item: &hir::TraitItem
}
}
fn check_impl_item(tcx: TyCtxt<'_>, impl_item: &hir::ImplItem<'_>) {
fn check_impl_item(tcx: TyCtxt<'_>, impl_item: &hir::ImplItem<'_>) -> Result<(), ErrorGuaranteed> {
let (method_sig, span) = match impl_item.kind {
hir::ImplItemKind::Fn(ref sig, _) => (Some(sig), impl_item.span),
// Constrain binding and overflow error spans to `<Ty>` in `type foo = <Ty>`.
@ -834,13 +849,13 @@ fn check_impl_item(tcx: TyCtxt<'_>, impl_item: &hir::ImplItem<'_>) {
_ => (None, impl_item.span),
};
check_associated_item(tcx, impl_item.owner_id.def_id, span, method_sig);
check_associated_item(tcx, impl_item.owner_id.def_id, span, method_sig)
}
fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) {
fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) -> Result<(), ErrorGuaranteed> {
match param.kind {
// We currently only check wf of const params here.
hir::GenericParamKind::Lifetime { .. } | hir::GenericParamKind::Type { .. } => (),
hir::GenericParamKind::Lifetime { .. } | hir::GenericParamKind::Type { .. } => Ok(()),
// Const parameters are well formed if their type is structural match.
hir::GenericParamKind::Const { ty: hir_ty, default: _ } => {
@ -860,68 +875,66 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) {
ty,
trait_def_id,
);
});
})
} else {
let diag = match ty.kind() {
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Error(_) => None,
ty::FnPtr(_) => Some(tcx.sess.struct_span_err(
let mut diag = match ty.kind() {
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Error(_) => return Ok(()),
ty::FnPtr(_) => tcx.sess.struct_span_err(
hir_ty.span,
"using function pointers as const generic parameters is forbidden",
)),
ty::RawPtr(_) => Some(tcx.sess.struct_span_err(
),
ty::RawPtr(_) => tcx.sess.struct_span_err(
hir_ty.span,
"using raw pointers as const generic parameters is forbidden",
)),
_ => Some(tcx.sess.struct_span_err(
),
_ => tcx.sess.struct_span_err(
hir_ty.span,
format!("`{}` is forbidden as the type of a const generic parameter", ty),
)),
),
};
if let Some(mut diag) = diag {
diag.note("the only supported types are integers, `bool` and `char`");
diag.note("the only supported types are integers, `bool` and `char`");
let cause = ObligationCause::misc(hir_ty.span, param.def_id);
let may_suggest_feature = match type_allowed_to_implement_const_param_ty(
tcx,
tcx.param_env(param.def_id),
ty,
cause,
) {
// Can never implement `ConstParamTy`, don't suggest anything.
Err(ConstParamTyImplementationError::NotAnAdtOrBuiltinAllowed) => false,
// May be able to implement `ConstParamTy`. Only emit the feature help
// if the type is local, since the user may be able to fix the local type.
Err(ConstParamTyImplementationError::InfrigingFields(..)) => {
fn ty_is_local(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Adt(adt_def, ..) => adt_def.did().is_local(),
// Arrays and slices use the inner type's `ConstParamTy`.
ty::Array(ty, ..) => ty_is_local(*ty),
ty::Slice(ty) => ty_is_local(*ty),
// `&` references use the inner type's `ConstParamTy`.
// `&mut` are not supported.
ty::Ref(_, ty, ast::Mutability::Not) => ty_is_local(*ty),
// Say that a tuple is local if any of its components are local.
// This is not strictly correct, but it's likely that the user can fix the local component.
ty::Tuple(tys) => tys.iter().any(|ty| ty_is_local(ty)),
_ => false,
}
let cause = ObligationCause::misc(hir_ty.span, param.def_id);
let may_suggest_feature = match type_allowed_to_implement_const_param_ty(
tcx,
tcx.param_env(param.def_id),
ty,
cause,
) {
// Can never implement `ConstParamTy`, don't suggest anything.
Err(ConstParamTyImplementationError::NotAnAdtOrBuiltinAllowed) => false,
// May be able to implement `ConstParamTy`. Only emit the feature help
// if the type is local, since the user may be able to fix the local type.
Err(ConstParamTyImplementationError::InfrigingFields(..)) => {
fn ty_is_local(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Adt(adt_def, ..) => adt_def.did().is_local(),
// Arrays and slices use the inner type's `ConstParamTy`.
ty::Array(ty, ..) => ty_is_local(*ty),
ty::Slice(ty) => ty_is_local(*ty),
// `&` references use the inner type's `ConstParamTy`.
// `&mut` are not supported.
ty::Ref(_, ty, ast::Mutability::Not) => ty_is_local(*ty),
// Say that a tuple is local if any of its components are local.
// This is not strictly correct, but it's likely that the user can fix the local component.
ty::Tuple(tys) => tys.iter().any(|ty| ty_is_local(ty)),
_ => false,
}
ty_is_local(ty)
}
// Implments `ConstParamTy`, suggest adding the feature to enable.
Ok(..) => true,
};
if may_suggest_feature && tcx.sess.is_nightly_build() {
diag.help(
ty_is_local(ty)
}
// Implments `ConstParamTy`, suggest adding the feature to enable.
Ok(..) => true,
};
if may_suggest_feature && tcx.sess.is_nightly_build() {
diag.help(
"add `#![feature(adt_const_params)]` to the crate attributes to enable more complex and user defined types",
);
}
diag.emit();
}
Err(diag.emit())
}
}
}
@ -933,7 +946,7 @@ fn check_associated_item(
item_id: LocalDefId,
span: Span,
sig_if_method: Option<&hir::FnSig<'_>>,
) {
) -> Result<(), ErrorGuaranteed> {
let loc = Some(WellFormedLoc::Ty(item_id));
enter_wf_checking_ctxt(tcx, span, item_id, |wfcx| {
let item = tcx.associated_item(item_id);
@ -985,7 +998,11 @@ fn item_adt_kind(kind: &ItemKind<'_>) -> Option<AdtKind> {
}
/// In a type definition, we check that to ensure that the types of the fields are well-formed.
fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: bool) {
fn check_type_defn<'tcx>(
tcx: TyCtxt<'tcx>,
item: &hir::Item<'tcx>,
all_sized: bool,
) -> Result<(), ErrorGuaranteed> {
let _ = tcx.representability(item.owner_id.def_id);
let adt_def = tcx.adt_def(item.owner_id);
@ -1080,11 +1097,11 @@ fn check_type_defn<'tcx>(tcx: TyCtxt<'tcx>, item: &hir::Item<'tcx>, all_sized: b
}
check_where_clauses(wfcx, item.span, item.owner_id.def_id);
});
})
}
#[instrument(skip(tcx, item))]
fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) -> Result<(), ErrorGuaranteed> {
debug!(?item.owner_id);
let def_id = item.owner_id.def_id;
@ -1103,7 +1120,7 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
}
}
enter_wf_checking_ctxt(tcx, item.span, def_id, |wfcx| {
let res = enter_wf_checking_ctxt(tcx, item.span, def_id, |wfcx| {
check_where_clauses(wfcx, item.span, def_id)
});
@ -1111,6 +1128,7 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) {
if let hir::ItemKind::Trait(..) = item.kind {
check_gat_where_clauses(tcx, item.owner_id.def_id);
}
res
}
/// Checks all associated type defaults of trait `trait_def_id`.
@ -1142,7 +1160,7 @@ fn check_item_fn(
ident: Ident,
span: Span,
decl: &hir::FnDecl<'_>,
) {
) -> Result<(), ErrorGuaranteed> {
enter_wf_checking_ctxt(tcx, span, def_id, |wfcx| {
let sig = tcx.fn_sig(def_id).instantiate_identity();
check_fn_or_method(wfcx, ident.span, sig, decl, def_id);
@ -1160,7 +1178,7 @@ fn check_item_type(
item_id: LocalDefId,
ty_span: Span,
unsized_handling: UnsizedHandling,
) {
) -> Result<(), ErrorGuaranteed> {
debug!("check_item_type: {:?}", item_id);
enter_wf_checking_ctxt(tcx, ty_span, item_id, |wfcx| {
@ -1200,7 +1218,7 @@ fn check_item_type(
tcx.require_lang_item(LangItem::Sync, Some(ty_span)),
);
}
});
})
}
#[instrument(level = "debug", skip(tcx, ast_self_ty, ast_trait_ref))]
@ -1209,7 +1227,7 @@ fn check_impl<'tcx>(
item: &'tcx hir::Item<'tcx>,
ast_self_ty: &hir::Ty<'_>,
ast_trait_ref: &Option<hir::TraitRef<'_>>,
) {
) -> Result<(), ErrorGuaranteed> {
enter_wf_checking_ctxt(tcx, item.span, item.owner_id.def_id, |wfcx| {
match ast_trait_ref {
Some(ast_trait_ref) => {
@ -1258,7 +1276,7 @@ fn check_impl<'tcx>(
}
check_where_clauses(wfcx, item.span, item.owner_id.def_id);
});
})
}
/// Checks where-clauses and inline bounds that are declared on `def_id`.
@ -1879,12 +1897,12 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> {
}
}
fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalModDefId) {
fn check_mod_type_wf(tcx: TyCtxt<'_>, module: LocalModDefId) -> Result<(), ErrorGuaranteed> {
let items = tcx.hir_module_items(module);
items.par_items(|item| tcx.ensure().check_well_formed(item.owner_id));
items.par_impl_items(|item| tcx.ensure().check_well_formed(item.owner_id));
items.par_trait_items(|item| tcx.ensure().check_well_formed(item.owner_id));
items.par_foreign_items(|item| tcx.ensure().check_well_formed(item.owner_id));
let mut res = items.par_items(|item| tcx.ensure().check_well_formed(item.owner_id));
res = res.and(items.par_impl_items(|item| tcx.ensure().check_well_formed(item.owner_id)));
res = res.and(items.par_trait_items(|item| tcx.ensure().check_well_formed(item.owner_id)));
res.and(items.par_foreign_items(|item| tcx.ensure().check_well_formed(item.owner_id)))
}
fn error_392(

View file

@ -205,10 +205,8 @@ pub fn check_crate(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed> {
})?;
}
tcx.sess.track_errors(|| {
tcx.sess.time("wf_checking", || {
tcx.hir().par_for_each_module(|module| tcx.ensure().check_mod_type_wf(module))
});
tcx.sess.time("wf_checking", || {
tcx.hir().try_par_for_each_module(|module| tcx.ensure().check_mod_type_wf(module))
})?;
// NOTE: This is copy/pasted in librustdoc/core.rs and should be kept in sync.

View file

@ -4,9 +4,7 @@ mod arg_matrix;
mod checks;
mod suggestions;
pub use _impl::*;
use rustc_errors::ErrorGuaranteed;
pub use suggestions::*;
use crate::coercion::DynamicCoerceMany;
use crate::{Diverges, EnclosingBreakables, Inherited};

View file

@ -12,6 +12,7 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator;
use rustc_hir::{HirId, Pat, PatKind};
use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitableExt};
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_span::edit_distance::find_best_match_for_name;
@ -2164,7 +2165,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
len: ty::Const<'tcx>,
min_len: u64,
) -> (Option<Ty<'tcx>>, Ty<'tcx>) {
let guar = if let Some(len) = len.try_eval_target_usize(self.tcx, self.param_env) {
let len = match len.eval(self.tcx, self.param_env, None) {
Ok(val) => val
.try_to_scalar()
.and_then(|scalar| scalar.try_to_int().ok())
.and_then(|int| int.try_to_target_usize(self.tcx).ok()),
Err(ErrorHandled::Reported(..)) => {
let guar = self.error_scrutinee_unfixed_length(span);
return (Some(Ty::new_error(self.tcx, guar)), arr_ty);
}
Err(ErrorHandled::TooGeneric(..)) => None,
};
let guar = if let Some(len) = len {
// Now we know the length...
if slice.is_none() {
// ...and since there is no variable-length pattern,

View file

@ -588,60 +588,60 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
self.tcx
}
fn print_region(self, _region: ty::Region<'_>) -> Result<Self, PrintError> {
fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn print_type(self, _ty: Ty<'tcx>) -> Result<Self, PrintError> {
fn print_type(&mut self, _ty: Ty<'tcx>) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn print_dyn_existential(
self,
&mut self,
_predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn print_const(self, _ct: ty::Const<'tcx>) -> Result<Self, PrintError> {
fn print_const(&mut self, _ct: ty::Const<'tcx>) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn path_crate(mut self, cnum: CrateNum) -> Result<Self, PrintError> {
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.segments = vec![self.tcx.crate_name(cnum).to_string()];
Ok(self)
Ok(())
}
fn path_qualified(
self,
&mut self,
_self_ty: Ty<'tcx>,
_trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn path_append_impl(
self,
_print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
_print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_disambiguated_data: &DisambiguatedDefPathData,
_self_ty: Ty<'tcx>,
_trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
Err(fmt::Error)
}
fn path_append(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError> {
self = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
self.segments.push(disambiguated_data.to_string());
Ok(self)
Ok(())
}
fn path_generic_args(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
print_prefix(self)
}
}
@ -652,9 +652,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
// let _ = [{struct Foo; Foo}, {struct Foo; Foo}];
if did1.krate != did2.krate {
let abs_path = |def_id| {
AbsolutePathPrinter { tcx: self.tcx, segments: vec![] }
.print_def_path(def_id, &[])
.map(|p| p.segments)
let mut printer = AbsolutePathPrinter { tcx: self.tcx, segments: vec![] };
printer.print_def_path(def_id, &[]).map(|_| printer.segments)
};
// We compare strings because DefPath can be different
@ -1071,7 +1070,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
let get_lifetimes = |sig| {
use rustc_hir::def::Namespace;
let (_, sig, reg) = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS)
let (sig, reg) = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS)
.name_all_regions(sig)
.unwrap();
let lts: Vec<String> = reg.into_values().map(|kind| kind.to_string()).collect();

View file

@ -200,12 +200,15 @@ fn ty_to_string<'tcx>(
ty: Ty<'tcx>,
called_method_def_id: Option<DefId>,
) -> String {
let printer = fmt_printer(infcx, Namespace::TypeNS);
let mut printer = fmt_printer(infcx, Namespace::TypeNS);
let ty = infcx.resolve_vars_if_possible(ty);
match (ty.kind(), called_method_def_id) {
// We don't want the regular output for `fn`s because it includes its path in
// invalid pseudo-syntax, we want the `fn`-pointer output instead.
(ty::FnDef(..), _) => ty.fn_sig(infcx.tcx).print(printer).unwrap().into_buffer(),
(ty::FnDef(..), _) => {
ty.fn_sig(infcx.tcx).print(&mut printer).unwrap();
printer.into_buffer()
}
(_, Some(def_id))
if ty.is_ty_or_numeric_infer()
&& infcx.tcx.get_diagnostic_item(sym::iterator_collect_fn) == Some(def_id) =>
@ -218,7 +221,10 @@ fn ty_to_string<'tcx>(
//
// We do have to hide the `extern "rust-call"` ABI in that case though,
// which is too much of a bother for now.
_ => ty.print(printer).unwrap().into_buffer(),
_ => {
ty.print(&mut printer).unwrap();
printer.into_buffer()
}
}
}
@ -285,8 +291,9 @@ impl<'tcx> InferCtxt<'tcx> {
if let Some(highlight) = highlight {
printer.region_highlight_mode = highlight;
}
ty.print(&mut printer).unwrap();
InferenceDiagnosticsData {
name: ty.print(printer).unwrap().into_buffer(),
name: printer.into_buffer(),
span: None,
kind: UnderspecifiedArgKind::Type { prefix: ty.prefix_string(self.tcx) },
parent: None,
@ -312,8 +319,9 @@ impl<'tcx> InferCtxt<'tcx> {
if let Some(highlight) = highlight {
printer.region_highlight_mode = highlight;
}
ct.print(&mut printer).unwrap();
InferenceDiagnosticsData {
name: ct.print(printer).unwrap().into_buffer(),
name: printer.into_buffer(),
span: Some(origin.span),
kind: UnderspecifiedArgKind::Const { is_parameter: false },
parent: None,
@ -329,8 +337,9 @@ impl<'tcx> InferCtxt<'tcx> {
if let Some(highlight) = highlight {
printer.region_highlight_mode = highlight;
}
ct.print(&mut printer).unwrap();
InferenceDiagnosticsData {
name: ct.print(printer).unwrap().into_buffer(),
name: printer.into_buffer(),
span: None,
kind: UnderspecifiedArgKind::Const { is_parameter: false },
parent: None,
@ -487,7 +496,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
{
"Vec<_>".to_string()
} else {
fmt_printer(self, Namespace::TypeNS)
let mut printer = fmt_printer(self, Namespace::TypeNS);
printer
.comma_sep(generic_args.iter().copied().map(|arg| {
if arg.is_suggestable(self.tcx, true) {
return arg;
@ -512,8 +522,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
.into(),
}
}))
.unwrap()
.into_buffer()
.unwrap();
printer.into_buffer()
};
if !have_turbofish {
@ -525,8 +535,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
}
}
InferSourceKind::FullyQualifiedMethodCall { receiver, successor, args, def_id } => {
let printer = fmt_printer(self, Namespace::ValueNS);
let def_path = printer.print_def_path(def_id, args).unwrap().into_buffer();
let mut printer = fmt_printer(self, Namespace::ValueNS);
printer.print_def_path(def_id, args).unwrap();
let def_path = printer.into_buffer();
// We only care about whether we have to add `&` or `&mut ` for now.
// This is the case if the last adjustment is a borrow and the

View file

@ -49,8 +49,8 @@ where
let mut printer = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS);
printer.region_highlight_mode = self.highlight;
let s = self.value.print(printer)?.into_buffer();
f.write_str(&s)
self.value.print(&mut printer)?;
f.write_str(&printer.into_buffer())
}
}

View file

@ -763,9 +763,9 @@ fn foo(&self) -> Self::T { String::new() }
}
pub fn format_generic_args(&self, args: &[ty::GenericArg<'tcx>]) -> String {
FmtPrinter::new(self.tcx, hir::def::Namespace::TypeNS)
.path_generic_args(Ok, args)
.expect("could not write to `String`.")
.into_buffer()
FmtPrinter::print_string(self.tcx, hir::def::Namespace::TypeNS, |cx| {
cx.path_generic_args(|_| Ok(()), args)
})
.expect("could not write to `String`.")
}
}

View file

@ -19,7 +19,6 @@ use rustc_span::Span;
pub use self::FulfillmentErrorCode::*;
pub use self::ImplSource::*;
pub use self::ObligationCauseCode::*;
pub use self::SelectionError::*;
pub use self::engine::{TraitEngine, TraitEngineExt};

View file

@ -1210,35 +1210,35 @@ impl<'tcx> LateContext<'tcx> {
self.tcx
}
fn print_region(self, _region: ty::Region<'_>) -> Result<Self, PrintError> {
Ok(self)
fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
Ok(())
}
fn print_type(self, _ty: Ty<'tcx>) -> Result<Self, PrintError> {
Ok(self)
fn print_type(&mut self, _ty: Ty<'tcx>) -> Result<(), PrintError> {
Ok(())
}
fn print_dyn_existential(
self,
&mut self,
_predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError> {
Ok(self)
) -> Result<(), PrintError> {
Ok(())
}
fn print_const(self, _ct: ty::Const<'tcx>) -> Result<Self, PrintError> {
Ok(self)
fn print_const(&mut self, _ct: ty::Const<'tcx>) -> Result<(), PrintError> {
Ok(())
}
fn path_crate(mut self, cnum: CrateNum) -> Result<Self, PrintError> {
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.path = vec![self.tcx.crate_name(cnum)];
Ok(self)
Ok(())
}
fn path_qualified(
mut self,
&mut self,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
if trait_ref.is_none() {
if let ty::Adt(def, args) = self_ty.kind() {
return self.print_def_path(def.did(), args);
@ -1251,21 +1251,21 @@ impl<'tcx> LateContext<'tcx> {
Some(trait_ref) => Symbol::intern(&format!("{trait_ref:?}")),
None => Symbol::intern(&format!("<{self_ty}>")),
}];
Ok(self)
Ok(())
})
}
fn path_append_impl(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_disambiguated_data: &DisambiguatedDefPathData,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
let mut path = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
// This shouldn't ever be needed, but just in case:
path.path.push(match trait_ref {
self.path.push(match trait_ref {
Some(trait_ref) => {
with_no_trimmed_paths!(Symbol::intern(&format!(
"<impl {} for {}>",
@ -1278,38 +1278,37 @@ impl<'tcx> LateContext<'tcx> {
}
});
Ok(path)
Ok(())
}
fn path_append(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError> {
let mut path = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
// Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs.
if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data {
return Ok(path);
return Ok(());
}
path.path.push(Symbol::intern(&disambiguated_data.data.to_string()));
Ok(path)
self.path.push(Symbol::intern(&disambiguated_data.data.to_string()));
Ok(())
}
fn path_generic_args(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
print_prefix(self)
}
}
AbsolutePathPrinter { tcx: self.tcx, path: vec![] }
.print_def_path(def_id, &[])
.unwrap()
.path
let mut printer = AbsolutePathPrinter { tcx: self.tcx, path: vec![] };
printer.print_def_path(def_id, &[]).unwrap();
printer.path
}
/// Returns the associated type `name` for `self_ty` as an implementation of `trait_id`.

View file

@ -114,6 +114,11 @@ struct QueryModifiers {
/// Generate a `feed` method to set the query's value from another query.
feedable: Option<Ident>,
/// Forward the result on ensure if the query gets recomputed, and
/// return `Ok(())` otherwise. Only applicable to queries returning
/// `Result<(), ErrorGuaranteed>`
ensure_forwards_result_if_red: Option<Ident>,
}
fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
@ -128,6 +133,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
let mut depth_limit = None;
let mut separate_provide_extern = None;
let mut feedable = None;
let mut ensure_forwards_result_if_red = None;
while !input.is_empty() {
let modifier: Ident = input.parse()?;
@ -187,6 +193,8 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
try_insert!(separate_provide_extern = modifier);
} else if modifier == "feedable" {
try_insert!(feedable = modifier);
} else if modifier == "ensure_forwards_result_if_red" {
try_insert!(ensure_forwards_result_if_red = modifier);
} else {
return Err(Error::new(modifier.span(), "unknown query modifier"));
}
@ -206,6 +214,7 @@ fn parse_query_modifiers(input: ParseStream<'_>) -> Result<QueryModifiers> {
depth_limit,
separate_provide_extern,
feedable,
ensure_forwards_result_if_red,
})
}
@ -325,6 +334,7 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream {
eval_always,
depth_limit,
separate_provide_extern,
ensure_forwards_result_if_red,
);
if modifiers.cache.is_some() {

View file

@ -6,7 +6,7 @@ use rustc_ast as ast;
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::svh::Svh;
use rustc_data_structures::sync::{par_for_each_in, DynSend, DynSync};
use rustc_data_structures::sync::{par_for_each_in, try_par_for_each_in, DynSend, DynSync};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
use rustc_hir::definitions::{DefKey, DefPath, DefPathData, DefPathHash};
@ -16,7 +16,7 @@ use rustc_index::Idx;
use rustc_middle::hir::nested_filter;
use rustc_span::def_id::StableCrateId;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::Span;
use rustc_span::{ErrorGuaranteed, Span};
use rustc_target::spec::abi::Abi;
#[inline]
@ -632,6 +632,17 @@ impl<'hir> Map<'hir> {
})
}
#[inline]
pub fn try_par_for_each_module(
self,
f: impl Fn(LocalModDefId) -> Result<(), ErrorGuaranteed> + DynSend + DynSync,
) -> Result<(), ErrorGuaranteed> {
let crate_items = self.tcx.hir_crate_items(());
try_par_for_each_in(&crate_items.submodules[..], |module| {
f(LocalModDefId::new_unchecked(module.def_id))
})
}
/// Returns an iterator for the nodes in the ancestor tree of the `current_id`
/// until the crate root is reached. Prefer this over your own loop using `parent_id`.
#[inline]

View file

@ -9,12 +9,12 @@ pub mod place;
use crate::query::Providers;
use crate::ty::{EarlyBinder, ImplSubject, TyCtxt};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::sync::{par_for_each_in, DynSend, DynSync};
use rustc_data_structures::sync::{try_par_for_each_in, DynSend, DynSync};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
use rustc_hir::*;
use rustc_query_system::ich::StableHashingContext;
use rustc_span::{ExpnId, DUMMY_SP};
use rustc_span::{ErrorGuaranteed, ExpnId, DUMMY_SP};
/// Top-level HIR node for current owner. This only contains the node for which
/// `HirId::local_id == 0`, and excludes bodies.
@ -78,20 +78,32 @@ impl ModuleItems {
self.owners().map(|id| id.def_id)
}
pub fn par_items(&self, f: impl Fn(ItemId) + DynSend + DynSync) {
par_for_each_in(&self.items[..], |&id| f(id))
pub fn par_items(
&self,
f: impl Fn(ItemId) -> Result<(), ErrorGuaranteed> + DynSend + DynSync,
) -> Result<(), ErrorGuaranteed> {
try_par_for_each_in(&self.items[..], |&id| f(id))
}
pub fn par_trait_items(&self, f: impl Fn(TraitItemId) + DynSend + DynSync) {
par_for_each_in(&self.trait_items[..], |&id| f(id))
pub fn par_trait_items(
&self,
f: impl Fn(TraitItemId) -> Result<(), ErrorGuaranteed> + DynSend + DynSync,
) -> Result<(), ErrorGuaranteed> {
try_par_for_each_in(&self.trait_items[..], |&id| f(id))
}
pub fn par_impl_items(&self, f: impl Fn(ImplItemId) + DynSend + DynSync) {
par_for_each_in(&self.impl_items[..], |&id| f(id))
pub fn par_impl_items(
&self,
f: impl Fn(ImplItemId) -> Result<(), ErrorGuaranteed> + DynSend + DynSync,
) -> Result<(), ErrorGuaranteed> {
try_par_for_each_in(&self.impl_items[..], |&id| f(id))
}
pub fn par_foreign_items(&self, f: impl Fn(ForeignItemId) + DynSend + DynSync) {
par_for_each_in(&self.foreign_items[..], |&id| f(id))
pub fn par_foreign_items(
&self,
f: impl Fn(ForeignItemId) -> Result<(), ErrorGuaranteed> + DynSend + DynSync,
) -> Result<(), ErrorGuaranteed> {
try_par_for_each_in(&self.foreign_items[..], |&id| f(id))
}
}

View file

@ -998,9 +998,9 @@ impl<'tcx> Debug for Rvalue<'tcx> {
ty::tls::with(|tcx| {
let variant_def = &tcx.adt_def(adt_did).variant(variant);
let args = tcx.lift(args).expect("could not lift for printing");
let name = FmtPrinter::new(tcx, Namespace::ValueNS)
.print_def_path(variant_def.def_id, args)?
.into_buffer();
let name = FmtPrinter::print_string(tcx, Namespace::ValueNS, |cx| {
cx.print_def_path(variant_def.def_id, args)
})?;
match variant_def.ctor_kind() {
Some(CtorKind::Const) => fmt.write_str(&name),
@ -1740,7 +1740,7 @@ fn pretty_print_const_value_tcx<'tcx>(
let args = tcx.lift(args).unwrap();
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.print_value_path(variant_def.def_id, args)?;
cx.print_value_path(variant_def.def_id, args)?;
fmt.write_str(&cx.into_buffer())?;
match variant_def.ctor_kind() {
@ -1775,14 +1775,14 @@ fn pretty_print_const_value_tcx<'tcx>(
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let ty = tcx.lift(ty).unwrap();
cx = cx.pretty_print_const_scalar(scalar, ty)?;
cx.pretty_print_const_scalar(scalar, ty)?;
fmt.write_str(&cx.into_buffer())?;
return Ok(());
}
(ConstValue::ZeroSized, ty::FnDef(d, s)) => {
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.print_alloc_ids = true;
let cx = cx.print_value_path(*d, s)?;
cx.print_value_path(*d, s)?;
fmt.write_str(&cx.into_buffer())?;
return Ok(());
}

View file

@ -3,12 +3,10 @@ use rustc_hir::LangItem;
use smallvec::SmallVec;
use super::{BasicBlock, InlineAsmOperand, Operand, SourceInfo, TerminatorKind, UnwindAction};
pub use rustc_ast::Mutability;
use rustc_macros::HashStable;
use std::iter;
use std::slice;
pub use super::query::*;
use super::*;
impl SwitchTargets {
@ -28,6 +26,15 @@ impl SwitchTargets {
Self { values: smallvec![value], targets: smallvec![then, else_] }
}
/// Inverse of `SwitchTargets::static_if`.
pub fn as_static_if(&self) -> Option<(u128, BasicBlock, BasicBlock)> {
if let &[value] = &self.values[..] && let &[then, else_] = &self.targets[..] {
Some((value, then, else_))
} else {
None
}
}
/// Returns the fallback target that is jumped to when none of the values match the operand.
pub fn otherwise(&self) -> BasicBlock {
*self.targets.last().unwrap()

View file

@ -25,7 +25,9 @@ use crate::mir::interpret::{
use crate::mir::interpret::{LitToConstError, LitToConstInput};
use crate::mir::mono::CodegenUnit;
use crate::query::erase::{erase, restore, Erase};
use crate::query::plumbing::{query_ensure, query_get_at, CyclePlaceholder, DynamicQuery};
use crate::query::plumbing::{
query_ensure, query_ensure_error_guaranteed, query_get_at, CyclePlaceholder, DynamicQuery,
};
use crate::thir;
use crate::traits::query::{
CanonicalPredicateGoal, CanonicalProjectionGoal, CanonicalTyGoal,
@ -965,8 +967,9 @@ rustc_queries! {
desc { |tcx| "checking that impls are well-formed in {}", describe_as_module(key, tcx) }
}
query check_mod_type_wf(key: LocalModDefId) -> () {
query check_mod_type_wf(key: LocalModDefId) -> Result<(), ErrorGuaranteed> {
desc { |tcx| "checking that types are well-formed in {}", describe_as_module(key, tcx) }
ensure_forwards_result_if_red
}
query collect_mod_item_types(key: LocalModDefId) -> () {
@ -1499,8 +1502,9 @@ rustc_queries! {
feedable
}
query check_well_formed(key: hir::OwnerId) -> () {
query check_well_formed(key: hir::OwnerId) -> Result<(), ErrorGuaranteed> {
desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key) }
ensure_forwards_result_if_red
}
// The `DefId`s of all non-generic functions and statics in the given crate

View file

@ -173,6 +173,45 @@ pub fn query_ensure<'tcx, Cache>(
}
}
#[inline]
pub fn query_ensure_error_guaranteed<'tcx, Cache>(
tcx: TyCtxt<'tcx>,
execute_query: fn(TyCtxt<'tcx>, Span, Cache::Key, QueryMode) -> Option<Cache::Value>,
query_cache: &Cache,
key: Cache::Key,
check_cache: bool,
) -> Result<(), ErrorGuaranteed>
where
Cache: QueryCache<Value = super::erase::Erase<Result<(), ErrorGuaranteed>>>,
{
let key = key.into_query_param();
if let Some(res) = try_get_cached(tcx, query_cache, &key) {
super::erase::restore(res)
} else {
execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { check_cache })
.map(super::erase::restore)
// Either we actually executed the query, which means we got a full `Result`,
// or we can just assume the query succeeded, because it was green in the
// incremental cache. If it is green, that means that the previous compilation
// that wrote to the incremental cache compiles successfully. That is only
// possible if the cache entry was `Ok(())`, so we emit that here, without
// actually encoding the `Result` in the cache or loading it from there.
.unwrap_or(Ok(()))
}
}
macro_rules! query_ensure {
([]$($args:tt)*) => {
query_ensure($($args)*)
};
([(ensure_forwards_result_if_red) $($rest:tt)*]$($args:tt)*) => {
query_ensure_error_guaranteed($($args)*)
};
([$other:tt $($modifiers:tt)*]$($args:tt)*) => {
query_ensure!([$($modifiers)*]$($args)*)
};
}
macro_rules! query_helper_param_ty {
(DefId) => { impl IntoQueryParam<DefId> };
(LocalDefId) => { impl IntoQueryParam<LocalDefId> };
@ -220,6 +259,18 @@ macro_rules! separate_provide_extern_decl {
};
}
macro_rules! ensure_result {
([][$ty:ty]) => {
()
};
([(ensure_forwards_result_if_red) $($rest:tt)*][$ty:ty]) => {
Result<(), ErrorGuaranteed>
};
([$other:tt $($modifiers:tt)*][$($args:tt)*]) => {
ensure_result!([$($modifiers)*][$($args)*])
};
}
macro_rules! separate_provide_extern_default {
([][$name:ident]) => {
()
@ -343,14 +394,15 @@ macro_rules! define_callbacks {
impl<'tcx> TyCtxtEnsure<'tcx> {
$($(#[$attr])*
#[inline(always)]
pub fn $name(self, key: query_helper_param_ty!($($K)*)) {
query_ensure(
pub fn $name(self, key: query_helper_param_ty!($($K)*)) -> ensure_result!([$($modifiers)*][$V]) {
query_ensure!(
[$($modifiers)*]
self.tcx,
self.tcx.query_system.fns.engine.$name,
&self.tcx.query_system.caches.$name,
key.into_query_param(),
false,
);
)
})*
}

View file

@ -1,5 +1,3 @@
pub use self::AssocItemContainer::*;
use crate::ty;
use rustc_data_structures::sorted_map::SortedIndexMultiMap;
use rustc_hir as hir;

View file

@ -315,26 +315,25 @@ impl<'tcx> Ty<'tcx> {
impl<'tcx> TyCtxt<'tcx> {
pub fn ty_string_with_limit(self, ty: Ty<'tcx>, length_limit: usize) -> String {
let mut type_limit = 50;
let regular = FmtPrinter::new(self, hir::def::Namespace::TypeNS)
.pretty_print_type(ty)
.expect("could not write to `String`")
.into_buffer();
let regular = FmtPrinter::print_string(self, hir::def::Namespace::TypeNS, |cx| {
cx.pretty_print_type(ty)
})
.expect("could not write to `String`");
if regular.len() <= length_limit {
return regular;
}
let mut short;
loop {
// Look for the longest properly trimmed path that still fits in length_limit.
short = with_forced_trimmed_paths!(
FmtPrinter::new_with_limit(
short = with_forced_trimmed_paths!({
let mut cx = FmtPrinter::new_with_limit(
self,
hir::def::Namespace::TypeNS,
rustc_session::Limit(type_limit),
)
.pretty_print_type(ty)
.expect("could not write to `String`")
.into_buffer()
);
);
cx.pretty_print_type(ty).expect("could not write to `String`");
cx.into_buffer()
});
if short.len() <= length_limit || type_limit == 0 {
break;
}
@ -344,10 +343,10 @@ impl<'tcx> TyCtxt<'tcx> {
}
pub fn short_ty_string(self, ty: Ty<'tcx>) -> (String, Option<PathBuf>) {
let regular = FmtPrinter::new(self, hir::def::Namespace::TypeNS)
.pretty_print_type(ty)
.expect("could not write to `String`")
.into_buffer();
let regular = FmtPrinter::print_string(self, hir::def::Namespace::TypeNS, |cx| {
cx.pretty_print_type(ty)
})
.expect("could not write to `String`");
if !self.sess.opts.unstable_opts.write_long_types_to_disk {
return (regular, None);

View file

@ -298,9 +298,9 @@ fn fmt_instance(
ty::tls::with(|tcx| {
let args = tcx.lift(instance.args).expect("could not lift for printing");
let s = FmtPrinter::new_with_limit(tcx, Namespace::ValueNS, type_length)
.print_def_path(instance.def_id(), args)?
.into_buffer();
let mut cx = FmtPrinter::new_with_limit(tcx, Namespace::ValueNS, type_length);
cx.print_def_path(instance.def_id(), args)?;
let s = cx.into_buffer();
f.write_str(&s)
})?;

View file

@ -15,7 +15,7 @@ pub type PrintError = std::fmt::Error;
// FIXME(eddyb) false positive, the lifetime parameters are used with `P: Printer<...>`.
#[allow(unused_lifetimes)]
pub trait Print<'tcx, P> {
fn print(&self, cx: P) -> Result<P, PrintError>;
fn print(&self, cx: &mut P) -> Result<(), PrintError>;
}
/// Interface for outputting user-facing "type-system entities"
@ -31,70 +31,70 @@ pub trait Printer<'tcx>: Sized {
fn tcx<'a>(&'a self) -> TyCtxt<'tcx>;
fn print_def_path(
self,
&mut self,
def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.default_print_def_path(def_id, args)
}
fn print_impl_path(
self,
&mut self,
impl_def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.default_print_impl_path(impl_def_id, args, self_ty, trait_ref)
}
fn print_region(self, region: ty::Region<'tcx>) -> Result<Self, PrintError>;
fn print_region(&mut self, region: ty::Region<'tcx>) -> Result<(), PrintError>;
fn print_type(self, ty: Ty<'tcx>) -> Result<Self, PrintError>;
fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError>;
fn print_dyn_existential(
self,
&mut self,
predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError>;
) -> Result<(), PrintError>;
fn print_const(self, ct: ty::Const<'tcx>) -> Result<Self, PrintError>;
fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError>;
fn path_crate(self, cnum: CrateNum) -> Result<Self, PrintError>;
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError>;
fn path_qualified(
self,
&mut self,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError>;
) -> Result<(), PrintError>;
fn path_append_impl(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError>;
) -> Result<(), PrintError>;
fn path_append(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError>;
) -> Result<(), PrintError>;
fn path_generic_args(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError>;
) -> Result<(), PrintError>;
// Defaults (should not be overridden):
#[instrument(skip(self), level = "debug")]
fn default_print_def_path(
self,
&mut self,
def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
let key = self.tcx().def_key(def_id);
debug!(?key);
@ -161,7 +161,7 @@ pub trait Printer<'tcx>: Sized {
}
self.path_append(
|cx: Self| {
|cx: &mut Self| {
if trait_qualify_parent {
let trait_ref = ty::TraitRef::new(
cx.tcx(),
@ -180,12 +180,12 @@ pub trait Printer<'tcx>: Sized {
}
fn default_print_impl_path(
self,
&mut self,
impl_def_id: DefId,
_args: &'tcx [GenericArg<'tcx>],
self_ty: Ty<'tcx>,
impl_trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
debug!(
"default_print_impl_path: impl_def_id={:?}, self_ty={}, impl_trait_ref={:?}",
impl_def_id, self_ty, impl_trait_ref
@ -286,25 +286,25 @@ pub fn characteristic_def_id_of_type(ty: Ty<'_>) -> Option<DefId> {
}
impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for ty::Region<'tcx> {
fn print(&self, cx: P) -> Result<P, PrintError> {
fn print(&self, cx: &mut P) -> Result<(), PrintError> {
cx.print_region(*self)
}
}
impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for Ty<'tcx> {
fn print(&self, cx: P) -> Result<P, PrintError> {
fn print(&self, cx: &mut P) -> Result<(), PrintError> {
cx.print_type(*self)
}
}
impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>> {
fn print(&self, cx: P) -> Result<P, PrintError> {
fn print(&self, cx: &mut P) -> Result<(), PrintError> {
cx.print_dyn_existential(self)
}
}
impl<'tcx, P: Printer<'tcx>> Print<'tcx, P> for ty::Const<'tcx> {
fn print(&self, cx: P) -> Result<P, PrintError> {
fn print(&self, cx: &mut P) -> Result<(), PrintError> {
cx.print_const(*self)
}
}

File diff suppressed because it is too large Load diff

View file

@ -22,11 +22,10 @@ impl fmt::Debug for ty::TraitDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ty::tls::with(|tcx| {
with_no_trimmed_paths!({
f.write_str(
&FmtPrinter::new(tcx, Namespace::TypeNS)
.print_def_path(self.def_id, &[])?
.into_buffer(),
)
let s = FmtPrinter::print_string(tcx, Namespace::TypeNS, |cx| {
cx.print_def_path(self.def_id, &[])
})?;
f.write_str(&s)
})
})
}
@ -36,11 +35,10 @@ impl<'tcx> fmt::Debug for ty::AdtDef<'tcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
ty::tls::with(|tcx| {
with_no_trimmed_paths!({
f.write_str(
&FmtPrinter::new(tcx, Namespace::TypeNS)
.print_def_path(self.did(), &[])?
.into_buffer(),
)
let s = FmtPrinter::print_string(tcx, Namespace::TypeNS, |cx| {
cx.print_def_path(self.did(), &[])
})?;
f.write_str(&s)
})
})
}
@ -350,9 +348,8 @@ impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::Const<'tcx> {
let ConstKind::Value(valtree) = lifted.kind() else {
bug!("we checked that this is a valtree")
};
let cx = FmtPrinter::new(tcx, Namespace::ValueNS);
let cx =
cx.pretty_print_const_valtree(valtree, lifted.ty(), /*print_ty*/ true)?;
let mut cx = FmtPrinter::new(tcx, Namespace::ValueNS);
cx.pretty_print_const_valtree(valtree, lifted.ty(), /*print_ty*/ true)?;
f.write_str(&cx.into_buffer())
});
}

View file

@ -1,6 +1,6 @@
use crate::elaborate_drops::DropFlagState;
use rustc_middle::mir::{self, Body, Location, Terminator, TerminatorKind};
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::VariantIdx;
use super::indexes::MovePathIndex;
@ -55,60 +55,6 @@ pub fn on_all_children_bits<'tcx, F>(
) where
F: FnMut(MovePathIndex),
{
#[inline]
fn is_terminal_path<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
path: MovePathIndex,
) -> bool {
let place = move_data.move_paths[path].place;
// When enumerating the child fragments of a path, don't recurse into
// paths (1.) past arrays, slices, and pointers, nor (2.) into a type
// that implements `Drop`.
//
// Places behind references or arrays are not tracked by elaboration
// and are always assumed to be initialized when accessible. As
// references and indexes can be reseated, trying to track them can
// only lead to trouble.
//
// Places behind ADT's with a Drop impl are not tracked by
// elaboration since they can never have a drop-flag state that
// differs from that of the parent with the Drop impl.
//
// In both cases, the contents can only be accessed if and only if
// their parents are initialized. This implies for example that there
// is no need to maintain separate drop flags to track such state.
//
// FIXME: we have to do something for moving slice patterns.
let ty = place.ty(body, tcx).ty;
match ty.kind() {
ty::Adt(def, _) if (def.has_dtor(tcx) && !def.is_box()) || def.is_union() => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} Drop => true",
place, ty
);
true
}
ty::Array(..) => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} => false",
place, ty
);
false
}
ty::Slice(..) | ty::Ref(..) | ty::RawPtr(..) => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} refd => true",
place, ty
);
true
}
_ => false,
}
}
fn on_all_children_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
@ -120,10 +66,6 @@ pub fn on_all_children_bits<'tcx, F>(
{
each_child(move_path_index);
if is_terminal_path(tcx, body, move_data, move_path_index) {
return;
}
let mut next_child_index = move_data.move_paths[move_path_index].first_child;
while let Some(child_index) = next_child_index {
on_all_children_bits(tcx, body, move_data, child_index, each_child);
@ -133,29 +75,6 @@ pub fn on_all_children_bits<'tcx, F>(
on_all_children_bits(tcx, body, move_data, move_path_index, &mut each_child);
}
pub fn on_all_drop_children_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
ctxt: &MoveDataParamEnv<'tcx>,
path: MovePathIndex,
mut each_child: F,
) where
F: FnMut(MovePathIndex),
{
on_all_children_bits(tcx, body, &ctxt.move_data, path, |child| {
let place = &ctxt.move_data.move_paths[path].place;
let ty = place.ty(body, tcx).ty;
debug!("on_all_drop_children_bits({:?}, {:?} : {:?})", path, place, ty);
let erased_ty = tcx.erase_regions(ty);
if erased_ty.needs_drop(tcx, ctxt.param_env) {
each_child(child);
} else {
debug!("on_all_drop_children_bits - skipping")
}
})
}
pub fn drop_flag_effects_for_function_entry<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,

View file

@ -48,7 +48,7 @@ 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, MaybeReachable, MeetSemiLattice};
pub use self::lattice::{JoinSemiLattice, MaybeReachable};
pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor};
/// Analysis domains are all bitsets of various kinds. This trait holds

View file

@ -10,7 +10,7 @@ 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::{drop_flag_effects, on_all_children_bits};
use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis, MaybeReachable};
/// `MaybeInitializedPlaces` tracks all places that might be
@ -72,7 +72,7 @@ impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
) -> 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| {
on_all_children_bits(self.tcx, self.body, self.move_data(), path, |child| {
maybe_live |= state.contains(child);
});
!maybe_live
@ -690,9 +690,13 @@ impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
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());
if let Some(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());
}
}
}

View file

@ -23,8 +23,7 @@ use rustc_span::symbol::{sym, Symbol};
pub use self::drop_flag_effects::{
drop_flag_effects_for_function_entry, drop_flag_effects_for_location,
move_path_children_matching, on_all_children_bits, on_all_drop_children_bits,
on_lookup_result_bits,
move_path_children_matching, on_all_children_bits, on_lookup_result_bits,
};
pub use self::framework::{
fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, AnalysisResults, Backward,

View file

@ -1,58 +1,66 @@
use rustc_index::IndexVec;
use rustc_middle::mir::tcx::RvalueInitializationState;
use rustc_middle::mir::tcx::{PlaceTy, RvalueInitializationState};
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use rustc_middle::ty::{self, Ty, TyCtxt};
use smallvec::{smallvec, SmallVec};
use std::mem;
use super::abs_domain::Lift;
use super::IllegalMoveOriginKind::*;
use super::{Init, InitIndex, InitKind, InitLocation, LookupResult, MoveError};
use super::{Init, InitIndex, InitKind, InitLocation, LookupResult};
use super::{
LocationMap, MoveData, MoveOut, MoveOutIndex, MovePath, MovePathIndex, MovePathLookup,
};
struct MoveDataBuilder<'a, 'tcx> {
struct MoveDataBuilder<'a, 'tcx, F> {
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
data: MoveData<'tcx>,
errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
filter: F,
}
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
fn new(body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self {
impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
fn new(
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
filter: F,
) -> Self {
let mut move_paths = IndexVec::new();
let mut path_map = IndexVec::new();
let mut init_path_map = IndexVec::new();
let locals = body
.local_decls
.iter_enumerated()
.map(|(i, l)| {
if l.is_deref_temp() {
return None;
}
if filter(l.ty) {
Some(new_move_path(
&mut move_paths,
&mut path_map,
&mut init_path_map,
None,
Place::from(i),
))
} else {
None
}
})
.collect();
MoveDataBuilder {
body,
tcx,
param_env,
errors: Vec::new(),
data: MoveData {
moves: IndexVec::new(),
loc_map: LocationMap::new(body),
rev_lookup: MovePathLookup {
locals: body
.local_decls
.iter_enumerated()
.map(|(i, l)| {
if l.is_deref_temp() {
MovePathIndex::MAX
} else {
Self::new_move_path(
&mut move_paths,
&mut path_map,
&mut init_path_map,
None,
Place::from(i),
)
}
})
.collect(),
locals,
projections: Default::default(),
un_derefer: Default::default(),
},
@ -62,35 +70,42 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
init_loc_map: LocationMap::new(body),
init_path_map,
},
filter,
}
}
fn new_move_path(
move_paths: &mut IndexVec<MovePathIndex, MovePath<'tcx>>,
path_map: &mut IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>,
init_path_map: &mut IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>,
parent: Option<MovePathIndex>,
place: Place<'tcx>,
) -> MovePathIndex {
let move_path =
move_paths.push(MovePath { next_sibling: None, first_child: None, parent, place });
if let Some(parent) = parent {
let next_sibling = mem::replace(&mut move_paths[parent].first_child, Some(move_path));
move_paths[move_path].next_sibling = next_sibling;
}
let path_map_ent = path_map.push(smallvec![]);
assert_eq!(path_map_ent, move_path);
let init_path_map_ent = init_path_map.push(smallvec![]);
assert_eq!(init_path_map_ent, move_path);
move_path
}
}
impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
fn new_move_path<'tcx>(
move_paths: &mut IndexVec<MovePathIndex, MovePath<'tcx>>,
path_map: &mut IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>,
init_path_map: &mut IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>,
parent: Option<MovePathIndex>,
place: Place<'tcx>,
) -> MovePathIndex {
let move_path =
move_paths.push(MovePath { next_sibling: None, first_child: None, parent, place });
if let Some(parent) = parent {
let next_sibling = mem::replace(&mut move_paths[parent].first_child, Some(move_path));
move_paths[move_path].next_sibling = next_sibling;
}
let path_map_ent = path_map.push(smallvec![]);
assert_eq!(path_map_ent, move_path);
let init_path_map_ent = init_path_map.push(smallvec![]);
assert_eq!(init_path_map_ent, move_path);
move_path
}
enum MovePathResult {
Path(MovePathIndex),
Union(MovePathIndex),
Error,
}
impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
/// This creates a MovePath for a given place, returning an `MovePathError`
/// if that place can't be moved from.
///
@ -98,11 +113,13 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
/// problematic for borrowck.
///
/// Maybe we should have separate "borrowck" and "moveck" modes.
fn move_path_for(&mut self, place: Place<'tcx>) -> Result<MovePathIndex, MoveError<'tcx>> {
fn move_path_for(&mut self, place: Place<'tcx>) -> MovePathResult {
let data = &mut self.builder.data;
debug!("lookup({:?})", place);
let mut base = data.rev_lookup.find_local(place.local);
let Some(mut base) = data.rev_lookup.find_local(place.local) else {
return MovePathResult::Error;
};
// The move path index of the first union that we find. Once this is
// some we stop creating child move paths, since moves from unions
@ -118,12 +135,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
match elem {
ProjectionElem::Deref => match place_ty.kind() {
ty::Ref(..) | ty::RawPtr(..) => {
return Err(MoveError::cannot_move_out_of(
self.loc,
BorrowedContent {
target_place: place_ref.project_deeper(&[elem], tcx),
},
));
return MovePathResult::Error;
}
ty::Adt(adt, _) => {
if !adt.is_box() {
@ -159,10 +171,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
ProjectionElem::Field(_, _) => match place_ty.kind() {
ty::Adt(adt, _) => {
if adt.has_dtor(tcx) {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfTypeWithDestructor { container_ty: place_ty },
));
return MovePathResult::Error;
}
if adt.is_union() {
union_path.get_or_insert(base);
@ -197,33 +206,15 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
match place_ty.kind() {
ty::Slice(_) => {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfSliceOrArray {
ty: place_ty,
is_index: matches!(elem, ProjectionElem::Index(..)),
},
));
return MovePathResult::Error;
}
ty::Array(_, _) => (),
_ => bug!("Unexpected type {:#?}", place_ty.is_array()),
}
}
ProjectionElem::Index(_) => match place_ty.kind() {
ty::Array(..) => {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfSliceOrArray { ty: place_ty, is_index: true },
));
}
ty::Slice(_) => {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfSliceOrArray {
ty: place_ty,
is_index: matches!(elem, ProjectionElem::Index(..)),
},
));
ty::Array(..) | ty::Slice(_) => {
return MovePathResult::Error;
}
_ => bug!("Unexpected type {place_ty:#?}"),
},
@ -235,11 +226,15 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
| ProjectionElem::Subtype(_)
| ProjectionElem::Downcast(_, _) => (),
}
let elem_ty = PlaceTy::from_ty(place_ty).projection_ty(tcx, elem).ty;
if !(self.builder.filter)(elem_ty) {
return MovePathResult::Error;
}
if union_path.is_none() {
// inlined from add_move_path because of a borrowck conflict with the iterator
base =
*data.rev_lookup.projections.entry((base, elem.lift())).or_insert_with(|| {
MoveDataBuilder::new_move_path(
new_move_path(
&mut data.move_paths,
&mut data.path_map,
&mut data.init_path_map,
@ -252,9 +247,9 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
if let Some(base) = union_path {
// Move out of union - always move the entire union.
Err(MoveError::UnionMove { path: base })
MovePathResult::Union(base)
} else {
Ok(base)
MovePathResult::Path(base)
}
}
@ -270,13 +265,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
..
} = self.builder;
*rev_lookup.projections.entry((base, elem.lift())).or_insert_with(move || {
MoveDataBuilder::new_move_path(
move_paths,
path_map,
init_path_map,
Some(base),
mk_place(*tcx),
)
new_move_path(move_paths, path_map, init_path_map, Some(base), mk_place(*tcx))
})
}
@ -287,11 +276,8 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
}
}
pub type MoveDat<'tcx> =
Result<MoveData<'tcx>, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)>;
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
fn finalize(self) -> MoveDat<'tcx> {
impl<'a, 'tcx, F> MoveDataBuilder<'a, 'tcx, F> {
fn finalize(self) -> MoveData<'tcx> {
debug!("{}", {
debug!("moves for {:?}:", self.body.span);
for (j, mo) in self.data.moves.iter_enumerated() {
@ -304,7 +290,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
"done dumping moves"
});
if self.errors.is_empty() { Ok(self.data) } else { Err((self.data, self.errors)) }
self.data
}
}
@ -312,8 +298,9 @@ pub(super) fn gather_moves<'tcx>(
body: &Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> MoveDat<'tcx> {
let mut builder = MoveDataBuilder::new(body, tcx, param_env);
filter: impl Fn(Ty<'tcx>) -> bool,
) -> MoveData<'tcx> {
let mut builder = MoveDataBuilder::new(body, tcx, param_env, filter);
builder.gather_args();
@ -330,20 +317,20 @@ pub(super) fn gather_moves<'tcx>(
builder.finalize()
}
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
impl<'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> MoveDataBuilder<'a, 'tcx, F> {
fn gather_args(&mut self) {
for arg in self.body.args_iter() {
let path = self.data.rev_lookup.find_local(arg);
if let Some(path) = self.data.rev_lookup.find_local(arg) {
let init = self.data.inits.push(Init {
path,
kind: InitKind::Deep,
location: InitLocation::Argument(arg),
});
let init = self.data.inits.push(Init {
path,
kind: InitKind::Deep,
location: InitLocation::Argument(arg),
});
debug!("gather_args: adding init {:?} of {:?} for argument {:?}", init, path, arg);
debug!("gather_args: adding init {:?} of {:?} for argument {:?}", init, path, arg);
self.data.init_path_map[path].push(init);
self.data.init_path_map[path].push(init);
}
}
}
@ -358,12 +345,12 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
}
}
struct Gatherer<'b, 'a, 'tcx> {
builder: &'b mut MoveDataBuilder<'a, 'tcx>,
struct Gatherer<'b, 'a, 'tcx, F> {
builder: &'b mut MoveDataBuilder<'a, 'tcx, F>,
loc: Location,
}
impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> {
fn gather_statement(&mut self, stmt: &Statement<'tcx>) {
match &stmt.kind {
StatementKind::Assign(box (place, Rvalue::CopyForDeref(reffed))) => {
@ -546,13 +533,12 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
let base_place =
Place { local: place.local, projection: self.builder.tcx.mk_place_elems(base) };
let base_path = match self.move_path_for(base_place) {
Ok(path) => path,
Err(MoveError::UnionMove { path }) => {
MovePathResult::Path(path) => path,
MovePathResult::Union(path) => {
self.record_move(place, path);
return;
}
Err(error @ MoveError::IllegalMove { .. }) => {
self.builder.errors.push((base_place, error));
MovePathResult::Error => {
return;
}
};
@ -572,10 +558,10 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
}
} else {
match self.move_path_for(place) {
Ok(path) | Err(MoveError::UnionMove { path }) => self.record_move(place, path),
Err(error @ MoveError::IllegalMove { .. }) => {
self.builder.errors.push((place, error));
MovePathResult::Path(path) | MovePathResult::Union(path) => {
self.record_move(place, path)
}
MovePathResult::Error => {}
};
}
}

View file

@ -1,4 +1,3 @@
use crate::move_paths::builder::MoveDat;
use crate::un_derefer::UnDerefer;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::{IndexSlice, IndexVec};
@ -291,7 +290,7 @@ impl Init {
/// Tables mapping from a place to its MovePathIndex.
#[derive(Debug)]
pub struct MovePathLookup<'tcx> {
locals: IndexVec<Local, MovePathIndex>,
locals: IndexVec<Local, Option<MovePathIndex>>,
/// projections are made from a base-place and a projection
/// elem. The base-place will have a unique MovePathIndex; we use
@ -318,7 +317,9 @@ impl<'tcx> MovePathLookup<'tcx> {
// unknown place, but will rather return the nearest available
// parent.
pub fn find(&self, place: PlaceRef<'tcx>) -> LookupResult {
let mut result = self.find_local(place.local);
let Some(mut result) = self.find_local(place.local) else {
return LookupResult::Parent(None);
};
for (_, elem) in self.un_derefer.iter_projections(place) {
if let Some(&subpath) = self.projections.get(&(result, elem.lift())) {
@ -332,7 +333,7 @@ impl<'tcx> MovePathLookup<'tcx> {
}
#[inline]
pub fn find_local(&self, local: Local) -> MovePathIndex {
pub fn find_local(&self, local: Local) -> Option<MovePathIndex> {
self.locals[local]
}
@ -340,46 +341,8 @@ impl<'tcx> MovePathLookup<'tcx> {
/// `MovePathIndex`es.
pub fn iter_locals_enumerated(
&self,
) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> + ExactSizeIterator + '_ {
self.locals.iter_enumerated().map(|(l, &idx)| (l, idx))
}
}
#[derive(Debug)]
pub struct IllegalMoveOrigin<'tcx> {
pub location: Location,
pub kind: IllegalMoveOriginKind<'tcx>,
}
#[derive(Debug)]
pub enum IllegalMoveOriginKind<'tcx> {
/// Illegal move due to attempt to move from behind a reference.
BorrowedContent {
/// The place the reference refers to: if erroneous code was trying to
/// move from `(*x).f` this will be `*x`.
target_place: Place<'tcx>,
},
/// Illegal move due to attempt to move from field of an ADT that
/// implements `Drop`. Rust maintains invariant that all `Drop`
/// ADT's remain fully-initialized so that user-defined destructor
/// can safely read from all of the ADT's fields.
InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
/// Illegal move due to attempt to move out of a slice or array.
InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },
}
#[derive(Debug)]
pub enum MoveError<'tcx> {
IllegalMove { cannot_move_out_of: IllegalMoveOrigin<'tcx> },
UnionMove { path: MovePathIndex },
}
impl<'tcx> MoveError<'tcx> {
fn cannot_move_out_of(location: Location, kind: IllegalMoveOriginKind<'tcx>) -> Self {
let origin = IllegalMoveOrigin { location, kind };
MoveError::IllegalMove { cannot_move_out_of: origin }
) -> impl DoubleEndedIterator<Item = (Local, MovePathIndex)> + '_ {
self.locals.iter_enumerated().filter_map(|(l, &idx)| Some((l, idx?)))
}
}
@ -388,8 +351,9 @@ impl<'tcx> MoveData<'tcx> {
body: &Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
) -> MoveDat<'tcx> {
builder::gather_moves(body, tcx, param_env)
filter: impl Fn(Ty<'tcx>) -> bool,
) -> MoveData<'tcx> {
builder::gather_moves(body, tcx, param_env, filter)
}
/// For the move path `mpi`, returns the root local variable (if any) that starts the path.

View file

@ -34,7 +34,7 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
}
let param_env = tcx.param_env(def_id);
let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap();
let move_data = MoveData::gather_moves(&body, tcx, param_env, |_| true);
let mdpe = MoveDataParamEnv { move_data, param_env };
if has_rustc_mir_with(tcx, def_id, sym::rustc_peek_maybe_init).is_some() {

View file

@ -463,7 +463,19 @@ impl<V: Clone> Clone for State<V> {
}
}
impl<V: Clone + HasTop + HasBottom> State<V> {
impl<V: Clone> State<V> {
pub fn new(init: V, map: &Map) -> State<V> {
let values = IndexVec::from_elem_n(init, map.value_count);
State(StateData::Reachable(values))
}
pub fn all(&self, f: impl Fn(&V) -> bool) -> bool {
match self.0 {
StateData::Unreachable => true,
StateData::Reachable(ref values) => values.iter().all(f),
}
}
pub fn is_reachable(&self) -> bool {
matches!(&self.0, StateData::Reachable(_))
}
@ -472,7 +484,10 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
self.0 = StateData::Unreachable;
}
pub fn flood_all(&mut self) {
pub fn flood_all(&mut self)
where
V: HasTop,
{
self.flood_all_with(V::TOP)
}
@ -481,28 +496,52 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
values.raw.fill(value);
}
/// Assign `value` to all places that are contained in `place` or may alias one.
pub fn flood_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
let StateData::Reachable(values) = &mut self.0 else { return };
map.for_each_aliasing_place(place, None, &mut |vi| {
values[vi] = value.clone();
});
self.flood_with_tail_elem(place, None, map, value)
}
pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map) {
/// Assign `TOP` to all places that are contained in `place` or may alias one.
pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map)
where
V: HasTop,
{
self.flood_with(place, map, V::TOP)
}
/// Assign `value` to the discriminant of `place` and all places that may alias it.
pub fn flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
let StateData::Reachable(values) = &mut self.0 else { return };
map.for_each_aliasing_place(place, Some(TrackElem::Discriminant), &mut |vi| {
values[vi] = value.clone();
});
self.flood_with_tail_elem(place, Some(TrackElem::Discriminant), map, value)
}
pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map) {
/// Assign `TOP` to the discriminant of `place` and all places that may alias it.
pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map)
where
V: HasTop,
{
self.flood_discr_with(place, map, V::TOP)
}
/// This method is the most general version of the `flood_*` method.
///
/// Assign `value` on the given place and all places that may alias it. In particular, when
/// the given place has a variant downcast, we invoke the function on all the other variants.
///
/// `tail_elem` allows to support discriminants that are not a place in MIR, but that we track
/// as such.
pub fn flood_with_tail_elem(
&mut self,
place: PlaceRef<'_>,
tail_elem: Option<TrackElem>,
map: &Map,
value: V,
) {
let StateData::Reachable(values) = &mut self.0 else { return };
map.for_each_aliasing_place(place, tail_elem, &mut |vi| {
values[vi] = value.clone();
});
}
/// Low-level method that assigns to a place.
/// This does nothing if the place is not tracked.
///
@ -553,7 +592,10 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
}
/// Helper method to interpret `target = result`.
pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)
where
V: HasTop,
{
self.flood(target, map);
if let Some(target) = map.find(target) {
self.insert_idx(target, result, map);
@ -561,36 +603,93 @@ impl<V: Clone + HasTop + HasBottom> State<V> {
}
/// Helper method for assignments to a discriminant.
pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)
where
V: HasTop,
{
self.flood_discr(target, map);
if let Some(target) = map.find_discr(target) {
self.insert_idx(target, result, map);
}
}
/// Retrieve the value stored for a place, or if it is not tracked.
pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V {
map.find(place).map(|place| self.get_idx(place, map)).unwrap_or(V::TOP)
/// Retrieve the value stored for a place, or `None` if it is not tracked.
pub fn try_get(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
let place = map.find(place)?;
self.try_get_idx(place, map)
}
/// Retrieve the value stored for a place, or if it is not tracked.
pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map) -> V {
match map.find_discr(place) {
Some(place) => self.get_idx(place, map),
None => V::TOP,
/// Retrieve the discriminant stored for a place, or `None` if it is not tracked.
pub fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
let place = map.find_discr(place)?;
self.try_get_idx(place, map)
}
/// Retrieve the slice length stored for a place, or `None` if it is not tracked.
pub fn try_get_len(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
let place = map.find_len(place)?;
self.try_get_idx(place, map)
}
/// Retrieve the value stored for a place index, or `None` if it is not tracked.
pub fn try_get_idx(&self, place: PlaceIndex, map: &Map) -> Option<V> {
match &self.0 {
StateData::Reachable(values) => {
map.places[place].value_index.map(|v| values[v].clone())
}
StateData::Unreachable => None,
}
}
/// Retrieve the value stored for a place, or if it is not tracked.
pub fn get_len(&self, place: PlaceRef<'_>, map: &Map) -> V {
match map.find_len(place) {
Some(place) => self.get_idx(place, map),
None => V::TOP,
///
/// This method returns ⊥ if the place is tracked and the state is unreachable.
pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V
where
V: HasBottom + HasTop,
{
match &self.0 {
StateData::Reachable(_) => self.try_get(place, map).unwrap_or(V::TOP),
// Because this is unreachable, we can return any value we want.
StateData::Unreachable => V::BOTTOM,
}
}
/// Retrieve the value stored for a place, or if it is not tracked.
///
/// This method returns ⊥ the current state is unreachable.
pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map) -> V
where
V: HasBottom + HasTop,
{
match &self.0 {
StateData::Reachable(_) => self.try_get_discr(place, map).unwrap_or(V::TOP),
// Because this is unreachable, we can return any value we want.
StateData::Unreachable => V::BOTTOM,
}
}
/// Retrieve the value stored for a place, or if it is not tracked.
///
/// This method returns ⊥ the current state is unreachable.
pub fn get_len(&self, place: PlaceRef<'_>, map: &Map) -> V
where
V: HasBottom + HasTop,
{
match &self.0 {
StateData::Reachable(_) => self.try_get_len(place, map).unwrap_or(V::TOP),
// Because this is unreachable, we can return any value we want.
StateData::Unreachable => V::BOTTOM,
}
}
/// Retrieve the value stored for a place index, or if it is not tracked.
pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V {
///
/// This method returns ⊥ the current state is unreachable.
pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V
where
V: HasBottom + HasTop,
{
match &self.0 {
StateData::Reachable(values) => {
map.places[place].value_index.map(|v| values[v].clone()).unwrap_or(V::TOP)

View file

@ -11,6 +11,7 @@ smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
tracing = "0.1"
either = "1"
rustc_ast = { path = "../rustc_ast" }
rustc_arena = { path = "../rustc_arena" }
rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }

View file

@ -0,0 +1,98 @@
use rustc_middle::mir::visit::*;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
const INSTR_COST: usize = 5;
const CALL_PENALTY: usize = 25;
const LANDINGPAD_PENALTY: usize = 50;
const RESUME_PENALTY: usize = 45;
/// Verify that the callee body is compatible with the caller.
#[derive(Clone)]
pub(crate) struct CostChecker<'b, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
cost: usize,
callee_body: &'b Body<'tcx>,
instance: Option<ty::Instance<'tcx>>,
}
impl<'b, 'tcx> CostChecker<'b, 'tcx> {
pub fn new(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
instance: Option<ty::Instance<'tcx>>,
callee_body: &'b Body<'tcx>,
) -> CostChecker<'b, 'tcx> {
CostChecker { tcx, param_env, callee_body, instance, cost: 0 }
}
pub fn cost(&self) -> usize {
self.cost
}
fn instantiate_ty(&self, v: Ty<'tcx>) -> Ty<'tcx> {
if let Some(instance) = self.instance {
instance.instantiate_mir(self.tcx, ty::EarlyBinder::bind(&v))
} else {
v
}
}
}
impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
// Don't count StorageLive/StorageDead in the inlining cost.
match statement.kind {
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Deinit(_)
| StatementKind::Nop => {}
_ => self.cost += INSTR_COST,
}
}
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _: Location) {
let tcx = self.tcx;
match terminator.kind {
TerminatorKind::Drop { ref place, unwind, .. } => {
// If the place doesn't actually need dropping, treat it like a regular goto.
let ty = self.instantiate_ty(place.ty(self.callee_body, tcx).ty);
if ty.needs_drop(tcx, self.param_env) {
self.cost += CALL_PENALTY;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
} else {
self.cost += INSTR_COST;
}
}
TerminatorKind::Call { func: Operand::Constant(ref f), unwind, .. } => {
let fn_ty = self.instantiate_ty(f.const_.ty());
self.cost += if let ty::FnDef(def_id, _) = *fn_ty.kind() && tcx.is_intrinsic(def_id) {
// Don't give intrinsics the extra penalty for calls
INSTR_COST
} else {
CALL_PENALTY
};
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
TerminatorKind::Assert { unwind, .. } => {
self.cost += CALL_PENALTY;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
TerminatorKind::UnwindResume => self.cost += RESUME_PENALTY,
TerminatorKind::InlineAsm { unwind, .. } => {
self.cost += INSTR_COST;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
_ => self.cost += INSTR_COST,
}
}
}

View file

@ -9,9 +9,9 @@ use rustc_mir_dataflow::elaborate_drops::{elaborate_drop, DropFlagState, Unwind}
use rustc_mir_dataflow::elaborate_drops::{DropElaborator, DropFlagMode, DropStyle};
use rustc_mir_dataflow::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
use rustc_mir_dataflow::on_all_children_bits;
use rustc_mir_dataflow::on_lookup_result_bits;
use rustc_mir_dataflow::MoveDataParamEnv;
use rustc_mir_dataflow::{on_all_children_bits, on_all_drop_children_bits};
use rustc_mir_dataflow::{Analysis, ResultsCursor};
use rustc_span::Span;
use rustc_target::abi::{FieldIdx, VariantIdx};
@ -54,16 +54,10 @@ impl<'tcx> MirPass<'tcx> for ElaborateDrops {
let def_id = body.source.def_id();
let param_env = tcx.param_env_reveal_all_normalized(def_id);
let move_data = match MoveData::gather_moves(body, tcx, param_env) {
Ok(move_data) => move_data,
Err((move_data, _)) => {
tcx.sess.delay_span_bug(
body.span,
"No `move_errors` should be allowed in MIR borrowck",
);
move_data
}
};
// For types that do not need dropping, the behaviour is trivial. So we only need to track
// init/uninit for types that do need dropping.
let move_data =
MoveData::gather_moves(&body, tcx, param_env, |ty| ty.needs_drop(tcx, param_env));
let elaborate_patch = {
let env = MoveDataParamEnv { move_data, param_env };
@ -178,13 +172,19 @@ impl<'a, 'tcx> DropElaborator<'a, 'tcx> for Elaborator<'a, '_, 'tcx> {
let mut some_live = false;
let mut some_dead = false;
let mut children_count = 0;
on_all_drop_children_bits(self.tcx(), self.body(), self.ctxt.env, path, |child| {
let (live, dead) = self.ctxt.init_data.maybe_live_dead(child);
debug!("elaborate_drop: state({:?}) = {:?}", child, (live, dead));
some_live |= live;
some_dead |= dead;
children_count += 1;
});
on_all_children_bits(
self.tcx(),
self.body(),
self.ctxt.move_data(),
path,
|child| {
let (live, dead) = self.ctxt.init_data.maybe_live_dead(child);
debug!("elaborate_drop: state({:?}) = {:?}", child, (live, dead));
some_live |= live;
some_dead |= dead;
children_count += 1;
},
);
((some_live, some_dead), children_count != 1)
}
};
@ -296,26 +296,36 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
fn collect_drop_flags(&mut self) {
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
let terminator = data.terminator();
let place = match terminator.kind {
TerminatorKind::Drop { ref place, .. } => place,
_ => continue,
};
self.init_data.seek_before(self.body.terminator_loc(bb));
let TerminatorKind::Drop { ref place, .. } = terminator.kind else { continue };
let path = self.move_data().rev_lookup.find(place.as_ref());
debug!("collect_drop_flags: {:?}, place {:?} ({:?})", bb, place, path);
let path = match path {
LookupResult::Exact(e) => e,
LookupResult::Parent(None) => continue,
match path {
LookupResult::Exact(path) => {
self.init_data.seek_before(self.body.terminator_loc(bb));
on_all_children_bits(self.tcx, self.body, self.move_data(), path, |child| {
let (maybe_live, maybe_dead) = self.init_data.maybe_live_dead(child);
debug!(
"collect_drop_flags: collecting {:?} from {:?}@{:?} - {:?}",
child,
place,
path,
(maybe_live, maybe_dead)
);
if maybe_live && maybe_dead {
self.create_drop_flag(child, terminator.source_info.span)
}
});
}
LookupResult::Parent(None) => {}
LookupResult::Parent(Some(parent)) => {
let (_maybe_live, maybe_dead) = self.init_data.maybe_live_dead(parent);
if self.body.local_decls[place.local].is_deref_temp() {
continue;
}
self.init_data.seek_before(self.body.terminator_loc(bb));
let (_maybe_live, maybe_dead) = self.init_data.maybe_live_dead(parent);
if maybe_dead {
self.tcx.sess.delay_span_bug(
terminator.source_info.span,
@ -324,80 +334,74 @@ impl<'b, 'tcx> ElaborateDropsCtxt<'b, 'tcx> {
),
);
}
continue;
}
};
on_all_drop_children_bits(self.tcx, self.body, self.env, path, |child| {
let (maybe_live, maybe_dead) = self.init_data.maybe_live_dead(child);
debug!(
"collect_drop_flags: collecting {:?} from {:?}@{:?} - {:?}",
child,
place,
path,
(maybe_live, maybe_dead)
);
if maybe_live && maybe_dead {
self.create_drop_flag(child, terminator.source_info.span)
}
});
}
}
fn elaborate_drops(&mut self) {
// This function should mirror what `collect_drop_flags` does.
for (bb, data) in self.body.basic_blocks.iter_enumerated() {
let loc = Location { block: bb, statement_index: data.statements.len() };
let terminator = data.terminator();
let TerminatorKind::Drop { place, target, unwind, replace } = terminator.kind else {
continue;
};
match terminator.kind {
TerminatorKind::Drop { place, target, unwind, replace } => {
self.init_data.seek_before(loc);
match self.move_data().rev_lookup.find(place.as_ref()) {
LookupResult::Exact(path) => {
let unwind = if data.is_cleanup {
Unwind::InCleanup
} else {
match unwind {
UnwindAction::Cleanup(cleanup) => Unwind::To(cleanup),
UnwindAction::Continue => Unwind::To(self.patch.resume_block()),
UnwindAction::Unreachable => {
Unwind::To(self.patch.unreachable_cleanup_block())
}
UnwindAction::Terminate(reason) => {
debug_assert_ne!(
reason,
UnwindTerminateReason::InCleanup,
"we are not in a cleanup block, InCleanup reason should be impossible"
);
Unwind::To(self.patch.terminate_block(reason))
}
}
};
elaborate_drop(
&mut Elaborator { ctxt: self },
terminator.source_info,
place,
path,
target,
unwind,
bb,
)
// This place does not need dropping. It does not have an associated move-path, so the
// match below will conservatively keep an unconditional drop. As that drop is useless,
// just remove it here and now.
if !place
.ty(&self.body.local_decls, self.tcx)
.ty
.needs_drop(self.tcx, self.env.param_env)
{
self.patch.patch_terminator(bb, TerminatorKind::Goto { target });
continue;
}
let path = self.move_data().rev_lookup.find(place.as_ref());
match path {
LookupResult::Exact(path) => {
let unwind = match unwind {
_ if data.is_cleanup => Unwind::InCleanup,
UnwindAction::Cleanup(cleanup) => Unwind::To(cleanup),
UnwindAction::Continue => Unwind::To(self.patch.resume_block()),
UnwindAction::Unreachable => {
Unwind::To(self.patch.unreachable_cleanup_block())
}
LookupResult::Parent(..) => {
if !replace {
self.tcx.sess.delay_span_bug(
terminator.source_info.span,
format!("drop of untracked value {bb:?}"),
);
}
// A drop and replace behind a pointer/array/whatever.
// The borrow checker requires that these locations are initialized before the assignment,
// so we just leave an unconditional drop.
assert!(!data.is_cleanup);
UnwindAction::Terminate(reason) => {
debug_assert_ne!(
reason,
UnwindTerminateReason::InCleanup,
"we are not in a cleanup block, InCleanup reason should be impossible"
);
Unwind::To(self.patch.terminate_block(reason))
}
}
};
self.init_data.seek_before(self.body.terminator_loc(bb));
elaborate_drop(
&mut Elaborator { ctxt: self },
terminator.source_info,
place,
path,
target,
unwind,
bb,
)
}
LookupResult::Parent(None) => {}
LookupResult::Parent(Some(_)) => {
if !replace {
self.tcx.sess.delay_span_bug(
terminator.source_info.span,
format!("drop of untracked value {bb:?}"),
);
}
// A drop and replace behind a pointer/array/whatever.
// The borrow checker requires that these locations are initialized before the assignment,
// so we just leave an unconditional drop.
assert!(!data.is_cleanup);
}
_ => continue,
}
}
}

View file

@ -14,6 +14,7 @@ use rustc_session::config::OptLevel;
use rustc_target::abi::FieldIdx;
use rustc_target::spec::abi::Abi;
use crate::cost_checker::CostChecker;
use crate::simplify::{remove_dead_blocks, CfgSimplifier};
use crate::util;
use crate::MirPass;
@ -22,11 +23,6 @@ use std::ops::{Range, RangeFrom};
pub(crate) mod cycle;
const INSTR_COST: usize = 5;
const CALL_PENALTY: usize = 25;
const LANDINGPAD_PENALTY: usize = 50;
const RESUME_PENALTY: usize = 45;
const TOP_DOWN_DEPTH_LIMIT: usize = 5;
pub struct Inline;
@ -479,13 +475,8 @@ impl<'tcx> Inliner<'tcx> {
// FIXME: Give a bonus to functions with only a single caller
let mut checker = CostChecker {
tcx: self.tcx,
param_env: self.param_env,
instance: callsite.callee,
callee_body,
cost: 0,
};
let mut checker =
CostChecker::new(self.tcx, self.param_env, Some(callsite.callee), callee_body);
// Traverse the MIR manually so we can account for the effects of inlining on the CFG.
let mut work_list = vec![START_BLOCK];
@ -530,7 +521,7 @@ impl<'tcx> Inliner<'tcx> {
// That attribute is often applied to very large functions that exceed LLVM's (very
// generous) inlining threshold. Such functions are very poor MIR inlining candidates.
// Always inlining #[inline(always)] functions in MIR, on net, slows down the compiler.
let cost = checker.cost;
let cost = checker.cost();
if cost <= threshold {
debug!("INLINING {:?} [cost={} <= threshold={}]", callsite, cost, threshold);
Ok(())
@ -803,81 +794,6 @@ impl<'tcx> Inliner<'tcx> {
}
}
/// Verify that the callee body is compatible with the caller.
///
/// This visitor mostly computes the inlining cost,
/// but also needs to verify that types match because of normalization failure.
struct CostChecker<'b, 'tcx> {
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
cost: usize,
callee_body: &'b Body<'tcx>,
instance: ty::Instance<'tcx>,
}
impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
// Don't count StorageLive/StorageDead in the inlining cost.
match statement.kind {
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Deinit(_)
| StatementKind::Nop => {}
_ => self.cost += INSTR_COST,
}
}
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, _: Location) {
let tcx = self.tcx;
match terminator.kind {
TerminatorKind::Drop { ref place, unwind, .. } => {
// If the place doesn't actually need dropping, treat it like a regular goto.
let ty = self.instance.instantiate_mir(
tcx,
ty::EarlyBinder::bind(&place.ty(self.callee_body, tcx).ty),
);
if ty.needs_drop(tcx, self.param_env) {
self.cost += CALL_PENALTY;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
} else {
self.cost += INSTR_COST;
}
}
TerminatorKind::Call { func: Operand::Constant(ref f), unwind, .. } => {
let fn_ty =
self.instance.instantiate_mir(tcx, ty::EarlyBinder::bind(&f.const_.ty()));
self.cost += if let ty::FnDef(def_id, _) = *fn_ty.kind()
&& tcx.is_intrinsic(def_id)
{
// Don't give intrinsics the extra penalty for calls
INSTR_COST
} else {
CALL_PENALTY
};
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
TerminatorKind::Assert { unwind, .. } => {
self.cost += CALL_PENALTY;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
TerminatorKind::UnwindResume => self.cost += RESUME_PENALTY,
TerminatorKind::InlineAsm { unwind, .. } => {
self.cost += INSTR_COST;
if let UnwindAction::Cleanup(_) = unwind {
self.cost += LANDINGPAD_PENALTY;
}
}
_ => self.cost += INSTR_COST,
}
}
}
/**
* Integrator.
*

View file

@ -0,0 +1,759 @@
//! A jump threading optimization.
//!
//! This optimization seeks to replace join-then-switch control flow patterns by straight jumps
//! X = 0 X = 0
//! ------------\ /-------- ------------
//! X = 1 X----X SwitchInt(X) => X = 1
//! ------------/ \-------- ------------
//!
//!
//! We proceed by walking the cfg backwards starting from each `SwitchInt` terminator,
//! looking for assignments that will turn the `SwitchInt` into a simple `Goto`.
//!
//! The algorithm maintains a set of replacement conditions:
//! - `conditions[place]` contains `Condition { value, polarity: Eq, target }`
//! if assigning `value` to `place` turns the `SwitchInt` into `Goto { target }`.
//! - `conditions[place]` contains `Condition { value, polarity: Ne, target }`
//! if assigning anything different from `value` to `place` turns the `SwitchInt`
//! into `Goto { target }`.
//!
//! In this file, we denote as `place ?= value` the existence of a replacement condition
//! on `place` with given `value`, irrespective of the polarity and target of that
//! replacement condition.
//!
//! We then walk the CFG backwards transforming the set of conditions.
//! When we find a fulfilling assignment, we record a `ThreadingOpportunity`.
//! All `ThreadingOpportunity`s are applied to the body, by duplicating blocks if required.
//!
//! The optimization search can be very heavy, as it performs a DFS on MIR starting from
//! each `SwitchInt` terminator. To manage the complexity, we:
//! - bound the maximum depth by a constant `MAX_BACKTRACK`;
//! - we only traverse `Goto` terminators.
//!
//! We try to avoid creating irreducible control-flow by not threading through a loop header.
//!
//! Likewise, applying the optimisation can create a lot of new MIR, so we bound the instruction
//! cost by `MAX_COST`.
use rustc_arena::DroplessArena;
use rustc_data_structures::fx::FxHashSet;
use rustc_index::bit_set::BitSet;
use rustc_index::IndexVec;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
use rustc_mir_dataflow::value_analysis::{Map, PlaceIndex, State, TrackElem};
use crate::cost_checker::CostChecker;
use crate::MirPass;
pub struct JumpThreading;
const MAX_BACKTRACK: usize = 5;
const MAX_COST: usize = 100;
const MAX_PLACES: usize = 100;
impl<'tcx> MirPass<'tcx> for JumpThreading {
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() >= 4
}
#[instrument(skip_all level = "debug")]
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
debug!(?def_id);
let param_env = tcx.param_env_reveal_all_normalized(def_id);
let map = Map::new(tcx, body, Some(MAX_PLACES));
let loop_headers = loop_headers(body);
let arena = DroplessArena::default();
let mut finder = TOFinder {
tcx,
param_env,
body,
arena: &arena,
map: &map,
loop_headers: &loop_headers,
opportunities: Vec::new(),
};
for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
debug!(?bb, term = ?bbdata.terminator());
if bbdata.is_cleanup || loop_headers.contains(bb) {
continue;
}
let Some((discr, targets)) = bbdata.terminator().kind.as_switch() else { continue };
let Some(discr) = discr.place() else { continue };
debug!(?discr, ?bb);
let discr_ty = discr.ty(body, tcx).ty;
let Ok(discr_layout) = tcx.layout_of(param_env.and(discr_ty)) else { continue };
let Some(discr) = finder.map.find(discr.as_ref()) else { continue };
debug!(?discr);
let cost = CostChecker::new(tcx, param_env, None, body);
let mut state = State::new(ConditionSet::default(), &finder.map);
let conds = if let Some((value, then, else_)) = targets.as_static_if() {
let Some(value) = ScalarInt::try_from_uint(value, discr_layout.size) else {
continue;
};
arena.alloc_from_iter([
Condition { value, polarity: Polarity::Eq, target: then },
Condition { value, polarity: Polarity::Ne, target: else_ },
])
} else {
arena.alloc_from_iter(targets.iter().filter_map(|(value, target)| {
let value = ScalarInt::try_from_uint(value, discr_layout.size)?;
Some(Condition { value, polarity: Polarity::Eq, target })
}))
};
let conds = ConditionSet(conds);
state.insert_value_idx(discr, conds, &finder.map);
finder.find_opportunity(bb, state, cost, 0);
}
let opportunities = finder.opportunities;
debug!(?opportunities);
if opportunities.is_empty() {
return;
}
// Verify that we do not thread through a loop header.
for to in opportunities.iter() {
assert!(to.chain.iter().all(|&block| !loop_headers.contains(block)));
}
OpportunitySet::new(body, opportunities).apply(body);
}
}
#[derive(Debug)]
struct ThreadingOpportunity {
/// The list of `BasicBlock`s from the one that found the opportunity to the `SwitchInt`.
chain: Vec<BasicBlock>,
/// The `SwitchInt` will be replaced by `Goto { target }`.
target: BasicBlock,
}
struct TOFinder<'tcx, 'a> {
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
body: &'a Body<'tcx>,
map: &'a Map,
loop_headers: &'a BitSet<BasicBlock>,
/// We use an arena to avoid cloning the slices when cloning `state`.
arena: &'a DroplessArena,
opportunities: Vec<ThreadingOpportunity>,
}
/// Represent the following statement. If we can prove that the current local is equal/not-equal
/// to `value`, jump to `target`.
#[derive(Copy, Clone, Debug)]
struct Condition {
value: ScalarInt,
polarity: Polarity,
target: BasicBlock,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Polarity {
Ne,
Eq,
}
impl Condition {
fn matches(&self, value: ScalarInt) -> bool {
(self.value == value) == (self.polarity == Polarity::Eq)
}
fn inv(mut self) -> Self {
self.polarity = match self.polarity {
Polarity::Eq => Polarity::Ne,
Polarity::Ne => Polarity::Eq,
};
self
}
}
#[derive(Copy, Clone, Debug, Default)]
struct ConditionSet<'a>(&'a [Condition]);
impl<'a> ConditionSet<'a> {
fn iter(self) -> impl Iterator<Item = Condition> + 'a {
self.0.iter().copied()
}
fn iter_matches(self, value: ScalarInt) -> impl Iterator<Item = Condition> + 'a {
self.iter().filter(move |c| c.matches(value))
}
fn map(self, arena: &'a DroplessArena, f: impl Fn(Condition) -> Condition) -> ConditionSet<'a> {
ConditionSet(arena.alloc_from_iter(self.iter().map(f)))
}
}
impl<'tcx, 'a> TOFinder<'tcx, 'a> {
fn is_empty(&self, state: &State<ConditionSet<'a>>) -> bool {
state.all(|cs| cs.0.is_empty())
}
/// Recursion entry point to find threading opportunities.
#[instrument(level = "trace", skip(self, cost), ret)]
fn find_opportunity(
&mut self,
bb: BasicBlock,
mut state: State<ConditionSet<'a>>,
mut cost: CostChecker<'_, 'tcx>,
depth: usize,
) {
// Do not thread through loop headers.
if self.loop_headers.contains(bb) {
return;
}
debug!(cost = ?cost.cost());
for (statement_index, stmt) in
self.body.basic_blocks[bb].statements.iter().enumerate().rev()
{
if self.is_empty(&state) {
return;
}
cost.visit_statement(stmt, Location { block: bb, statement_index });
if cost.cost() > MAX_COST {
return;
}
// Attempt to turn the `current_condition` on `lhs` into a condition on another place.
self.process_statement(bb, stmt, &mut state);
// When a statement mutates a place, assignments to that place that happen
// above the mutation cannot fulfill a condition.
// _1 = 5 // Whatever happens here, it won't change the result of a `SwitchInt`.
// _1 = 6
if let Some((lhs, tail)) = self.mutated_statement(stmt) {
state.flood_with_tail_elem(lhs.as_ref(), tail, self.map, ConditionSet::default());
}
}
if self.is_empty(&state) || depth >= MAX_BACKTRACK {
return;
}
let last_non_rec = self.opportunities.len();
let predecessors = &self.body.basic_blocks.predecessors()[bb];
if let &[pred] = &predecessors[..] && bb != START_BLOCK {
let term = self.body.basic_blocks[pred].terminator();
match term.kind {
TerminatorKind::SwitchInt { ref discr, ref targets } => {
self.process_switch_int(discr, targets, bb, &mut state);
self.find_opportunity(pred, state, cost, depth + 1);
}
_ => self.recurse_through_terminator(pred, &state, &cost, depth),
}
} else {
for &pred in predecessors {
self.recurse_through_terminator(pred, &state, &cost, depth);
}
}
let new_tos = &mut self.opportunities[last_non_rec..];
debug!(?new_tos);
// Try to deduplicate threading opportunities.
if new_tos.len() > 1
&& new_tos.len() == predecessors.len()
&& predecessors
.iter()
.zip(new_tos.iter())
.all(|(&pred, to)| to.chain == &[pred] && to.target == new_tos[0].target)
{
// All predecessors have a threading opportunity, and they all point to the same block.
debug!(?new_tos, "dedup");
let first = &mut new_tos[0];
*first = ThreadingOpportunity { chain: vec![bb], target: first.target };
self.opportunities.truncate(last_non_rec + 1);
return;
}
for op in self.opportunities[last_non_rec..].iter_mut() {
op.chain.push(bb);
}
}
/// Extract the mutated place from a statement.
///
/// This method returns the `Place` so we can flood the state in case of a partial assignment.
/// (_1 as Ok).0 = _5;
/// (_1 as Err).0 = _6;
/// We want to ensure that a `SwitchInt((_1 as Ok).0)` does not see the first assignment, as
/// the value may have been mangled by the second assignment.
///
/// In case we assign to a discriminant, we return `Some(TrackElem::Discriminant)`, so we can
/// stop at flooding the discriminant, and preserve the variant fields.
/// (_1 as Some).0 = _6;
/// SetDiscriminant(_1, 1);
/// switchInt((_1 as Some).0)
#[instrument(level = "trace", skip(self), ret)]
fn mutated_statement(
&self,
stmt: &Statement<'tcx>,
) -> Option<(Place<'tcx>, Option<TrackElem>)> {
match stmt.kind {
StatementKind::Assign(box (place, _))
| StatementKind::Deinit(box place) => Some((place, None)),
StatementKind::SetDiscriminant { box place, variant_index: _ } => {
Some((place, Some(TrackElem::Discriminant)))
}
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
Some((Place::from(local), None))
}
StatementKind::Retag(..)
| StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(..))
// copy_nonoverlapping takes pointers and mutated the pointed-to value.
| StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(..))
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::FakeRead(..)
| StatementKind::ConstEvalCounter
| StatementKind::PlaceMention(..)
| StatementKind::Nop => None,
}
}
#[instrument(level = "trace", skip(self))]
fn process_operand(
&mut self,
bb: BasicBlock,
lhs: PlaceIndex,
rhs: &Operand<'tcx>,
state: &mut State<ConditionSet<'a>>,
) -> Option<!> {
let register_opportunity = |c: Condition| {
debug!(?bb, ?c.target, "register");
self.opportunities.push(ThreadingOpportunity { chain: vec![bb], target: c.target })
};
match rhs {
// If we expect `lhs ?= A`, we have an opportunity if we assume `constant == A`.
Operand::Constant(constant) => {
let conditions = state.try_get_idx(lhs, self.map)?;
let constant =
constant.const_.normalize(self.tcx, self.param_env).try_to_scalar_int()?;
conditions.iter_matches(constant).for_each(register_opportunity);
}
// Transfer the conditions on the copied rhs.
Operand::Move(rhs) | Operand::Copy(rhs) => {
let rhs = self.map.find(rhs.as_ref())?;
state.insert_place_idx(rhs, lhs, self.map);
}
}
None
}
#[instrument(level = "trace", skip(self))]
fn process_statement(
&mut self,
bb: BasicBlock,
stmt: &Statement<'tcx>,
state: &mut State<ConditionSet<'a>>,
) -> Option<!> {
let register_opportunity = |c: Condition| {
debug!(?bb, ?c.target, "register");
self.opportunities.push(ThreadingOpportunity { chain: vec![bb], target: c.target })
};
// Below, `lhs` is the return value of `mutated_statement`,
// the place to which `conditions` apply.
let discriminant_for_variant = |enum_ty: Ty<'tcx>, variant_index| {
let discr = enum_ty.discriminant_for_variant(self.tcx, variant_index)?;
let discr_layout = self.tcx.layout_of(self.param_env.and(discr.ty)).ok()?;
let scalar = ScalarInt::try_from_uint(discr.val, discr_layout.size)?;
Some(Operand::const_from_scalar(
self.tcx,
discr.ty,
scalar.into(),
rustc_span::DUMMY_SP,
))
};
match &stmt.kind {
// If we expect `discriminant(place) ?= A`,
// we have an opportunity if `variant_index ?= A`.
StatementKind::SetDiscriminant { box place, variant_index } => {
let discr_target = self.map.find_discr(place.as_ref())?;
let enum_ty = place.ty(self.body, self.tcx).ty;
let discr = discriminant_for_variant(enum_ty, *variant_index)?;
self.process_operand(bb, discr_target, &discr, state)?;
}
// If we expect `lhs ?= true`, we have an opportunity if we assume `lhs == true`.
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(
Operand::Copy(place) | Operand::Move(place),
)) => {
let conditions = state.try_get(place.as_ref(), self.map)?;
conditions.iter_matches(ScalarInt::TRUE).for_each(register_opportunity);
}
StatementKind::Assign(box (lhs_place, rhs)) => {
if let Some(lhs) = self.map.find(lhs_place.as_ref()) {
match rhs {
Rvalue::Use(operand) => self.process_operand(bb, lhs, operand, state)?,
// Transfer the conditions on the copy rhs.
Rvalue::CopyForDeref(rhs) => {
self.process_operand(bb, lhs, &Operand::Copy(*rhs), state)?
}
Rvalue::Discriminant(rhs) => {
let rhs = self.map.find_discr(rhs.as_ref())?;
state.insert_place_idx(rhs, lhs, self.map);
}
// If we expect `lhs ?= A`, we have an opportunity if we assume `constant == A`.
Rvalue::Aggregate(box ref kind, ref operands) => {
let agg_ty = lhs_place.ty(self.body, self.tcx).ty;
let lhs = match kind {
// Do not support unions.
AggregateKind::Adt(.., Some(_)) => return None,
AggregateKind::Adt(_, variant_index, ..) if agg_ty.is_enum() => {
if let Some(discr_target) = self.map.apply(lhs, TrackElem::Discriminant)
&& let Some(discr_value) = discriminant_for_variant(agg_ty, *variant_index)
{
self.process_operand(bb, discr_target, &discr_value, state);
}
self.map.apply(lhs, TrackElem::Variant(*variant_index))?
}
_ => lhs,
};
for (field_index, operand) in operands.iter_enumerated() {
if let Some(field) =
self.map.apply(lhs, TrackElem::Field(field_index))
{
self.process_operand(bb, field, operand, state);
}
}
}
// Transfer the conditions on the copy rhs, after inversing polarity.
Rvalue::UnaryOp(UnOp::Not, Operand::Move(place) | Operand::Copy(place)) => {
let conditions = state.try_get_idx(lhs, self.map)?;
let place = self.map.find(place.as_ref())?;
let conds = conditions.map(self.arena, Condition::inv);
state.insert_value_idx(place, conds, self.map);
}
// We expect `lhs ?= A`. We found `lhs = Eq(rhs, B)`.
// Create a condition on `rhs ?= B`.
Rvalue::BinaryOp(
op,
box (
Operand::Move(place) | Operand::Copy(place),
Operand::Constant(value),
)
| box (
Operand::Constant(value),
Operand::Move(place) | Operand::Copy(place),
),
) => {
let conditions = state.try_get_idx(lhs, self.map)?;
let place = self.map.find(place.as_ref())?;
let equals = match op {
BinOp::Eq => ScalarInt::TRUE,
BinOp::Ne => ScalarInt::FALSE,
_ => return None,
};
let value = value
.const_
.normalize(self.tcx, self.param_env)
.try_to_scalar_int()?;
let conds = conditions.map(self.arena, |c| Condition {
value,
polarity: if c.matches(equals) {
Polarity::Eq
} else {
Polarity::Ne
},
..c
});
state.insert_value_idx(place, conds, self.map);
}
_ => {}
}
}
}
_ => {}
}
None
}
#[instrument(level = "trace", skip(self, cost))]
fn recurse_through_terminator(
&mut self,
bb: BasicBlock,
state: &State<ConditionSet<'a>>,
cost: &CostChecker<'_, 'tcx>,
depth: usize,
) {
let register_opportunity = |c: Condition| {
debug!(?bb, ?c.target, "register");
self.opportunities.push(ThreadingOpportunity { chain: vec![bb], target: c.target })
};
let term = self.body.basic_blocks[bb].terminator();
let place_to_flood = match term.kind {
// We come from a target, so those are not possible.
TerminatorKind::UnwindResume
| TerminatorKind::UnwindTerminate(_)
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::CoroutineDrop => bug!("{term:?} has no terminators"),
// Disallowed during optimizations.
TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Yield { .. } => bug!("{term:?} invalid"),
// Cannot reason about inline asm.
TerminatorKind::InlineAsm { .. } => return,
// `SwitchInt` is handled specially.
TerminatorKind::SwitchInt { .. } => return,
// We can recurse, no thing particular to do.
TerminatorKind::Goto { .. } => None,
// Flood the overwritten place, and progress through.
TerminatorKind::Drop { place: destination, .. }
| TerminatorKind::Call { destination, .. } => Some(destination),
// Treat as an `assume(cond == expected)`.
TerminatorKind::Assert { ref cond, expected, .. } => {
if let Some(place) = cond.place()
&& let Some(conditions) = state.try_get(place.as_ref(), self.map)
{
let expected = if expected { ScalarInt::TRUE } else { ScalarInt::FALSE };
conditions.iter_matches(expected).for_each(register_opportunity);
}
None
}
};
// We can recurse through this terminator.
let mut state = state.clone();
if let Some(place_to_flood) = place_to_flood {
state.flood_with(place_to_flood.as_ref(), self.map, ConditionSet::default());
}
self.find_opportunity(bb, state, cost.clone(), depth + 1);
}
#[instrument(level = "trace", skip(self))]
fn process_switch_int(
&mut self,
discr: &Operand<'tcx>,
targets: &SwitchTargets,
target_bb: BasicBlock,
state: &mut State<ConditionSet<'a>>,
) -> Option<!> {
debug_assert_ne!(target_bb, START_BLOCK);
debug_assert_eq!(self.body.basic_blocks.predecessors()[target_bb].len(), 1);
let discr = discr.place()?;
let discr_ty = discr.ty(self.body, self.tcx).ty;
let discr_layout = self.tcx.layout_of(self.param_env.and(discr_ty)).ok()?;
let conditions = state.try_get(discr.as_ref(), self.map)?;
if let Some((value, _)) = targets.iter().find(|&(_, target)| target == target_bb) {
let value = ScalarInt::try_from_uint(value, discr_layout.size)?;
debug_assert_eq!(targets.iter().filter(|&(_, target)| target == target_bb).count(), 1);
// We are inside `target_bb`. Since we have a single predecessor, we know we passed
// through the `SwitchInt` before arriving here. Therefore, we know that
// `discr == value`. If one condition can be fulfilled by `discr == value`,
// that's an opportunity.
for c in conditions.iter_matches(value) {
debug!(?target_bb, ?c.target, "register");
self.opportunities.push(ThreadingOpportunity { chain: vec![], target: c.target });
}
} else if let Some((value, _, else_bb)) = targets.as_static_if()
&& target_bb == else_bb
{
let value = ScalarInt::try_from_uint(value, discr_layout.size)?;
// We only know that `discr != value`. That's much weaker information than
// the equality we had in the previous arm. All we can conclude is that
// the replacement condition `discr != value` can be threaded, and nothing else.
for c in conditions.iter() {
if c.value == value && c.polarity == Polarity::Ne {
debug!(?target_bb, ?c.target, "register");
self.opportunities
.push(ThreadingOpportunity { chain: vec![], target: c.target });
}
}
}
None
}
}
struct OpportunitySet {
opportunities: Vec<ThreadingOpportunity>,
/// For each bb, give the TOs in which it appears. The pair corresponds to the index
/// in `opportunities` and the index in `ThreadingOpportunity::chain`.
involving_tos: IndexVec<BasicBlock, Vec<(usize, usize)>>,
/// Cache the number of predecessors for each block, as we clear the basic block cache..
predecessors: IndexVec<BasicBlock, usize>,
}
impl OpportunitySet {
fn new(body: &Body<'_>, opportunities: Vec<ThreadingOpportunity>) -> OpportunitySet {
let mut involving_tos = IndexVec::from_elem(Vec::new(), &body.basic_blocks);
for (index, to) in opportunities.iter().enumerate() {
for (ibb, &bb) in to.chain.iter().enumerate() {
involving_tos[bb].push((index, ibb));
}
involving_tos[to.target].push((index, to.chain.len()));
}
let predecessors = predecessor_count(body);
OpportunitySet { opportunities, involving_tos, predecessors }
}
/// Apply the opportunities on the graph.
fn apply(&mut self, body: &mut Body<'_>) {
for i in 0..self.opportunities.len() {
self.apply_once(i, body);
}
}
#[instrument(level = "trace", skip(self, body))]
fn apply_once(&mut self, index: usize, body: &mut Body<'_>) {
debug!(?self.predecessors);
debug!(?self.involving_tos);
// Check that `predecessors` satisfies its invariant.
debug_assert_eq!(self.predecessors, predecessor_count(body));
// Remove the TO from the vector to allow modifying the other ones later.
let op = &mut self.opportunities[index];
debug!(?op);
let op_chain = std::mem::take(&mut op.chain);
let op_target = op.target;
debug_assert_eq!(op_chain.len(), op_chain.iter().collect::<FxHashSet<_>>().len());
let Some((current, chain)) = op_chain.split_first() else { return };
let basic_blocks = body.basic_blocks.as_mut();
// Invariant: the control-flow is well-formed at the end of each iteration.
let mut current = *current;
for &succ in chain {
debug!(?current, ?succ);
// `succ` must be a successor of `current`. If it is not, this means this TO is not
// satisfiable and a previous TO erased this edge, so we bail out.
if basic_blocks[current].terminator().successors().find(|s| *s == succ).is_none() {
debug!("impossible");
return;
}
// Fast path: `succ` is only used once, so we can reuse it directly.
if self.predecessors[succ] == 1 {
debug!("single");
current = succ;
continue;
}
let new_succ = basic_blocks.push(basic_blocks[succ].clone());
debug!(?new_succ);
// Replace `succ` by `new_succ` where it appears.
let mut num_edges = 0;
for s in basic_blocks[current].terminator_mut().successors_mut() {
if *s == succ {
*s = new_succ;
num_edges += 1;
}
}
// Update predecessors with the new block.
let _new_succ = self.predecessors.push(num_edges);
debug_assert_eq!(new_succ, _new_succ);
self.predecessors[succ] -= num_edges;
self.update_predecessor_count(basic_blocks[new_succ].terminator(), Update::Incr);
// Replace the `current -> succ` edge by `current -> new_succ` in all the following
// TOs. This is necessary to avoid trying to thread through a non-existing edge. We
// use `involving_tos` here to avoid traversing the full set of TOs on each iteration.
let mut new_involved = Vec::new();
for &(to_index, in_to_index) in &self.involving_tos[current] {
// That TO has already been applied, do nothing.
if to_index <= index {
continue;
}
let other_to = &mut self.opportunities[to_index];
if other_to.chain.get(in_to_index) != Some(&current) {
continue;
}
let s = other_to.chain.get_mut(in_to_index + 1).unwrap_or(&mut other_to.target);
if *s == succ {
// `other_to` references the `current -> succ` edge, so replace `succ`.
*s = new_succ;
new_involved.push((to_index, in_to_index + 1));
}
}
// The TOs that we just updated now reference `new_succ`. Update `involving_tos`
// in case we need to duplicate an edge starting at `new_succ` later.
let _new_succ = self.involving_tos.push(new_involved);
debug_assert_eq!(new_succ, _new_succ);
current = new_succ;
}
let current = &mut basic_blocks[current];
self.update_predecessor_count(current.terminator(), Update::Decr);
current.terminator_mut().kind = TerminatorKind::Goto { target: op_target };
self.predecessors[op_target] += 1;
}
fn update_predecessor_count(&mut self, terminator: &Terminator<'_>, incr: Update) {
match incr {
Update::Incr => {
for s in terminator.successors() {
self.predecessors[s] += 1;
}
}
Update::Decr => {
for s in terminator.successors() {
self.predecessors[s] -= 1;
}
}
}
}
}
fn predecessor_count(body: &Body<'_>) -> IndexVec<BasicBlock, usize> {
let mut predecessors: IndexVec<_, _> =
body.basic_blocks.predecessors().iter().map(|ps| ps.len()).collect();
predecessors[START_BLOCK] += 1; // Account for the implicit entry edge.
predecessors
}
enum Update {
Incr,
Decr,
}
/// Compute the set of loop headers in the given body. We define a loop header as a block which has
/// at least a predecessor which it dominates. This definition is only correct for reducible CFGs.
/// But if the CFG is already irreducible, there is no point in trying much harder.
/// is already irreducible.
fn loop_headers(body: &Body<'_>) -> BitSet<BasicBlock> {
let mut loop_headers = BitSet::new_empty(body.basic_blocks.len());
let dominators = body.basic_blocks.dominators();
// Only visit reachable blocks.
for (bb, bbdata) in traversal::preorder(body) {
for succ in bbdata.terminator().successors() {
if dominators.dominates(succ, bb) {
loop_headers.insert(succ);
}
}
}
loop_headers
}

View file

@ -62,6 +62,7 @@ mod const_prop;
mod const_prop_lint;
mod copy_prop;
mod coroutine;
mod cost_checker;
mod coverage;
mod cross_crate_inline;
mod ctfe_limit;
@ -81,6 +82,7 @@ mod function_item_references;
mod gvn;
pub mod inline;
mod instsimplify;
mod jump_threading;
mod large_enums;
mod lower_intrinsics;
mod lower_slice_len;
@ -571,6 +573,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
&dataflow_const_prop::DataflowConstProp,
&const_debuginfo::ConstDebugInfo,
&o1(simplify_branches::SimplifyConstCondition::AfterConstProp),
&jump_threading::JumpThreading,
&early_otherwise_branch::EarlyOtherwiseBranch,
&simplify_comparison_integral::SimplifyComparisonIntegral,
&dead_store_elimination::DeadStoreElimination,

View file

@ -24,11 +24,8 @@ pub struct RemoveUninitDrops;
impl<'tcx> MirPass<'tcx> for RemoveUninitDrops {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let param_env = tcx.param_env(body.source.def_id());
let Ok(move_data) = MoveData::gather_moves(body, tcx, param_env) else {
// We could continue if there are move errors, but there's not much point since our
// init data isn't complete.
return;
};
let move_data =
MoveData::gather_moves(&body, tcx, param_env, |ty| ty.needs_drop(tcx, param_env));
let mdpe = MoveDataParamEnv { move_data, param_env };
let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe)

View file

@ -332,7 +332,7 @@ impl<D: Deps> DepGraphData<D> {
/// - If you need 3+ arguments, use a tuple for the
/// `arg` parameter.
///
/// [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/incremental-compilation.html
/// [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html
#[inline(always)]
pub fn with_task<Ctxt: HasDepContext<Deps = D>, A: Debug, R>(
&self,

View file

@ -59,7 +59,6 @@ struct UnusedImportCheckVisitor<'a, 'b, 'tcx> {
base_use_tree: Option<&'a ast::UseTree>,
base_id: ast::NodeId,
item_span: Span,
base_use_is_pub: bool,
}
struct ExternCrateToLint {
@ -146,7 +145,6 @@ impl<'a, 'b, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
// because this means that they were generated in some fashion by the
// compiler and we don't need to consider them.
ast::ItemKind::Use(..) if item.span.is_dummy() => return,
ast::ItemKind::Use(..) => self.base_use_is_pub = item.vis.kind.is_pub(),
ast::ItemKind::ExternCrate(orig_name) => {
self.extern_crate_items.push(ExternCrateToLint {
id: item.id,
@ -173,7 +171,7 @@ impl<'a, 'b, 'tcx> Visitor<'a> for UnusedImportCheckVisitor<'a, 'b, 'tcx> {
self.base_use_tree = Some(use_tree);
}
if self.base_use_is_pub {
if self.r.effective_visibilities.is_exported(self.r.local_def_id(id)) {
self.check_import_as_underscore(use_tree, id);
return;
}
@ -332,7 +330,6 @@ impl Resolver<'_, '_> {
base_use_tree: None,
base_id: ast::DUMMY_NODE_ID,
item_span: DUMMY_SP,
base_use_is_pub: false,
};
visit::walk_crate(&mut visitor, krate);

View file

@ -3463,9 +3463,10 @@ impl DumpMonoStatsFormat {
/// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy,
/// or future prototype.
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)]
pub enum Polonius {
/// The default value: disabled.
#[default]
Off,
/// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`.
@ -3475,12 +3476,6 @@ pub enum Polonius {
Next,
}
impl Default for Polonius {
fn default() -> Self {
Polonius::Off
}
}
impl Polonius {
/// Returns whether the legacy version of polonius is enabled
pub fn is_legacy_enabled(&self) -> bool {

View file

@ -5,9 +5,7 @@ edition = "2021"
[dependencies]
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_driver = { path = "../rustc_driver" }
rustc_hir = { path = "../rustc_hir" }
rustc_interface = { path = "../rustc_interface" }
rustc_middle = { path = "../rustc_middle" }
rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" }

View file

@ -3,22 +3,18 @@
//! For that, we define APIs that will temporarily be public to 3P that exposes rustc internal APIs
//! until stable MIR is complete.
use crate::rustc_internal;
use crate::rustc_smir::Tables;
use rustc_data_structures::fx;
use rustc_data_structures::fx::FxIndexMap;
use rustc_driver::{Callbacks, Compilation, RunCompiler};
use rustc_interface::{interface, Queries};
use rustc_middle::mir::interpret::AllocId;
use rustc_middle::ty;
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::Span;
use stable_mir::ty::IndexedVal;
use stable_mir::CompilerError;
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::{ControlFlow, Index};
use std::ops::Index;
mod internal;
@ -143,63 +139,81 @@ pub fn run(tcx: TyCtxt<'_>, f: impl FnOnce()) {
);
}
pub struct StableMir<B = (), C = ()>
where
B: Send,
C: Send,
{
args: Vec<String>,
callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>,
result: Option<ControlFlow<B, C>>,
}
#[macro_export]
macro_rules! run {
($args:expr, $callback:expr) => {
run!($args, tcx, $callback)
};
($args:expr, $tcx:ident, $callback:expr) => {{
use rustc_driver::{Callbacks, Compilation, RunCompiler};
use rustc_interface::{interface, Queries};
use stable_mir::CompilerError;
use std::ops::ControlFlow;
impl<B, C> StableMir<B, C>
where
B: Send,
C: Send,
{
/// Creates a new `StableMir` instance, with given test_function and arguments.
pub fn new(args: Vec<String>, callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>) -> Self {
StableMir { args, callback, result: None }
}
/// Runs the compiler against given target and tests it with `test_function`
pub fn run(&mut self) -> Result<C, CompilerError<B>> {
let compiler_result =
rustc_driver::catch_fatal_errors(|| RunCompiler::new(&self.args.clone(), self).run());
match (compiler_result, self.result.take()) {
(Ok(Ok(())), Some(ControlFlow::Continue(value))) => Ok(value),
(Ok(Ok(())), Some(ControlFlow::Break(value))) => Err(CompilerError::Interrupted(value)),
(Ok(Ok(_)), None) => Err(CompilerError::Skipped),
(Ok(Err(_)), _) => Err(CompilerError::CompilationFailed),
(Err(_), _) => Err(CompilerError::ICE),
pub struct StableMir<B = (), C = ()>
where
B: Send,
C: Send,
{
args: Vec<String>,
callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>,
result: Option<ControlFlow<B, C>>,
}
}
}
impl<B, C> Callbacks for StableMir<B, C>
where
B: Send,
C: Send,
{
/// Called after analysis. Return value instructs the compiler whether to
/// continue the compilation afterwards (defaults to `Compilation::Continue`)
fn after_analysis<'tcx>(
&mut self,
_compiler: &interface::Compiler,
queries: &'tcx Queries<'tcx>,
) -> Compilation {
queries.global_ctxt().unwrap().enter(|tcx| {
rustc_internal::run(tcx, || {
self.result = Some((self.callback)(tcx));
});
if self.result.as_ref().is_some_and(|val| val.is_continue()) {
Compilation::Continue
} else {
Compilation::Stop
impl<B, C> StableMir<B, C>
where
B: Send,
C: Send,
{
/// Creates a new `StableMir` instance, with given test_function and arguments.
pub fn new(args: Vec<String>, callback: fn(TyCtxt<'_>) -> ControlFlow<B, C>) -> Self {
StableMir { args, callback, result: None }
}
})
}
/// Runs the compiler against given target and tests it with `test_function`
pub fn run(&mut self) -> Result<C, CompilerError<B>> {
let compiler_result = rustc_driver::catch_fatal_errors(|| {
RunCompiler::new(&self.args.clone(), self).run()
});
match (compiler_result, self.result.take()) {
(Ok(Ok(())), Some(ControlFlow::Continue(value))) => Ok(value),
(Ok(Ok(())), Some(ControlFlow::Break(value))) => {
Err(CompilerError::Interrupted(value))
}
(Ok(Ok(_)), None) => Err(CompilerError::Skipped),
(Ok(Err(_)), _) => Err(CompilerError::CompilationFailed),
(Err(_), _) => Err(CompilerError::ICE),
}
}
}
impl<B, C> Callbacks for StableMir<B, C>
where
B: Send,
C: Send,
{
/// Called after analysis. Return value instructs the compiler whether to
/// continue the compilation afterwards (defaults to `Compilation::Continue`)
fn after_analysis<'tcx>(
&mut self,
_compiler: &interface::Compiler,
queries: &'tcx Queries<'tcx>,
) -> Compilation {
queries.global_ctxt().unwrap().enter(|tcx| {
rustc_internal::run(tcx, || {
self.result = Some((self.callback)(tcx));
});
if self.result.as_ref().is_some_and(|val| val.is_continue()) {
Compilation::Continue
} else {
Compilation::Stop
}
})
}
}
StableMir::new($args, |$tcx| $callback).run()
}};
}
/// Simmilar to rustc's `FxIndexMap`, `IndexMap` with extra

View file

@ -2247,7 +2247,7 @@ where
/// Useful type to use with `Result<>` indicate that an error has already
/// been reported to the user, so no need to continue checking.
#[derive(Clone, Copy, Debug, Encodable, Decodable, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(HashStable_Generic)]
pub struct ErrorGuaranteed(());
@ -2259,3 +2259,20 @@ impl ErrorGuaranteed {
ErrorGuaranteed(())
}
}
impl<E: rustc_serialize::Encoder> Encodable<E> for ErrorGuaranteed {
#[inline]
fn encode(&self, _e: &mut E) {
panic!(
"should never serialize an `ErrorGuaranteed`, as we do not write metadata or incremental caches in case errors occurred"
)
}
}
impl<D: rustc_serialize::Decoder> Decodable<D> for ErrorGuaranteed {
#[inline]
fn decode(_d: &mut D) -> ErrorGuaranteed {
panic!(
"`ErrorGuaranteed` should never have been serialized to metadata or incremental caches"
)
}
}

View file

@ -199,16 +199,16 @@ struct SymbolPrinter<'tcx> {
// `PrettyPrinter` aka pretty printing of e.g. types in paths,
// symbol names should have their own printing machinery.
impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
impl<'tcx> Printer<'tcx> for SymbolPrinter<'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn print_region(self, _region: ty::Region<'_>) -> Result<Self, PrintError> {
Ok(self)
fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
Ok(())
}
fn print_type(mut self, ty: Ty<'tcx>) -> Result<Self, PrintError> {
fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> {
match *ty.kind() {
// Print all nominal types as paths (unlike `pretty_print_type`).
ty::FnDef(def_id, args)
@ -220,17 +220,17 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
// -Zverbose flag, so we cannot reuse it here.
ty::Array(ty, size) => {
self.write_str("[")?;
self = self.print_type(ty)?;
self.print_type(ty)?;
self.write_str("; ")?;
if let Some(size) = size.try_to_target_usize(self.tcx()) {
write!(self, "{size}")?
} else if let ty::ConstKind::Param(param) = size.kind() {
self = param.print(self)?
param.print(self)?
} else {
self.write_str("_")?
}
self.write_str("]")?;
Ok(self)
Ok(())
}
ty::Alias(ty::Inherent, _) => panic!("unexpected inherent projection"),
@ -240,21 +240,21 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
}
fn print_dyn_existential(
mut self,
&mut self,
predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
let mut first = true;
for p in predicates {
if !first {
write!(self, "+")?;
}
first = false;
self = p.print(self)?;
p.print(self)?;
}
Ok(self)
Ok(())
}
fn print_const(self, ct: ty::Const<'tcx>) -> Result<Self, PrintError> {
fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError> {
// only print integers
match (ct.kind(), ct.ty().kind()) {
(ty::ConstKind::Value(ty::ValTree::Leaf(scalar)), ty::Int(_) | ty::Uint(_)) => {
@ -269,18 +269,18 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
}
_ => self.write_str("_")?,
}
Ok(self)
Ok(())
}
fn path_crate(self, cnum: CrateNum) -> Result<Self, PrintError> {
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.write_str(self.tcx.crate_name(cnum).as_str())?;
Ok(self)
Ok(())
}
fn path_qualified(
self,
&mut self,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
// Similar to `pretty_path_qualified`, but for the other
// types that are printed as paths (see `print_type` above).
match self_ty.kind() {
@ -295,15 +295,15 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
}
fn path_append_impl(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_disambiguated_data: &DisambiguatedDefPathData,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
self.pretty_path_append_impl(
|mut cx| {
cx = print_prefix(cx)?;
|cx| {
print_prefix(cx)?;
if cx.keep_within_component {
// HACK(eddyb) print the path similarly to how `FmtPrinter` prints it.
@ -312,22 +312,22 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
cx.path.finalize_pending_component();
}
Ok(cx)
Ok(())
},
self_ty,
trait_ref,
)
}
fn path_append(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError> {
self = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
// Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs.
if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data {
return Ok(self);
return Ok(());
}
if self.keep_within_component {
@ -339,14 +339,14 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
write!(self, "{}", disambiguated_data.data)?;
Ok(self)
Ok(())
}
fn path_generic_args(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError> {
self = print_prefix(self)?;
) -> Result<(), PrintError> {
print_prefix(self)?;
let args =
args.iter().cloned().filter(|arg| !matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
@ -354,42 +354,42 @@ impl<'tcx> Printer<'tcx> for &mut SymbolPrinter<'tcx> {
if args.clone().next().is_some() {
self.generic_delimiters(|cx| cx.comma_sep(args))
} else {
Ok(self)
Ok(())
}
}
}
impl<'tcx> PrettyPrinter<'tcx> for &mut SymbolPrinter<'tcx> {
impl<'tcx> PrettyPrinter<'tcx> for SymbolPrinter<'tcx> {
fn should_print_region(&self, _region: ty::Region<'_>) -> bool {
false
}
fn comma_sep<T>(mut self, mut elems: impl Iterator<Item = T>) -> Result<Self, PrintError>
fn comma_sep<T>(&mut self, mut elems: impl Iterator<Item = T>) -> Result<(), PrintError>
where
T: Print<'tcx, Self>,
{
if let Some(first) = elems.next() {
self = first.print(self)?;
first.print(self)?;
for elem in elems {
self.write_str(",")?;
self = elem.print(self)?;
elem.print(self)?;
}
}
Ok(self)
Ok(())
}
fn generic_delimiters(
mut self,
f: impl FnOnce(Self) -> Result<Self, PrintError>,
) -> Result<Self, PrintError> {
&mut self,
f: impl FnOnce(&mut Self) -> Result<(), PrintError>,
) -> Result<(), PrintError> {
write!(self, "<")?;
let kept_within_component = mem::replace(&mut self.keep_within_component, true);
self = f(self)?;
f(self)?;
self.keep_within_component = kept_within_component;
write!(self, ">")?;
Ok(self)
Ok(())
}
}

View file

@ -30,7 +30,7 @@ pub(super) fn mangle<'tcx>(
let args = tcx.normalize_erasing_regions(ty::ParamEnv::reveal_all(), instance.args);
let prefix = "_R";
let mut cx = &mut SymbolMangler {
let mut cx: SymbolMangler<'_> = SymbolMangler {
tcx,
start_offset: prefix.len(),
paths: FxHashMap::default(),
@ -49,13 +49,13 @@ pub(super) fn mangle<'tcx>(
_ => None,
};
cx = if let Some(shim_kind) = shim_kind {
if let Some(shim_kind) = shim_kind {
cx.path_append_ns(|cx| cx.print_def_path(def_id, args), 'S', 0, shim_kind).unwrap()
} else {
cx.print_def_path(def_id, args).unwrap()
};
if let Some(instantiating_crate) = instantiating_crate {
cx = cx.print_def_path(instantiating_crate.as_def_id(), &[]).unwrap();
cx.print_def_path(instantiating_crate.as_def_id(), &[]).unwrap();
}
std::mem::take(&mut cx.out)
}
@ -65,7 +65,7 @@ pub(super) fn mangle_typeid_for_trait_ref<'tcx>(
trait_ref: ty::PolyExistentialTraitRef<'tcx>,
) -> String {
// FIXME(flip1995): See comment in `mangle_typeid_for_fnabi`.
let mut cx = &mut SymbolMangler {
let mut cx = SymbolMangler {
tcx,
start_offset: 0,
paths: FxHashMap::default(),
@ -74,7 +74,7 @@ pub(super) fn mangle_typeid_for_trait_ref<'tcx>(
binders: vec![],
out: String::new(),
};
cx = cx.print_def_path(trait_ref.def_id(), &[]).unwrap();
cx.print_def_path(trait_ref.def_id(), &[]).unwrap();
std::mem::take(&mut cx.out)
}
@ -179,32 +179,32 @@ impl<'tcx> SymbolMangler<'tcx> {
self.push(ident);
}
fn path_append_ns<'a>(
mut self: &'a mut Self,
print_prefix: impl FnOnce(&'a mut Self) -> Result<&'a mut Self, PrintError>,
fn path_append_ns(
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
ns: char,
disambiguator: u64,
name: &str,
) -> Result<&'a mut Self, PrintError> {
) -> Result<(), PrintError> {
self.push("N");
self.out.push(ns);
self = print_prefix(self)?;
print_prefix(self)?;
self.push_disambiguator(disambiguator as u64);
self.push_ident(name);
Ok(self)
Ok(())
}
fn print_backref(&mut self, i: usize) -> Result<&mut Self, PrintError> {
fn print_backref(&mut self, i: usize) -> Result<(), PrintError> {
self.push("B");
self.push_integer_62((i - self.start_offset) as u64);
Ok(self)
Ok(())
}
fn in_binder<'a, T>(
mut self: &'a mut Self,
fn in_binder<T>(
&mut self,
value: &ty::Binder<'tcx, T>,
print_value: impl FnOnce(&'a mut Self, &T) -> Result<&'a mut Self, PrintError>,
) -> Result<&'a mut Self, PrintError>
print_value: impl FnOnce(&mut Self, &T) -> Result<(), PrintError>,
) -> Result<(), PrintError>
where
T: TypeVisitable<TyCtxt<'tcx>>,
{
@ -222,45 +222,45 @@ impl<'tcx> SymbolMangler<'tcx> {
lifetime_depths.end += lifetimes;
self.binders.push(BinderLevel { lifetime_depths });
self = print_value(self, value.as_ref().skip_binder())?;
print_value(self, value.as_ref().skip_binder())?;
self.binders.pop();
Ok(self)
Ok(())
}
}
impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
impl<'tcx> Printer<'tcx> for SymbolMangler<'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn print_def_path(
mut self,
&mut self,
def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
if let Some(&i) = self.paths.get(&(def_id, args)) {
return self.print_backref(i);
}
let start = self.out.len();
self = self.default_print_def_path(def_id, args)?;
self.default_print_def_path(def_id, args)?;
// Only cache paths that do not refer to an enclosing
// binder (which would change depending on context).
if !args.iter().any(|k| k.has_escaping_bound_vars()) {
self.paths.insert((def_id, args), start);
}
Ok(self)
Ok(())
}
fn print_impl_path(
mut self,
&mut self,
impl_def_id: DefId,
args: &'tcx [GenericArg<'tcx>],
mut self_ty: Ty<'tcx>,
mut impl_trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
let key = self.tcx.def_key(impl_def_id);
let parent_def_id = DefId { index: key.parent.unwrap(), ..impl_def_id };
@ -288,7 +288,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
// Encode impl generic params if the substitutions contain parameters (implying
// polymorphization is enabled) and this isn't an inherent impl.
if impl_trait_ref.is_some() && args.iter().any(|a| a.has_non_region_param()) {
self = self.path_generic_args(
self.path_generic_args(
|this| {
this.path_append_ns(
|cx| cx.print_def_path(parent_def_id, &[]),
@ -301,19 +301,19 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
)?;
} else {
self.push_disambiguator(key.disambiguated_data.disambiguator as u64);
self = self.print_def_path(parent_def_id, &[])?;
self.print_def_path(parent_def_id, &[])?;
}
self = self_ty.print(self)?;
self_ty.print(self)?;
if let Some(trait_ref) = impl_trait_ref {
self = self.print_def_path(trait_ref.def_id, trait_ref.args)?;
self.print_def_path(trait_ref.def_id, trait_ref.args)?;
}
Ok(self)
Ok(())
}
fn print_region(self, region: ty::Region<'_>) -> Result<Self, PrintError> {
fn print_region(&mut self, region: ty::Region<'_>) -> Result<(), PrintError> {
let i = match *region {
// Erased lifetimes use the index 0, for a
// shorter mangling of `L_`.
@ -332,10 +332,10 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
};
self.push("L");
self.push_integer_62(i as u64);
Ok(self)
Ok(())
}
fn print_type(mut self, ty: Ty<'tcx>) -> Result<Self, PrintError> {
fn print_type(&mut self, ty: Ty<'tcx>) -> Result<(), PrintError> {
// Basic types, never cached (single-character).
let basic_type = match ty.kind() {
ty::Bool => "b",
@ -365,7 +365,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
};
if !basic_type.is_empty() {
self.push(basic_type);
return Ok(self);
return Ok(());
}
if let Some(&i) = self.types.get(&ty) {
@ -391,9 +391,9 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
hir::Mutability::Mut => "Q",
});
if !r.is_erased() {
self = r.print(self)?;
r.print(self)?;
}
self = ty.print(self)?;
ty.print(self)?;
}
ty::RawPtr(mt) => {
@ -401,23 +401,23 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
hir::Mutability::Not => "P",
hir::Mutability::Mut => "O",
});
self = mt.ty.print(self)?;
mt.ty.print(self)?;
}
ty::Array(ty, len) => {
self.push("A");
self = ty.print(self)?;
self = self.print_const(len)?;
ty.print(self)?;
self.print_const(len)?;
}
ty::Slice(ty) => {
self.push("S");
self = ty.print(self)?;
ty.print(self)?;
}
ty::Tuple(tys) => {
self.push("T");
for ty in tys.iter() {
self = ty.print(self)?;
ty.print(self)?;
}
self.push("E");
}
@ -428,15 +428,15 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
| ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. })
| ty::Closure(def_id, args)
| ty::Coroutine(def_id, args, _) => {
self = self.print_def_path(def_id, args)?;
self.print_def_path(def_id, args)?;
}
ty::Foreign(def_id) => {
self = self.print_def_path(def_id, &[])?;
self.print_def_path(def_id, &[])?;
}
ty::FnPtr(sig) => {
self.push("F");
self = self.in_binder(&sig, |mut cx, sig| {
self.in_binder(&sig, |cx, sig| {
if sig.unsafety == hir::Unsafety::Unsafe {
cx.push("U");
}
@ -454,7 +454,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
}
}
for &ty in sig.inputs() {
cx = ty.print(cx)?;
ty.print(cx)?;
}
if sig.c_variadic {
cx.push("v");
@ -470,8 +470,8 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
// FIXME(dyn-star): need to update v0 mangling docs
ty::DynStar => "D*",
});
self = self.print_dyn_existential(predicates)?;
self = r.print(self)?;
self.print_dyn_existential(predicates)?;
r.print(self)?;
}
ty::Alias(ty::Inherent, _) => bug!("symbol_names: unexpected inherent projection"),
@ -484,13 +484,13 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
if !ty.has_escaping_bound_vars() {
self.types.insert(ty, start);
}
Ok(self)
Ok(())
}
fn print_dyn_existential(
mut self,
&mut self,
predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
// Okay, so this is a bit tricky. Imagine we have a trait object like
// `dyn for<'a> Foo<'a, Bar = &'a ()>`. When we mangle this, the
// output looks really close to the syntax, where the `Bar = &'a ()` bit
@ -517,7 +517,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
// [<Trait> [{<Projection>}]] [{<Auto>}]
// Since any predicates after the first one shouldn't change the binders,
// just put them all in the binders of the first.
self = self.in_binder(&predicates[0], |mut cx, _| {
self.in_binder(&predicates[0], |cx, _| {
for predicate in predicates.iter() {
// It would be nice to be able to validate bound vars here, but
// projections can actually include bound vars from super traits
@ -528,30 +528,30 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
// Use a type that can't appear in defaults of type parameters.
let dummy_self = Ty::new_fresh(cx.tcx, 0);
let trait_ref = trait_ref.with_self_ty(cx.tcx, dummy_self);
cx = cx.print_def_path(trait_ref.def_id, trait_ref.args)?;
cx.print_def_path(trait_ref.def_id, trait_ref.args)?;
}
ty::ExistentialPredicate::Projection(projection) => {
let name = cx.tcx.associated_item(projection.def_id).name;
cx.push("p");
cx.push_ident(name.as_str());
cx = match projection.term.unpack() {
match projection.term.unpack() {
ty::TermKind::Ty(ty) => ty.print(cx),
ty::TermKind::Const(c) => c.print(cx),
}?;
}
ty::ExistentialPredicate::AutoTrait(def_id) => {
cx = cx.print_def_path(*def_id, &[])?;
cx.print_def_path(*def_id, &[])?;
}
}
}
Ok(cx)
Ok(())
})?;
self.push("E");
Ok(self)
Ok(())
}
fn print_const(mut self, ct: ty::Const<'tcx>) -> Result<Self, PrintError> {
fn print_const(&mut self, ct: ty::Const<'tcx>) -> Result<(), PrintError> {
// We only mangle a typed value if the const can be evaluated.
let ct = ct.normalize(self.tcx, ty::ParamEnv::reveal_all());
match ct.kind() {
@ -570,12 +570,13 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
| ty::ConstKind::Error(_) => {
// Never cached (single-character).
self.push("p");
return Ok(self);
return Ok(());
}
}
if let Some(&i) = self.consts.get(&ct) {
return self.print_backref(i);
self.print_backref(i)?;
return Ok(());
}
let start = self.out.len();
@ -583,7 +584,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
match ty.kind() {
ty::Uint(_) | ty::Int(_) | ty::Bool | ty::Char => {
self = ty.print(self)?;
ty.print(self)?;
let mut bits = ct.eval_bits(self.tcx, ty::ParamEnv::reveal_all());
@ -645,7 +646,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
.ty;
// FIXME(const_generics): add an assert that we only do this for valtrees.
let dereferenced_const = self.tcx.mk_ct_from_kind(ct.kind(), pointee_ty);
self = dereferenced_const.print(self)?;
dereferenced_const.print(self)?;
}
}
}
@ -654,22 +655,22 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
let contents = self.tcx.destructure_const(ct);
let fields = contents.fields.iter().copied();
let print_field_list = |mut this: Self| {
let print_field_list = |this: &mut Self| {
for field in fields.clone() {
this = field.print(this)?;
field.print(this)?;
}
this.push("E");
Ok(this)
Ok(())
};
match *ct.ty().kind() {
ty::Array(..) | ty::Slice(_) => {
self.push("A");
self = print_field_list(self)?;
print_field_list(self)?;
}
ty::Tuple(..) => {
self.push("T");
self = print_field_list(self)?;
print_field_list(self)?;
}
ty::Adt(def, args) => {
let variant_idx =
@ -677,7 +678,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
let variant_def = &def.variant(variant_idx);
self.push("V");
self = self.print_def_path(variant_def.def_id, args)?;
self.print_def_path(variant_def.def_id, args)?;
match variant_def.ctor_kind() {
Some(CtorKind::Const) => {
@ -685,7 +686,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
}
Some(CtorKind::Fn) => {
self.push("T");
self = print_field_list(self)?;
print_field_list(self)?;
}
None => {
self.push("S");
@ -701,7 +702,7 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
);
self.push_ident(field_name.unwrap_or(kw::Empty).as_str());
self = field.print(self)?;
field.print(self)?;
}
self.push("E");
}
@ -720,47 +721,47 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
if !ct.has_escaping_bound_vars() {
self.consts.insert(ct, start);
}
Ok(self)
Ok(())
}
fn path_crate(self, cnum: CrateNum) -> Result<Self, PrintError> {
fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.push("C");
let stable_crate_id = self.tcx.def_path_hash(cnum.as_def_id()).stable_crate_id();
self.push_disambiguator(stable_crate_id.as_u64());
let name = self.tcx.crate_name(cnum);
self.push_ident(name.as_str());
Ok(self)
Ok(())
}
fn path_qualified(
mut self,
&mut self,
self_ty: Ty<'tcx>,
trait_ref: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
assert!(trait_ref.is_some());
let trait_ref = trait_ref.unwrap();
self.push("Y");
self = self_ty.print(self)?;
self_ty.print(self)?;
self.print_def_path(trait_ref.def_id, trait_ref.args)
}
fn path_append_impl(
self,
_: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
_: impl FnOnce(&mut Self) -> Result<(), PrintError>,
_: &DisambiguatedDefPathData,
_: Ty<'tcx>,
_: Option<ty::TraitRef<'tcx>>,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
// Inlined into `print_impl_path`
unreachable!()
}
fn path_append(
self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
disambiguated_data: &DisambiguatedDefPathData,
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
let ns = match disambiguated_data.data {
// Extern block segments can be skipped, names from extern blocks
// are effectively living in their parent modules.
@ -797,10 +798,10 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
}
fn path_generic_args(
mut self,
print_prefix: impl FnOnce(Self) -> Result<Self, PrintError>,
&mut self,
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
args: &[GenericArg<'tcx>],
) -> Result<Self, PrintError> {
) -> Result<(), PrintError> {
// Don't print any regions if they're all erased.
let print_regions = args.iter().any(|arg| match arg.unpack() {
GenericArgKind::Lifetime(r) => !r.is_erased(),
@ -816,23 +817,23 @@ impl<'tcx> Printer<'tcx> for &mut SymbolMangler<'tcx> {
}
self.push("I");
self = print_prefix(self)?;
print_prefix(self)?;
for arg in args {
match arg.unpack() {
GenericArgKind::Lifetime(lt) => {
self = lt.print(self)?;
lt.print(self)?;
}
GenericArgKind::Type(ty) => {
self = ty.print(self)?;
ty.print(self)?;
}
GenericArgKind::Const(c) => {
self.push("K");
self = c.print(self)?;
c.print(self)?;
}
}
}
self.push("E");
Ok(self)
Ok(())
}
}

View file

@ -12,7 +12,7 @@ pub fn target() -> Target {
options: TargetOptions {
abi: "abiv2".into(),
features: "+2e3,+3e7,+7e10,+cache,+dsp1e2,+dspe60,+e1,+e2,+edsp,+elrw,+hard-tp,+high-registers,+hwdiv,+mp,+mp1e2,+nvic,+trust".into(),
late_link_args_static: TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-l:libatomic.a"]),
late_link_args: TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-l:libatomic.a"]),
max_atomic_width: Some(32),
..super::linux_gnu_base::opts()
},

View file

@ -0,0 +1,21 @@
use crate::spec::{Cc, LinkerFlavor, Lld, Target, TargetOptions};
// This target is for glibc Linux on Csky
pub fn target() -> Target {
Target {
//https://github.com/llvm/llvm-project/blob/8b76aea8d8b1b71f6220bc2845abc749f18a19b7/clang/lib/Basic/Targets/CSKY.h
llvm_target: "csky-unknown-linux-gnuabiv2".into(),
pointer_width: 32,
data_layout: "e-m:e-S32-p:32:32-i32:32:32-i64:32:32-f32:32:32-f64:32:32-v64:32:32-v128:32:32-a:0:32-Fi32-n32".into(),
arch: "csky".into(),
options: TargetOptions {
abi: "abiv2hf".into(),
cpu: "ck860fv".into(),
features: "+hard-float,+hard-float-abi,+2e3,+3e7,+7e10,+cache,+dsp1e2,+dspe60,+e1,+e2,+edsp,+elrw,+hard-tp,+high-registers,+hwdiv,+mp,+mp1e2,+nvic,+trust".into(),
late_link_args: TargetOptions::link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-l:libatomic.a", "-mhard-float"]),
max_atomic_width: Some(32),
..super::linux_gnu_base::opts()
},
}
}

View file

@ -1344,6 +1344,7 @@ supported_targets! {
("loongarch64-unknown-linux-gnu", loongarch64_unknown_linux_gnu),
("m68k-unknown-linux-gnu", m68k_unknown_linux_gnu),
("csky-unknown-linux-gnuabiv2", csky_unknown_linux_gnuabiv2),
("csky-unknown-linux-gnuabiv2hf", csky_unknown_linux_gnuabiv2hf),
("mips-unknown-linux-gnu", mips_unknown_linux_gnu),
("mips64-unknown-linux-gnuabi64", mips64_unknown_linux_gnuabi64),
("mips64el-unknown-linux-gnuabi64", mips64el_unknown_linux_gnuabi64),

View file

@ -20,7 +20,6 @@ use std::ops::ControlFlow;
pub use self::infer_ctxt_ext::*;
pub use self::type_err_ctxt_ext::*;
pub use rustc_infer::traits::error_reporting::*;
// When outputting impl candidates, prefer showing those that are more similar.
//

View file

@ -246,14 +246,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
if pred_str.len() > 50 {
// We don't need to save the type to a file, we will be talking about this type already
// in a separate note when we explain the obligation, so it will be available that way.
pred_str = predicate
.print(FmtPrinter::new_with_limit(
self.tcx,
Namespace::TypeNS,
rustc_session::Limit(6),
))
.unwrap()
.into_buffer();
let mut cx: FmtPrinter<'_, '_> =
FmtPrinter::new_with_limit(self.tcx, Namespace::TypeNS, rustc_session::Limit(6));
predicate.print(&mut cx).unwrap();
pred_str = cx.into_buffer();
}
let mut err = struct_span_err!(
self.tcx.sess,
@ -1408,17 +1404,15 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
self.maybe_detailed_projection_msg(predicate, normalized_term, expected_term)
})
.unwrap_or_else(|| {
with_forced_trimmed_paths!(format!(
"type mismatch resolving `{}`",
self.resolve_vars_if_possible(predicate)
.print(FmtPrinter::new_with_limit(
self.tcx,
Namespace::TypeNS,
rustc_session::Limit(10),
))
.unwrap()
.into_buffer()
))
let mut cx = FmtPrinter::new_with_limit(
self.tcx,
Namespace::TypeNS,
rustc_session::Limit(10),
);
with_forced_trimmed_paths!(format!("type mismatch resolving `{}`", {
self.resolve_vars_if_possible(predicate).print(&mut cx).unwrap();
cx.into_buffer()
}))
});
let mut diag = struct_span_err!(self.tcx.sess, obligation.cause.span, E0271, "{msg}");
@ -1463,14 +1457,15 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
ty.span,
with_forced_trimmed_paths!(Cow::from(format!(
"type mismatch resolving `{}`",
self.resolve_vars_if_possible(predicate)
.print(FmtPrinter::new_with_limit(
{
let mut cx = FmtPrinter::new_with_limit(
self.tcx,
Namespace::TypeNS,
rustc_session::Limit(5),
))
.unwrap()
.into_buffer()
);
self.resolve_vars_if_possible(predicate).print(&mut cx).unwrap();
cx.into_buffer()
}
))),
)),
_ => None,

View file

@ -41,11 +41,6 @@ use std::ops::ControlFlow;
pub(crate) use self::project::{needs_normalization, BoundVarReplacer, PlaceholderReplacer};
pub use self::FulfillmentErrorCode::*;
pub use self::ImplSource::*;
pub use self::ObligationCauseCode::*;
pub use self::SelectionError::*;
pub use self::coherence::{add_placeholder_note, orphan_check, overlapping_impls};
pub use self::coherence::{OrphanCheckErr, OverlapResult};
pub use self::engine::{ObligationCtxt, TraitEngineExt};

View file

@ -9,7 +9,7 @@ use rustc_middle::ty::{self, ImplSubject, ToPredicate, Ty, TyCtxt, TypeVisitable
use rustc_span::Span;
use smallvec::SmallVec;
pub use rustc_infer::traits::{self, util::*};
pub use rustc_infer::traits::util::*;
///////////////////////////////////////////////////////////////////////////
// `TraitAliasExpander` iterator

View file

@ -30,7 +30,7 @@
#
# If `change-id` does not match the version that is currently running,
# `x.py` will prompt you to update it and check the related PR for more details.
change-id = 115898
change-id = 116998
# =============================================================================
# Tweaking how LLVM is compiled
@ -377,6 +377,9 @@ change-id = 115898
# this is not intended to be used during local development.
#metrics = false
# Specify the location of the Android NDK. Used when targeting Android.
#android-ndk = "/path/to/android-ndk-r25b"
# =============================================================================
# General install configuration options
# =============================================================================
@ -756,12 +759,6 @@ change-id = 115898
# it must link to `libgcc_eh.a` to get a working output, and this option have no effect.
#llvm-libunwind = 'no' if Linux, 'in-tree' if Fuchsia
# If this target is for Android, this option will be required to specify where
# the NDK for the target lives. This is used to find the C compiler to link and
# build native code.
# See `src/bootstrap/cc_detect.rs` for details.
#android-ndk = <none> (path)
# Build the sanitizer runtimes for this target.
# This option will override the same option under [build] section.
#sanitizers = build.sanitizers (bool)

View file

@ -1,5 +1,6 @@
#![doc = include_str!("../../stdarch/crates/core_arch/src/core_arch_docs.md")]
#[allow(unused_imports)]
#[stable(feature = "simd_arch", since = "1.27.0")]
pub use crate::core_arch::arch::*;

View file

@ -120,8 +120,6 @@
#![deny(unsafe_op_in_unsafe_fn)]
#![deny(fuzzy_provenance_casts)]
extern crate test;
mod alloc;
mod any;
mod array;

View file

@ -8,8 +8,6 @@ use core::num::flt2dec::{
};
use core::num::fmt::{Formatted, Part};
pub use test::Bencher;
mod estimator;
mod strategy {
mod dragon;

View file

@ -35,6 +35,5 @@ pub mod simd {
pub use crate::core_simd::masks::*;
pub use crate::core_simd::ord::*;
pub use crate::core_simd::swizzle::*;
pub use crate::core_simd::swizzle_dyn::*;
pub use crate::core_simd::vector::*;
}

View file

@ -12,6 +12,7 @@
pub mod alloc;
pub mod small_c_string;
#[allow(unused_imports)]
pub mod thread_local;
#[cfg(test)]

View file

@ -241,6 +241,7 @@ pub unsafe fn cleanup() {
#[cfg(target_os = "android")]
pub use crate::sys::android::signal;
#[allow(unused_imports)]
#[cfg(not(target_os = "android"))]
pub use libc::signal;

View file

@ -1,7 +1,6 @@
pub use self::process_common::{Command, CommandArgs, ExitCode, Stdio, StdioPipes};
pub use self::process_inner::{ExitStatus, ExitStatusError, Process};
pub use crate::ffi::OsString as EnvKey;
pub use crate::sys_common::process::CommandEnvs;
#[cfg_attr(any(target_os = "espidf", target_os = "horizon"), allow(unused))]
mod process_common;

View file

@ -75,6 +75,7 @@ cfg_if::cfg_if! {
return 0;
}
} else {
#[allow(unused_imports)]
pub use libc::{sigemptyset, sigaddset};
}
}

View file

@ -62,18 +62,15 @@ mod imp {
unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) }
}
#[cfg(any(target_os = "espidf", target_os = "horizon"))]
#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "freebsd"))]
fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }
}
#[cfg(target_os = "freebsd")]
fn getrandom(buf: &mut [u8]) -> libc::ssize_t {
// FIXME: using the above when libary std's libc is updated
#[cfg(not(target_os = "freebsd"))]
use libc::getrandom;
#[cfg(target_os = "freebsd")]
extern "C" {
fn getrandom(
buffer: *mut libc::c_void,
length: libc::size_t,
buf: *mut libc::c_void,
buflen: libc::size_t,
flags: libc::c_uint,
) -> libc::ssize_t;
}
@ -236,6 +233,7 @@ mod imp {
}
}
// FIXME: once the 10.x release becomes the minimum, this can be dropped for simplification.
#[cfg(target_os = "netbsd")]
mod imp {
use crate::ptr;

View file

@ -1066,6 +1066,14 @@ impl DirBuilder {
}
pub fn readdir(p: &Path) -> io::Result<ReadDir> {
// We push a `*` to the end of the path which cause the empty path to be
// treated as the current directory. So, for consistency with other platforms,
// we explicitly error on the empty path.
if p.as_os_str().is_empty() {
// Return an error code consistent with other ways of opening files.
// E.g. fs::metadata or File::open.
return Err(io::Error::from_raw_os_error(c::ERROR_PATH_NOT_FOUND as i32));
}
let root = p.to_path_buf();
let star = p.join("*");
let path = maybe_verbatim(&star)?;

View file

@ -103,7 +103,7 @@ class GenerateAndParseConfig(unittest.TestCase):
"""Test that we can serialize and deserialize a config.toml file"""
def test_no_args(self):
build = serialize_and_parse([])
self.assertEqual(build.get_toml("change-id"), '115898')
self.assertEqual(build.get_toml("change-id"), '116998')
self.assertEqual(build.get_toml("profile"), 'dist')
self.assertIsNone(build.get_toml("llvm.download-ci-llvm"))

View file

@ -97,20 +97,7 @@ v("llvm-root", None, "set LLVM root")
v("llvm-config", None, "set path to llvm-config")
v("llvm-filecheck", None, "set path to LLVM's FileCheck utility")
v("python", "build.python", "set path to python")
v("android-cross-path", "target.arm-linux-androideabi.android-ndk",
"Android NDK standalone path (deprecated)")
v("i686-linux-android-ndk", "target.i686-linux-android.android-ndk",
"i686-linux-android NDK standalone path")
v("arm-linux-androideabi-ndk", "target.arm-linux-androideabi.android-ndk",
"arm-linux-androideabi NDK standalone path")
v("armv7-linux-androideabi-ndk", "target.armv7-linux-androideabi.android-ndk",
"armv7-linux-androideabi NDK standalone path")
v("thumbv7neon-linux-androideabi-ndk", "target.thumbv7neon-linux-androideabi.android-ndk",
"thumbv7neon-linux-androideabi NDK standalone path")
v("aarch64-linux-android-ndk", "target.aarch64-linux-android.android-ndk",
"aarch64-linux-android NDK standalone path")
v("x86_64-linux-android-ndk", "target.x86_64-linux-android.android-ndk",
"x86_64-linux-android NDK standalone path")
v("android-ndk", "build.android-ndk", "set path to Android NDK")
v("musl-root", "target.x86_64-unknown-linux-musl.musl-root",
"MUSL root installation directory (deprecated)")
v("musl-root-x86_64", "target.x86_64-unknown-linux-musl.musl-root",

View file

@ -21,7 +21,6 @@ use std::str::FromStr;
use crate::core::build_steps::compile::CODEGEN_BACKEND_PREFIX;
use crate::core::config::flags::{Color, Flags, Warnings};
use crate::utils::cache::{Interned, INTERNER};
use crate::utils::cc_detect::{ndk_compiler, Language};
use crate::utils::channel::{self, GitInfo};
use crate::utils::helpers::{exe, output, t};
use build_helper::exit;
@ -142,6 +141,7 @@ pub struct Config {
pub color: Color,
pub patch_binaries_for_nix: Option<bool>,
pub stage0_metadata: Stage0Metadata,
pub android_ndk: Option<PathBuf>,
pub stdout_is_tty: bool,
pub stderr_is_tty: bool,
@ -521,7 +521,6 @@ pub struct Target {
pub ranlib: Option<PathBuf>,
pub default_linker: Option<PathBuf>,
pub linker: Option<PathBuf>,
pub ndk: Option<PathBuf>,
pub sanitizers: Option<bool>,
pub profiler: Option<StringOrBool>,
pub rpath: Option<bool>,
@ -799,6 +798,7 @@ define_config! {
patch_binaries_for_nix: Option<bool> = "patch-binaries-for-nix",
// NOTE: only parsed by bootstrap.py, `--feature build-metrics` enables metrics unconditionally
metrics: Option<bool> = "metrics",
android_ndk: Option<PathBuf> = "android-ndk",
}
}
@ -1039,7 +1039,6 @@ define_config! {
llvm_has_rust_patches: Option<bool> = "llvm-has-rust-patches",
llvm_filecheck: Option<String> = "llvm-filecheck",
llvm_libunwind: Option<String> = "llvm-libunwind",
android_ndk: Option<String> = "android-ndk",
sanitizers: Option<bool> = "sanitizers",
profiler: Option<StringOrBool> = "profiler",
rpath: Option<bool> = "rpath",
@ -1320,6 +1319,7 @@ impl Config {
config.python = build.python.map(PathBuf::from);
config.reuse = build.reuse.map(PathBuf::from);
config.submodules = build.submodules;
config.android_ndk = build.android_ndk;
set(&mut config.low_priority, build.low_priority);
set(&mut config.compiler_docs, build.compiler_docs);
set(&mut config.library_docs_private_items, build.library_docs_private_items);
@ -1600,18 +1600,11 @@ impl Config {
.llvm_libunwind
.as_ref()
.map(|v| v.parse().expect("failed to parse rust.llvm-libunwind"));
if let Some(ref s) = cfg.android_ndk {
target.ndk = Some(config.src.join(s));
}
if let Some(s) = cfg.no_std {
target.no_std = s;
}
target.cc = cfg.cc.map(PathBuf::from).or_else(|| {
target.ndk.as_ref().map(|ndk| ndk_compiler(Language::C, &triple, ndk))
});
target.cxx = cfg.cxx.map(PathBuf::from).or_else(|| {
target.ndk.as_ref().map(|ndk| ndk_compiler(Language::CPlusPlus, &triple, ndk))
});
target.cc = cfg.cc.map(PathBuf::from);
target.cxx = cfg.cxx.map(PathBuf::from);
target.ar = cfg.ar.map(PathBuf::from);
target.ranlib = cfg.ranlib.map(PathBuf::from);
target.linker = cfg.linker.map(PathBuf::from);

View file

@ -78,7 +78,7 @@ const LLD_FILE_NAMES: &[&str] = &["ld.lld", "ld64.lld", "lld-link", "wasm-ld"];
///
/// If you make any major changes (such as adding new values or changing default values), please
/// ensure that the associated PR ID is added to the end of this list.
pub const CONFIG_CHANGE_HISTORY: &[usize] = &[115898];
pub const CONFIG_CHANGE_HISTORY: &[usize] = &[115898, 116998];
/// Extra --check-cfg to add when building
/// (Mode restriction, config name, config values (if any))

View file

@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, iter};
use crate::core::config::{Target, TargetSelection};
use crate::core::config::TargetSelection;
use crate::utils::helpers::output;
use crate::{Build, CLang, GitRepo};
@ -107,10 +107,11 @@ pub fn find(build: &Build) {
pub fn find_target(build: &Build, target: TargetSelection) {
let mut cfg = new_cc_build(build, target);
let config = build.config.target_config.get(&target);
if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
if let Some(cc) = config
.and_then(|c| c.cc.clone())
.or_else(|| default_compiler(&mut cfg, Language::C, target, build))
{
cfg.compiler(cc);
} else {
set_compiler(&mut cfg, Language::C, target, config, build);
}
let compiler = cfg.get_compiler();
@ -127,12 +128,12 @@ pub fn find_target(build: &Build, target: TargetSelection) {
// We'll need one anyways if the target triple is also a host triple
let mut cfg = new_cc_build(build, target);
cfg.cpp(true);
let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
let cxx_configured = if let Some(cxx) = config
.and_then(|c| c.cxx.clone())
.or_else(|| default_compiler(&mut cfg, Language::CPlusPlus, target, build))
{
cfg.compiler(cxx);
true
} else if build.hosts.contains(&target) || build.build == target {
set_compiler(&mut cfg, Language::CPlusPlus, target, config, build);
true
} else {
// Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars).
cfg.try_get_compiler().is_ok()
@ -161,22 +162,21 @@ pub fn find_target(build: &Build, target: TargetSelection) {
}
}
fn set_compiler(
fn default_compiler(
cfg: &mut cc::Build,
compiler: Language,
target: TargetSelection,
config: Option<&Target>,
build: &Build,
) {
) -> Option<PathBuf> {
match &*target.triple {
// When compiling for android we may have the NDK configured in the
// config.toml in which case we look there. Otherwise the default
// compiler already takes into account the triple in question.
t if t.contains("android") => {
if let Some(ndk) = config.and_then(|c| c.ndk.as_ref()) {
cfg.compiler(ndk_compiler(compiler, &*target.triple, ndk));
}
}
t if t.contains("android") => build
.config
.android_ndk
.as_ref()
.map(|ndk| ndk_compiler(compiler, &*target.triple, ndk)),
// The default gcc version from OpenBSD may be too old, try using egcc,
// which is a gcc version from ports, if this is the case.
@ -184,45 +184,48 @@ fn set_compiler(
let c = cfg.get_compiler();
let gnu_compiler = compiler.gcc();
if !c.path().ends_with(gnu_compiler) {
return;
return None;
}
let output = output(c.to_command().arg("--version"));
let i = match output.find(" 4.") {
Some(i) => i,
None => return,
};
let i = output.find(" 4.")?;
match output[i + 3..].chars().next().unwrap() {
'0'..='6' => {}
_ => return,
_ => return None,
}
let alternative = format!("e{gnu_compiler}");
if Command::new(&alternative).output().is_ok() {
cfg.compiler(alternative);
Some(PathBuf::from(alternative))
} else {
None
}
}
"mips-unknown-linux-musl" => {
"mips-unknown-linux-musl" if compiler == Language::C => {
if cfg.get_compiler().path().to_str() == Some("gcc") {
cfg.compiler("mips-linux-musl-gcc");
Some(PathBuf::from("mips-linux-musl-gcc"))
} else {
None
}
}
"mipsel-unknown-linux-musl" => {
"mipsel-unknown-linux-musl" if compiler == Language::C => {
if cfg.get_compiler().path().to_str() == Some("gcc") {
cfg.compiler("mipsel-linux-musl-gcc");
Some(PathBuf::from("mipsel-linux-musl-gcc"))
} else {
None
}
}
t if t.contains("musl") => {
t if t.contains("musl") && compiler == Language::C => {
if let Some(root) = build.musl_root(target) {
let guess = root.join("bin/musl-gcc");
if guess.exists() {
cfg.compiler(guess);
}
if guess.exists() { Some(guess) } else { None }
} else {
None
}
}
_ => {}
_ => None,
}
}
@ -243,10 +246,22 @@ pub(crate) fn ndk_compiler(compiler: Language, triple: &str, ndk: &Path) -> Path
let api_level =
if triple.contains("aarch64") || triple.contains("x86_64") { "21" } else { "19" };
let compiler = format!("{}{}-{}", triple_translated, api_level, compiler.clang());
ndk.join("bin").join(compiler)
let host_tag = if cfg!(target_os = "macos") {
// The NDK uses universal binaries, so this is correct even on ARM.
"darwin-x86_64"
} else if cfg!(target_os = "windows") {
"windows-x86_64"
} else {
// NDK r25b only has official releases for macOS, Windows and Linux.
// Try the Linux directory everywhere else, on the assumption that the OS has an
// emulation layer that can cope (e.g. BSDs).
"linux-x86_64"
};
ndk.join("toolchains").join("llvm").join("prebuilt").join(host_tag).join("bin").join(compiler)
}
/// The target programming language for a native compiler.
#[derive(PartialEq)]
pub(crate) enum Language {
/// The compiler is targeting C.
C,

View file

@ -30,7 +30,7 @@ ENV PATH=$PATH:/android/sdk/platform-tools
ENV TARGETS=arm-linux-androideabi
ENV RUST_CONFIGURE_ARGS --arm-linux-androideabi-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/
ENV RUST_CONFIGURE_ARGS --android-ndk=/android/ndk/
ENV SCRIPT python3 ../x.py --stage 2 test --host='' --target $TARGETS

View file

@ -19,12 +19,7 @@ ENV TARGETS=$TARGETS,x86_64-linux-android
ENV RUST_CONFIGURE_ARGS \
--enable-extended \
--enable-profiler \
--arm-linux-androideabi-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--armv7-linux-androideabi-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--thumbv7neon-linux-androideabi-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--i686-linux-android-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--aarch64-linux-android-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--x86_64-linux-android-ndk=/android/ndk/toolchains/llvm/prebuilt/linux-x86_64/ \
--android-ndk=/android/ndk/ \
--disable-docs
ENV SCRIPT python3 ../x.py dist --host='' --target $TARGETS

View file

@ -33,7 +33,7 @@
- [\*-esp-espidf](platform-support/esp-idf.md)
- [\*-unknown-fuchsia](platform-support/fuchsia.md)
- [\*-kmc-solid_\*](platform-support/kmc-solid.md)
- [csky-unknown-linux-gnuabiv2](platform-support/csky-unknown-linux-gnuabiv2.md)
- [csky-unknown-linux-gnuabiv2\*](platform-support/csky-unknown-linux-gnuabiv2.md)
- [loongarch\*-unknown-linux-\*](platform-support/loongarch-linux.md)
- [loongarch\*-unknown-none\*](platform-support/loongarch-none.md)
- [m68k-unknown-linux-gnu](platform-support/m68k-unknown-linux-gnu.md)

View file

@ -259,7 +259,8 @@ target | std | host | notes
`avr-unknown-gnu-atmega328` | * | | AVR. Requires `-Z build-std=core`
`bpfeb-unknown-none` | * | | BPF (big endian)
`bpfel-unknown-none` | * | | BPF (little endian)
`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux(little endian)
`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux (little endian)
`csky-unknown-linux-gnuabiv2hf` | ✓ | | C-SKY abiv2 Linux, hardfloat (little endian)
`hexagon-unknown-linux-musl` | ? | |
`i386-apple-ios` | ✓ | | 32-bit x86 iOS [^x86_32-floats-return-ABI]
[`i586-pc-nto-qnx700`](platform-support/nto-qnx.md) | * | | 32-bit x86 QNX Neutrino 7.0 RTOS [^x86_32-floats-return-ABI]

View file

@ -4,7 +4,14 @@
This target supports [C-SKY](https://github.com/c-sky) CPUs with `abi` v2 and `glibc`.
target | std | host | notes
-------|:---:|:----:|-------
`csky-unknown-linux-gnuabiv2` | ✓ | | C-SKY abiv2 Linux (little endian)
`csky-unknown-linux-gnuabiv2hf` | ✓ | | C-SKY abiv2 Linux, hardfloat (little endian)
Reference:
https://c-sky.github.io/
https://gitlab.com/c-sky/
## Target maintainers
@ -13,7 +20,6 @@ https://gitlab.com/c-sky/
## Requirements
## Building the target
### Get a C toolchain
@ -28,13 +34,17 @@ The target can be built by enabling it for a `rustc` build, by placing the follo
```toml
[build]
target = ["x86_64-unknown-linux-gnu", "csky-unknown-linux-gnuabiv2"]
target = ["x86_64-unknown-linux-gnu", "csky-unknown-linux-gnuabiv2", "csky-unknown-linux-gnuabiv2hf"]
stage = 2
[target.csky-unknown-linux-gnuabiv2]
# ADJUST THIS PATH TO POINT AT YOUR TOOLCHAIN
cc = "${TOOLCHAIN_PATH}/bin/csky-linux-gnuabiv2-gcc"
[target.csky-unknown-linux-gnuabiv2hf]
# ADJUST THIS PATH TO POINT AT YOUR TOOLCHAIN
cc = "${TOOLCHAIN_PATH}/bin/csky-linux-gnuabiv2-gcc"
### Build
```sh

Some files were not shown because too many files have changed in this diff Show more